Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/graphql/resolvers/projects/grafana_integration_resolver.rb17
-rw-r--r--app/graphql/types/grafana_integration_type.rb23
-rw-r--r--app/graphql/types/project_type.rb6
-rw-r--r--app/policies/grafana_integration_policy.rb5
-rw-r--r--changelogs/unreleased/33681-api.yml5
-rw-r--r--changelogs/unreleased/35195-graphql-implementation-of-grafana-auth.yml5
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--db/migrate/20191225071320_add_index_to_elasticsearch_indexed_namespaces.rb17
-rw-r--r--db/schema.rb1
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql37
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json135
-rw-r--r--doc/api/graphql/reference/index.md12
-rw-r--r--lib/gitlab/graphql/connections.rb4
-rw-r--r--lib/gitlab/graphql/connections/externally_paginated_array_connection.rb35
-rw-r--r--lib/gitlab/graphql/externally_paginated_array.rb15
-rw-r--r--lib/sentry/client.rb39
-rw-r--r--lib/sentry/client/event.rb43
-rw-r--r--package.json2
-rw-r--r--spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb29
-rw-r--r--spec/graphql/types/grafana_integration_type_spec.rb22
-rw-r--r--spec/graphql/types/project_type_spec.rb38
-rw-r--r--spec/lib/gitlab/graphql/connections/externally_paginated_array_connection_spec.rb87
-rw-r--r--spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb4
-rw-r--r--spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb4
-rw-r--r--spec/lib/sentry/client/event_spec.rb73
-rw-r--r--spec/lib/sentry/client_spec.rb58
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb64
-rw-r--r--spec/support/shared_examples/graphql/connection_paged_nodes.rb4
-rw-r--r--yarn.lock8
29 files changed, 668 insertions, 125 deletions
diff --git a/app/graphql/resolvers/projects/grafana_integration_resolver.rb b/app/graphql/resolvers/projects/grafana_integration_resolver.rb
new file mode 100644
index 00000000000..030139734ed
--- /dev/null
+++ b/app/graphql/resolvers/projects/grafana_integration_resolver.rb
@@ -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
diff --git a/app/graphql/types/grafana_integration_type.rb b/app/graphql/types/grafana_integration_type.rb
new file mode 100644
index 00000000000..e6c865fea53
--- /dev/null
+++ b/app/graphql/types/grafana_integration_type.rb
@@ -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
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index bd80ad7ff74..cd894f9775a 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -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,
diff --git a/app/policies/grafana_integration_policy.rb b/app/policies/grafana_integration_policy.rb
new file mode 100644
index 00000000000..529a1fe0493
--- /dev/null
+++ b/app/policies/grafana_integration_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class GrafanaIntegrationPolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/changelogs/unreleased/33681-api.yml b/changelogs/unreleased/33681-api.yml
new file mode 100644
index 00000000000..8224949b31f
--- /dev/null
+++ b/changelogs/unreleased/33681-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add API for rollout Elasticsearch per plan level
+merge_request: 22240
+author:
+type: added
diff --git a/changelogs/unreleased/35195-graphql-implementation-of-grafana-auth.yml b/changelogs/unreleased/35195-graphql-implementation-of-grafana-auth.yml
new file mode 100644
index 00000000000..5bdd7f07fb1
--- /dev/null
+++ b/changelogs/unreleased/35195-graphql-implementation-of-grafana-auth.yml
@@ -0,0 +1,5 @@
+---
+title: Add fetching of Grafana Auth via the GraphQL API
+merge_request: 21756
+author:
+type: changed
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index e0885bc5ca5..a12cc930201 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -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]
diff --git a/db/migrate/20191225071320_add_index_to_elasticsearch_indexed_namespaces.rb b/db/migrate/20191225071320_add_index_to_elasticsearch_indexed_namespaces.rb
new file mode 100644
index 00000000000..758838cb775
--- /dev/null
+++ b/db/migrate/20191225071320_add_index_to_elasticsearch_indexed_namespaces.rb
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index 2b6f8474412..328948dea66 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -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
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 5dddbaff2ee..731d6dc05cd 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -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
@@ -4613,6 +4645,11 @@ type Project {
fullPath: ID!
"""
+ Grafana integration details for the project
+ """
+ grafanaIntegration: GrafanaIntegration
+
+ """
Group of the project
"""
group: Group
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 7ee261de599..a68fe0e0e52 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -429,6 +429,20 @@
"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",
"args": [
@@ -15670,6 +15684,127 @@
},
{
"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",
"description": null,
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4c6819ac699..dbff0b1759b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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. |
diff --git a/lib/gitlab/graphql/connections.rb b/lib/gitlab/graphql/connections.rb
index 38c7d98f37c..08d5cd0b72e 100644
--- a/lib/gitlab/graphql/connections.rb
+++ b/lib/gitlab/graphql/connections.rb
@@ -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
diff --git a/lib/gitlab/graphql/connections/externally_paginated_array_connection.rb b/lib/gitlab/graphql/connections/externally_paginated_array_connection.rb
new file mode 100644
index 00000000000..f0861260691
--- /dev/null
+++ b/lib/gitlab/graphql/connections/externally_paginated_array_connection.rb
@@ -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
diff --git a/lib/gitlab/graphql/externally_paginated_array.rb b/lib/gitlab/graphql/externally_paginated_array.rb
new file mode 100644
index 00000000000..4797fe15cd3
--- /dev/null
+++ b/lib/gitlab/graphql/externally_paginated_array.rb
@@ -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
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 211c828ccc3..e3b8305b664 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -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'),
diff --git a/lib/sentry/client/event.rb b/lib/sentry/client/event.rb
new file mode 100644
index 00000000000..8bcada5a9ce
--- /dev/null
+++ b/lib/sentry/client/event.rb
@@ -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
diff --git a/package.json b/package.json
index a712923faca..954f501d6dd 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
new file mode 100644
index 00000000000..416a90a841f
--- /dev/null
+++ b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
@@ -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
diff --git a/spec/graphql/types/grafana_integration_type_spec.rb b/spec/graphql/types/grafana_integration_type_spec.rb
new file mode 100644
index 00000000000..ddfedc5a75c
--- /dev/null
+++ b/spec/graphql/types/grafana_integration_type_spec.rb
@@ -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
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index a3c51f24307..3c16994495d 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/graphql/connections/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/connections/externally_paginated_array_connection_spec.rb
new file mode 100644
index 00000000000..83c94ed6260
--- /dev/null
+++ b/spec/lib/gitlab/graphql/connections/externally_paginated_array_connection_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb
index 20e87daa0d6..b2f0862be62 100644
--- a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb
@@ -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] } }
diff --git a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb
index bd0fcbbdeb2..f617e8b3ce7 100644
--- a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb
@@ -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 } }
diff --git a/spec/lib/sentry/client/event_spec.rb b/spec/lib/sentry/client/event_spec.rb
new file mode 100644
index 00000000000..c8604d72ada
--- /dev/null
+++ b/spec/lib/sentry/client/event_spec.rb
@@ -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
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index 07547a92fb9..8500f67b8e9 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
new file mode 100644
index 00000000000..6075efb0cbd
--- /dev/null
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/graphql/connection_paged_nodes.rb b/spec/support/shared_examples/graphql/connection_paged_nodes.rb
index 830d2d2d4b1..93de7f619f7 100644
--- a/spec/support/shared_examples/graphql/connection_paged_nodes.rb
+++ b/spec/support/shared_examples/graphql/connection_paged_nodes.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index 603fa80a955..decb9e3e5d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"