Commit 20c17ab2 authored by Martin Wortschack's avatar Martin Wortschack Committed by Brandon Labuschagne

Move VSA metrics to shared analytics

Moves the value_stream_metrics component and its dependencies
to the shared analytics folder

Changelog: changed
EE: true
parent cd4260c0
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { redirectTo } from '~/lib/utils/url_utility';
import MetricPopover from './metric_popover.vue';
export default {
name: 'MetricTile',
components: {
GlSingleStat,
MetricPopover,
},
props: {
metric: {
type: Object,
required: true,
},
},
computed: {
decimalPlaces() {
const parsedFloat = parseFloat(this.metric.value);
return Number.isNaN(parsedFloat) || Number.isInteger(parsedFloat) ? 0 : 1;
},
hasLinks() {
return this.metric.links?.length && this.metric.links[0].url;
},
},
methods: {
clickHandler({ links }) {
if (this.hasLinks) {
redirectTo(links[0].url);
}
},
},
};
</script>
<template>
<div v-bind="$attrs">
<gl-single-stat
:id="metric.identifier"
:value="`${metric.value}`"
:title="metric.label"
:unit="metric.unit || ''"
:should-animate="true"
:animation-decimal-places="decimalPlaces"
:class="{ 'gl-hover-cursor-pointer': hasLinks }"
tabindex="0"
@click="clickHandler(metric)"
/>
<metric-popover :metric="metric" :target="metric.identifier" />
</div>
</template>
import { masks } from 'dateformat';
import { s__ } from '~/locale';
export const DATE_RANGE_LIMIT = 180;
export const OFFSET_DATE_BY_ONE = 1;
......@@ -11,3 +12,47 @@ export const dateFormats = {
defaultDateTime: 'mmm d, yyyy h:MMtt',
month: 'mmmm',
};
// 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 = {
'lead-time': {
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': {
description: s__(
"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': {
description: s__(
'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-issues': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
deploys: { description: s__('ValueStreamAnalytics|Total number of deploys to production.') },
'deployment-frequency': {
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: {
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
},
};
import dateFormat from 'dateformat';
import { hideFlash } from '~/flash';
import { slugify } from '~/lib/utils/text_utility';
import { urlQueryToFilter } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from './constants';
......@@ -69,3 +71,28 @@ export const getDataZoomOption = ({
};
});
};
export const removeFlash = (type = 'alert') => {
const flashEl = document.querySelector(`.flash-${type}`);
if (flashEl) {
hideFlash(flashEl);
}
};
/**
* Prepares metric data to be rendered in the metric_card component
*
* @param {MetricData[]} data - The metric data to be rendered
* @param {Object} popoverContent - Key value pair of data to display in the popover
* @returns {TransformedMetricData[]} An array of metrics ready to render in the metric_card
*/
export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
data.map(({ title: label, identifier, ...rest }) => {
const metricIdentifier = identifier || slugify(label);
return {
...rest,
label,
identifier: metricIdentifier,
description: popoverContent[metricIdentifier]?.description || '',
};
});
......@@ -2,11 +2,11 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { __ } from '~/locale';
import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants';
......
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { redirectTo } from '~/lib/utils/url_utility';
import MetricPopover from './metric_popover.vue';
import MetricPopover from '~/analytics/shared/components/metric_popover.vue';
export default {
name: 'MetricTile',
......
......@@ -36,50 +36,6 @@ export const OVERVIEW_METRICS = {
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 = {
'lead-time': {
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': {
description: s__(
"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': {
description: s__(
'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-issues': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
deploys: { description: s__('ValueStreamAnalytics|Total number of deploys to production.') },
'deployment-frequency': {
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: {
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
},
};
export const SUMMARY_METRICS_REQUEST = [
{ endpoint: METRIC_TYPE_SUMMARY, name: __('recent activity'), request: getValueStreamMetrics },
];
......
import { hideFlash } from '~/flash';
import { parseSeconds } from '~/lib/utils/datetime_utility';
import { formatTimeAsSummary } from '~/lib/utils/datetime/date_format_utility';
import { slugify } from '~/lib/utils/text_utility';
export const removeFlash = (type = 'alert') => {
const flashEl = document.querySelector(`.flash-${type}`);
if (flashEl) {
hideFlash(flashEl);
}
};
/**
* Takes the stages and median data, combined with the selected stage, to build an
......@@ -85,25 +76,6 @@ export const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
* @property {String} unit - String representing the decimal point value, e.g '1.5'
*/
/**
* Prepares metric data to be rendered in the metric_card component
*
* @param {MetricData[]} data - The metric data to be rendered
* @param {Object} popoverContent - Key value pair of data to display in the popover
* @returns {TransformedMetricData[]} An array of metrics ready to render in the metric_card
*/
export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
data.map(({ title: label, identifier, ...rest }) => {
const metricIdentifier = identifier || slugify(label);
return {
...rest,
label,
identifier: metricIdentifier,
description: popoverContent[metricIdentifier]?.description || '',
};
});
const extractFeatures = (gon) => ({
cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups),
});
......
<script>
import { GlEmptyState } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { METRICS_REQUESTS } from '../constants';
......
......@@ -10,7 +10,7 @@ import {
import { debounce } from 'lodash';
import { mapGetters } from 'vuex';
import Api from 'ee/api';
import { removeFlash } from '~/cycle_analytics/utils';
import { removeFlash } from '~/analytics/shared/utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { DATA_REFETCH_DELAY } from '../../shared/constants';
......
<script>
import { GlDropdownDivider, GlSegmentedControl, GlIcon, GlSprintf } from '@gitlab/ui';
import { removeFlash } from '~/cycle_analytics/utils';
import { removeFlash } from '~/analytics/shared/utils';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import {
......
import Api from 'ee/api';
import { removeFlash } from '~/cycle_analytics/utils';
import { removeFlash } from '~/analytics/shared/utils';
import createFlash from '~/flash';
import httpStatus from '~/lib/utils/http_status';
import { __ } from '~/locale';
......
<script>
import * as Sentry from '@sentry/browser';
import * as DoraApi from 'ee/api/dora_api';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { SUMMARY_METRICS_REQUEST } from '~/cycle_analytics/constants';
import CiCdAnalyticsCharts from '~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue';
import DoraChartHeader from './dora_chart_header.vue';
......
......@@ -17,11 +17,11 @@ import {
selectedProjects,
initialPaginationQuery,
} from 'jest/cycle_analytics/mock_data';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import {
OVERVIEW_STAGE_ID,
I18N_VSA_ERROR_STAGES,
......
......@@ -6,7 +6,7 @@ import lastMonthData from 'test_fixtures/api/dora/metrics/daily_deployment_frequ
import last90DaysData from 'test_fixtures/api/dora/metrics/daily_deployment_frequency_for_last_90_days.json';
import { useFixturesFakeDate } from 'helpers/fake_date';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
......
import { GlLink, GlIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import MetricPopover from '~/cycle_analytics/components/metric_popover.vue';
import MetricPopover from '~/analytics/shared/components/metric_popover.vue';
const MOCK_METRIC = {
key: 'deployment-frequency',
......
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
import MetricPopover from '~/cycle_analytics/components/metric_popover.vue';
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
import MetricPopover from '~/analytics/shared/components/metric_popover.vue';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
......
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import {
filterBySearchTerm,
extractFilterQueryParameters,
extractPaginationQueryParameters,
getDataZoomOption,
prepareTimeMetricsData,
} from '~/analytics/shared/utils';
import { slugify } from '~/lib/utils/text_utility';
import { objectToQuery } from '~/lib/utils/url_utility';
describe('filterBySearchTerm', () => {
......@@ -176,3 +179,36 @@ describe('getDataZoomOption', () => {
});
});
});
describe('prepareTimeMetricsData', () => {
let prepared;
const [first, second] = metricsData;
delete second.identifier; // testing the case when identifier is missing
const firstIdentifier = first.identifier;
const secondIdentifier = slugify(second.title);
beforeEach(() => {
prepared = prepareTimeMetricsData([first, second], {
[firstIdentifier]: { description: 'Is a value that is good' },
});
});
it('will add a `identifier` based on the title', () => {
expect(prepared).toMatchObject([
{ identifier: firstIdentifier },
{ identifier: secondIdentifier },
]);
});
it('will add a `label` key', () => {
expect(prepared).toMatchObject([{ label: 'New Issues' }, { label: 'Commits' }]);
});
it('will add a popover description using the key if it is provided', () => {
expect(prepared).toMatchObject([
{ description: 'Is a value that is good' },
{ description: '' },
]);
});
});
......@@ -3,11 +3,11 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import BaseComponent from '~/cycle_analytics/components/base.vue';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
import initState from '~/cycle_analytics/store/state';
import {
......
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import {
transformStagesForPathNavigation,
medianTimeToParsedSeconds,
formatMedianValues,
filterStagesByHiddenStatus,
prepareTimeMetricsData,
buildCycleAnalyticsInitialData,
} from '~/cycle_analytics/utils';
import { slugify } from '~/lib/utils/text_utility';
import {
selectedStage,
allowedStages,
......@@ -89,39 +86,6 @@ describe('Value stream analytics utils', () => {
});
});
describe('prepareTimeMetricsData', () => {
let prepared;
const [first, second] = metricsData;
delete second.identifier; // testing the case when identifier is missing
const firstIdentifier = first.identifier;
const secondIdentifier = slugify(second.title);
beforeEach(() => {
prepared = prepareTimeMetricsData([first, second], {
[firstIdentifier]: { description: 'Is a value that is good' },
});
});
it('will add a `identifier` based on the title', () => {
expect(prepared).toMatchObject([
{ identifier: firstIdentifier },
{ identifier: secondIdentifier },
]);
});
it('will add a `label` key', () => {
expect(prepared).toMatchObject([{ label: 'New Issues' }, { label: 'Commits' }]);
});
it('will add a popover description using the key if it is provided', () => {
expect(prepared).toMatchObject([
{ description: 'Is a value that is good' },
{ description: '' },
]);
});
});
describe('buildCycleAnalyticsInitialData', () => {
let res = null;
const projectId = '5';
......
......@@ -3,11 +3,11 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import waitForPromises from 'helpers/wait_for_promises';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { METRICS_POPOVER_CONTENT } from '~/cycle_analytics/constants';
import { prepareTimeMetricsData } from '~/cycle_analytics/utils';
import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
import { METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
import createFlash from '~/flash';
import { group } from './mock_data';
......
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