Commit 99841486 authored by Adam Hegyi's avatar Adam Hegyi

Merge branch '349880-add-identifier-to-summary-endpoint-items' into 'master'

Add identifier to VSA summary metrics

See merge request gitlab-org/gitlab!78470
parents 4f68cd1c f037767d
...@@ -92,9 +92,14 @@ export default { ...@@ -92,9 +92,14 @@ export default {
<template> <template>
<div class="gl-display-flex gl-flex-wrap" data-testid="vsa-time-metrics"> <div class="gl-display-flex gl-flex-wrap" data-testid="vsa-time-metrics">
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" /> <gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
<div v-for="metric in metrics" v-show="!isLoading" :key="metric.key" class="gl-my-6 gl-pr-9"> <div
v-for="metric in metrics"
v-show="!isLoading"
:key="metric.identifier"
class="gl-my-6 gl-pr-9"
>
<gl-single-stat <gl-single-stat
:id="metric.key" :id="metric.identifier"
:value="`${metric.value}`" :value="`${metric.value}`"
:title="metric.label" :title="metric.label"
:unit="metric.unit || ''" :unit="metric.unit || ''"
...@@ -104,7 +109,7 @@ export default { ...@@ -104,7 +109,7 @@ export default {
tabindex="0" tabindex="0"
@click="clickHandler(metric)" @click="clickHandler(metric)"
/> />
<metric-popover :metric="metric" :target="metric.key" /> <metric-popover :metric="metric" :target="metric.identifier" />
</div> </div>
</div> </div>
</template> </template>
...@@ -36,26 +36,45 @@ export const OVERVIEW_METRICS = { ...@@ -36,26 +36,45 @@ export const OVERVIEW_METRICS = {
RECENT_ACTIVITY: 'RECENT_ACTIVITY', RECENT_ACTIVITY: 'RECENT_ACTIVITY',
}; };
// Some content is duplicated due to backward compatibility.
// It will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/350614 in 14.9
export const METRICS_POPOVER_CONTENT = { export const METRICS_POPOVER_CONTENT = {
'lead-time': { 'lead-time': {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'), description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
}, },
lead_time: {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
},
'cycle-time': { 'cycle-time': {
description: s__( description: s__(
"ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.", "ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
), ),
}, },
cycle_time: {
description: s__(
"ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
),
},
'lead-time-for-changes': { 'lead-time-for-changes': {
description: s__( description: s__(
'ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period.', 'ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period.',
), ),
}, },
lead_time_for_changes: {
description: s__(
'ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period.',
),
},
issues: { description: s__('ValueStreamAnalytics|Number of new issues created.') },
'new-issue': { description: s__('ValueStreamAnalytics|Number of new issues created.') }, 'new-issue': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
'new-issues': { description: s__('ValueStreamAnalytics|Number of new issues created.') }, 'new-issues': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
deploys: { description: s__('ValueStreamAnalytics|Total number of deploys to production.') }, deploys: { description: s__('ValueStreamAnalytics|Total number of deploys to production.') },
'deployment-frequency': { 'deployment-frequency': {
description: s__('ValueStreamAnalytics|Average number of deployments to production per day.'), description: s__('ValueStreamAnalytics|Average number of deployments to production per day.'),
}, },
deployment_frequency: {
description: s__('ValueStreamAnalytics|Average number of deployments to production per day.'),
},
commits: { commits: {
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'), description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
}, },
......
...@@ -80,7 +80,7 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) => ...@@ -80,7 +80,7 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
* @typedef {Object} TransformedMetricData * @typedef {Object} TransformedMetricData
* @property {String} label - Title of the metric measured * @property {String} label - Title of the metric measured
* @property {String} value - String representing the decimal point value, e.g '1.5' * @property {String} value - String representing the decimal point value, e.g '1.5'
* @property {String} key - Slugified string based on the 'title' * @property {String} identifier - Slugified string based on the 'title' or the provided 'identifier' attribute
* @property {String} description - String to display for a description * @property {String} description - String to display for a description
* @property {String} unit - String representing the decimal point value, e.g '1.5' * @property {String} unit - String representing the decimal point value, e.g '1.5'
*/ */
...@@ -94,13 +94,13 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) => ...@@ -94,13 +94,13 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
*/ */
export const prepareTimeMetricsData = (data = [], popoverContent = {}) => export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
data.map(({ title: label, ...rest }) => { data.map(({ title: label, identifier, ...rest }) => {
const key = slugify(label); const metricIdentifier = identifier || slugify(label);
return { return {
...rest, ...rest,
label, label,
key, identifier: metricIdentifier,
description: popoverContent[key]?.description || '', description: popoverContent[metricIdentifier]?.description || '',
}; };
}); });
......
# frozen_string_literal: true # frozen_string_literal: true
class AnalyticsSummaryEntity < Grape::Entity class AnalyticsSummaryEntity < Grape::Entity
expose :identifier
expose :value, safe: true expose :value, safe: true
expose :title expose :title
expose :unit, if: { with_unit: true } expose :unit, if: { with_unit: true }
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
assign_stage_metadata assign_stage_metadata
end end
def identifier
self.class.name.demodulize.underscore.to_sym
end
def value def value
@value ||= Gitlab::CycleAnalytics::Summary::Value::PrettyNumeric.new(data_collector.median.days&.round(1)) @value ||= Gitlab::CycleAnalytics::Summary::Value::PrettyNumeric.new(data_collector.median.days&.round(1))
end end
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
@options = options @options = options
end end
def identifier
self.class.name.demodulize.underscore.to_sym
end
def title def title
raise NotImplementedError, "Expected #{self.name} to implement title" raise NotImplementedError, "Expected #{self.name} to implement title"
end end
......
...@@ -12,6 +12,10 @@ module Gitlab ...@@ -12,6 +12,10 @@ module Gitlab
n_('Deploy', 'Deploys', value.to_i) n_('Deploy', 'Deploys', value.to_i)
end end
def identifier
:deploys
end
def value def value
@value ||= ::Gitlab::CycleAnalytics::Summary::Value::PrettyNumeric.new(deployments_count) @value ||= ::Gitlab::CycleAnalytics::Summary::Value::PrettyNumeric.new(deployments_count)
end end
......
...@@ -14,6 +14,10 @@ module Gitlab ...@@ -14,6 +14,10 @@ module Gitlab
@options = options @options = options
end end
def identifier
:issues
end
def title def title
n_('New Issue', 'New Issues', value.to_i) n_('New Issue', 'New Issues', value.to_i)
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module Summary module Summary
class LeadTimeForChanges class LeadTimeForChanges < BaseTime
def initialize(stage:, current_user:, options:) def initialize(stage:, current_user:, options:)
@stage = stage @stage = stage
@current_user = current_user @current_user = current_user
......
...@@ -14,6 +14,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary d ...@@ -14,6 +14,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary d
group.add_owner(user) group.add_owner(user)
end end
describe '#identifier' do
it 'returns identifiers for each metric' do
identifiers = subject.pluck(:identifier)
expect(identifiers).to eq(%i[issues deploys deployment_frequency])
end
end
describe "#new_issues" do describe "#new_issues" do
context 'with from date' do context 'with from date' do
before do before do
......
...@@ -23,6 +23,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::StageTimeSummary do ...@@ -23,6 +23,13 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::StageTimeSummary do
group.add_owner(user) group.add_owner(user)
end end
describe '#identifier' do
it 'returns identifiers for each metric' do
identifiers = subject.pluck(:identifier)
expect(identifiers).to eq(%i[lead_time cycle_time])
end
end
context 'when the use_aggregated_data_collector option is given' do context 'when the use_aggregated_data_collector option is given' do
context 'when aggregated data is available yet' do context 'when aggregated data is available yet' do
it 'shows no value' do it 'shows no value' do
......
...@@ -9,6 +9,10 @@ module Gitlab ...@@ -9,6 +9,10 @@ module Gitlab
@options = options @options = options
end end
def identifier
self.class.name.demodulize.underscore.to_sym
end
def title def title
raise NotImplementedError, "Expected #{self.name} to implement title" raise NotImplementedError, "Expected #{self.name} to implement title"
end end
......
...@@ -4,6 +4,10 @@ module Gitlab ...@@ -4,6 +4,10 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Summary module Summary
class Commit < Base class Commit < Base
def identifier
:commits
end
def title def title
n_('Commit', 'Commits', value.to_i) n_('Commit', 'Commits', value.to_i)
end end
......
...@@ -4,6 +4,10 @@ module Gitlab ...@@ -4,6 +4,10 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Summary module Summary
class Deploy < Base class Deploy < Base
def identifier
:deploys
end
def title def title
n_('Deploy', 'Deploys', value.to_i) n_('Deploy', 'Deploys', value.to_i)
end end
......
...@@ -10,6 +10,10 @@ module Gitlab ...@@ -10,6 +10,10 @@ module Gitlab
@current_user = current_user @current_user = current_user
end end
def identifier
:issues
end
def title def title
n_('New Issue', 'New Issues', value.to_i) n_('New Issue', 'New Issues', value.to_i)
end end
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
"type": "object", "type": "object",
"required": ["value", "title"], "required": ["value", "title"],
"properties": { "properties": {
"identifier": {
"type": "string"
},
"value": { "value": {
"type": "string" "type": "string"
}, },
......
...@@ -92,17 +92,22 @@ describe('Value stream analytics utils', () => { ...@@ -92,17 +92,22 @@ describe('Value stream analytics utils', () => {
describe('prepareTimeMetricsData', () => { describe('prepareTimeMetricsData', () => {
let prepared; let prepared;
const [first, second] = metricsData; const [first, second] = metricsData;
const firstKey = slugify(first.title); delete second.identifier; // testing the case when identifier is missing
const secondKey = slugify(second.title);
const firstIdentifier = first.identifier;
const secondIdentifier = slugify(second.title);
beforeEach(() => { beforeEach(() => {
prepared = prepareTimeMetricsData([first, second], { prepared = prepareTimeMetricsData([first, second], {
[firstKey]: { description: 'Is a value that is good' }, [firstIdentifier]: { description: 'Is a value that is good' },
}); });
}); });
it('will add a `key` based on the title', () => { it('will add a `identifier` based on the title', () => {
expect(prepared).toMatchObject([{ key: firstKey }, { key: secondKey }]); expect(prepared).toMatchObject([
{ identifier: firstIdentifier },
{ identifier: secondIdentifier },
]);
}); });
it('will add a `label` key', () => { it('will add a `label` key', () => {
......
...@@ -15,6 +15,13 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do ...@@ -15,6 +15,13 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
let(:stage_summary) { described_class.new(project, **args).data } let(:stage_summary) { described_class.new(project, **args).data }
describe '#identifier' do
it 'returns identifiers for each metric' do
identifiers = stage_summary.pluck(:identifier)
expect(identifiers).to eq(%i[issues commits deploys deployment_frequency])
end
end
describe "#new_issues" do describe "#new_issues" do
subject { stage_summary.first } subject { stage_summary.first }
......
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