Skip to content
Snippets Groups Projects
Verified Commit 17dfcf58 authored by Simon Knox's avatar Simon Knox Committed by GitLab
Browse files

Merge branch '498179-add-legacy-value-stream-metrics-component' into 'master'

Add LegacyValueStreamMetrics component for graphql migration

See merge request !173072



Merged-by: Simon Knox's avatarSimon Knox <simon@gitlab.com>
Approved-by: default avatarJulie Huang <julhuang@gitlab.com>
Approved-by: Simon Knox's avatarSimon Knox <simon@gitlab.com>
Reviewed-by: Simon Knox's avatarSimon Knox <simon@gitlab.com>
Reviewed-by: default avatarEzekiel Kigbo <3397881-ekigbo@users.noreply.gitlab.com>
Reviewed-by: default avatarJulie Huang <julhuang@gitlab.com>
Co-authored-by: default avatarEzekiel Kigbo <ekigbo@gitlab.com>
parents b1fec307 62df0255
No related branches found
No related tags found
1 merge request!173072Add LegacyValueStreamMetrics component for graphql migration
Pipeline #1555821383 passed
Showing
with 423 additions and 156 deletions
......@@ -3,7 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
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 LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants';
import { toYmd, generateValueStreamsDashboardLink } from '~/analytics/shared/utils';
import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue';
......@@ -24,7 +24,7 @@ export default {
PathNavigation,
StageTable,
ValueStreamFilters,
ValueStreamMetrics,
LegacyValueStreamMetrics,
UrlSync,
},
props: {
......@@ -188,7 +188,7 @@ export default {
@selected="onSelectStage"
/>
</div>
<value-stream-metrics
<legacy-value-stream-metrics
:request-path="namespace.fullPath"
:request-params="filterParams"
:requests="metricsRequests"
......
<script>
// NOTE: the API requests for this component are being migrated to graphql
// related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/498179
import { GlSkeletonLoader } from '@gitlab/ui';
import { isEqual, keyBy } from 'lodash';
import { createAlert } from '~/alert';
import { sprintf, s__ } from '~/locale';
import { fetchMetricsData, removeFlash } from '../utils';
import ValueStreamsDashboardLink from './value_streams_dashboard_link.vue';
import MetricTile from './metric_tile.vue';
const extractMetricsGroupData = (keyList = [], data = []) => {
if (!keyList.length || !data.length) return [];
const kv = keyBy(data, 'identifier');
return keyList.map((id) => kv[id] || null).filter((obj) => Boolean(obj));
};
const groupRawMetrics = (groups = [], rawData = []) => {
return groups.map((curr) => {
const { keys, ...rest } = curr;
return {
data: extractMetricsGroupData(keys, rawData),
keys,
...rest,
};
});
};
export default {
name: 'LegacyValueStreamMetrics',
components: {
GlSkeletonLoader,
MetricTile,
ValueStreamsDashboardLink,
},
props: {
requestPath: {
type: String,
required: true,
},
requestParams: {
type: Object,
required: true,
},
requests: {
type: Array,
required: true,
},
filterFn: {
type: Function,
required: false,
default: null,
},
groupBy: {
type: Array,
required: false,
default: () => [],
},
dashboardsPath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
metrics: [],
groupedMetrics: [],
isLoading: false,
};
},
computed: {
hasGroupedMetrics() {
return Boolean(this.groupBy.length);
},
},
watch: {
requestParams(newVal, oldVal) {
if (!isEqual(newVal, oldVal)) {
this.fetchData();
}
},
},
mounted() {
this.fetchData();
},
methods: {
shouldDisplayDashboardLink(index) {
// When we have groups of metrics, we should only display the link for the first group
return index === 0 && this.dashboardsPath;
},
fetchData() {
removeFlash();
this.isLoading = true;
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
.then((data) => {
this.metrics = this.filterFn ? this.filterFn(data) : data;
if (this.hasGroupedMetrics) {
this.groupedMetrics = groupRawMetrics(this.groupBy, this.metrics);
}
this.isLoading = false;
})
.catch((err) => {
const message = sprintf(
s__(
'ValueStreamAnalytics|There was an error while fetching value stream analytics %{requestTypeName} data.',
),
{ requestTypeName: err.message },
);
createAlert({ message });
this.isLoading = false;
});
},
},
};
</script>
<template>
<div class="gl-flex" data-testid="vsa-metrics" :class="isLoading ? 'gl-my-6' : 'gl-mt-6'">
<gl-skeleton-loader v-if="isLoading" />
<template v-else>
<div v-if="hasGroupedMetrics" class="gl-flex-col">
<div
v-for="(group, groupIndex) in groupedMetrics"
:key="group.key"
class="gl-mb-7"
data-testid="vsa-metrics-group"
>
<h4 class="gl-my-0">{{ group.title }}</h4>
<div class="gl-flex gl-flex-wrap">
<metric-tile
v-for="metric in group.data"
:key="metric.identifier"
:metric="metric"
class="gl-mt-5 gl-pr-10"
/>
<value-streams-dashboard-link
v-if="shouldDisplayDashboardLink(groupIndex)"
class="gl-mt-5"
:request-path="dashboardsPath"
/>
</div>
</div>
</div>
<div v-else class="gl-mb-7 gl-flex gl-flex-wrap">
<metric-tile
v-for="metric in metrics"
:key="metric.identifier"
:metric="metric"
class="gl-mt-5 gl-pr-10"
/>
</div>
</template>
</div>
</template>
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
import { isEqual, keyBy } from 'lodash';
import { isEqual } from 'lodash';
import { createAlert } from '~/alert';
import { sprintf, s__ } from '~/locale';
import { fetchMetricsData, removeFlash } from '../utils';
import { s__ } from '~/locale';
import {
DORA_METRICS_QUERY_TYPE,
FLOW_METRICS_QUERY_TYPE,
ALL_METRICS_QUERY_TYPE,
VALUE_STREAM_METRIC_TILE_METADATA,
} from '../constants';
import { rawMetricToMetricTile, extractQueryResponseFromNamespace } from '../utils';
import { BUCKETING_INTERVAL_ALL } from '../graphql/constants';
import FlowMetricsQuery from '../graphql/flow_metrics.query.graphql';
import DoraMetricsQuery from '../graphql/dora_metrics.query.graphql';
import ValueStreamsDashboardLink from './value_streams_dashboard_link.vue';
import MetricTile from './metric_tile.vue';
const extractMetricsGroupData = (keyList = [], data = []) => {
if (!keyList.length || !data.length) return [];
const kv = keyBy(data, 'identifier');
return keyList.map((id) => kv[id] || null).filter((obj) => Boolean(obj));
const dataKeys = data.map(({ identifier }) => identifier);
if (!keyList.length || !dataKeys.some((key) => keyList.includes(key))) return [];
return keyList.reduce((acc, curr) => {
const metric = data.find((item) => item.identifier === curr);
return metric ? [...acc, metric] : acc;
}, []);
};
const groupRawMetrics = (groups = [], rawData = []) => {
......@@ -40,9 +53,10 @@ export default {
type: Object,
required: true,
},
requests: {
type: Array,
required: true,
queryType: {
type: String,
required: false,
default: ALL_METRICS_QUERY_TYPE,
},
filterFn: {
type: Function,
......@@ -62,56 +76,111 @@ export default {
},
data() {
return {
metrics: [],
groupedMetrics: [],
isLoading: false,
flowMetrics: [],
doraMetrics: [],
};
},
computed: {
hasGroupedMetrics() {
return Boolean(this.groupBy.length);
},
isLoading() {
return Boolean(
this.$apollo.queries.doraMetrics.loading || this.$apollo.queries.flowMetrics.loading,
);
},
groupedMetrics() {
return groupRawMetrics(this.groupBy, this.metrics);
},
isFlowMetricsQuery() {
return [ALL_METRICS_QUERY_TYPE, FLOW_METRICS_QUERY_TYPE].includes(this.queryType);
},
isDoraMetricsQuery() {
return [ALL_METRICS_QUERY_TYPE, DORA_METRICS_QUERY_TYPE].includes(this.queryType);
},
displayableMetrics() {
// NOTE: workaround while the flowMetrics/doraMetrics dont support including/excluding unwanted metrics from the response
return Object.keys(VALUE_STREAM_METRIC_TILE_METADATA);
},
metrics() {
const combined = [...this.flowMetrics, ...this.doraMetrics].filter(({ identifier }) =>
this.displayableMetrics.includes(identifier),
);
const filtered = this.filterFn ? this.filterFn(combined) : combined;
return filtered.map((metric) => rawMetricToMetricTile(metric));
},
},
watch: {
requestParams(newVal, oldVal) {
async requestParams(newVal, oldVal) {
if (!isEqual(newVal, oldVal)) {
this.fetchData();
await Promise.all([
this.$apollo.queries.doraMetrics.refetch(),
this.$apollo.queries.flowMetrics.refetch(),
]);
}
},
},
mounted() {
this.fetchData();
apollo: {
flowMetrics: {
query: FlowMetricsQuery,
variables() {
const { created_after: startDate, created_before: endDate } = this.requestParams;
return { startDate, endDate, fullPath: this.requestPath };
},
skip() {
return !this.isFlowMetricsQuery;
},
update(data) {
const metrics = extractQueryResponseFromNamespace({
result: { data },
resultKey: 'flowMetrics',
});
return Object.values(metrics).filter((metric) => metric?.identifier);
},
error() {
createAlert({
message: s__('ValueStreamAnalytics|There was an error while fetching flow metrics data.'),
});
},
},
doraMetrics: {
query: DoraMetricsQuery,
variables() {
const { created_after: startDate, created_before: endDate } = this.requestParams;
return {
fullPath: this.requestPath,
interval: BUCKETING_INTERVAL_ALL,
startDate,
endDate,
};
},
skip() {
return !this.isDoraMetricsQuery;
},
update(data) {
const responseData = extractQueryResponseFromNamespace({
result: { data },
resultKey: 'dora',
});
const [rawMetrics] = responseData.metrics;
return Object.entries(rawMetrics).reduce((acc, [identifier, value]) => {
return [...acc, { identifier, value }];
}, []);
},
error() {
createAlert({
message: s__('ValueStreamAnalytics|There was an error while fetching DORA metrics data.'),
});
},
},
},
methods: {
shouldDisplayDashboardLink(index) {
// When we have groups of metrics, we should only display the link for the first group
return index === 0 && this.dashboardsPath;
},
fetchData() {
removeFlash();
this.isLoading = true;
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
.then((data) => {
this.metrics = this.filterFn ? this.filterFn(data) : data;
if (this.hasGroupedMetrics) {
this.groupedMetrics = groupRawMetrics(this.groupBy, this.metrics);
}
this.isLoading = false;
})
.catch((err) => {
const message = sprintf(
s__(
'ValueStreamAnalytics|There was an error while fetching value stream analytics %{requestTypeName} data.',
),
{ requestTypeName: err.message },
);
createAlert({ message });
this.isLoading = false;
});
},
},
};
</script>
......
......@@ -93,6 +93,10 @@ export const METRIC_POPOVER_LABEL = s__('ValueStreamAnalytics|View details');
export const ISSUES_COMPLETED_TYPE = 'issues_completed';
export const ALL_METRICS_QUERY_TYPE = 'ALL_METRICS_QUERY_TYPE';
export const DORA_METRICS_QUERY_TYPE = 'DORA_METRICS_QUERY_TYPE';
export const FLOW_METRICS_QUERY_TYPE = 'FLOW_METRICS_QUERY_TYPE';
export const FLOW_METRICS = {
LEAD_TIME: 'lead_time',
CYCLE_TIME: 'cycle_time',
......@@ -110,13 +114,19 @@ export const DORA_METRICS = {
CHANGE_FAILURE_RATE: 'change_failure_rate',
};
const VSA_FLOW_METRICS_GROUP = {
key: 'lifecycle_metrics',
title: s__('ValueStreamAnalytics|Lifecycle metrics'),
keys: Object.values(FLOW_METRICS),
};
export const VSA_METRICS_GROUPS = [VSA_FLOW_METRICS_GROUP];
export const VSA_METRICS_GROUPS = [
{
key: 'lifecycle_metrics',
title: s__('ValueStreamAnalytics|Lifecycle metrics'),
keys: [
FLOW_METRICS.LEAD_TIME,
FLOW_METRICS.CYCLE_TIME,
FLOW_METRICS.ISSUES,
FLOW_METRICS.COMMITS,
FLOW_METRICS.DEPLOYS,
],
},
];
export const VULNERABILITY_CRITICAL_TYPE = 'vulnerability_critical';
export const VULNERABILITY_HIGH_TYPE = 'vulnerability_high';
......@@ -151,6 +161,9 @@ export const VALUE_STREAM_METRIC_DISPLAY_UNITS = {
[UNITS.PERCENT]: '%',
};
// NOTE: ideally we would return these fields in the metrics queries
// the flow metrics query returns some but not all fields we need
// while the DORA query do not return any.
export const VALUE_STREAM_METRIC_TILE_METADATA = {
[DORA_METRICS.DEPLOYMENT_FREQUENCY]: {
label: s__('DORA4Metrics|Deployment frequency'),
......@@ -215,7 +228,7 @@ export const VALUE_STREAM_METRIC_TILE_METADATA = {
unit: UNITS.DAYS,
},
[FLOW_METRICS.ISSUES]: {
label: s__('DORA4Metrics|Issues created'),
label: s__('DORA4Metrics|New issues'),
unit: UNITS.COUNT,
description: s__('ValueStreamAnalytics|Number of new issues created.'),
groupLink: '-/issues_analytics',
......
import { flatten } from 'lodash';
import dateFormat from '~/lib/dateformat';
import { SECONDS_IN_DAY } from '~/lib/utils/datetime_utility';
import { slugify } from '~/lib/utils/text_utility';
import { joinPaths } from '~/lib/utils/url_utility';
import { urlQueryToFilter } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats, VALUE_STREAM_METRIC_METADATA } from './constants';
import {
dateFormats,
FLOW_METRICS,
MAX_METRIC_PRECISION,
UNITS,
VALUE_STREAM_METRIC_DISPLAY_UNITS,
VALUE_STREAM_METRIC_TILE_METADATA,
} from './constants';
export const filterBySearchTerm = (data = [], searchTerm = '', filterByKey = 'name') => {
if (!searchTerm?.length) return data;
......@@ -117,10 +125,76 @@ const requestData = ({ request, endpoint, requestPath, params, name }) => {
export const fetchMetricsData = (requests = [], requestPath, params) => {
const promises = requests.map((r) => requestData({ ...r, requestPath, params }));
return Promise.all(promises).then((responses) =>
prepareTimeMetricsData(flatten(responses), VALUE_STREAM_METRIC_METADATA),
prepareTimeMetricsData(flatten(responses), VALUE_STREAM_METRIC_TILE_METADATA),
);
};
/**
* Formats any valid number as percentage
*
* @param {number|string} decimalValue Decimal value between 0 and 1 to be converted to a percentage
* @param {number} precision The number of decimal places to round to
*
* @returns {string} Returns a formatted string multiplied by 100
*/
export const formatAsPercentageWithoutSymbol = (decimalValue = 0, precision = 1) => {
const parsed = Number.isNaN(Number(decimalValue)) ? 0 : decimalValue;
return (parsed * 100).toFixed(precision);
};
/**
* Converts a time in seconds to number of days, with variable precision
*
* @param {Number} seconds Time in seconds
* @param {Number} precision Specifies the number of digits after the decimal
*
* @returns {Float} The number of days
*/
export const secondsToDays = (seconds, precision = 1) =>
(seconds / SECONDS_IN_DAY).toFixed(precision);
export const scaledValueForDisplay = (value, units, precision = MAX_METRIC_PRECISION) => {
switch (units) {
case UNITS.PERCENT:
return formatAsPercentageWithoutSymbol(value);
case UNITS.DAYS:
return secondsToDays(value, precision);
default:
return value;
}
};
const prepareMetricValue = ({ identifier, value, unit }) => {
// NOTE: the flow metrics graphql endpoint returns values already scaled for display
if (!value) {
// ensures we return `-` for 0/null etc
return '-';
}
return Object.values(FLOW_METRICS).includes(identifier)
? value
: scaledValueForDisplay(value, unit);
};
/**
* Prepares metric data to be rendered in the metric_tile component
*
* @param {MetricData[]} data - The metric data to be rendered
* @returns {TransformedMetricData[]} An array of metrics ready to render in the metric_tile
*/
export const rawMetricToMetricTile = (metric) => {
const { identifier, value, ...metricRest } = metric;
const { unit, label, ...metadataRest } = VALUE_STREAM_METRIC_TILE_METADATA[identifier];
return {
...metadataRest,
...metricRest,
title: label,
identifier,
label,
unit: VALUE_STREAM_METRIC_DISPLAY_UNITS[unit],
value: prepareMetricValue({ value, unit, identifier }),
};
};
/**
* Generates a URL link to the VSD dashboard based on the group
* and project paths passed into the method.
......@@ -149,3 +223,24 @@ export const extractVSAFeaturesFromGON = () => ({
cycleAnalyticsForProjects: Boolean(gon?.licensed_features?.cycleAnalyticsForProjects),
groupLevelAnalyticsDashboard: Boolean(gon?.licensed_features?.groupLevelAnalyticsDashboard),
});
/**
* Takes a raw GraphQL response which could contain data for a group or project namespace,
* and returns the data for the namespace which is present in the response.
*
* @param {Object} params
* @param {string} params.resultKey - The data to be extracted from the namespace.
* @param {Object} params.result
* @param {Object} params.result.data
* @param {Object} params.result.data.group - The group GraphQL response.
* @param {Object} params.result.data.project - The project GraphQL response.
* @returns {Object} The data extracted from either group[resultKey] or project[resultKey].
*/
export const extractQueryResponseFromNamespace = ({ result, resultKey }) => {
const { group = null, project = null } = result.data;
if (group || project) {
const namespace = group ?? project;
return namespace[resultKey] || {};
}
return {};
};
......@@ -5,10 +5,6 @@ import {
AI_IMPACT_OVER_TIME_METRICS_TOOLTIPS,
} from 'ee/analytics/dashboards/ai_impact/constants';
import { calculateRate } from 'ee/analytics/dashboards/ai_impact/utils';
import {
extractQueryResponseFromNamespace,
scaledValueForDisplay,
} from 'ee/analytics/dashboards/api';
import {
LAST_30_DAYS,
LAST_180_DAYS,
......@@ -16,6 +12,7 @@ import {
startOfTomorrow,
} from 'ee/dora/components/static_data/shared';
import { AI_METRICS } from '~/analytics/shared/constants';
import { scaledValueForDisplay, extractQueryResponseFromNamespace } from '~/analytics/shared/utils';
import { defaultClient } from '../graphql/client';
const DATE_RANGE_TITLES = { [LAST_30_DAYS]: sprintf(__('Last %{days} days'), { days: 30 }) };
......
import { BUCKETING_INTERVAL_ALL } from '~/analytics/shared/graphql/constants';
import DoraMetricsQuery from '~/analytics/shared/graphql/dora_metrics.query.graphql';
import { extractQueryResponseFromNamespace, scaledValueForDisplay } from '~/analytics/shared/utils';
import { TABLE_METRICS } from 'ee/analytics/dashboards/constants';
import {
extractQueryResponseFromNamespace,
scaledValueForDisplay,
} from 'ee/analytics/dashboards/api';
import {
LAST_180_DAYS,
DORA_METRIC_QUERY_RANGES,
......
......@@ -5,7 +5,7 @@ import { GlEmptyState } from '@gitlab/ui';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants';
import { generateValueStreamsDashboardLink } from '~/analytics/shared/utils';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue';
......@@ -34,7 +34,7 @@ export default {
ValueStreamAggregatingWarning,
ValueStreamEmptyState,
ValueStreamFilters,
ValueStreamMetrics,
LegacyValueStreamMetrics,
ValueStreamSelect,
UrlSync,
DurationOverviewChart,
......@@ -260,7 +260,7 @@ export default {
"
/>
<template v-else>
<value-stream-metrics
<legacy-value-stream-metrics
v-if="isOverviewStageSelected"
:request-path="namespacePath"
:request-params="cycleAnalyticsRequestParams"
......
......@@ -9,7 +9,7 @@ import {
GlLink,
} from '@gitlab/ui';
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
import { toYmd } from '~/analytics/shared/utils';
import { toYmd, extractQueryResponseFromNamespace } from '~/analytics/shared/utils';
import { AI_METRICS, UNITS } from '~/analytics/shared/constants';
import { BUCKETING_INTERVAL_ALL } from '~/analytics/shared/graphql/constants';
import { dasherize } from '~/lib/utils/text_utility';
......@@ -57,7 +57,6 @@ import {
extractGraphqlVulnerabilitiesData,
extractGraphqlDoraData,
extractGraphqlFlowData,
extractQueryResponseFromNamespace,
} from '../../api';
import { extractGraphqlAiData } from '../api';
......
import { formatAsPercentageWithoutSymbol, secondsToDays } from 'ee/dora/components/util';
import {
CONTRIBUTOR_METRICS,
VULNERABILITY_METRICS,
UNITS,
MAX_METRIC_PRECISION,
} from '~/analytics/shared/constants';
import { CONTRIBUTOR_METRICS, VULNERABILITY_METRICS } from '~/analytics/shared/constants';
import { scaledValueForDisplay } from '~/analytics/shared/utils';
import { TABLE_METRICS } from './constants';
/**
......@@ -49,17 +44,6 @@ export const extractGraphqlVulnerabilitiesData = (rawVulnerabilityData = []) =>
};
};
export const scaledValueForDisplay = (value, units, precision = MAX_METRIC_PRECISION) => {
switch (units) {
case UNITS.PERCENT:
return formatAsPercentageWithoutSymbol(value);
case UNITS.DAYS:
return secondsToDays(value, precision);
default:
return value;
}
};
/**
* @typedef {Object} DoraMetricItem
* @property {String} date - ISO 8601 date
......@@ -240,24 +224,3 @@ export const fetchMetricsForTimePeriods = async (timePeriods, queryFn, queryPara
return Promise.all(promises);
};
/**
* Takes a raw GraphQL response which could contain data for a group or project namespace,
* and returns the data for the namespace which is present in the response.
*
* @param {Object} params
* @param {string} params.resultKey - The data to be extracted from the namespace.
* @param {Object} params.result
* @param {Object} params.result.data
* @param {Object} params.result.data.group - The group GraphQL response.
* @param {Object} params.result.data.project - The project GraphQL response.
* @returns {Object} The data extracted from either group[resultKey] or project[resultKey].
*/
export const extractQueryResponseFromNamespace = ({ result, resultKey }) => {
const { group = null, project = null } = result.data;
if (group || project) {
const namespace = group ?? project;
return namespace[resultKey] || {};
}
return {};
};
<script>
import { uniq } from 'lodash';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { toYmd } from '~/analytics/shared/utils';
import { toYmd, extractQueryResponseFromNamespace } from '~/analytics/shared/utils';
import { CONTRIBUTOR_METRICS } from '~/analytics/shared/constants';
import { BUCKETING_INTERVAL_ALL } from '~/analytics/shared/graphql/constants';
import FlowMetricsQuery from '~/analytics/shared/graphql/flow_metrics.query.graphql';
......@@ -28,7 +28,6 @@ import {
extractGraphqlFlowData,
extractGraphqlMergeRequestsData,
extractGraphqlContributorCountData,
extractQueryResponseFromNamespace,
} from '../api';
import {
generateSkeletonTableData,
......
<script>
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import * as DoraApi from 'ee/api/dora_api';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import { createAlert } from '~/alert';
import { s__, sprintf } from '~/locale';
......@@ -42,7 +42,7 @@ export default {
CiCdAnalyticsCharts,
DoraChartHeader,
ChartTooltipText,
ValueStreamMetrics,
LegacyValueStreamMetrics,
},
inject: {
projectPath: {
......@@ -192,7 +192,7 @@ export default {
:format-tooltip-text="formatTooltipText"
>
<template #metrics="{ selectedChart }">
<value-stream-metrics
<legacy-value-stream-metrics
:request-path="metricsRequestPath"
:requests="$options.metricsRequest"
:request-params="getMetricsRequestParams(selectedChart)"
......
......@@ -5,7 +5,7 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { BASE_FORECAST_SERIES_OPTIONS } from 'ee/analytics/shared/constants';
import * as DoraApi from 'ee/api/dora_api';
import SafeHtml from '~/vue_shared/directives/safe_html';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import { createAlert } from '~/alert';
import { __, s__, sprintf } from '~/locale';
......@@ -50,7 +50,7 @@ export default {
components: {
CiCdAnalyticsCharts,
DoraChartHeader,
ValueStreamMetrics,
LegacyValueStreamMetrics,
GlToggle,
GlBadge,
GlAlert,
......@@ -431,7 +431,7 @@ export default {
{{ forecastRequestErrorMessage }}
</template>
</gl-alert>
<value-stream-metrics
<legacy-value-stream-metrics
:request-path="metricsRequestPath"
:requests="$options.metricsRequest"
:request-params="getMetricsRequestParams(selectedChart)"
......
import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import { secondsToDays } from '../util';
import { secondsToDays } from '~/analytics/shared/utils';
export * from './shared';
......
<script>
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import * as DoraApi from 'ee/api/dora_api';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import { createAlert } from '~/alert';
import { s__, sprintf } from '~/locale';
......@@ -37,7 +37,7 @@ export default {
CiCdAnalyticsCharts,
DoraChartHeader,
ChartTooltipText,
ValueStreamMetrics,
LegacyValueStreamMetrics,
},
inject: {
projectPath: {
......@@ -183,7 +183,7 @@ export default {
:format-tooltip-text="formatTooltipText"
>
<template #metrics="{ selectedChart }">
<value-stream-metrics
<legacy-value-stream-metrics
:request-path="metricsRequestPath"
:requests="$options.metricsRequest"
:request-params="getMetricsRequestParams(selectedChart)"
......
......@@ -5,10 +5,10 @@ import {
nDaysAfter,
getStartOfDay,
humanizeTimeInterval,
SECONDS_IN_DAY,
} from '~/lib/utils/datetime_utility';
import { median } from '~/lib/utils/number_utils';
import { dateFormats } from '~/analytics/shared/constants';
import { formatAsPercentageWithoutSymbol } from '~/analytics/shared/utils';
import { linearRegression } from 'ee/analytics/shared/utils';
import { buildForecast } from '../graphql/api';
......@@ -97,17 +97,6 @@ export const seriesToMedianSeries = (chartSeriesData, seriesName) => {
};
};
/**
* Converts a time in seconds to number of days, with variable precision
*
* @param {Number} seconds Time in seconds
* @param {Number} precision Specifies the number of digits after the decimal
*
* @returns {Float} The number of days
*/
export const secondsToDays = (seconds, precision = 1) =>
(seconds / SECONDS_IN_DAY).toFixed(precision);
/**
* Generates the tooltip text and value for time interval series
*
......@@ -151,19 +140,6 @@ export const extractTimeSeriesTooltip = (params, seriesName, formatter = humaniz
};
};
/**
* Formats any valid number as percentage
*
* @param {number|string} decimalValue Decimal value between 0 and 1 to be converted to a percentage
* @param {number} precision The number of decimal places to round to
*
* @returns {string} Returns a formatted string multiplied by 100
*/
export const formatAsPercentageWithoutSymbol = (decimalValue = 0, precision = 1) => {
const parsed = Number.isNaN(Number(decimalValue)) ? 0 : decimalValue;
return (parsed * 100).toFixed(precision);
};
/**
* Formats any valid number as percentage
*
......
import { mockDoraMetricsResponseData } from 'ee_jest/analytics/dashboards/mock_data';
import { mockDoraMetricsResponseData } from 'jest/analytics/shared/mock_data';
import fetch from 'ee/analytics/analytics_dashboards/data_sources/dora_metrics_over_time';
import { defaultClient } from 'ee/analytics/analytics_dashboards/graphql/client';
import { LAST_WEEK, LAST_180_DAYS } from 'ee/dora/components/static_data/shared';
......
......@@ -23,7 +23,7 @@ import {
initialPaginationQuery,
selectedProjects as rawSelectedProjects,
} from 'jest/analytics/cycle_analytics/mock_data';
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
import { toYmd } from '~/analytics/shared/utils';
import PathNavigation from '~/analytics/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue';
......@@ -184,7 +184,7 @@ describe('EE Value Stream Analytics component', () => {
const findAggregationStatus = () => wrapper.findComponent(ValueStreamAggregationStatus);
const findPathNavigation = () => wrapper.findComponent(PathNavigation);
const findStageTable = () => wrapper.findComponent(StageTable);
const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
const findOverviewMetrics = () => wrapper.findComponent(LegacyValueStreamMetrics);
const findFilterBar = () => wrapper.findComponent(ValueStreamFilters);
const findDurationChart = () => wrapper.findComponent(DurationChart);
const findDurationOverviewChart = () => wrapper.findComponent(DurationOverviewChart);
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { UNITS } from '~/analytics/shared/constants';
import { extractQueryResponseFromNamespace, scaledValueForDisplay } from '~/analytics/shared/utils';
import {
extractGraphqlDoraData,
extractGraphqlFlowData,
extractGraphqlVulnerabilitiesData,
extractGraphqlMergeRequestsData,
scaledValueForDisplay,
extractGraphqlContributorCountData,
extractQueryResponseFromNamespace,
} from 'ee/analytics/dashboards/api';
import {
mockDoraMetricsResponseData,
mockLastVulnerabilityCountData,
mockFlowMetricsResponseData,
} from 'jest/analytics/shared/mock_data';
import {
mockLastVulnerabilityCountData,
mockMergeRequestsResponseData,
mockContributorCountResponseData,
} from './mock_data';
......
......@@ -20,13 +20,19 @@ import FlowMetricsQuery from '~/analytics/shared/graphql/flow_metrics.query.grap
import DoraMetricsQuery from '~/analytics/shared/graphql/dora_metrics.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
mockDoraMetricsResponseData,
mockFlowMetricsResponseData,
} from 'jest/analytics/shared/mock_data';
import {
mockGraphqlDoraMetricsResponse,
mockGraphqlFlowMetricsResponse,
} from 'jest/analytics/shared/helpers';
import {
doraMetricsParamsHelper,
flowMetricsParamsHelper,
vulnerabilityParamsHelper,
mergeRequestsParamsHelper,
mockGraphqlFlowMetricsResponse,
mockGraphqlDoraMetricsResponse,
mockGraphqlVulnerabilityResponse,
mockGraphqlMergeRequestsResponse,
expectTimePeriodRequests,
......@@ -38,8 +44,6 @@ import {
MOCK_CHART_TIME_PERIODS,
mockComparativeTableData,
mockLastVulnerabilityCountData,
mockDoraMetricsResponseData,
mockFlowMetricsResponseData,
mockMergeRequestsResponseData,
mockContributorCountResponseData,
} from '../mock_data';
......@@ -60,9 +64,6 @@ const allTimePeriods = [...MOCK_TABLE_TIME_PERIODS, ...MOCK_CHART_TIME_PERIODS];
jest.mock('~/sentry/sentry_browser_wrapper');
jest.mock('~/alert');
jest.mock('~/analytics/shared/utils', () => ({
toYmd: jest.requireActual('~/analytics/shared/utils').toYmd,
}));
Vue.use(VueApollo);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment