diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /lib/gitlab/cycle_analytics | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'lib/gitlab/cycle_analytics')
26 files changed, 1 insertions, 751 deletions
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb deleted file mode 100644 index 6c6dd90e450..00000000000 --- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class BaseEventFetcher - include BaseQuery - include GroupProjectsProvider - - attr_reader :projections, :query, :stage, :options - - MAX_EVENTS = 50 - - def initialize(stage:, options:) - @stage = stage - @options = options - end - - def fetch - update_author! - - event_result.map do |event| - serialize(event) if has_permission?(event['id']) - end.compact - end - - def order - @order || default_order - end - - private - - def update_author! - return unless event_result.any? && event_result.first['author_id'] - - Updater.update!(event_result, from: 'author_id', to: 'author', klass: User) - end - - def event_result - @event_result ||= ActiveRecord::Base.connection.exec_query(events_query.to_sql).to_a - end - - def events_query - diff_fn = subtract_datetimes_diff(base_query, options[:start_time_attrs], options[:end_time_attrs]) - - base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc).take(MAX_EVENTS) - end - - def default_order - [options[:start_time_attrs]].flatten.first - end - - def serialize(_event) - raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") - end - - def has_permission?(id) - allowed_ids.nil? || allowed_ids.include?(id.to_i) - end - - def allowed_ids - @allowed_ids ||= allowed_ids_finder_class - .new(options[:current_user], allowed_ids_source) - .execute.where(id: event_result_ids).pluck(:id) - end - - def event_result_ids - event_result.map { |event| event['id'] } - end - - def allowed_ids_source - group ? { group_id: group.id, include_subgroups: true } : { project_id: project.id } - end - - def serialization_context - {} - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb deleted file mode 100644 index 6aedbf64f26..00000000000 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module BaseQuery - include MetricsTables - include Gitlab::Database::Median - include Gitlab::Database::DateTime - - private - - def base_query - @base_query ||= stage_query(projects.map(&:id)) - end - - def stage_query(project_ids) - query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) - .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) - .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) - .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) - .project(issue_table[:project_id].as("project_id")) - .project(projects_table[:path].as("project_path")) - .project(routes_table[:path].as("namespace_path")) - - query = limit_query(query, project_ids) - query = limit_query_by_date_range(query) - - # Load merge_requests - - query = load_merge_requests(query) - - query - end - - def limit_query(query, project_ids) - query.where(issue_table[:project_id].in(project_ids)) - .where(routes_table[:source_type].eq('Namespace')) - end - - def limit_query_by_date_range(query) - query = query.where(issue_table[:created_at].gteq(options[:from])) - query = query.where(issue_table[:created_at].lteq(options[:to])) if options[:to] - query - end - - def load_merge_requests(query) - query.join(mr_table, Arel::Nodes::OuterJoin) - .on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])) - .join(mr_metrics_table) - .on(mr_table[:id].eq(mr_metrics_table[:merge_request_id])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb deleted file mode 100644 index 06f0cbed147..00000000000 --- a/lib/gitlab/cycle_analytics/base_stage.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class BaseStage - include BaseQuery - include GroupProjectsProvider - - attr_reader :options - - def initialize(options:) - @options = options - end - - def events - event_fetcher.fetch - end - - def as_json(serializer: AnalyticsStageSerializer) - serializer.new.represent(self) - end - - def title - raise NotImplementedError.new("Expected #{self.name} to implement title") - end - - def project_median - return if project.nil? - - BatchLoader.for(project.id).batch(key: name) do |project_ids, loader| - if project_ids.one? - loader.call(project.id, median_query(project_ids)) - else - begin - median_datetimes(cte_table, interval_query(project_ids), name, :project_id)&.each do |project_id, median| - loader.call(project_id, median) - end - rescue NotSupportedError - {} - end - end - end - end - - def group_median - median_query(projects.map(&:id)) - end - - def median_query(project_ids) - # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). - # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). - # We compute the (end_time - start_time) interval, and give it an alias based on the current - # value stream analytics stage. - - median_datetime(cte_table, interval_query(project_ids), name) - end - - def name - raise NotImplementedError.new("Expected #{self.name} to implement name") - end - - def cte_table - Arel::Table.new("cte_table_for_#{name}") - end - - def interval_query(project_ids) - Arel::Nodes::As.new(cte_table, - subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s)) - end - - private - - def event_fetcher - @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(stage: name, - options: event_options) - end - - def event_options - options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/builds_event_helper.rb b/lib/gitlab/cycle_analytics/builds_event_helper.rb deleted file mode 100644 index c39d41578e9..00000000000 --- a/lib/gitlab/cycle_analytics/builds_event_helper.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module BuildsEventHelper - def initialize(...) - @projections = [build_table[:id]] - @order = build_table[:created_at] - - super(...) - end - - def fetch - Updater.update!(event_result, from: 'id', to: 'build', klass: ::Ci::Build) - - super - end - - def events_query - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - - super - end - - private - - def allowed_ids - nil - end - - def serialize(event) - AnalyticsBuildSerializer.new.represent(event['build']) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb deleted file mode 100644 index 790bf32c6c7..00000000000 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class CodeEventFetcher < BaseEventFetcher - include CodeHelper - - def initialize(...) - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state_id], - mr_table[:author_id]] - @order = mr_table[:created_at] - - super(...) - end - - private - - def serialize(event) - AnalyticsMergeRequestSerializer.new(serialization_context).represent(event) - end - - def allowed_ids_finder_class - MergeRequestsFinder - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/code_helper.rb b/lib/gitlab/cycle_analytics/code_helper.rb deleted file mode 100644 index 8f28bdd2502..00000000000 --- a/lib/gitlab/cycle_analytics/code_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module CodeHelper - def stage_query(project_ids) - super(project_ids).where(mr_table[:created_at].gteq(issue_metrics_table[:first_mentioned_in_commit_at])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb deleted file mode 100644 index 89a6430221c..00000000000 --- a/lib/gitlab/cycle_analytics/code_stage.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class CodeStage < BaseStage - include CodeHelper - - def start_time_attrs - @start_time_attrs ||= issue_metrics_table[:first_mentioned_in_commit_at] - end - - def end_time_attrs - @end_time_attrs ||= mr_table[:created_at] - end - - def name - :code - end - - def title - s_('CycleAnalyticsStage|Code') - end - - def legend - _("Related Merge Requests") - end - - def description - _("Time until first merge request") - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/event_fetcher.rb b/lib/gitlab/cycle_analytics/event_fetcher.rb deleted file mode 100644 index 04f4b4f053f..00000000000 --- a/lib/gitlab/cycle_analytics/event_fetcher.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module EventFetcher - def self.[](stage_name) - CycleAnalytics.const_get("#{stage_name.to_s.camelize}EventFetcher", false) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb deleted file mode 100644 index fd04ec090b3..00000000000 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class IssueEventFetcher < BaseEventFetcher - include IssueHelper - - def initialize(...) - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - - super(...) - end - - private - - def serialize(event) - AnalyticsIssueSerializer.new(serialization_context).represent(event) - end - - def allowed_ids_finder_class - IssuesFinder - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb deleted file mode 100644 index f6f85b84ed8..00000000000 --- a/lib/gitlab/cycle_analytics/issue_helper.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module IssueHelper - def stage_query(project_ids) - query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) - .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) - .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) - .project(issue_table[:project_id].as("project_id")) - .project(projects_table[:path].as("project_path")) - .project(routes_table[:path].as("namespace_path")) - - query = limit_query(query, project_ids) - limit_query_by_date_range(query) - end - - def limit_query(query, project_ids) - query.where(issue_table[:project_id].in(project_ids)) - .where(routes_table[:source_type].eq('Namespace')) - .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb deleted file mode 100644 index 738cb3eba03..00000000000 --- a/lib/gitlab/cycle_analytics/issue_stage.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class IssueStage < BaseStage - include IssueHelper - - def start_time_attrs - @start_time_attrs ||= issue_table[:created_at] - end - - def end_time_attrs - @end_time_attrs ||= [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]] - end - - def name - :issue - end - - def title - s_('CycleAnalyticsStage|Issue') - end - - def legend - _("Related Issues") - end - - def description - _("Time before an issue gets scheduled") - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb index 0e094fabb01..9164c8b1bff 100644 --- a/lib/gitlab/cycle_analytics/permissions.rb +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -23,7 +23,7 @@ module Gitlab end def get - ::CycleAnalytics::LevelBase::STAGES.each do |stage| + Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names.each do |stage| @stage_permission_hash[stage] = authorized_stage?(stage) end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb deleted file mode 100644 index 4d98d589e46..00000000000 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class PlanEventFetcher < BaseEventFetcher - include PlanHelper - - def initialize(...) - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - - super(...) - end - - private - - def serialize(event) - AnalyticsIssueSerializer.new(serialization_context).represent(event) - end - - def allowed_ids_finder_class - IssuesFinder - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb deleted file mode 100644 index af4bf6ed3eb..00000000000 --- a/lib/gitlab/cycle_analytics/plan_helper.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module PlanHelper - def stage_query(project_ids) - query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) - .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id])) - .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id])) - .project(issue_table[:project_id].as("project_id")) - .project(projects_table[:path].as("project_path")) - .project(routes_table[:path].as("namespace_path")) - .where(issue_table[:project_id].in(project_ids)) - .where(routes_table[:source_type].eq('Namespace')) - query = limit_query(query) - - limit_query_by_date_range(query) - end - - def limit_query(query) - query.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) - .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil)) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb deleted file mode 100644 index 0b27d114f52..00000000000 --- a/lib/gitlab/cycle_analytics/plan_stage.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class PlanStage < BaseStage - include PlanHelper - - def start_time_attrs - @start_time_attrs ||= [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]] - end - - def end_time_attrs - @end_time_attrs ||= issue_metrics_table[:first_mentioned_in_commit_at] - end - - def name - :plan - end - - def title - s_('CycleAnalyticsStage|Plan') - end - - def legend - _("Related Issues") - end - - def description - _("Time before an issue starts implementation") - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb deleted file mode 100644 index 5fa286bd3df..00000000000 --- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class ProductionEventFetcher < BaseEventFetcher - include ProductionHelper - - def initialize(...) - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id], - routes_table[:path]] - - super(...) - end - - private - - def serialize(event) - AnalyticsIssueSerializer.new(serialization_context).represent(event) - end - - def allowed_ids_finder_class - IssuesFinder - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/production_helper.rb b/lib/gitlab/cycle_analytics/production_helper.rb deleted file mode 100644 index 778757a9ede..00000000000 --- a/lib/gitlab/cycle_analytics/production_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module ProductionHelper - def stage_query(project_ids) - super(project_ids) - .where(mr_metrics_table[:first_deployed_to_production_at] - .gteq(options[:from])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb deleted file mode 100644 index 0b7d160c7de..00000000000 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class ReviewEventFetcher < BaseEventFetcher - include ReviewHelper - - def initialize(...) - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state_id], - mr_table[:author_id]] - - super(...) - end - - private - - def serialize(event) - AnalyticsMergeRequestSerializer.new(serialization_context).represent(event) - end - - def allowed_ids_finder_class - MergeRequestsFinder - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/review_helper.rb b/lib/gitlab/cycle_analytics/review_helper.rb deleted file mode 100644 index c53249652b5..00000000000 --- a/lib/gitlab/cycle_analytics/review_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module ReviewHelper - def stage_query(project_ids) - super(project_ids).where(mr_metrics_table[:merged_at].not_eq(nil)) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb deleted file mode 100644 index e9df8cd5a05..00000000000 --- a/lib/gitlab/cycle_analytics/review_stage.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class ReviewStage < BaseStage - include ReviewHelper - - def start_time_attrs - @start_time_attrs ||= mr_table[:created_at] - end - - def end_time_attrs - @end_time_attrs ||= mr_metrics_table[:merged_at] - end - - def name - :review - end - - def title - s_('CycleAnalyticsStage|Review') - end - - def legend - _("Related Merged Requests") - end - - def description - _("Time between merge request creation and merge/close") - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/stage.rb b/lib/gitlab/cycle_analytics/stage.rb deleted file mode 100644 index 5cfd9ea4730..00000000000 --- a/lib/gitlab/cycle_analytics/stage.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module Stage - def self.[](stage_name) - CycleAnalytics.const_get("#{stage_name.to_s.camelize}Stage", false) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb deleted file mode 100644 index 1454a1a33eb..00000000000 --- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class StagingEventFetcher < BaseEventFetcher - include ProductionHelper - include BuildsEventHelper - end - end -end diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb deleted file mode 100644 index e03627c6cd1..00000000000 --- a/lib/gitlab/cycle_analytics/staging_stage.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class StagingStage < BaseStage - include ProductionHelper - - def start_time_attrs - @start_time_attrs ||= mr_metrics_table[:merged_at] - end - - def end_time_attrs - @end_time_attrs ||= mr_metrics_table[:first_deployed_to_production_at] - end - - def name - :staging - end - - def title - s_('CycleAnalyticsStage|Staging') - end - - def legend - _("Related Deployed Jobs") - end - - def description - _("From merge request merge until deploy to production") - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/test_event_fetcher.rb b/lib/gitlab/cycle_analytics/test_event_fetcher.rb deleted file mode 100644 index 2fa44b1b364..00000000000 --- a/lib/gitlab/cycle_analytics/test_event_fetcher.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class TestEventFetcher < BaseEventFetcher - include TestHelper - include BuildsEventHelper - end - end -end diff --git a/lib/gitlab/cycle_analytics/test_helper.rb b/lib/gitlab/cycle_analytics/test_helper.rb deleted file mode 100644 index d9124d62c7c..00000000000 --- a/lib/gitlab/cycle_analytics/test_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - module TestHelper - def stage_query(project_ids) - if branch - super(project_ids).where(build_table[:ref].eq(branch)) - else - super(project_ids) - end - end - - private - - def branch - @branch ||= options[:branch] - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb deleted file mode 100644 index 4787a906c07..00000000000 --- a/lib/gitlab/cycle_analytics/test_stage.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CycleAnalytics - class TestStage < BaseStage - include TestHelper - - def start_time_attrs - @start_time_attrs ||= mr_metrics_table[:latest_build_started_at] - end - - def end_time_attrs - @end_time_attrs ||= mr_metrics_table[:latest_build_finished_at] - end - - def name - :test - end - - def title - s_('CycleAnalyticsStage|Test') - end - - def legend - _("Related Jobs") - end - - def description - _("Total test time for all commits/merges") - end - end - end -end |