Commit 91a22097 authored by Jose Vargas's avatar Jose Vargas

Add CI/CD analytics GraphQL types

This adds the GraphQL types and fields to
get the pipeline analytics for the Analytics
CI/CD page
parent 8c4d1fb8
# frozen_string_literal: true
module Resolvers
class ProjectPipelineStatisticsResolver < BaseResolver
type Types::Ci::AnalyticsType, null: true
def resolve
weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object)
monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object)
yearly_stats = Gitlab::Ci::Charts::YearChart.new(object)
pipeline_times = Gitlab::Ci::Charts::PipelineTime.new(object)
{
week_pipelines_labels: weekly_stats.labels,
week_pipelines_totals: weekly_stats.total,
week_pipelines_successful: weekly_stats.success,
month_pipelines_labels: monthly_stats.labels,
month_pipelines_totals: monthly_stats.total,
month_pipelines_successful: monthly_stats.success,
year_pipelines_labels: yearly_stats.labels,
year_pipelines_totals: yearly_stats.total,
year_pipelines_successful: yearly_stats.success,
pipeline_times_labels: pipeline_times.labels,
pipeline_times_values: pipeline_times.pipeline_times
}
end
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class AnalyticsType < BaseObject
graphql_name 'PipelineAnalytics'
field :week_pipelines_totals, [GraphQL::INT_TYPE], null: true,
description: 'Total weekly pipeline count'
field :week_pipelines_successful, [GraphQL::INT_TYPE], null: true,
description: 'Total weekly successful pipeline count'
field :week_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
description: 'Labels for the weekly pipeline count'
field :month_pipelines_totals, [GraphQL::INT_TYPE], null: true,
description: 'Total monthly pipeline count'
field :month_pipelines_successful, [GraphQL::INT_TYPE], null: true,
description: 'Total monthly successful pipeline count'
field :month_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
description: 'Labels for the monthly pipeline count'
field :year_pipelines_totals, [GraphQL::INT_TYPE], null: true,
description: 'Total yearly pipeline count'
field :year_pipelines_successful, [GraphQL::INT_TYPE], null: true,
description: 'Total yearly successful pipeline count'
field :year_pipelines_labels, [GraphQL::STRING_TYPE], null: true,
description: 'Labels for the yearly pipeline count'
field :pipeline_times_values, [GraphQL::INT_TYPE], null: true,
description: 'Pipeline times'
field :pipeline_times_labels, [GraphQL::STRING_TYPE], null: true,
description: 'Pipeline times labels'
end
end
end
...@@ -304,6 +304,13 @@ module Types ...@@ -304,6 +304,13 @@ module Types
description: 'Terraform states associated with the project', description: 'Terraform states associated with the project',
resolver: Resolvers::Terraform::StatesResolver resolver: Resolvers::Terraform::StatesResolver
field :pipeline_analytics, Types::Ci::AnalyticsType, null: true,
description: 'Pipeline analytics',
resolver: Resolvers::ProjectPipelineStatisticsResolver
field :total_pipeline_duration, GraphQL::INT_TYPE, null: true,
description: 'Total pipeline duration for all of the pipelines in a project'
def label(title:) def label(title:)
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
LabelsFinder LabelsFinder
...@@ -348,6 +355,10 @@ module Types ...@@ -348,6 +355,10 @@ module Types
project.container_repositories.size project.container_repositories.size
end end
def total_pipeline_duration
object.all_pipelines.total_duration
end
private private
def project def project
......
---
title: Add CI/CD analytics GraphQL types
merge_request: 49384
author:
type: added
...@@ -15502,6 +15502,63 @@ type Pipeline { ...@@ -15502,6 +15502,63 @@ type Pipeline {
userPermissions: PipelinePermissions! userPermissions: PipelinePermissions!
} }
type PipelineAnalytics {
"""
Labels for the monthly pipeline count
"""
monthPipelinesLabels: [String!]
"""
Total monthly successful pipeline count
"""
monthPipelinesSuccessful: [Int!]
"""
Total monthly pipeline count
"""
monthPipelinesTotals: [Int!]
"""
Pipeline times labels
"""
pipelineTimesLabels: [String!]
"""
Pipeline times
"""
pipelineTimesValues: [Int!]
"""
Labels for the weekly pipeline count
"""
weekPipelinesLabels: [String!]
"""
Total weekly successful pipeline count
"""
weekPipelinesSuccessful: [Int!]
"""
Total weekly pipeline count
"""
weekPipelinesTotals: [Int!]
"""
Labels for the yearly pipeline count
"""
yearPipelinesLabels: [String!]
"""
Total yearly successful pipeline count
"""
yearPipelinesSuccessful: [Int!]
"""
Total yearly pipeline count
"""
yearPipelinesTotals: [Int!]
}
""" """
Autogenerated input type of PipelineCancel Autogenerated input type of PipelineCancel
""" """
...@@ -16936,6 +16993,11 @@ type Project { ...@@ -16936,6 +16993,11 @@ type Project {
iid: ID! iid: ID!
): Pipeline ): Pipeline
"""
Pipeline analytics
"""
pipelineAnalytics: PipelineAnalytics
""" """
Build pipelines of the project Build pipelines of the project
""" """
...@@ -17347,6 +17409,11 @@ type Project { ...@@ -17347,6 +17409,11 @@ type Project {
last: Int last: Int
): TerraformStateConnection ): TerraformStateConnection
"""
Total pipeline duration for all of the pipelines in a project
"""
totalPipelineDuration: Int
""" """
Permissions for the current user on the resource Permissions for the current user on the resource
""" """
......
...@@ -46054,6 +46054,261 @@ ...@@ -46054,6 +46054,261 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "PipelineAnalytics",
"description": null,
"fields": [
{
"name": "monthPipelinesLabels",
"description": "Labels for the monthly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "monthPipelinesSuccessful",
"description": "Total monthly successful pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "monthPipelinesTotals",
"description": "Total monthly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pipelineTimesLabels",
"description": "Pipeline times labels",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pipelineTimesValues",
"description": "Pipeline times",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "weekPipelinesLabels",
"description": "Labels for the weekly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "weekPipelinesSuccessful",
"description": "Total weekly successful pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "weekPipelinesTotals",
"description": "Total weekly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "yearPipelinesLabels",
"description": "Labels for the yearly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "yearPipelinesSuccessful",
"description": "Total yearly successful pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "yearPipelinesTotals",
"description": "Total yearly pipeline count",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "PipelineCancelInput", "name": "PipelineCancelInput",
...@@ -49630,6 +49885,20 @@ ...@@ -49630,6 +49885,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "pipelineAnalytics",
"description": "Pipeline analytics",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "PipelineAnalytics",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "pipelines", "name": "pipelines",
"description": "Build pipelines of the project", "description": "Build pipelines of the project",
...@@ -50620,6 +50889,20 @@ ...@@ -50620,6 +50889,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "totalPipelineDuration",
"description": "Total pipeline duration for all of the pipelines in a project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "userPermissions", "name": "userPermissions",
"description": "Permissions for the current user on the resource", "description": "Permissions for the current user on the resource",
...@@ -2402,6 +2402,22 @@ Information about pagination in a connection.. ...@@ -2402,6 +2402,22 @@ Information about pagination in a connection..
| `user` | User | Pipeline user | | `user` | User | Pipeline user |
| `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource | | `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource |
### PipelineAnalytics
| Field | Type | Description |
| ----- | ---- | ----------- |
| `monthPipelinesLabels` | String! => Array | Labels for the monthly pipeline count |
| `monthPipelinesSuccessful` | Int! => Array | Total monthly successful pipeline count |
| `monthPipelinesTotals` | Int! => Array | Total monthly pipeline count |
| `pipelineTimesLabels` | String! => Array | Pipeline times labels |
| `pipelineTimesValues` | Int! => Array | Pipeline times |
| `weekPipelinesLabels` | String! => Array | Labels for the weekly pipeline count |
| `weekPipelinesSuccessful` | Int! => Array | Total weekly successful pipeline count |
| `weekPipelinesTotals` | Int! => Array | Total weekly pipeline count |
| `yearPipelinesLabels` | String! => Array | Labels for the yearly pipeline count |
| `yearPipelinesSuccessful` | Int! => Array | Total yearly successful pipeline count |
| `yearPipelinesTotals` | Int! => Array | Total yearly pipeline count |
### PipelineCancelPayload ### PipelineCancelPayload
Autogenerated return type of PipelineCancel. Autogenerated return type of PipelineCancel.
...@@ -2505,6 +2521,7 @@ Autogenerated return type of PipelineRetry. ...@@ -2505,6 +2521,7 @@ Autogenerated return type of PipelineRetry.
| `packages` | PackageConnection | Packages of the project | | `packages` | PackageConnection | Packages of the project |
| `path` | String! | Path of the project | | `path` | String! | Path of the project |
| `pipeline` | Pipeline | Build pipeline of the project | | `pipeline` | Pipeline | Build pipeline of the project |
| `pipelineAnalytics` | PipelineAnalytics | Pipeline analytics |
| `pipelines` | PipelineConnection | Build pipelines of the project | | `pipelines` | PipelineConnection | Build pipelines of the project |
| `printingMergeRequestLinkEnabled` | Boolean | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line | | `printingMergeRequestLinkEnabled` | Boolean | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line |
| `projectMembers` | MemberInterfaceConnection | Members of the project | | `projectMembers` | MemberInterfaceConnection | Members of the project |
...@@ -2535,6 +2552,7 @@ Autogenerated return type of PipelineRetry. ...@@ -2535,6 +2552,7 @@ Autogenerated return type of PipelineRetry.
| `suggestionCommitMessage` | String | The commit message used to apply merge request suggestions | | `suggestionCommitMessage` | String | The commit message used to apply merge request suggestions |
| `tagList` | String | List of project topics (not Git tags) | | `tagList` | String | List of project topics (not Git tags) |
| `terraformStates` | TerraformStateConnection | Terraform states associated with the project | | `terraformStates` | TerraformStateConnection | Terraform states associated with the project |
| `totalPipelineDuration` | Int | Total pipeline duration for all of the pipelines in a project |
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource | | `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the project | | `visibility` | String | Visibility of the project |
| `vulnerabilities` | VulnerabilityConnection | Vulnerabilities reported on the project | | `vulnerabilities` | VulnerabilityConnection | Vulnerabilities reported on the project |
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
specify do
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end
def resolve_statistics(project, args)
resolve(described_class, obj: project, args: args)
end
describe '#resolve' do
it 'returns the pipelines statistics for a given project' do
result = resolve_statistics(project, {})
expect(result.keys).to contain_exactly(
:week_pipelines_labels,
:week_pipelines_totals,
:week_pipelines_successful,
:month_pipelines_labels,
:month_pipelines_totals,
:month_pipelines_successful,
:year_pipelines_labels,
:year_pipelines_totals,
:year_pipelines_successful,
:pipeline_times_labels,
:pipeline_times_values
)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::AnalyticsType do
it 'exposes the expected fields' do
expected_fields = %i[
weekPipelinesTotals
weekPipelinesLabels
weekPipelinesSuccessful
monthPipelinesLabels
monthPipelinesTotals
monthPipelinesSuccessful
yearPipelinesLabels
yearPipelinesTotals
yearPipelinesSuccessful
pipelineTimesLabels
pipelineTimesValues
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
...@@ -31,6 +31,7 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -31,6 +31,7 @@ RSpec.describe GitlabSchema.types['Project'] do
container_expiration_policy service_desk_enabled service_desk_address container_expiration_policy service_desk_enabled service_desk_address
issue_status_counts terraform_states alert_management_integrations issue_status_counts terraform_states alert_management_integrations
container_repositories container_repositories_count container_repositories container_repositories_count
pipeline_analytics total_pipeline_duration
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
...@@ -186,4 +187,11 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -186,4 +187,11 @@ RSpec.describe GitlabSchema.types['Project'] do
end end
end end
end end
describe 'pipeline_analytics field' do
subject { described_class.fields['pipelineAnalytics'] }
it { is_expected.to have_graphql_type(Types::Ci::AnalyticsType) }
it { is_expected.to have_graphql_resolver(Resolvers::ProjectPipelineStatisticsResolver) }
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'rendering project pipeline statistics' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
let(:fields) do
<<~QUERY
weekPipelinesTotals
weekPipelinesLabels
monthPipelinesLabels
monthPipelinesTotals
yearPipelinesLabels
yearPipelinesTotals
QUERY
end
let(:query) do
graphql_query_for('project',
{ 'fullPath' => project.full_path },
query_graphql_field('pipelineAnalytics', {}, fields))
end
before do
project.add_maintainer(user)
end
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: user)
end
end
it "contains two arrays of 8 elements each for the week pipelines" do
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesTotals).length).to eq(8)
expect(graphql_data_at(:project, :pipelineAnalytics, :weekPipelinesLabels).length).to eq(8)
end
it "contains two arrays of 31 elements each for the month pipelines" do
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesTotals).length).to eq(31)
expect(graphql_data_at(:project, :pipelineAnalytics, :monthPipelinesLabels).length).to eq(31)
end
it "contains two arrays of 13 elements each for the year pipelines" do
post_graphql(query, current_user: user)
expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesTotals).length).to eq(13)
expect(graphql_data_at(:project, :pipelineAnalytics, :yearPipelinesLabels).length).to eq(13)
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