GraphQL: Add additional grouping support for PipelineAnalytics
In order to support the following UI in the Project CI/CD Analytics page, we need to refactor/improve Gitlab::Ci::Charts::Chart
and Resolvers::ProjectPipelineStatisticsResolver
to allow grouping by additional statuses (not just success
):
Proposal
The existing pipelineAnalytics
object is too verbose, with its flattened structure which doesn't take advantage of GraphQL's ability to structure data, and pass arguments to fields. I'd propose replacing the following code:
{
project(fullPath: "gitlab-org/playground") {
pipelineAnalytics {
monthlyPipelinesLabels
monthlyPipelinesTotals
monthlyPipelinesSuccessful
}
}
}
with
{
project(fullPath: "gitlab-org/playground") {
pipelineAnalytics {
monthlyPipelines {
labels
total
success: total(status: SUCCESS)
failed: total(status: FAILED) # New possibilities due to passing an argument
other: total(status: OTHER) #
}
}
}
}
weeklyPipelines
, monthlyPipelines
, yearlyPipelines
would all be instances of a new type (PipelineAnalyticsPeriod
) which would be initially introduced as alpha. In time, we'd deprecate the corresponding legacy fields.
Patch
diff --git a/app/graphql/resolvers/project_pipeline_statistics_resolver.rb b/app/graphql/resolvers/project_pipeline_statistics_resolver.rb
index 79d01b9bf2eb..22ee7e60bf9d 100644
--- a/app/graphql/resolvers/project_pipeline_statistics_resolver.rb
+++ b/app/graphql/resolvers/project_pipeline_statistics_resolver.rb
@@ -15,15 +15,20 @@ def resolve
pipeline_times = Gitlab::Ci::Charts::PipelineTime.new(object)
{
+ weekly_pipelines: weekly_stats,
+ monthly_pipelines: monthly_stats,
+ yearly_pipelines: yearly_stats,
+
week_pipelines_labels: weekly_stats.labels,
week_pipelines_totals: weekly_stats.total,
- week_pipelines_successful: weekly_stats.success,
+ week_pipelines_successful: weekly_stats.total_by_status[:success],
month_pipelines_labels: monthly_stats.labels,
month_pipelines_totals: monthly_stats.total,
- month_pipelines_successful: monthly_stats.success,
+ month_pipelines_successful: monthly_stats.total_by_status[:success],
year_pipelines_labels: yearly_stats.labels,
year_pipelines_totals: yearly_stats.total,
- year_pipelines_successful: yearly_stats.success,
+ year_pipelines_successful: yearly_stats.total_by_status[:success],
+
pipeline_times_labels: pipeline_times.labels,
pipeline_times_values: pipeline_times.pipeline_times
}
diff --git a/app/graphql/types/ci/analytics_type.rb b/app/graphql/types/ci/analytics_type.rb
index 63395d8a8d57..df5960ae87d8 100644
--- a/app/graphql/types/ci/analytics_type.rb
+++ b/app/graphql/types/ci/analytics_type.rb
@@ -6,28 +6,80 @@ module Ci
class AnalyticsType < BaseObject
graphql_name 'PipelineAnalytics'
+ field :weekly_pipelines, Types::Ci::AnalyticsPeriodType, null: true,
+ description: 'Weekly pipeline analytics.'
+ field :monthly_pipelines, Types::Ci::AnalyticsPeriodType, null: true,
+ description: 'Monthly pipeline analytics.'
+ field :yearly_pipelines, Types::Ci::AnalyticsPeriodType, null: true,
+ description: 'Yearly pipeline analytics.'
+
field :month_pipelines_labels, [GraphQL::Types::String], null: true,
- description: 'Labels for the monthly pipeline count.'
+ description: 'Labels for the monthly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.monthlyPipelines.labels',
+ milestone: '17.2'
+ }
field :month_pipelines_successful, [GraphQL::Types::Int], null: true,
- description: 'Total monthly successful pipeline count.'
+ description: 'Total monthly successful pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.monthlyPipelines.total(status: SUCCESS)',
+ milestone: '17.2'
+ }
field :month_pipelines_totals, [GraphQL::Types::Int], null: true,
- description: 'Total monthly pipeline count.'
+ description: 'Total monthly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.monthlyPipelines.total',
+ milestone: '17.2'
+ }
field :pipeline_times_labels, [GraphQL::Types::String], null: true,
description: 'Pipeline times labels.'
field :pipeline_times_values, [GraphQL::Types::Int], null: true,
description: 'Pipeline times.'
field :week_pipelines_labels, [GraphQL::Types::String], null: true,
- description: 'Labels for the weekly pipeline count.'
+ description: 'Labels for the weekly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.weeklyPipelines.labels',
+ milestone: '17.2'
+ }
field :week_pipelines_successful, [GraphQL::Types::Int], null: true,
- description: 'Total weekly successful pipeline count.'
+ description: 'Total weekly successful pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.weeklyPipelines.labels',
+ milestone: '17.2'
+ }
field :week_pipelines_totals, [GraphQL::Types::Int], null: true,
- description: 'Total weekly pipeline count.'
+ description: 'Total weekly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.weeklyPipelines.labels',
+ milestone: '17.2'
+ }
field :year_pipelines_labels, [GraphQL::Types::String], null: true,
- description: 'Labels for the yearly pipeline count.'
+ description: 'Labels for the yearly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.yearlyPipelines.labels',
+ milestone: '17.2'
+ }
field :year_pipelines_successful, [GraphQL::Types::Int], null: true,
- description: 'Total yearly successful pipeline count.'
+ description: 'Total yearly successful pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.yearlyPipelines.labels',
+ milestone: '17.2'
+ }
field :year_pipelines_totals, [GraphQL::Types::Int], null: true,
- description: 'Total yearly pipeline count.'
+ description: 'Total yearly pipeline count.',
+ deprecated: {
+ reason: :renamed,
+ replacement: 'PipelineAnalytics.yearlyPipelines.labels',
+ milestone: '17.2'
+ }
end
end
end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c1dfcccdbaf8..17f7933fa8db 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -27185,17 +27185,42 @@ Returns [`TestSuite`](#testsuite).
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="pipelineanalyticsmonthpipelineslabels"></a>`monthPipelinesLabels` | [`[String!]`](#string) | Labels for the monthly pipeline count. |
-| <a id="pipelineanalyticsmonthpipelinessuccessful"></a>`monthPipelinesSuccessful` | [`[Int!]`](#int) | Total monthly successful pipeline count. |
-| <a id="pipelineanalyticsmonthpipelinestotals"></a>`monthPipelinesTotals` | [`[Int!]`](#int) | Total monthly pipeline count. |
+| <a id="pipelineanalyticsmonthpipelineslabels"></a>`monthPipelinesLabels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.monthlyPipelines.labels`](#pipelineanalyticsmonthlypipelineslabels). |
+| <a id="pipelineanalyticsmonthpipelinessuccessful"></a>`monthPipelinesSuccessful` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.monthlyPipelines.total(status: SUCCESS)`](#pipelineanalyticsmonthlypipelinestotal(status: success)). |
+| <a id="pipelineanalyticsmonthpipelinestotals"></a>`monthPipelinesTotals` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.monthlyPipelines.total`](#pipelineanalyticsmonthlypipelinestotal). |
+| <a id="pipelineanalyticsmonthlypipelines"></a>`monthlyPipelines` | [`PipelineAnalyticsPeriod`](#pipelineanalyticsperiod) | Monthly pipeline analytics. |
| <a id="pipelineanalyticspipelinetimeslabels"></a>`pipelineTimesLabels` | [`[String!]`](#string) | Pipeline times labels. |
| <a id="pipelineanalyticspipelinetimesvalues"></a>`pipelineTimesValues` | [`[Int!]`](#int) | Pipeline times. |
-| <a id="pipelineanalyticsweekpipelineslabels"></a>`weekPipelinesLabels` | [`[String!]`](#string) | Labels for the weekly pipeline count. |
-| <a id="pipelineanalyticsweekpipelinessuccessful"></a>`weekPipelinesSuccessful` | [`[Int!]`](#int) | Total weekly successful pipeline count. |
-| <a id="pipelineanalyticsweekpipelinestotals"></a>`weekPipelinesTotals` | [`[Int!]`](#int) | Total weekly pipeline count. |
-| <a id="pipelineanalyticsyearpipelineslabels"></a>`yearPipelinesLabels` | [`[String!]`](#string) | Labels for the yearly pipeline count. |
-| <a id="pipelineanalyticsyearpipelinessuccessful"></a>`yearPipelinesSuccessful` | [`[Int!]`](#int) | Total yearly successful pipeline count. |
-| <a id="pipelineanalyticsyearpipelinestotals"></a>`yearPipelinesTotals` | [`[Int!]`](#int) | Total yearly pipeline count. |
+| <a id="pipelineanalyticsweekpipelineslabels"></a>`weekPipelinesLabels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.weeklyPipelines.labels`](#pipelineanalyticsweeklypipelineslabels). |
+| <a id="pipelineanalyticsweekpipelinessuccessful"></a>`weekPipelinesSuccessful` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.weeklyPipelines.labels`](#pipelineanalyticsweeklypipelineslabels). |
+| <a id="pipelineanalyticsweekpipelinestotals"></a>`weekPipelinesTotals` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.weeklyPipelines.labels`](#pipelineanalyticsweeklypipelineslabels). |
+| <a id="pipelineanalyticsweeklypipelines"></a>`weeklyPipelines` | [`PipelineAnalyticsPeriod`](#pipelineanalyticsperiod) | Weekly pipeline analytics. |
+| <a id="pipelineanalyticsyearpipelineslabels"></a>`yearPipelinesLabels` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.yearlyPipelines.labels`](#pipelineanalyticsyearlypipelineslabels). |
+| <a id="pipelineanalyticsyearpipelinessuccessful"></a>`yearPipelinesSuccessful` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.yearlyPipelines.labels`](#pipelineanalyticsyearlypipelineslabels). |
+| <a id="pipelineanalyticsyearpipelinestotals"></a>`yearPipelinesTotals` **{warning-solid}** | [`[Int!]`](#int) | **Deprecated** in GitLab 17.2. This was renamed. Use: [`PipelineAnalytics.yearlyPipelines.labels`](#pipelineanalyticsyearlypipelineslabels). |
+| <a id="pipelineanalyticsyearlypipelines"></a>`yearlyPipelines` | [`PipelineAnalyticsPeriod`](#pipelineanalyticsperiod) | Yearly pipeline analytics. |
+
+### `PipelineAnalyticsPeriod`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="pipelineanalyticsperiodlabels"></a>`labels` | [`[String!]`](#string) | Labels for the pipeline count. |
+
+#### Fields with arguments
+
+##### `PipelineAnalyticsPeriod.total`
+
+Total pipeline count.
+
+Returns [`[Int!]`](#int).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="pipelineanalyticsperiodtotalstatus"></a>`status` | [`CiAnalyticsJobStatus`](#cianalyticsjobstatus) | Filter totals by status. |
### `PipelineArtifactRegistry`
@@ -33350,6 +33375,14 @@ Status of a merge train's car.
| <a id="carstatusskip_merged"></a>`SKIP_MERGED` | Car's status: skip_merged. |
| <a id="carstatusstale"></a>`STALE` | Car's status: stale. |
+### `CiAnalyticsJobStatus`
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="cianalyticsjobstatusfailed"></a>`FAILED` | A job that failed. |
+| <a id="cianalyticsjobstatusother"></a>`OTHER` | A job that was canceled or skipped. |
+| <a id="cianalyticsjobstatussuccess"></a>`SUCCESS` | A job that is successful. |
+
### `CiCatalogResourceScope`
Values for scoping catalog resources.
diff --git a/lib/gitlab/ci/charts.rb b/lib/gitlab/ci/charts.rb
index 797193a6be57..72f5afc5b99c 100644
--- a/lib/gitlab/ci/charts.rb
+++ b/lib/gitlab/ci/charts.rb
@@ -4,12 +4,12 @@ module Gitlab
module Ci
module Charts
class Chart
- attr_reader :from, :to, :labels, :total, :success, :project, :pipeline_times
+ attr_reader :from, :to, :labels, :total, :total_by_status, :project, :pipeline_times
def initialize(project)
@labels = []
@total = []
- @success = []
+ @total_by_status = {}
@pipeline_times = []
@project = project
@@ -26,30 +26,37 @@ def collect
.where(::Ci::Pipeline.arel_table['created_at'].gteq(@from))
.where(::Ci::Pipeline.arel_table['created_at'].lteq(@to))
- totals_count = grouped_count(query)
- success_count = grouped_count(query.success)
+ daily_group_sql = "date_trunc('#{interval}', #{::Ci::Pipeline.table_name}.created_at)"
+ totals_count =
+ query
+ .group(daily_group_sql)
+ .count(:created_at)
+ .transform_keys { |date| date.strftime(@format) }
+ count_by_status =
+ query
+ # .where(status: ::Ci::Pipeline::UNLOCKABLE_STATUSES)
+ .group('status', daily_group_sql)
+ .count(:created_at)
+ .group_by { |status, _date| status.shift.to_sym }
+ .transform_values { |value| value.to_h.transform_keys { |key| key.first.strftime(@format) } }
current = @from
while current <= @to
label = current.strftime(@format)
@labels << label
@total << (totals_count[label] || 0)
- @success << (success_count[label] || 0)
+ count_by_status.each do |status, counts|
+ status = :other unless status.in?(%i[success failed])
+
+ @total_by_status[status] ||= []
+ @total_by_status[status] << (counts[label] || 0)
+ end
current += interval_step
end
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- def grouped_count(query)
- query
- .group("date_trunc('#{interval}', #{::Ci::Pipeline.table_name}.created_at)")
- .count(:created_at)
- .transform_keys { |date| date.strftime(@format) }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def interval_step
@interval_step ||= 1.public_send(interval) # rubocop: disable GitlabSecurity/PublicSend
end
diff --git a/spec/graphql/types/ci/analytics_type_spec.rb b/spec/graphql/types/ci/analytics_type_spec.rb
index c8462d407698..87307ff13437 100644
--- a/spec/graphql/types/ci/analytics_type_spec.rb
+++ b/spec/graphql/types/ci/analytics_type_spec.rb
@@ -5,6 +5,10 @@
RSpec.describe Types::Ci::AnalyticsType do
it 'exposes the expected fields' do
expected_fields = %i[
+ weeklyPipelines
+ monthlyPipelines
+ yearlyPipelines
+
weekPipelinesTotals
weekPipelinesLabels
weekPipelinesSuccessful
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index 3a82d0588194..65ce9835297f 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Charts do
+RSpec.describe Gitlab::Ci::Charts, feature_category: :fleet_visibility do
context 'yearchart' do
let(:project) { create(:project) }
let(:chart) { Gitlab::Ci::Charts::YearChart.new(project) }
This page may contain information related to upcoming products, features and functionality. It is important to note that the information presented is for informational purposes only, so please do not rely on the information for purchasing or planning purposes. Just like with all projects, the items mentioned on the page are subject to change or delay, and the development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.