Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-01-07 21:07:50 +00:00
parent 8dafc3b65a
commit d203316c80
29 changed files with 668 additions and 125 deletions

View file

@ -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

View 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

View file

@ -152,6 +152,12 @@ module Types
description: 'Detailed version of a Sentry error on the project', description: 'Detailed version of a Sentry error on the project',
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
field :grafana_integration,
Types::GrafanaIntegrationType,
null: true,
description: 'Grafana integration details for the project',
resolver: Resolvers::Projects::GrafanaIntegrationResolver
field :snippets, field :snippets,
Types::SnippetType.connection_type, Types::SnippetType.connection_type,
null: true, null: true,

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class GrafanaIntegrationPolicy < BasePolicy
delegate { @subject.project }
end

View file

@ -0,0 +1,5 @@
---
title: Add API for rollout Elasticsearch per plan level
merge_request: 22240
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Add fetching of Grafana Auth via the GraphQL API
merge_request: 21756
author:
type: changed

View file

@ -117,6 +117,7 @@
- [elastic_full_index, 1] - [elastic_full_index, 1]
- [elastic_commit_indexer, 1] - [elastic_commit_indexer, 1]
- [elastic_namespace_indexer, 1] - [elastic_namespace_indexer, 1]
- [elastic_namespace_rollout, 1]
- [export_csv, 1] - [export_csv, 1]
- [incident_management, 2] - [incident_management, 2]
- [jira_connect, 1] - [jira_connect, 1]

View file

@ -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

View file

@ -1460,6 +1460,7 @@ ActiveRecord::Schema.define(version: 2020_01_06_085831) do
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
t.integer "namespace_id" t.integer "namespace_id"
t.index ["created_at"], name: "index_elasticsearch_indexed_namespaces_on_created_at"
t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true
end end

View file

@ -2275,6 +2275,38 @@ type EpicTreeReorderPayload {
errors: [String!]! errors: [String!]!
} }
type GrafanaIntegration {
"""
Timestamp of the issue's creation
"""
createdAt: Time!
"""
Indicates whether Grafana integration is enabled
"""
enabled: Boolean!
"""
Url for the Grafana host for the Grafana integration
"""
grafanaUrl: String!
"""
Internal ID of the Grafana integration
"""
id: ID!
"""
API token for the Grafana integration
"""
token: String!
"""
Timestamp of the issue's last activity
"""
updatedAt: Time!
}
type Group { type Group {
""" """
Avatar URL of the group Avatar URL of the group
@ -4612,6 +4644,11 @@ type Project {
""" """
fullPath: ID! fullPath: ID!
"""
Grafana integration details for the project
"""
grafanaIntegration: GrafanaIntegration
""" """
Group of the project Group of the project
""" """

View file

@ -428,6 +428,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "grafanaIntegration",
"description": "Grafana integration details for the project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "GrafanaIntegration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "group", "name": "group",
"description": "Group of the project", "description": "Group of the project",
@ -15668,6 +15682,127 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "GrafanaIntegration",
"description": null,
"fields": [
{
"name": "createdAt",
"description": "Timestamp of the issue's creation",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enabled",
"description": "Indicates whether Grafana integration is enabled",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "grafanaUrl",
"description": "Url for the Grafana host for the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "Internal ID of the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "token",
"description": "API token for the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of the issue's last activity",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Metadata", "name": "Metadata",

View file

@ -317,6 +317,17 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. | | `errors` | String! => Array | Reasons why the mutation failed. |
### GrafanaIntegration
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | Internal ID of the Grafana integration |
| `grafanaUrl` | String! | Url for the Grafana host for the Grafana integration |
| `token` | String! | API token for the Grafana integration |
| `enabled` | Boolean! | Indicates whether Grafana integration is enabled |
| `createdAt` | Time! | Timestamp of the issue's creation |
| `updatedAt` | Time! | Timestamp of the issue's last activity |
### Group ### Group
| Name | Type | Description | | Name | Type | Description |
@ -700,6 +711,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `mergeRequest` | MergeRequest | A single merge request of the project | | `mergeRequest` | MergeRequest | A single merge request of the project |
| `issue` | Issue | A single issue of the project | | `issue` | Issue | A single issue of the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project | | `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `grafanaIntegration` | GrafanaIntegration | Grafana integration details for the project |
| `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. | | `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. |
| `serviceDeskAddress` | String | E-mail address of the service desk. | | `serviceDeskAddress` | String | E-mail address of the service desk. |

View file

@ -12,6 +12,10 @@ module Gitlab
Gitlab::Graphql::FilterableArray, Gitlab::Graphql::FilterableArray,
Gitlab::Graphql::Connections::FilterableArrayConnection Gitlab::Graphql::Connections::FilterableArrayConnection
) )
GraphQL::Relay::BaseConnection.register_connection_implementation(
Gitlab::Graphql::ExternallyPaginatedArray,
Gitlab::Graphql::Connections::ExternallyPaginatedArrayConnection
)
end end
end end
end end

View file

@ -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

View 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

View file

@ -2,6 +2,7 @@
module Sentry module Sentry
class Client class Client
include Sentry::Client::Event
include Sentry::Client::Projects include Sentry::Client::Projects
include Sentry::Client::Issue include Sentry::Client::Issue
@ -24,12 +25,6 @@ module Sentry
@token = token @token = token
end end
def issue_latest_event(issue_id:)
latest_event = get_issue_latest_event(issue_id: issue_id)
map_to_event(latest_event)
end
def list_issues(**keyword_args) def list_issues(**keyword_args)
response = get_issues(keyword_args) response = get_issues(keyword_args)
@ -115,10 +110,6 @@ module Sentry
}.compact }.compact
end end
def get_issue_latest_event(issue_id:)
http_get(issue_latest_event_api_url(issue_id))[:body]
end
def handle_request_exceptions def handle_request_exceptions
yield yield
rescue Gitlab::HTTP::Error => e rescue Gitlab::HTTP::Error => e
@ -149,13 +140,6 @@ module Sentry
raise Client::Error, message raise Client::Error, message
end end
def issue_latest_event_api_url(issue_id)
latest_event_url = URI(@url)
latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/"
latest_event_url
end
def issues_api_url def issues_api_url
issues_url = URI(@url + '/issues/') issues_url = URI(@url + '/issues/')
issues_url.path.squeeze!('/') issues_url.path.squeeze!('/')
@ -188,27 +172,6 @@ module Sentry
uri uri
end end
def map_to_event(event)
stack_trace = parse_stack_trace(event)
Gitlab::ErrorTracking::ErrorEvent.new(
issue_id: event.dig('groupID'),
date_received: event.dig('dateReceived'),
stack_trace_entries: stack_trace
)
end
def parse_stack_trace(event)
exception_entry = event.dig('entries')&.detect { |h| h['type'] == 'exception' }
return [] unless exception_entry
exception_values = exception_entry.dig('data', 'values')
stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? }
return [] unless stack_trace_entry
stack_trace_entry.dig('stacktrace', 'frames') || []
end
def map_to_error(issue) def map_to_error(issue)
Gitlab::ErrorTracking::Error.new( Gitlab::ErrorTracking::Error.new(
id: issue.fetch('id'), id: issue.fetch('id'),

View 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

View file

@ -43,7 +43,7 @@
"@gitlab/ui": "8.10.0", "@gitlab/ui": "8.10.0",
"@gitlab/visual-review-tools": "1.5.1", "@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2", "@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "^0.0.16", "@sourcegraph/code-host-integration": "^0.0.18",
"apollo-cache-inmemory": "^1.6.3", "apollo-cache-inmemory": "^1.6.3",
"apollo-client": "^2.6.4", "apollo-client": "^2.6.4",
"apollo-link": "^1.2.11", "apollo-link": "^1.2.11",

View file

@ -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

View 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

View file

@ -23,6 +23,7 @@ describe GitlabSchema.types['Project'] do
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
namespace group statistics repository merge_requests merge_request issues namespace group statistics repository merge_requests merge_request issues
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
grafanaIntegration
] ]
is_expected.to include_graphql_fields(*expected_fields) is_expected.to include_graphql_fields(*expected_fields)
@ -31,45 +32,42 @@ describe GitlabSchema.types['Project'] do
describe 'issue field' do describe 'issue field' do
subject { described_class.fields['issue'] } subject { described_class.fields['issue'] }
it 'returns issue' do it { is_expected.to have_graphql_type(Types::IssueType) }
is_expected.to have_graphql_type(Types::IssueType) it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single) }
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single)
end
end end
describe 'issues field' do describe 'issues field' do
subject { described_class.fields['issues'] } subject { described_class.fields['issues'] }
it 'returns issue' do it { is_expected.to have_graphql_type(Types::IssueType.connection_type) }
is_expected.to have_graphql_type(Types::IssueType.connection_type) it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver)
end
end end
describe 'merge_requests field' do describe 'merge_requests field' do
subject { described_class.fields['mergeRequest'] } subject { described_class.fields['mergeRequest'] }
it 'returns merge requests' do it { is_expected.to have_graphql_type(Types::MergeRequestType) }
is_expected.to have_graphql_type(Types::MergeRequestType) it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single)
end
end end
describe 'merge_request field' do describe 'merge_request field' do
subject { described_class.fields['mergeRequests'] } subject { described_class.fields['mergeRequests'] }
it 'returns merge request' do it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver)
end
end end
describe 'snippets field' do describe 'snippets field' do
subject { described_class.fields['snippets'] } subject { described_class.fields['snippets'] }
it 'returns snippets' do it { is_expected.to have_graphql_type(Types::SnippetType.connection_type) }
is_expected.to have_graphql_type(Types::SnippetType.connection_type) it { is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver) }
is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver) end
end
describe 'grafana_integration field' do
subject { described_class.fields['grafanaIntegration'] }
it { is_expected.to have_graphql_type(Types::GrafanaIntegrationType) }
it { is_expected.to have_graphql_resolver(Resolvers::Projects::GrafanaIntegrationResolver) }
end end
end end

View file

@ -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

View file

@ -14,7 +14,9 @@ describe Gitlab::Graphql::Connections::FilterableArrayConnection do
describe '#paged_nodes' do describe '#paged_nodes' do
let(:paged_nodes) { subject.paged_nodes } let(:paged_nodes) { subject.paged_nodes }
it_behaves_like "connection with paged nodes" it_behaves_like "connection with paged nodes" do
let(:paged_nodes_size) { 3 }
end
context 'when callback filters some nodes' do context 'when callback filters some nodes' do
let(:callback) { proc { |nodes| nodes[1..-1] } } let(:callback) { proc { |nodes| nodes[1..-1] } }

View file

@ -232,7 +232,9 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
let_it_be(:all_nodes) { create_list(:project, 5) } let_it_be(:all_nodes) { create_list(:project, 5) }
let(:paged_nodes) { subject.paged_nodes } let(:paged_nodes) { subject.paged_nodes }
it_behaves_like "connection with paged nodes" it_behaves_like "connection with paged nodes" do
let(:paged_nodes_size) { 3 }
end
context 'when both are passed' do context 'when both are passed' do
let(:arguments) { { first: 2, last: 2 } } let(:arguments) { { first: 2, last: 2 } }

View 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

View file

@ -218,62 +218,4 @@ describe Sentry::Client do
it_behaves_like 'issues has correct length', 1 it_behaves_like 'issues has correct length', 1
end end
end end
describe '#issue_latest_event' do
let(:sample_response) do
Gitlab::Utils.deep_indifferent_access(
JSON.parse(fixture_file('sentry/issue_latest_event_sample_response.json'))
)
end
let(:issue_id) { '1234' }
let(:sentry_api_response) { sample_response }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
let(:sentry_request_url) { sentry_url + "/issues/#{issue_id}/events/latest/" }
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
subject { client.issue_latest_event(issue_id: issue_id) }
it_behaves_like 'calls sentry api'
it 'has correct return type' do
expect(subject).to be_a(Gitlab::ErrorTracking::ErrorEvent)
end
shared_examples 'assigns error tracking event correctly' do
using RSpec::Parameterized::TableSyntax
where(:event_object, :sentry_response) do
:issue_id | :groupID
:date_received | :dateReceived
end
with_them do
it { expect(subject.public_send(event_object)).to eq(sentry_api_response.dig(*sentry_response)) }
end
end
context 'error object created from sentry response' do
it_behaves_like 'assigns error tracking event correctly'
it 'parses the stack trace' do
expect(subject.stack_trace_entries).to be_a Array
expect(subject.stack_trace_entries).not_to be_empty
end
context 'error without stack trace' do
before do
sample_response['entries'] = []
stub_sentry_request(sentry_request_url, body: sample_response)
end
it_behaves_like 'assigns error tracking event correctly'
it 'returns an empty array for stack_trace_entries' do
expect(subject.stack_trace_entries).to eq []
end
end
end
end
end end

View file

@ -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

View file

@ -2,7 +2,7 @@
RSpec.shared_examples 'connection with paged nodes' do RSpec.shared_examples 'connection with paged nodes' do
it 'returns the collection limited to max page size' do it 'returns the collection limited to max page size' do
expect(paged_nodes.size).to eq(3) expect(paged_nodes.size).to eq(paged_nodes_size)
end end
it 'is a loaded memoized array' do it 'is a loaded memoized array' do
@ -22,7 +22,7 @@ RSpec.shared_examples 'connection with paged nodes' do
let(:arguments) { { last: 2 } } let(:arguments) { { last: 2 } }
it 'returns only the last elements' do it 'returns only the last elements' do
expect(paged_nodes).to contain_exactly(all_nodes[3], all_nodes[4]) expect(paged_nodes).to contain_exactly(*all_nodes.last(2))
end end
end end
end end

View file

@ -985,10 +985,10 @@
"@sentry/types" "5.10.0" "@sentry/types" "5.10.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sourcegraph/code-host-integration@^0.0.16": "@sourcegraph/code-host-integration@^0.0.18":
version "0.0.16" version "0.0.18"
resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.16.tgz#5a4b7c965298b5bae87a5bc4b013ba588db304ec" resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.18.tgz#814467cdbc94bbfee5768193acf89fdf404ca949"
integrity sha512-2ZMKr0BpkmCUkTuXXlqhZ6jtVcqz4N/Kz6B1fghi10XpiRZlDbVCYdpmeKb0ZyR+pHmuxNOmfJu9HbHc/7bPWA== integrity sha512-PNKR6QI2MK17YJ4BBmWBz7SVRPIJZKbGkQpdB9jHsvQhdSxspdpWFaMu+HKeg96zpStdLhFOcDPn1wlKbdGy+w==
"@types/anymatch@*": "@types/anymatch@*":
version "1.3.0" version "1.3.0"