gitlab-ce/app/assets/javascripts/analytics/shared/utils.js

260 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import dateFormat from '~/lib/dateformat';
import { SECONDS_IN_DAY } from '~/lib/utils/datetime_utility';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { urlQueryToFilter } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import {
dateFormats,
FLOW_METRICS,
MAX_METRIC_PRECISION,
UNITS,
VALUE_STREAM_METRIC_DISPLAY_UNITS,
VALUE_STREAM_METRIC_METADATA,
VALUE_STREAM_METRIC_TILE_METADATA,
} from './constants';
export const filterBySearchTerm = (data = [], searchTerm = '', filterByKey = 'name') => {
if (!searchTerm?.length) return data;
return data.filter((item) => item[filterByKey].toLowerCase().includes(searchTerm.toLowerCase()));
};
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
/**
* Takes a url and extracts query parameters used for the shared
* filter bar
*
* @param {string} url The URL to extract query parameters from
* @returns {Object}
*/
export const extractFilterQueryParameters = (url = '') => {
const {
source_branch_name: selectedSourceBranch = null,
target_branch_name: selectedTargetBranch = null,
author_username: selectedAuthor = null,
milestone_title: selectedMilestone = null,
assignee_username: selectedAssigneeList = [],
label_name: selectedLabelList = [],
} = urlQueryToFilter(url);
return {
selectedSourceBranch,
selectedTargetBranch,
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
selectedLabelList,
};
};
/**
* Takes a url and extracts sorting and pagination query parameters into an object
*
* @param {string} url The URL to extract query parameters from
* @returns {Object}
*/
export const extractPaginationQueryParameters = (url = '') => {
const { sort, direction, page } = urlQueryToFilter(url);
return {
sort: sort?.value || null,
direction: direction?.value || null,
page: page?.value || null,
};
};
export const getDataZoomOption = ({
totalItems = 0,
maxItemsPerPage = 40,
dataZoom = [{ type: 'slider', bottom: 10, start: 0 }],
}) => {
if (totalItems <= maxItemsPerPage) {
return {};
}
const intervalEnd = Math.ceil((maxItemsPerPage / totalItems) * 100);
return dataZoom.map((item) => {
return {
...item,
end: intervalEnd,
};
});
};
/**
* 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 {String} 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
* namespace path passed into the method.
*
* @param {String} namespacePath - Path of the specified namespace
* @param {Boolean} isProjectNamespace
* @returns a URL or blank string if there is no namespacePath set
*/
export const generateValueStreamsDashboardLink = (
namespacePath = null,
isProjectNamespace = false,
) => {
if (!namespacePath) return '';
const dashboardsSlug = '/-/analytics/dashboards/value_streams_dashboard';
const formattedNamespacePath = isProjectNamespace ? namespacePath : `groups/${namespacePath}`;
const segments = [gon.relative_url_root || '', '/', formattedNamespacePath, dashboardsSlug];
return joinPaths(...segments);
};
/**
* Generates a URL link to the metric's report based on the namespace type.
* @param {String} metricId Metric's identifier
* @param {String} namespacePath - Path of the specified namespace
* @param {Boolean} isProjectNamespace
* @returns {String} URL link to the metric's report
*/
export const generateMetricLink = ({
metricId = null,
namespacePath = null,
isProjectNamespace = false,
} = {}) => {
const { groupLink, projectLink } = VALUE_STREAM_METRIC_METADATA[metricId] ?? {};
const hasLinks = groupLink || projectLink;
if (!hasLinks || !namespacePath) return '';
return joinPaths(
'/',
gon.relative_url_root,
!isProjectNamespace ? 'groups' : '',
namespacePath,
isProjectNamespace ? projectLink : groupLink,
);
};
/**
* Extracts the relevant feature and license flags needed for VSA
*
* @param {Object} gon the global `window.gon` object populated when the page loads
* @returns an object containing the extracted feature flags and their boolean status
*/
export const extractVSAFeaturesFromGON = () => ({
// licensed feature toggles
cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups),
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 {};
};
/**
* Takes the raw snake_case query parameters and extracts + converts the relevant values
* for the overview metrics component
* @param {Object} params - Object containing the supported query parameters
* @param {Date} params.created_before
* @param {Date} params.created_after
* @param {string} params.author_username
* @param {string} params.milestone_title
* @param {Array} params.label_name
* @param {Array} params.assignee_username
*
* @returns {Object} CamelCased parameter names
*/
export const overviewMetricsRequestParams = (params = {}) => {
const {
createdAfter: startDate,
createdBefore: endDate,
labelName: labelNames,
assigneeUsername: assigneeUsernames,
...rest
} = convertObjectPropsToCamelCase(params);
return {
startDate,
endDate,
labelNames,
assigneeUsernames,
...rest,
};
};