Commit df1f8cbb authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '35195-graphql-implementation-of-grafana-auth' into 'master'

Fetch Grafana Auth via GraphQL

See merge request gitlab-org/gitlab!21756
parents 236ad879 c7e7ba57
# 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