Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8dafc3b65a
commit
d203316c80
29 changed files with 668 additions and 125 deletions
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Resolvers
|
||||||
|
module Projects
|
||||||
|
class GrafanaIntegrationResolver < BaseResolver
|
||||||
|
type Types::GrafanaIntegrationType, null: true
|
||||||
|
|
||||||
|
alias_method :project, :object
|
||||||
|
|
||||||
|
def resolve(**args)
|
||||||
|
return unless project.is_a? Project
|
||||||
|
|
||||||
|
project.grafana_integration
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
app/graphql/types/grafana_integration_type.rb
Normal file
23
app/graphql/types/grafana_integration_type.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class GrafanaIntegrationType < ::Types::BaseObject
|
||||||
|
graphql_name 'GrafanaIntegration'
|
||||||
|
|
||||||
|
authorize :admin_operations
|
||||||
|
|
||||||
|
field :id, GraphQL::ID_TYPE, null: false,
|
||||||
|
description: 'Internal ID of the Grafana integration'
|
||||||
|
field :grafana_url, GraphQL::STRING_TYPE, null: false,
|
||||||
|
description: 'Url for the Grafana host for the Grafana integration'
|
||||||
|
field :token, GraphQL::STRING_TYPE, null: false,
|
||||||
|
description: 'API token for the Grafana integration'
|
||||||
|
field :enabled, GraphQL::BOOLEAN_TYPE, null: false,
|
||||||
|
description: 'Indicates whether Grafana integration is enabled'
|
||||||
|
|
||||||
|
field :created_at, Types::TimeType, null: false,
|
||||||
|
description: 'Timestamp of the issue\'s creation'
|
||||||
|
field :updated_at, Types::TimeType, null: false,
|
||||||
|
description: 'Timestamp of the issue\'s last activity'
|
||||||
|
end
|
||||||
|
end
|
|
@ -152,6 +152,12 @@ module Types
|
||||||
description: 'Detailed version of a Sentry error on the project',
|
description: 'Detailed version of a Sentry error on the project',
|
||||||
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
|
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
|
||||||
|
|
||||||
|
field :grafana_integration,
|
||||||
|
Types::GrafanaIntegrationType,
|
||||||
|
null: true,
|
||||||
|
description: 'Grafana integration details for the project',
|
||||||
|
resolver: Resolvers::Projects::GrafanaIntegrationResolver
|
||||||
|
|
||||||
field :snippets,
|
field :snippets,
|
||||||
Types::SnippetType.connection_type,
|
Types::SnippetType.connection_type,
|
||||||
null: true,
|
null: true,
|
||||||
|
|
5
app/policies/grafana_integration_policy.rb
Normal file
5
app/policies/grafana_integration_policy.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class GrafanaIntegrationPolicy < BasePolicy
|
||||||
|
delegate { @subject.project }
|
||||||
|
end
|
5
changelogs/unreleased/33681-api.yml
Normal file
5
changelogs/unreleased/33681-api.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add API for rollout Elasticsearch per plan level
|
||||||
|
merge_request: 22240
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add fetching of Grafana Auth via the GraphQL API
|
||||||
|
merge_request: 21756
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -117,6 +117,7 @@
|
||||||
- [elastic_full_index, 1]
|
- [elastic_full_index, 1]
|
||||||
- [elastic_commit_indexer, 1]
|
- [elastic_commit_indexer, 1]
|
||||||
- [elastic_namespace_indexer, 1]
|
- [elastic_namespace_indexer, 1]
|
||||||
|
- [elastic_namespace_rollout, 1]
|
||||||
- [export_csv, 1]
|
- [export_csv, 1]
|
||||||
- [incident_management, 2]
|
- [incident_management, 2]
|
||||||
- [jira_connect, 1]
|
- [jira_connect, 1]
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddIndexToElasticsearchIndexedNamespaces < ActiveRecord::Migration[5.2]
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index(:elasticsearch_indexed_namespaces, :created_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index(:elasticsearch_indexed_namespaces, :created_at)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1460,6 +1460,7 @@ ActiveRecord::Schema.define(version: 2020_01_06_085831) do
|
||||||
t.datetime_with_timezone "created_at", null: false
|
t.datetime_with_timezone "created_at", null: false
|
||||||
t.datetime_with_timezone "updated_at", null: false
|
t.datetime_with_timezone "updated_at", null: false
|
||||||
t.integer "namespace_id"
|
t.integer "namespace_id"
|
||||||
|
t.index ["created_at"], name: "index_elasticsearch_indexed_namespaces_on_created_at"
|
||||||
t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true
|
t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2275,6 +2275,38 @@ type EpicTreeReorderPayload {
|
||||||
errors: [String!]!
|
errors: [String!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GrafanaIntegration {
|
||||||
|
"""
|
||||||
|
Timestamp of the issue's creation
|
||||||
|
"""
|
||||||
|
createdAt: Time!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Indicates whether Grafana integration is enabled
|
||||||
|
"""
|
||||||
|
enabled: Boolean!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Url for the Grafana host for the Grafana integration
|
||||||
|
"""
|
||||||
|
grafanaUrl: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Internal ID of the Grafana integration
|
||||||
|
"""
|
||||||
|
id: ID!
|
||||||
|
|
||||||
|
"""
|
||||||
|
API token for the Grafana integration
|
||||||
|
"""
|
||||||
|
token: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Timestamp of the issue's last activity
|
||||||
|
"""
|
||||||
|
updatedAt: Time!
|
||||||
|
}
|
||||||
|
|
||||||
type Group {
|
type Group {
|
||||||
"""
|
"""
|
||||||
Avatar URL of the group
|
Avatar URL of the group
|
||||||
|
@ -4612,6 +4644,11 @@ type Project {
|
||||||
"""
|
"""
|
||||||
fullPath: ID!
|
fullPath: ID!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Grafana integration details for the project
|
||||||
|
"""
|
||||||
|
grafanaIntegration: GrafanaIntegration
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Group of the project
|
Group of the project
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -428,6 +428,20 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "grafanaIntegration",
|
||||||
|
"description": "Grafana integration details for the project",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "GrafanaIntegration",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "group",
|
"name": "group",
|
||||||
"description": "Group of the project",
|
"description": "Group of the project",
|
||||||
|
@ -15668,6 +15682,127 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "GrafanaIntegration",
|
||||||
|
"description": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "createdAt",
|
||||||
|
"description": "Timestamp of the issue's creation",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Time",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enabled",
|
||||||
|
"description": "Indicates whether Grafana integration is enabled",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "grafanaUrl",
|
||||||
|
"description": "Url for the Grafana host for the Grafana integration",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"description": "Internal ID of the Grafana integration",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "ID",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "token",
|
||||||
|
"description": "API token for the Grafana integration",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updatedAt",
|
||||||
|
"description": "Timestamp of the issue's last activity",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Time",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "Metadata",
|
"name": "Metadata",
|
||||||
|
|
|
@ -317,6 +317,17 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
||||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||||
| `errors` | String! => Array | Reasons why the mutation failed. |
|
| `errors` | String! => Array | Reasons why the mutation failed. |
|
||||||
|
|
||||||
|
### GrafanaIntegration
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | ---- | ---------- |
|
||||||
|
| `id` | ID! | Internal ID of the Grafana integration |
|
||||||
|
| `grafanaUrl` | String! | Url for the Grafana host for the Grafana integration |
|
||||||
|
| `token` | String! | API token for the Grafana integration |
|
||||||
|
| `enabled` | Boolean! | Indicates whether Grafana integration is enabled |
|
||||||
|
| `createdAt` | Time! | Timestamp of the issue's creation |
|
||||||
|
| `updatedAt` | Time! | Timestamp of the issue's last activity |
|
||||||
|
|
||||||
### Group
|
### Group
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|
@ -700,6 +711,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
||||||
| `mergeRequest` | MergeRequest | A single merge request of the project |
|
| `mergeRequest` | MergeRequest | A single merge request of the project |
|
||||||
| `issue` | Issue | A single issue of the project |
|
| `issue` | Issue | A single issue of the project |
|
||||||
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
|
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
|
||||||
|
| `grafanaIntegration` | GrafanaIntegration | Grafana integration details for the project |
|
||||||
| `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. |
|
| `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. |
|
||||||
| `serviceDeskAddress` | String | E-mail address of the service desk. |
|
| `serviceDeskAddress` | String | E-mail address of the service desk. |
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,10 @@ module Gitlab
|
||||||
Gitlab::Graphql::FilterableArray,
|
Gitlab::Graphql::FilterableArray,
|
||||||
Gitlab::Graphql::Connections::FilterableArrayConnection
|
Gitlab::Graphql::Connections::FilterableArrayConnection
|
||||||
)
|
)
|
||||||
|
GraphQL::Relay::BaseConnection.register_connection_implementation(
|
||||||
|
Gitlab::Graphql::ExternallyPaginatedArray,
|
||||||
|
Gitlab::Graphql::Connections::ExternallyPaginatedArrayConnection
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Make a customized connection type
|
||||||
|
module Gitlab
|
||||||
|
module Graphql
|
||||||
|
module Connections
|
||||||
|
class ExternallyPaginatedArrayConnection < GraphQL::Relay::ArrayConnection
|
||||||
|
# As the pagination happens externally
|
||||||
|
# we just return all the nodes here.
|
||||||
|
def sliced_nodes
|
||||||
|
@nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_cursor
|
||||||
|
nodes.previous_cursor
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_cursor
|
||||||
|
nodes.next_cursor
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_page?
|
||||||
|
end_cursor.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous_page?
|
||||||
|
start_cursor.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :has_next_page, :next_page?
|
||||||
|
alias_method :has_previous_page, :previous_page?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
lib/gitlab/graphql/externally_paginated_array.rb
Normal file
15
lib/gitlab/graphql/externally_paginated_array.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Graphql
|
||||||
|
class ExternallyPaginatedArray < Array
|
||||||
|
attr_reader :previous_cursor, :next_cursor
|
||||||
|
|
||||||
|
def initialize(previous_cursor, next_cursor, *args)
|
||||||
|
super(args)
|
||||||
|
@previous_cursor = previous_cursor
|
||||||
|
@next_cursor = next_cursor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
module Sentry
|
module Sentry
|
||||||
class Client
|
class Client
|
||||||
|
include Sentry::Client::Event
|
||||||
include Sentry::Client::Projects
|
include Sentry::Client::Projects
|
||||||
include Sentry::Client::Issue
|
include Sentry::Client::Issue
|
||||||
|
|
||||||
|
@ -24,12 +25,6 @@ module Sentry
|
||||||
@token = token
|
@token = token
|
||||||
end
|
end
|
||||||
|
|
||||||
def issue_latest_event(issue_id:)
|
|
||||||
latest_event = get_issue_latest_event(issue_id: issue_id)
|
|
||||||
|
|
||||||
map_to_event(latest_event)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_issues(**keyword_args)
|
def list_issues(**keyword_args)
|
||||||
response = get_issues(keyword_args)
|
response = get_issues(keyword_args)
|
||||||
|
|
||||||
|
@ -115,10 +110,6 @@ module Sentry
|
||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_issue_latest_event(issue_id:)
|
|
||||||
http_get(issue_latest_event_api_url(issue_id))[:body]
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_request_exceptions
|
def handle_request_exceptions
|
||||||
yield
|
yield
|
||||||
rescue Gitlab::HTTP::Error => e
|
rescue Gitlab::HTTP::Error => e
|
||||||
|
@ -149,13 +140,6 @@ module Sentry
|
||||||
raise Client::Error, message
|
raise Client::Error, message
|
||||||
end
|
end
|
||||||
|
|
||||||
def issue_latest_event_api_url(issue_id)
|
|
||||||
latest_event_url = URI(@url)
|
|
||||||
latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/"
|
|
||||||
|
|
||||||
latest_event_url
|
|
||||||
end
|
|
||||||
|
|
||||||
def issues_api_url
|
def issues_api_url
|
||||||
issues_url = URI(@url + '/issues/')
|
issues_url = URI(@url + '/issues/')
|
||||||
issues_url.path.squeeze!('/')
|
issues_url.path.squeeze!('/')
|
||||||
|
@ -188,27 +172,6 @@ module Sentry
|
||||||
uri
|
uri
|
||||||
end
|
end
|
||||||
|
|
||||||
def map_to_event(event)
|
|
||||||
stack_trace = parse_stack_trace(event)
|
|
||||||
|
|
||||||
Gitlab::ErrorTracking::ErrorEvent.new(
|
|
||||||
issue_id: event.dig('groupID'),
|
|
||||||
date_received: event.dig('dateReceived'),
|
|
||||||
stack_trace_entries: stack_trace
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_stack_trace(event)
|
|
||||||
exception_entry = event.dig('entries')&.detect { |h| h['type'] == 'exception' }
|
|
||||||
return [] unless exception_entry
|
|
||||||
|
|
||||||
exception_values = exception_entry.dig('data', 'values')
|
|
||||||
stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? }
|
|
||||||
return [] unless stack_trace_entry
|
|
||||||
|
|
||||||
stack_trace_entry.dig('stacktrace', 'frames') || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def map_to_error(issue)
|
def map_to_error(issue)
|
||||||
Gitlab::ErrorTracking::Error.new(
|
Gitlab::ErrorTracking::Error.new(
|
||||||
id: issue.fetch('id'),
|
id: issue.fetch('id'),
|
||||||
|
|
43
lib/sentry/client/event.rb
Normal file
43
lib/sentry/client/event.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Sentry
|
||||||
|
class Client
|
||||||
|
module Event
|
||||||
|
def issue_latest_event(issue_id:)
|
||||||
|
latest_event = http_get(issue_latest_event_api_url(issue_id))[:body]
|
||||||
|
|
||||||
|
map_to_event(latest_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def issue_latest_event_api_url(issue_id)
|
||||||
|
latest_event_url = URI(url)
|
||||||
|
latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/"
|
||||||
|
|
||||||
|
latest_event_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def map_to_event(event)
|
||||||
|
stack_trace = parse_stack_trace(event)
|
||||||
|
|
||||||
|
Gitlab::ErrorTracking::ErrorEvent.new(
|
||||||
|
issue_id: event.dig('groupID'),
|
||||||
|
date_received: event.dig('dateReceived'),
|
||||||
|
stack_trace_entries: stack_trace
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_stack_trace(event)
|
||||||
|
exception_entry = event.dig('entries')&.detect { |h| h['type'] == 'exception' }
|
||||||
|
return [] unless exception_entry
|
||||||
|
|
||||||
|
exception_values = exception_entry.dig('data', 'values')
|
||||||
|
stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? }
|
||||||
|
return [] unless stack_trace_entry
|
||||||
|
|
||||||
|
stack_trace_entry.dig('stacktrace', 'frames') || []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,7 +43,7 @@
|
||||||
"@gitlab/ui": "8.10.0",
|
"@gitlab/ui": "8.10.0",
|
||||||
"@gitlab/visual-review-tools": "1.5.1",
|
"@gitlab/visual-review-tools": "1.5.1",
|
||||||
"@sentry/browser": "^5.10.2",
|
"@sentry/browser": "^5.10.2",
|
||||||
"@sourcegraph/code-host-integration": "^0.0.16",
|
"@sourcegraph/code-host-integration": "^0.0.18",
|
||||||
"apollo-cache-inmemory": "^1.6.3",
|
"apollo-cache-inmemory": "^1.6.3",
|
||||||
"apollo-client": "^2.6.4",
|
"apollo-client": "^2.6.4",
|
||||||
"apollo-link": "^1.2.11",
|
"apollo-link": "^1.2.11",
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Resolvers::Projects::GrafanaIntegrationResolver do
|
||||||
|
include GraphqlHelpers
|
||||||
|
|
||||||
|
let_it_be(:project) { create(:project) }
|
||||||
|
let_it_be(:current_user) { create(:user) }
|
||||||
|
let_it_be(:grafana_integration) { create(:grafana_integration, project: project)}
|
||||||
|
|
||||||
|
describe '#resolve' do
|
||||||
|
context 'when object is not a project' do
|
||||||
|
it { expect(resolve_integration(obj: current_user)).to eq nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when object is a project' do
|
||||||
|
it { expect(resolve_integration(obj: project)).to eq grafana_integration }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when object is nil' do
|
||||||
|
it { expect(resolve_integration(obj: nil)).to eq nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_integration(obj: project, context: { current_user: current_user })
|
||||||
|
resolve(described_class, obj: obj, ctx: context)
|
||||||
|
end
|
||||||
|
end
|
22
spec/graphql/types/grafana_integration_type_spec.rb
Normal file
22
spec/graphql/types/grafana_integration_type_spec.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GitlabSchema.types['GrafanaIntegration'] do
|
||||||
|
let(:expected_fields) do
|
||||||
|
%i[
|
||||||
|
id
|
||||||
|
grafana_url
|
||||||
|
token
|
||||||
|
enabled
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(described_class.graphql_name).to eq('GrafanaIntegration') }
|
||||||
|
|
||||||
|
it { expect(described_class).to require_graphql_authorizations(:admin_operations) }
|
||||||
|
|
||||||
|
it { is_expected.to have_graphql_fields(*expected_fields) }
|
||||||
|
end
|
|
@ -23,6 +23,7 @@ describe GitlabSchema.types['Project'] do
|
||||||
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
|
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
|
||||||
namespace group statistics repository merge_requests merge_request issues
|
namespace group statistics repository merge_requests merge_request issues
|
||||||
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
|
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
|
||||||
|
grafanaIntegration
|
||||||
]
|
]
|
||||||
|
|
||||||
is_expected.to include_graphql_fields(*expected_fields)
|
is_expected.to include_graphql_fields(*expected_fields)
|
||||||
|
@ -31,45 +32,42 @@ describe GitlabSchema.types['Project'] do
|
||||||
describe 'issue field' do
|
describe 'issue field' do
|
||||||
subject { described_class.fields['issue'] }
|
subject { described_class.fields['issue'] }
|
||||||
|
|
||||||
it 'returns issue' do
|
it { is_expected.to have_graphql_type(Types::IssueType) }
|
||||||
is_expected.to have_graphql_type(Types::IssueType)
|
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single) }
|
||||||
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'issues field' do
|
describe 'issues field' do
|
||||||
subject { described_class.fields['issues'] }
|
subject { described_class.fields['issues'] }
|
||||||
|
|
||||||
it 'returns issue' do
|
it { is_expected.to have_graphql_type(Types::IssueType.connection_type) }
|
||||||
is_expected.to have_graphql_type(Types::IssueType.connection_type)
|
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
|
||||||
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'merge_requests field' do
|
describe 'merge_requests field' do
|
||||||
subject { described_class.fields['mergeRequest'] }
|
subject { described_class.fields['mergeRequest'] }
|
||||||
|
|
||||||
it 'returns merge requests' do
|
it { is_expected.to have_graphql_type(Types::MergeRequestType) }
|
||||||
is_expected.to have_graphql_type(Types::MergeRequestType)
|
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
|
||||||
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'merge_request field' do
|
describe 'merge_request field' do
|
||||||
subject { described_class.fields['mergeRequests'] }
|
subject { described_class.fields['mergeRequests'] }
|
||||||
|
|
||||||
it 'returns merge request' do
|
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
|
||||||
is_expected.to have_graphql_type(Types::MergeRequestType.connection_type)
|
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
|
||||||
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'snippets field' do
|
describe 'snippets field' do
|
||||||
subject { described_class.fields['snippets'] }
|
subject { described_class.fields['snippets'] }
|
||||||
|
|
||||||
it 'returns snippets' do
|
it { is_expected.to have_graphql_type(Types::SnippetType.connection_type) }
|
||||||
is_expected.to have_graphql_type(Types::SnippetType.connection_type)
|
it { is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver) }
|
||||||
is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver)
|
end
|
||||||
end
|
|
||||||
|
describe 'grafana_integration field' do
|
||||||
|
subject { described_class.fields['grafanaIntegration'] }
|
||||||
|
|
||||||
|
it { is_expected.to have_graphql_type(Types::GrafanaIntegrationType) }
|
||||||
|
it { is_expected.to have_graphql_resolver(Resolvers::Projects::GrafanaIntegrationResolver) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Graphql::Connections::ExternallyPaginatedArrayConnection do
|
||||||
|
let(:prev_cursor) { 1 }
|
||||||
|
let(:next_cursor) { 6 }
|
||||||
|
let(:values) { [2, 3, 4, 5] }
|
||||||
|
let(:all_nodes) { Gitlab::Graphql::ExternallyPaginatedArray.new(prev_cursor, next_cursor, *values) }
|
||||||
|
let(:arguments) { {} }
|
||||||
|
|
||||||
|
subject(:connection) do
|
||||||
|
described_class.new(all_nodes, arguments)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#sliced_nodes' do
|
||||||
|
let(:sliced_nodes) { connection.sliced_nodes }
|
||||||
|
|
||||||
|
it 'returns all the nodes' do
|
||||||
|
expect(connection.sliced_nodes).to eq(values)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#paged_nodes' do
|
||||||
|
let(:paged_nodes) { connection.send(:paged_nodes) }
|
||||||
|
|
||||||
|
it_behaves_like "connection with paged nodes" do
|
||||||
|
let(:paged_nodes_size) { values.size }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#start_cursor' do
|
||||||
|
it 'returns the prev cursor' do
|
||||||
|
expect(connection.start_cursor).to eq(prev_cursor)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is none' do
|
||||||
|
let(:prev_cursor) { nil }
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(connection.start_cursor).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#end_cursor' do
|
||||||
|
it 'returns the next cursor' do
|
||||||
|
expect(connection.end_cursor).to eq(next_cursor)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is none' do
|
||||||
|
let(:next_cursor) { nil }
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(connection.end_cursor).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#has_next_page' do
|
||||||
|
it 'returns true when there is a end cursor' do
|
||||||
|
expect(connection.has_next_page).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'there is no end cursor' do
|
||||||
|
let(:next_cursor) { nil }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(connection.has_next_page).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#has_previous_page' do
|
||||||
|
it 'returns true when there is a start cursor' do
|
||||||
|
expect(connection.has_previous_page).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'there is no start cursor' do
|
||||||
|
let(:prev_cursor) { nil }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(connection.has_previous_page).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,7 +14,9 @@ describe Gitlab::Graphql::Connections::FilterableArrayConnection do
|
||||||
describe '#paged_nodes' do
|
describe '#paged_nodes' do
|
||||||
let(:paged_nodes) { subject.paged_nodes }
|
let(:paged_nodes) { subject.paged_nodes }
|
||||||
|
|
||||||
it_behaves_like "connection with paged nodes"
|
it_behaves_like "connection with paged nodes" do
|
||||||
|
let(:paged_nodes_size) { 3 }
|
||||||
|
end
|
||||||
|
|
||||||
context 'when callback filters some nodes' do
|
context 'when callback filters some nodes' do
|
||||||
let(:callback) { proc { |nodes| nodes[1..-1] } }
|
let(:callback) { proc { |nodes| nodes[1..-1] } }
|
||||||
|
|
|
@ -232,7 +232,9 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
|
||||||
let_it_be(:all_nodes) { create_list(:project, 5) }
|
let_it_be(:all_nodes) { create_list(:project, 5) }
|
||||||
let(:paged_nodes) { subject.paged_nodes }
|
let(:paged_nodes) { subject.paged_nodes }
|
||||||
|
|
||||||
it_behaves_like "connection with paged nodes"
|
it_behaves_like "connection with paged nodes" do
|
||||||
|
let(:paged_nodes_size) { 3 }
|
||||||
|
end
|
||||||
|
|
||||||
context 'when both are passed' do
|
context 'when both are passed' do
|
||||||
let(:arguments) { { first: 2, last: 2 } }
|
let(:arguments) { { first: 2, last: 2 } }
|
||||||
|
|
73
spec/lib/sentry/client/event_spec.rb
Normal file
73
spec/lib/sentry/client/event_spec.rb
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Sentry::Client do
|
||||||
|
include SentryClientHelpers
|
||||||
|
|
||||||
|
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
|
||||||
|
let(:token) { 'test-token' }
|
||||||
|
let(:default_httparty_options) do
|
||||||
|
{
|
||||||
|
follow_redirects: false,
|
||||||
|
headers: { "Authorization" => "Bearer test-token" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:client) { described_class.new(sentry_url, token) }
|
||||||
|
|
||||||
|
describe '#issue_latest_event' do
|
||||||
|
let(:sample_response) do
|
||||||
|
Gitlab::Utils.deep_indifferent_access(
|
||||||
|
JSON.parse(fixture_file('sentry/issue_latest_event_sample_response.json'))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let(:issue_id) { '1234' }
|
||||||
|
let(:sentry_api_response) { sample_response }
|
||||||
|
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
|
||||||
|
let(:sentry_request_url) { "#{sentry_url}/issues/#{issue_id}/events/latest/" }
|
||||||
|
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
|
||||||
|
|
||||||
|
subject { client.issue_latest_event(issue_id: issue_id) }
|
||||||
|
|
||||||
|
it_behaves_like 'calls sentry api'
|
||||||
|
|
||||||
|
it 'has correct return type' do
|
||||||
|
expect(subject).to be_a(Gitlab::ErrorTracking::ErrorEvent)
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'assigns error tracking event correctly' do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
where(:event_object, :sentry_response) do
|
||||||
|
:issue_id | :groupID
|
||||||
|
:date_received | :dateReceived
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it { expect(subject.public_send(event_object)).to eq(sentry_api_response.dig(*sentry_response)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'error object created from sentry response' do
|
||||||
|
it_behaves_like 'assigns error tracking event correctly'
|
||||||
|
|
||||||
|
it 'parses the stack trace' do
|
||||||
|
expect(subject.stack_trace_entries).to be_a Array
|
||||||
|
expect(subject.stack_trace_entries).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'error without stack trace' do
|
||||||
|
before do
|
||||||
|
sample_response['entries'] = []
|
||||||
|
stub_sentry_request(sentry_request_url, body: sample_response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'assigns error tracking event correctly'
|
||||||
|
|
||||||
|
it 'returns an empty array for stack_trace_entries' do
|
||||||
|
expect(subject.stack_trace_entries).to eq []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -218,62 +218,4 @@ describe Sentry::Client do
|
||||||
it_behaves_like 'issues has correct length', 1
|
it_behaves_like 'issues has correct length', 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#issue_latest_event' do
|
|
||||||
let(:sample_response) do
|
|
||||||
Gitlab::Utils.deep_indifferent_access(
|
|
||||||
JSON.parse(fixture_file('sentry/issue_latest_event_sample_response.json'))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:issue_id) { '1234' }
|
|
||||||
let(:sentry_api_response) { sample_response }
|
|
||||||
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
|
|
||||||
let(:sentry_request_url) { sentry_url + "/issues/#{issue_id}/events/latest/" }
|
|
||||||
|
|
||||||
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
|
|
||||||
|
|
||||||
subject { client.issue_latest_event(issue_id: issue_id) }
|
|
||||||
|
|
||||||
it_behaves_like 'calls sentry api'
|
|
||||||
|
|
||||||
it 'has correct return type' do
|
|
||||||
expect(subject).to be_a(Gitlab::ErrorTracking::ErrorEvent)
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'assigns error tracking event correctly' do
|
|
||||||
using RSpec::Parameterized::TableSyntax
|
|
||||||
|
|
||||||
where(:event_object, :sentry_response) do
|
|
||||||
:issue_id | :groupID
|
|
||||||
:date_received | :dateReceived
|
|
||||||
end
|
|
||||||
|
|
||||||
with_them do
|
|
||||||
it { expect(subject.public_send(event_object)).to eq(sentry_api_response.dig(*sentry_response)) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'error object created from sentry response' do
|
|
||||||
it_behaves_like 'assigns error tracking event correctly'
|
|
||||||
|
|
||||||
it 'parses the stack trace' do
|
|
||||||
expect(subject.stack_trace_entries).to be_a Array
|
|
||||||
expect(subject.stack_trace_entries).not_to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'error without stack trace' do
|
|
||||||
before do
|
|
||||||
sample_response['entries'] = []
|
|
||||||
stub_sentry_request(sentry_request_url, body: sample_response)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'assigns error tracking event correctly'
|
|
||||||
|
|
||||||
it 'returns an empty array for stack_trace_entries' do
|
|
||||||
expect(subject.stack_trace_entries).to eq []
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe 'Getting Grafana Integration' do
|
||||||
|
include GraphqlHelpers
|
||||||
|
|
||||||
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
|
let_it_be(:current_user) { project.owner }
|
||||||
|
let_it_be(:grafana_integration) { create(:grafana_integration, project: project) }
|
||||||
|
|
||||||
|
let(:fields) do
|
||||||
|
<<~QUERY
|
||||||
|
#{all_graphql_fields_for('GrafanaIntegration'.classify)}
|
||||||
|
QUERY
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:query) do
|
||||||
|
graphql_query_for(
|
||||||
|
'project',
|
||||||
|
{ 'fullPath' => project.full_path },
|
||||||
|
query_graphql_field('grafanaIntegration', {}, fields)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with grafana integration data' do
|
||||||
|
let(:integration_data) { graphql_data['project']['grafanaIntegration'] }
|
||||||
|
|
||||||
|
context 'without project admin permissions' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_developer(user)
|
||||||
|
post_graphql(query, current_user: user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'a working graphql query'
|
||||||
|
|
||||||
|
it { expect(integration_data).to be nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with project admin permissions' do
|
||||||
|
before do
|
||||||
|
post_graphql(query, current_user: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'a working graphql query'
|
||||||
|
|
||||||
|
it { expect(integration_data['token']).to eql grafana_integration.token }
|
||||||
|
it { expect(integration_data['grafanaUrl']).to eql grafana_integration.grafana_url }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(
|
||||||
|
integration_data['createdAt']
|
||||||
|
).to eql grafana_integration.created_at.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(
|
||||||
|
integration_data['updatedAt']
|
||||||
|
).to eql grafana_integration.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
RSpec.shared_examples 'connection with paged nodes' do
|
RSpec.shared_examples 'connection with paged nodes' do
|
||||||
it 'returns the collection limited to max page size' do
|
it 'returns the collection limited to max page size' do
|
||||||
expect(paged_nodes.size).to eq(3)
|
expect(paged_nodes.size).to eq(paged_nodes_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is a loaded memoized array' do
|
it 'is a loaded memoized array' do
|
||||||
|
@ -22,7 +22,7 @@ RSpec.shared_examples 'connection with paged nodes' do
|
||||||
let(:arguments) { { last: 2 } }
|
let(:arguments) { { last: 2 } }
|
||||||
|
|
||||||
it 'returns only the last elements' do
|
it 'returns only the last elements' do
|
||||||
expect(paged_nodes).to contain_exactly(all_nodes[3], all_nodes[4])
|
expect(paged_nodes).to contain_exactly(*all_nodes.last(2))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -985,10 +985,10 @@
|
||||||
"@sentry/types" "5.10.0"
|
"@sentry/types" "5.10.0"
|
||||||
tslib "^1.9.3"
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@sourcegraph/code-host-integration@^0.0.16":
|
"@sourcegraph/code-host-integration@^0.0.18":
|
||||||
version "0.0.16"
|
version "0.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.16.tgz#5a4b7c965298b5bae87a5bc4b013ba588db304ec"
|
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.18.tgz#814467cdbc94bbfee5768193acf89fdf404ca949"
|
||||||
integrity sha512-2ZMKr0BpkmCUkTuXXlqhZ6jtVcqz4N/Kz6B1fghi10XpiRZlDbVCYdpmeKb0ZyR+pHmuxNOmfJu9HbHc/7bPWA==
|
integrity sha512-PNKR6QI2MK17YJ4BBmWBz7SVRPIJZKbGkQpdB9jHsvQhdSxspdpWFaMu+HKeg96zpStdLhFOcDPn1wlKbdGy+w==
|
||||||
|
|
||||||
"@types/anymatch@*":
|
"@types/anymatch@*":
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
|
|
Loading…
Reference in a new issue