Commit c7e7ba57 authored by Allison Browne's avatar Allison Browne Committed by Mayra Cabrera

Add grafana integration to graphql api

Add grafana integration to the graphql api to act as
the backend for the grafana integration settings
with operations
parent 236ad879
# 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
# frozen_string_literal: true
module Types
class GrafanaIntegrationType < ::Types::BaseObject
graphql_name 'GrafanaIntegration'
authorize :admin_operations
field :id, GraphQL::ID_TYPE, null: false,
description: 'Internal ID of the Grafana integration'
field :grafana_url, GraphQL::STRING_TYPE, null: false,
description: 'Url for the Grafana host for the Grafana integration'
field :token, GraphQL::STRING_TYPE, null: false,
description: 'API token for the Grafana integration'
field :enabled, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates whether Grafana integration is enabled'
field :created_at, Types::TimeType, null: false,
description: 'Timestamp of the issue\'s creation'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of the issue\'s last activity'
end
end
......@@ -152,6 +152,12 @@ module Types
description: 'Detailed version of a Sentry error on the project',
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
field :grafana_integration,
Types::GrafanaIntegrationType,
null: true,
description: 'Grafana integration details for the project',
resolver: Resolvers::Projects::GrafanaIntegrationResolver
field :snippets,
Types::SnippetType.connection_type,
null: true,
......
# frozen_string_literal: true
class GrafanaIntegrationPolicy < BasePolicy
delegate { @subject.project }
end
---
title: Add fetching of Grafana Auth via the GraphQL API
merge_request: 21756
author:
type: changed
......@@ -2275,6 +2275,38 @@ type EpicTreeReorderPayload {
errors: [String!]!
}
type GrafanaIntegration {
"""
Timestamp of the issue's creation
"""
createdAt: Time!
"""
Indicates whether Grafana integration is enabled
"""
enabled: Boolean!
"""
Url for the Grafana host for the Grafana integration
"""
grafanaUrl: String!
"""
Internal ID of the Grafana integration
"""
id: ID!
"""
API token for the Grafana integration
"""
token: String!
"""
Timestamp of the issue's last activity
"""
updatedAt: Time!
}
type Group {
"""
Avatar URL of the group
......@@ -4612,6 +4644,11 @@ type Project {
"""
fullPath: ID!
"""
Grafana integration details for the project
"""
grafanaIntegration: GrafanaIntegration
"""
Group of the project
"""
......
......@@ -428,6 +428,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "grafanaIntegration",
"description": "Grafana integration details for the project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "GrafanaIntegration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "group",
"description": "Group of the project",
......@@ -15668,6 +15682,127 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "GrafanaIntegration",
"description": null,
"fields": [
{
"name": "createdAt",
"description": "Timestamp of the issue's creation",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enabled",
"description": "Indicates whether Grafana integration is enabled",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "grafanaUrl",
"description": "Url for the Grafana host for the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "Internal ID of the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "token",
"description": "API token for the Grafana integration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of the issue's last activity",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Metadata",
......
......@@ -317,6 +317,17 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
### GrafanaIntegration
| Name | Type | Description |
| --- | ---- | ---------- |
| `id` | ID! | Internal ID of the Grafana integration |
| `grafanaUrl` | String! | Url for the Grafana host for the Grafana integration |
| `token` | String! | API token for the Grafana integration |
| `enabled` | Boolean! | Indicates whether Grafana integration is enabled |
| `createdAt` | Time! | Timestamp of the issue's creation |
| `updatedAt` | Time! | Timestamp of the issue's last activity |
### Group
| Name | Type | Description |
......@@ -700,6 +711,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `mergeRequest` | MergeRequest | A single merge request of the project |
| `issue` | Issue | A single issue of the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `grafanaIntegration` | GrafanaIntegration | Grafana integration details for the project |
| `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. |
| `serviceDeskAddress` | String | E-mail address of the service desk. |
......
# 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
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['GrafanaIntegration'] do
let(:expected_fields) do
%i[
id
grafana_url
token
enabled
created_at
updated_at
]
end
it { expect(described_class.graphql_name).to eq('GrafanaIntegration') }
it { expect(described_class).to require_graphql_authorizations(:admin_operations) }
it { is_expected.to have_graphql_fields(*expected_fields) }
end
......@@ -23,6 +23,7 @@ describe GitlabSchema.types['Project'] do
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
namespace group statistics repository merge_requests merge_request issues
issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets
grafanaIntegration
]
is_expected.to include_graphql_fields(*expected_fields)
......@@ -31,45 +32,42 @@ describe GitlabSchema.types['Project'] do
describe 'issue field' do
subject { described_class.fields['issue'] }
it 'returns issue' do
is_expected.to have_graphql_type(Types::IssueType)
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single)
end
it { is_expected.to have_graphql_type(Types::IssueType) }
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver.single) }
end
describe 'issues field' do
subject { described_class.fields['issues'] }
it 'returns issue' do
is_expected.to have_graphql_type(Types::IssueType.connection_type)
is_expected.to have_graphql_resolver(Resolvers::IssuesResolver)
end
it { is_expected.to have_graphql_type(Types::IssueType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
end
describe 'merge_requests field' do
subject { described_class.fields['mergeRequest'] }
it 'returns merge requests' do
is_expected.to have_graphql_type(Types::MergeRequestType)
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single)
end
it { is_expected.to have_graphql_type(Types::MergeRequestType) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
end
describe 'merge_request field' do
subject { described_class.fields['mergeRequests'] }
it 'returns merge request' do
is_expected.to have_graphql_type(Types::MergeRequestType.connection_type)
is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver)
end
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
end
describe 'snippets field' do
subject { described_class.fields['snippets'] }
it 'returns snippets' do
is_expected.to have_graphql_type(Types::SnippetType.connection_type)
is_expected.to have_graphql_resolver(Resolvers::Projects::SnippetsResolver)
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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment