diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-08 00:07:50 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-08 00:07:50 +0300 |
commit | d203316c80aa27cf747aa29df9f7c2d374965b5f (patch) | |
tree | aab5cde76fbf19a2639f6f9f3cb4f2acdc95f803 | |
parent | 8dafc3b65aeb8f50fdfc38fb98d96c3db9e2f607 (diff) |
Add latest changes from gitlab-org/gitlab@master
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" |