Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
330ecbc47d
commit
afaae32aba
|
|
@ -203,7 +203,6 @@ Layout/EmptyLineAfterMagicComment:
|
|||
- 'ee/spec/features/projects/settings/merge_request_approvals_settings_spec.rb'
|
||||
- 'ee/spec/features/projects/settings/merge_requests_settings_spec.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/charts.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/metrics.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_code_stage.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_issue_stage.rb'
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/finders/work_items/widgets/filters/status_spec.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/charts.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/metrics.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_code_stage.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_issue_stage.rb'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ RSpec/SpecFilePathSuffix:
|
|||
Exclude:
|
||||
- 'ee/spec/frontend/fixtures/analytics/charts.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/metrics.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_code_stage.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/value_streams_issue_stage.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
9170d747c9dd37ca7434095f89d11f000bf7226c
|
||||
88843a6cba6fd37e13fe56faf1dd640382195314
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
9f6b60a00d85240eec76d6a6f9a4511686f80e78
|
||||
c9ad6326777be8ab69ddb1cf4b58e91a0fa38c81
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ 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 { VSA_METRICS_GROUPS, FLOW_METRICS_QUERY_TYPE } from '~/analytics/shared/constants';
|
||||
import { toYmd, generateValueStreamsDashboardLink } from '~/analytics/shared/utils';
|
||||
import {
|
||||
toYmd,
|
||||
generateValueStreamsDashboardLink,
|
||||
overviewMetricsRequestParams,
|
||||
} from '~/analytics/shared/utils';
|
||||
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';
|
||||
|
|
@ -121,6 +125,9 @@ export default {
|
|||
filterBarNamespacePath() {
|
||||
return this.groupPath || this.namespace.restApiRequestPath;
|
||||
},
|
||||
overviewRequestParams() {
|
||||
return overviewMetricsRequestParams(this.filterParams);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
|
|
@ -183,8 +190,8 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
<value-stream-metrics
|
||||
:request-path="namespace.restApiRequestPath"
|
||||
:request-params="filterParams"
|
||||
:request-path="namespace.path"
|
||||
:request-params="overviewRequestParams"
|
||||
:group-by="$options.VSA_METRICS_GROUPS"
|
||||
:dashboards-path="dashboardsPath"
|
||||
:query-type="$options.FLOW_METRICS_QUERY_TYPE"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
import {
|
||||
getValueStreamMetrics,
|
||||
METRIC_TYPE_SUMMARY,
|
||||
METRIC_TYPE_TIME_SUMMARY,
|
||||
} from '~/api/analytics_api';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
export const OVERVIEW_STAGE_ID = 'overview';
|
||||
|
|
@ -32,15 +27,6 @@ export const I18N_VSA_ERROR_SELECTED_STAGE = __(
|
|||
'There was an error fetching data for the selected stage',
|
||||
);
|
||||
|
||||
export const SUMMARY_METRICS_REQUEST = [
|
||||
{ endpoint: METRIC_TYPE_SUMMARY, name: __('recent activity'), request: getValueStreamMetrics },
|
||||
];
|
||||
|
||||
export const METRICS_REQUESTS = [
|
||||
{ endpoint: METRIC_TYPE_TIME_SUMMARY, name: __('time summary'), request: getValueStreamMetrics },
|
||||
...SUMMARY_METRICS_REQUEST,
|
||||
];
|
||||
|
||||
export const MILESTONES_ENDPOINT = '/-/milestones.json';
|
||||
export const LABELS_ENDPOINT = '/-/labels.json';
|
||||
export const MAX_LABELS = 100;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export const buildCycleAnalyticsInitialData = ({
|
|||
createdAfter,
|
||||
createdBefore,
|
||||
namespaceName,
|
||||
namespacePath,
|
||||
namespaceRestApiRequestPath,
|
||||
} = {}) => {
|
||||
return {
|
||||
|
|
@ -84,6 +85,7 @@ export const buildCycleAnalyticsInitialData = ({
|
|||
groupPath,
|
||||
namespace: {
|
||||
name: namespaceName,
|
||||
path: namespacePath,
|
||||
restApiRequestPath: namespaceRestApiRequestPath,
|
||||
},
|
||||
createdAfter: newDate(createdAfter),
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -19,9 +19,6 @@ import ValueStreamsDashboardLink from './value_streams_dashboard_link.vue';
|
|||
import MetricTile from './metric_tile.vue';
|
||||
|
||||
const extractMetricsGroupData = (keyList = [], data = []) => {
|
||||
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;
|
||||
|
|
@ -95,14 +92,13 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
queryDateRange() {
|
||||
const { created_after: startDate, created_before: endDate } = this.requestParams;
|
||||
const { startDate, endDate } = this.requestParams;
|
||||
return { startDate: toYmd(startDate), endDate: toYmd(endDate) };
|
||||
},
|
||||
flowMetricsVariables() {
|
||||
const additionalParams = Object.keys(FLOW_METRICS_QUERY_FILTERS).reduce((acc, key) => {
|
||||
const additionalParams = FLOW_METRICS_QUERY_FILTERS.reduce((acc, key) => {
|
||||
if (this.requestParams[key]) {
|
||||
const graphqlField = FLOW_METRICS_QUERY_FILTERS[key];
|
||||
return { ...acc, [graphqlField]: this.requestParams[key] };
|
||||
return { ...acc, [key]: this.requestParams[key] };
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ export const BUCKETING_INTERVAL_MONTHLY = 'MONTHLY';
|
|||
* Available filters for the flow metrics query, along with date range filters
|
||||
* NOTE: these additional do not apply to the `deploymentCount` field
|
||||
*/
|
||||
export const FLOW_METRICS_QUERY_FILTERS = {
|
||||
label_name: 'labelNames',
|
||||
project_ids: 'projectIds',
|
||||
assignee_username: 'assigneeUsernames',
|
||||
milestone_title: 'milestoneTitle',
|
||||
author_username: 'authorUsername',
|
||||
};
|
||||
export const FLOW_METRICS_QUERY_FILTERS = [
|
||||
'labelNames',
|
||||
'projectIds',
|
||||
'assigneeUsernames',
|
||||
'milestoneTitle',
|
||||
'authorUsername',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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 { 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 {
|
||||
|
|
@ -86,49 +85,6 @@ export const removeFlash = (type = 'alert') => {
|
|||
document.querySelector(`.flash-${type} .js-close`)?.click();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 requestData = ({ request, endpoint, requestPath, params, name }) => {
|
||||
return request({ endpoint, params, requestPath })
|
||||
.then(({ data }) => data)
|
||||
.catch(() => {
|
||||
throw new Error(name);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a configuration array of metrics requests (key metrics and DORA) and returns
|
||||
* a flat array of all the responses. Different metrics are retrieved from different endpoints
|
||||
* additionally we only support certain metrics for FOSS users.
|
||||
*
|
||||
* @param {Array} requests - array of metric api requests to be made
|
||||
* @param {String} requestPath - path for the group / project we are requesting
|
||||
* @param {Object} params - optional parameters to filter, including `created_after` and `created_before` dates
|
||||
* @returns a flat array of metrics
|
||||
*/
|
||||
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_TILE_METADATA),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats any valid number as percentage
|
||||
*
|
||||
|
|
@ -249,3 +205,33 @@ export const extractQueryResponseFromNamespace = ({ result, 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,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ export const CYCLE_TIME_METRIC_TYPE = 'cycle_time';
|
|||
export const ISSUES_METRIC_TYPE = 'issues';
|
||||
export const DEPLOYS_METRIC_TYPE = 'deploys';
|
||||
|
||||
export const METRIC_TYPE_SUMMARY = 'summary';
|
||||
export const METRIC_TYPE_TIME_SUMMARY = 'time_summary';
|
||||
|
||||
const buildProjectMetricsPath = (namespacePath) =>
|
||||
buildApiUrl(PROJECT_VSA_METRICS_BASE).replace(':namespace_path', namespacePath);
|
||||
|
||||
|
|
@ -76,15 +73,6 @@ export const getValueStreamStageCounts = (
|
|||
return axios.get(joinPaths(stageBase, 'count'), { params });
|
||||
};
|
||||
|
||||
export const getValueStreamMetrics = ({
|
||||
endpoint = METRIC_TYPE_SUMMARY,
|
||||
requestPath: namespacePath,
|
||||
params = {},
|
||||
}) => {
|
||||
const metricBase = buildProjectMetricsPath(namespacePath);
|
||||
return axios.get(joinPaths(metricBase, endpoint), { params });
|
||||
};
|
||||
|
||||
export const getValueStreamSummaryMetrics = (namespacePath, params = {}) => {
|
||||
const metricBase = buildProjectMetricsPath(namespacePath);
|
||||
return axios.get(joinPaths(metricBase, 'summary'), { params });
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
|
||||
import { findWidget } from '~/issues/list/utils';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import glAbilitiesMixin from '~/vue_shared/mixins/gl_abilities_mixin';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
|
||||
import WorkItemStateToggle from '~/work_items/components/work_item_state_toggle.vue';
|
||||
|
|
@ -51,10 +52,15 @@ export default {
|
|||
GlFormCheckbox,
|
||||
HelpIcon,
|
||||
WorkItemStateToggle,
|
||||
CommentTemperature: () =>
|
||||
import(
|
||||
/* webpackChunkName: 'comment_temperature' */ 'ee_component/ai/components/comment_temperature.vue'
|
||||
),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [glAbilitiesMixin()],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -169,6 +175,7 @@ export default {
|
|||
toggleResolveChecked: this.isDiscussionResolved,
|
||||
emailParticipants: [],
|
||||
workItem: {},
|
||||
isMeasuringCommentTemperature: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -217,6 +224,9 @@ export default {
|
|||
showInternalNoteCheckbox() {
|
||||
return this.canMarkNoteAsInternal && this.isNewDiscussion;
|
||||
},
|
||||
currentUserId() {
|
||||
return window.gon.current_user_id;
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
emailParticipants: {
|
||||
|
|
@ -296,7 +306,15 @@ export default {
|
|||
this.$emit('cancelEditing');
|
||||
clearDraft(this.autosaveKey);
|
||||
},
|
||||
submitForm() {
|
||||
submitForm(shouldMeasureTemperature = true) {
|
||||
this.isMeasuringCommentTemperature =
|
||||
this.glAbilities.measureCommentTemperature && shouldMeasureTemperature;
|
||||
|
||||
if (this.isMeasuringCommentTemperature) {
|
||||
this.$refs.commentTemperature.measureCommentTemperature();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -334,11 +352,20 @@ export default {
|
|||
supports-quick-actions
|
||||
:autofocus="autofocus"
|
||||
@input="setCommentText"
|
||||
@keydown.meta.enter="submitForm"
|
||||
@keydown.ctrl.enter="submitForm"
|
||||
@keydown.meta.enter="submitForm()"
|
||||
@keydown.ctrl.enter="submitForm()"
|
||||
@keydown.esc.stop="cancelEditing"
|
||||
/>
|
||||
</comment-field-layout>
|
||||
<comment-temperature
|
||||
v-if="glAbilities.measureCommentTemperature"
|
||||
ref="commentTemperature"
|
||||
v-model="commentText"
|
||||
:item-id="workItemId"
|
||||
:item-type="workItemTypeKey"
|
||||
:user-id="currentUserId"
|
||||
@save="submitForm(false)"
|
||||
/>
|
||||
<div class="note-form-actions" data-testid="work-item-comment-form-actions">
|
||||
<div v-if="showResolveDiscussionToggle">
|
||||
<label>
|
||||
|
|
@ -367,9 +394,9 @@ export default {
|
|||
category="primary"
|
||||
variant="confirm"
|
||||
data-testid="confirm-button"
|
||||
:disabled="!commentText.length"
|
||||
:disabled="!commentText.length || isMeasuringCommentTemperature"
|
||||
:loading="isSubmitting"
|
||||
@click="submitForm"
|
||||
@click="submitForm()"
|
||||
>{{ commentButtonTextComputed }}
|
||||
</gl-button>
|
||||
<work-item-state-toggle
|
||||
|
|
@ -380,9 +407,10 @@ export default {
|
|||
:work-item-type="workItemType"
|
||||
:full-path="fullPath"
|
||||
:has-comment="Boolean(commentText.length)"
|
||||
:disabled="Boolean(commentText.lengt) && isMeasuringCommentTemperature"
|
||||
:parent-id="parentId"
|
||||
can-update
|
||||
@submit-comment="submitForm"
|
||||
@submit-comment="submitForm()"
|
||||
@error="$emit('error', $event)"
|
||||
/>
|
||||
<gl-button
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
parentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -291,7 +296,7 @@ export default {
|
|||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-button v-else :loading="updateInProgress" @click="action">{{
|
||||
<gl-button v-else :loading="updateInProgress" :disabled="disabled" @click="action">{{
|
||||
toggleWorkItemStateText
|
||||
}}</gl-button>
|
||||
|
||||
|
|
|
|||
|
|
@ -817,8 +817,6 @@
|
|||
- 1
|
||||
- - search_zoekt_task_failed_event
|
||||
- 1
|
||||
- - search_zoekt_update_index_used_bytes
|
||||
- 1
|
||||
- - secrets_management_provision_project_secrets_manager
|
||||
- 1
|
||||
- - security_configuration_set_group_secret_push_protection
|
||||
|
|
|
|||
|
|
@ -14375,6 +14375,9 @@ msgstr ""
|
|||
msgid "Compliance Center|Frameworks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Compliance Center|Report and manage standards adherence, violations, and compliance frameworks for the group. %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Compliance Center|Standards Adherence"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47273,9 +47276,6 @@ msgstr ""
|
|||
msgid "Report abuse to administrator"
|
||||
msgstr ""
|
||||
|
||||
msgid "Report and manage standards adherence, violations, and compliance frameworks for the group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Report couldn't be prepared."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -62367,9 +62367,6 @@ msgstr ""
|
|||
msgid "ValueStreamAnalytics|There was an error while fetching flow metrics data."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|There was an error while fetching value stream analytics %{requestTypeName} data."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Total number of deploys to production."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -68529,9 +68526,6 @@ msgstr ""
|
|||
msgid "read-only"
|
||||
msgstr ""
|
||||
|
||||
msgid "recent activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "register"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -68784,9 +68778,6 @@ msgstr ""
|
|||
msgid "this document"
|
||||
msgstr ""
|
||||
|
||||
msgid "time summary"
|
||||
msgstr ""
|
||||
|
||||
msgid "to yourself"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ RSpec.describe 'Work item detail', :js, feature_category: :team_planning do
|
|||
let(:linked_item) { task }
|
||||
|
||||
before_all do
|
||||
stub_feature_flags(comment_temperature: false)
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import StageTable from '~/analytics/cycle_analytics/components/stage_table.vue';
|
|||
import ValueStreamFilters from '~/analytics/cycle_analytics/components/value_stream_filters.vue';
|
||||
import { NOT_ENOUGH_DATA_ERROR } from '~/analytics/cycle_analytics/constants';
|
||||
import initState from '~/analytics/cycle_analytics/store/state';
|
||||
import { filterParams } from '~/analytics/cycle_analytics/store/getters';
|
||||
import {
|
||||
transformedProjectStagePathData,
|
||||
selectedStage,
|
||||
|
|
@ -26,7 +27,7 @@ const selectedStageEvents = issueEvents.events;
|
|||
const noDataSvgPath = 'path/to/no/data';
|
||||
const noAccessSvgPath = 'path/to/no/access';
|
||||
const selectedStageCount = stageCounts[selectedStage.id];
|
||||
const namespaceRestApiRequestPath = 'full/path/to/foo';
|
||||
const namespaceRestApiRequestPath = 'rest/full/path/to/foo';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
|
@ -40,7 +41,13 @@ const defaultState = {
|
|||
createdAfter,
|
||||
stageCounts,
|
||||
groupPath,
|
||||
namespace: { restApiRequestPath: namespaceRestApiRequestPath },
|
||||
namespace: { restApiRequestPath: namespaceRestApiRequestPath, path },
|
||||
filters: {
|
||||
authors: {},
|
||||
milestones: {},
|
||||
assignees: {},
|
||||
labels: {},
|
||||
},
|
||||
};
|
||||
|
||||
function createStore({ initialState = {}, initialGetters = {} }) {
|
||||
|
|
@ -52,10 +59,7 @@ function createStore({ initialState = {}, initialGetters = {} }) {
|
|||
},
|
||||
getters: {
|
||||
pathNavigationData: () => transformedProjectStagePathData,
|
||||
filterParams: () => ({
|
||||
created_after: createdAfter,
|
||||
created_before: createdBefore,
|
||||
}),
|
||||
filterParams,
|
||||
...initialGetters,
|
||||
},
|
||||
});
|
||||
|
|
@ -102,10 +106,24 @@ describe('Value stream analytics component', () => {
|
|||
expect(findOverviewMetrics().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets the request params for the metrics component', () => {
|
||||
expect(findOverviewMetrics().props('requestParams')).toMatchObject({
|
||||
assigneeUsernames: null,
|
||||
authorUsername: null,
|
||||
milestoneTitle: null,
|
||||
labelNames: null,
|
||||
endDate: '2019-01-14',
|
||||
startDate: '2018-12-15',
|
||||
});
|
||||
});
|
||||
|
||||
it('passes relevant props to the metrics component', () => {
|
||||
expect(findOverviewMetrics().props('isLicensed')).toBe(false);
|
||||
expect(findOverviewMetrics().props('queryType')).toBe('FLOW_METRICS_QUERY_TYPE');
|
||||
expect(findOverviewMetrics().props('isProjectNamespace')).toBe(true);
|
||||
expect(findOverviewMetrics().props()).toMatchObject({
|
||||
requestPath: path,
|
||||
isLicensed: false,
|
||||
queryType: 'FLOW_METRICS_QUERY_TYPE',
|
||||
isProjectNamespace: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the stage table', () => {
|
||||
|
|
@ -169,7 +187,7 @@ describe('Value stream analytics component', () => {
|
|||
it('renders a link to the value streams dashboard using the namespace path', () => {
|
||||
expect(findOverviewMetrics().props('dashboardsPath')).toBeDefined();
|
||||
expect(findOverviewMetrics().props('dashboardsPath')).toBe(
|
||||
'/full/path/to/foo/-/analytics/dashboards/value_streams_dashboard',
|
||||
'/rest/full/path/to/foo/-/analytics/dashboards/value_streams_dashboard',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,208 +0,0 @@
|
|||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import LegacyValueStreamMetrics from '~/analytics/shared/components/legacy_value_stream_metrics.vue';
|
||||
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
|
||||
import { VSA_METRICS_GROUPS, VALUE_STREAM_METRIC_METADATA } from '~/analytics/shared/constants';
|
||||
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
|
||||
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
|
||||
import ValueStreamsDashboardLink from '~/analytics/shared/components/value_streams_dashboard_link.vue';
|
||||
import { createAlert } from '~/alert';
|
||||
import { group } from '../mock_data';
|
||||
|
||||
jest.mock('~/alert');
|
||||
|
||||
describe('LegacyValueStreamMetrics', () => {
|
||||
let wrapper;
|
||||
let mockGetValueStreamSummaryMetrics;
|
||||
let mockFilterFn;
|
||||
|
||||
const { full_path: requestPath } = group;
|
||||
const fakeReqName = 'Mock metrics';
|
||||
const metricsRequestFactory = () => ({
|
||||
request: mockGetValueStreamSummaryMetrics,
|
||||
endpoint: METRIC_TYPE_SUMMARY,
|
||||
name: fakeReqName,
|
||||
});
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
return shallowMountExtended(LegacyValueStreamMetrics, {
|
||||
propsData: {
|
||||
requestPath,
|
||||
requestParams: {},
|
||||
requests: [metricsRequestFactory()],
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findVSDLink = () => wrapper.findComponent(ValueStreamsDashboardLink);
|
||||
const findMetrics = () => wrapper.findAllComponents(MetricTile);
|
||||
const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group');
|
||||
|
||||
const expectToHaveRequest = (fields) => {
|
||||
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
|
||||
endpoint: METRIC_TYPE_SUMMARY,
|
||||
requestPath,
|
||||
...fields,
|
||||
});
|
||||
};
|
||||
|
||||
describe('with successful requests', () => {
|
||||
beforeEach(() => {
|
||||
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
|
||||
});
|
||||
|
||||
it('will display a loader with pending requests', async () => {
|
||||
wrapper = createComponent();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('with data loaded', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('fetches data from the value stream analytics endpoint', () => {
|
||||
expectToHaveRequest({ params: {} });
|
||||
});
|
||||
|
||||
describe.each`
|
||||
index | identifier | value | label
|
||||
${0} | ${metricsData[0].identifier} | ${metricsData[0].value} | ${metricsData[0].title}
|
||||
${1} | ${metricsData[1].identifier} | ${metricsData[1].value} | ${metricsData[1].title}
|
||||
${2} | ${metricsData[2].identifier} | ${metricsData[2].value} | ${metricsData[2].title}
|
||||
${3} | ${metricsData[3].identifier} | ${metricsData[3].value} | ${metricsData[3].title}
|
||||
`('metric tiles', ({ identifier, index, value, label }) => {
|
||||
it(`renders a metric tile component for "${label}"`, () => {
|
||||
const metric = findMetrics().at(index);
|
||||
expect(metric.props('metric')).toMatchObject({ identifier, value, label });
|
||||
expect(metric.isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('will not display a loading icon', () => {
|
||||
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('filterFn', () => {
|
||||
const transferredMetricsData = prepareTimeMetricsData(
|
||||
metricsData,
|
||||
VALUE_STREAM_METRIC_METADATA,
|
||||
);
|
||||
|
||||
it('with a filter function, will call the function with the metrics data', async () => {
|
||||
const filteredData = [
|
||||
{ identifier: 'issues', value: '3', title: 'New issues', description: 'foo' },
|
||||
];
|
||||
mockFilterFn = jest.fn(() => filteredData);
|
||||
|
||||
wrapper = createComponent({
|
||||
filterFn: mockFilterFn,
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockFilterFn).toHaveBeenCalledWith(transferredMetricsData);
|
||||
expect(findMetrics().at(0).props('metric')).toEqual(filteredData[0]);
|
||||
});
|
||||
|
||||
it('without a filter function, it will only update the metrics', async () => {
|
||||
wrapper = createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockFilterFn).not.toHaveBeenCalled();
|
||||
expect(findMetrics().at(0).props('metric')).toEqual(transferredMetricsData[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with additional params', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = createComponent({
|
||||
requestParams: {
|
||||
'project_ids[]': [1],
|
||||
created_after: '2020-01-01',
|
||||
created_before: '2020-02-01',
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('fetches data for the `getValueStreamSummaryMetrics` request', () => {
|
||||
expectToHaveRequest({
|
||||
params: {
|
||||
'project_ids[]': [1],
|
||||
created_after: '2020-01-01',
|
||||
created_before: '2020-02-01',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('groupBy', () => {
|
||||
beforeEach(async () => {
|
||||
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
|
||||
wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders the metrics as separate groups', () => {
|
||||
const groups = findMetricsGroups();
|
||||
expect(groups).toHaveLength(VSA_METRICS_GROUPS.length);
|
||||
});
|
||||
|
||||
it('renders titles for each group', () => {
|
||||
const groups = findMetricsGroups();
|
||||
groups.wrappers.forEach((g, index) => {
|
||||
const { title } = VSA_METRICS_GROUPS[index];
|
||||
expect(g.html()).toContain(title);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Value Streams Dashboard Link', () => {
|
||||
it('will render when a dashboardsPath is set', async () => {
|
||||
wrapper = createComponent({
|
||||
groupBy: VSA_METRICS_GROUPS,
|
||||
dashboardsPath: 'fake-group-path',
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
const vsdLink = findVSDLink();
|
||||
|
||||
expect(vsdLink.exists()).toBe(true);
|
||||
expect(vsdLink.props()).toEqual({ requestPath: 'fake-group-path' });
|
||||
});
|
||||
|
||||
it('does not render without a dashboardsPath', async () => {
|
||||
wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS });
|
||||
await waitForPromises();
|
||||
|
||||
expect(findVSDLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a request failing', () => {
|
||||
beforeEach(async () => {
|
||||
mockGetValueStreamSummaryMetrics = jest.fn().mockRejectedValue();
|
||||
wrapper = createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('should render an error message', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: `There was an error while fetching value stream analytics ${fakeReqName} data.`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -88,16 +88,19 @@ describe('ValueStreamMetrics', () => {
|
|||
const { path: requestPath } = group;
|
||||
|
||||
const createComponent = async ({ props = {}, apolloProvider = null } = {}) => {
|
||||
const { requestParams, ...propsRest } = props;
|
||||
|
||||
wrapper = shallowMountExtended(ValueStreamMetrics, {
|
||||
apolloProvider,
|
||||
propsData: {
|
||||
requestPath,
|
||||
requestParams: {
|
||||
created_after: mockStartDate,
|
||||
created_before: mockEndDate,
|
||||
startDate: mockStartDate,
|
||||
endDate: mockEndDate,
|
||||
...requestParams,
|
||||
},
|
||||
isLicensed: true,
|
||||
...props,
|
||||
...propsRest,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -129,12 +132,20 @@ describe('ValueStreamMetrics', () => {
|
|||
startDate = '2018-12-15',
|
||||
endDate = '2019-01-14',
|
||||
labelNames,
|
||||
projectIds,
|
||||
assigneeUsernames,
|
||||
authorUsername,
|
||||
milestoneTitle,
|
||||
} = {}) =>
|
||||
expect(flowMetricsRequestHandler).toHaveBeenCalledWith({
|
||||
fullPath,
|
||||
startDate,
|
||||
endDate,
|
||||
labelNames,
|
||||
projectIds,
|
||||
assigneeUsernames,
|
||||
authorUsername,
|
||||
milestoneTitle,
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -228,6 +239,10 @@ describe('ValueStreamMetrics', () => {
|
|||
});
|
||||
|
||||
describe('with additional params', () => {
|
||||
const assigneeUsernames = ['Rei Ayanami', 'Asuka Shikinami', 'Mari Makinami'];
|
||||
const authorUsername = 'Yui Ikari';
|
||||
const milestoneTitle = 'N3i';
|
||||
|
||||
beforeEach(async () => {
|
||||
setGraphqlQueryHandlerResponses();
|
||||
|
||||
|
|
@ -235,10 +250,13 @@ describe('ValueStreamMetrics', () => {
|
|||
apolloProvider: createMockApolloProvider(),
|
||||
props: {
|
||||
requestParams: {
|
||||
'project_ids[]': [1],
|
||||
created_after: '2020-01-01',
|
||||
created_before: '2020-02-01',
|
||||
'labelNames[]': ['some', 'fake', 'label'],
|
||||
startDate: new Date('2020-01-01'),
|
||||
endDate: new Date('2020-02-01'),
|
||||
projectIds: [1],
|
||||
labelNames: ['some', 'fake', 'label'],
|
||||
assigneeUsernames,
|
||||
authorUsername,
|
||||
milestoneTitle,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -246,15 +264,19 @@ describe('ValueStreamMetrics', () => {
|
|||
|
||||
it('fetches the flowMetrics data', () => {
|
||||
expectFlowMetricsRequests({
|
||||
'project_ids[]': [1],
|
||||
labelNames: ['some', 'fake', 'label'],
|
||||
projectIds: [1],
|
||||
startDate: '2020-01-01',
|
||||
endDate: '2020-02-01',
|
||||
assigneeUsernames,
|
||||
authorUsername,
|
||||
milestoneTitle,
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches the doraMetrics data', () => {
|
||||
expectDoraMetricsRequests({
|
||||
'project_ids[]': [1],
|
||||
projectIds: [1],
|
||||
startDate: '2020-01-01',
|
||||
endDate: '2020-02-01',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
|
||||
import {
|
||||
extractFilterQueryParameters,
|
||||
extractPaginationQueryParameters,
|
||||
filterBySearchTerm,
|
||||
generateValueStreamsDashboardLink,
|
||||
getDataZoomOption,
|
||||
prepareTimeMetricsData,
|
||||
overviewMetricsRequestParams,
|
||||
} from '~/analytics/shared/utils';
|
||||
import { slugify } from '~/lib/utils/text_utility';
|
||||
import { objectToQuery } from '~/lib/utils/url_utility';
|
||||
|
||||
describe('filterBySearchTerm', () => {
|
||||
|
|
@ -181,39 +179,6 @@ 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: '' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateValueStreamsDashboardLink', () => {
|
||||
it.each`
|
||||
namespacePath | isProjectNamespace | result
|
||||
|
|
@ -241,3 +206,22 @@ describe('generateValueStreamsDashboardLink', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overviewMetricsRequestParams', () => {
|
||||
it('returns empty object when no params provided', () => {
|
||||
expect(overviewMetricsRequestParams()).toEqual({});
|
||||
});
|
||||
|
||||
it.each`
|
||||
requestParam | value | expected
|
||||
${'created_after'} | ${'2024-01-01'} | ${'startDate'}
|
||||
${'created_before'} | ${'2024-12-31'} | ${'endDate'}
|
||||
${'label_name'} | ${['bug', 'feature']} | ${'labelNames'}
|
||||
${'assignee_username'} | ${['user1', 'user2']} | ${'assigneeUsernames'}
|
||||
${'author_username'} | ${'Author A'} | ${'authorUsername'}
|
||||
${'milestone_title'} | ${'some new milestone'} | ${'milestoneTitle'}
|
||||
`('correctly transforms the $requestParam parameter', ({ requestParam, value, expected }) => {
|
||||
const result = overviewMetricsRequestParams({ [requestParam]: value });
|
||||
expect(result[expected]).toBe(value);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,21 +47,4 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Projects::Analytics::CycleAnalytics::SummaryController, type: :controller do
|
||||
render_views
|
||||
let(:params) { { namespace_id: group, project_id: project, value_stream_id: value_stream_id } }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "projects/analytics/value_stream_analytics/summary.json" do
|
||||
get(:show, params: params, format: :json)
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ describe('Work Item State toggle button component', () => {
|
|||
workItemState = STATE_OPEN,
|
||||
workItemType = 'Task',
|
||||
hasComment = false,
|
||||
disabled = false,
|
||||
parentId = null,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemStateToggle, {
|
||||
|
|
@ -79,11 +80,20 @@ describe('Work Item State toggle button component', () => {
|
|||
workItemType,
|
||||
canUpdate,
|
||||
hasComment,
|
||||
disabled,
|
||||
parentId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('disables button when disabled prop is set', () => {
|
||||
createComponent({
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
expect(findStateToggleButton().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
describe('work item State button text', () => {
|
||||
it.each`
|
||||
workItemState | workItemType | buttonText
|
||||
|
|
|
|||
|
|
@ -337,7 +337,6 @@
|
|||
- './ee/spec/finders/users_finder_spec.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/charts.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/metrics.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/value_streams_code_stage.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/value_streams_issue_stage.rb'
|
||||
- './ee/spec/frontend/fixtures/analytics/value_streams_plan_stage.rb'
|
||||
|
|
|
|||
Loading…
Reference in New Issue