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',
|
||||
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
|
||||
|
||||
field :grafana_integration,
|
||||
Types::GrafanaIntegrationType,
|
||||
null: true,
|
||||
description: 'Grafana integration details for the project',
|
||||
resolver: Resolvers::Projects::GrafanaIntegrationResolver
|
||||
|
||||
field :snippets,
|
||||
Types::SnippetType.connection_type,
|
||||
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_commit_indexer, 1]
|
||||
- [elastic_namespace_indexer, 1]
|
||||
- [elastic_namespace_rollout, 1]
|
||||
- [export_csv, 1]
|
||||
- [incident_management, 2]
|
||||
- [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 "updated_at", null: false
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -2275,6 +2275,38 @@ type EpicTreeReorderPayload {
|
|||
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 {
|
||||
"""
|
||||
Avatar URL of the group
|
||||
|
@ -4612,6 +4644,11 @@ type Project {
|
|||
"""
|
||||
fullPath: ID!
|
||||
|
||||
"""
|
||||
Grafana integration details for the project
|
||||
"""
|
||||
grafanaIntegration: GrafanaIntegration
|
||||
|
||||
"""
|
||||
Group of the project
|
||||
"""
|
||||
|
|
|
@ -428,6 +428,20 @@
|
|||
"isDeprecated": false,
|
||||
"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",
|
||||
"description": "Group of the project",
|
||||
|
@ -15668,6 +15682,127 @@
|
|||
"enumValues": 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",
|
||||
"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. |
|
||||
| `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
|
||||
|
||||
| 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 |
|
||||
| `issue` | Issue | A single issue of 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. |
|
||||
| `serviceDeskAddress` | String | E-mail address of the service desk. |
|
||||
|
||||
|
|
|
@ -12,6 +12,10 @@ module Gitlab
|
|||
Gitlab::Graphql::FilterableArray,
|
||||
Gitlab::Graphql::Connections::FilterableArrayConnection
|
||||
)
|
||||
GraphQL::Relay::BaseConnection.register_connection_implementation(
|
||||
Gitlab::Graphql::ExternallyPaginatedArray,
|
||||
Gitlab::Graphql::Connections::ExternallyPaginatedArrayConnection
|
||||
)
|
||||
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
|
||||
class Client
|
||||
include Sentry::Client::Event
|
||||
include Sentry::Client::Projects
|
||||
include Sentry::Client::Issue
|
||||
|
||||
|
@ -24,12 +25,6 @@ module Sentry
|
|||
@token = token
|
||||
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)
|
||||
response = get_issues(keyword_args)
|
||||
|
||||
|
@ -115,10 +110,6 @@ module Sentry
|
|||
}.compact
|
||||
end
|
||||
|
||||
def get_issue_latest_event(issue_id:)
|
||||
http_get(issue_latest_event_api_url(issue_id))[:body]
|
||||
end
|
||||
|
||||
def handle_request_exceptions
|
||||
yield
|
||||
rescue Gitlab::HTTP::Error => e
|
||||
|
@ -149,13 +140,6 @@ module Sentry
|
|||
raise Client::Error, message
|
||||
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
|
||||
issues_url = URI(@url + '/issues/')
|
||||
issues_url.path.squeeze!('/')
|
||||
|
@ -188,27 +172,6 @@ module Sentry
|
|||
uri
|
||||
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)
|
||||
Gitlab::ErrorTracking::Error.new(
|
||||
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/visual-review-tools": "1.5.1",
|
||||
"@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-client": "^2.6.4",
|
||||
"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
|
||||
namespace group statistics repository merge_requests merge_request issues
|
||||
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
|
||||
grafanaIntegration
|
||||
]
|
||||
|
||||
is_expected.to include_graphql_fields(*expected_fields)
|
||||
|
@ -31,45 +32,42 @@ describe GitlabSchema.types['Project'] do
|
|||
describe 'issue field' do
|
||||
subject { described_class.fields['issue'] }
|
||||
|
||||
it 'returns issue' do
|
||||
is_expected.to have_graphql_type(Types::IssueType)
|
||||
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single)
|
||||
end
|
||||
it { is_expected.to have_graphql_type(Types::IssueType) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single) }
|
||||
end
|
||||
|
||||
describe 'issues field' do
|
||||
subject { described_class.fields['issues'] }
|
||||
|
||||
it 'returns issue' do
|
||||
is_expected.to have_graphql_type(Types::IssueType.connection_type)
|
||||
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver)
|
||||
end
|
||||
it { is_expected.to have_graphql_type(Types::IssueType.connection_type) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
|
||||
end
|
||||
|
||||
describe 'merge_requests field' do
|
||||
subject { described_class.fields['mergeRequest'] }
|
||||
|
||||
it 'returns merge requests' do
|
||||
is_expected.to have_graphql_type(Types::MergeRequestType)
|
||||
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single)
|
||||
end
|
||||
it { is_expected.to have_graphql_type(Types::MergeRequestType) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
|
||||
end
|
||||
|
||||
describe 'merge_request field' do
|
||||
subject { described_class.fields['mergeRequests'] }
|
||||
|
||||
it 'returns merge request' do
|
||||
is_expected.to have_graphql_type(Types::MergeRequestType.connection_type)
|
||||
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver)
|
||||
end
|
||||
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
|
||||
end
|
||||
|
||||
describe 'snippets field' do
|
||||
subject { described_class.fields['snippets'] }
|
||||
|
||||
it 'returns snippets' do
|
||||
is_expected.to have_graphql_type(Types::SnippetType.connection_type)
|
||||
is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver)
|
||||
end
|
||||
it { is_expected.to have_graphql_type(Types::SnippetType.connection_type) }
|
||||
it { is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver) }
|
||||
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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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(: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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
it 'is a loaded memoized array' do
|
||||
|
@ -22,7 +22,7 @@ RSpec.shared_examples 'connection with paged nodes' do
|
|||
let(:arguments) { { last: 2 } }
|
||||
|
||||
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
|
||||
|
|
|
@ -985,10 +985,10 @@
|
|||
"@sentry/types" "5.10.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sourcegraph/code-host-integration@^0.0.16":
|
||||
version "0.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.16.tgz#5a4b7c965298b5bae87a5bc4b013ba588db304ec"
|
||||
integrity sha512-2ZMKr0BpkmCUkTuXXlqhZ6jtVcqz4N/Kz6B1fghi10XpiRZlDbVCYdpmeKb0ZyR+pHmuxNOmfJu9HbHc/7bPWA==
|
||||
"@sourcegraph/code-host-integration@^0.0.18":
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.18.tgz#814467cdbc94bbfee5768193acf89fdf404ca949"
|
||||
integrity sha512-PNKR6QI2MK17YJ4BBmWBz7SVRPIJZKbGkQpdB9jHsvQhdSxspdpWFaMu+HKeg96zpStdLhFOcDPn1wlKbdGy+w==
|
||||
|
||||
"@types/anymatch@*":
|
||||
version "1.3.0"
|
||||
|
|
Loading…
Reference in a new issue