Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/cycle_analytics/components')
-rw-r--r--app/assets/javascripts/cycle_analytics/components/base.vue192
-rw-r--r--app/assets/javascripts/cycle_analytics/components/filter_bar.vue144
-rw-r--r--app/assets/javascripts/cycle_analytics/components/formatted_stage_count.vue32
-rw-r--r--app/assets/javascripts/cycle_analytics/components/metric_tile.vue51
-rw-r--r--app/assets/javascripts/cycle_analytics/components/path_navigation.vue113
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_table.vue305
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time.vue61
-rw-r--r--app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue109
8 files changed, 0 insertions, 1007 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue
deleted file mode 100644
index f06544f50c6..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/base.vue
+++ /dev/null
@@ -1,192 +0,0 @@
-<script>
-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 { VSA_METRICS_GROUPS } from '~/analytics/shared/constants';
-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 UrlSync from '~/vue_shared/components/url_sync.vue';
-import { __ } from '~/locale';
-import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants';
-
-const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
-
-export default {
- name: 'CycleAnalytics',
- components: {
- GlLoadingIcon,
- PathNavigation,
- StageTable,
- ValueStreamFilters,
- ValueStreamMetrics,
- UrlSync,
- },
- props: {
- noDataSvgPath: {
- type: String,
- required: true,
- },
- noAccessSvgPath: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- isOverviewDialogDismissed: getCookie(OVERVIEW_DIALOG_COOKIE),
- };
- },
- computed: {
- ...mapState([
- 'isLoading',
- 'isLoadingStage',
- 'isEmptyStage',
- 'selectedStage',
- 'selectedStageEvents',
- 'selectedStageError',
- 'stageCounts',
- 'endpoints',
- 'features',
- 'createdBefore',
- 'createdAfter',
- 'pagination',
- 'hasNoAccessError',
- ]),
- ...mapGetters(['pathNavigationData', 'filterParams']),
- isLoaded() {
- return !this.isLoading && !this.isLoadingStage;
- },
- displayStageEvents() {
- const { selectedStageEvents, isLoadingStage, isEmptyStage } = this;
- return selectedStageEvents.length && !isLoadingStage && !isEmptyStage;
- },
- displayNotEnoughData() {
- return !this.isLoadingStage && this.isEmptyStage;
- },
- displayNoAccess() {
- return !this.isLoadingStage && this.hasNoAccessError;
- },
- displayPathNavigation() {
- return this.isLoading || (this.selectedStage && this.pathNavigationData.length);
- },
- emptyStageTitle() {
- if (this.displayNoAccess) {
- return __('You need permission.');
- }
- return this.selectedStageError
- ? this.selectedStageError
- : __("We don't have enough data to show this stage.");
- },
- emptyStageText() {
- if (this.displayNoAccess) {
- return __('Want to see the data? Please ask an administrator for access.');
- }
- return !this.selectedStageError && this.selectedStage?.emptyStageText
- ? this.selectedStage?.emptyStageText
- : '';
- },
- selectedStageCount() {
- if (this.selectedStage) {
- const {
- stageCounts,
- selectedStage: { id },
- } = this;
- return stageCounts[id];
- }
- return 0;
- },
- metricsRequests() {
- return this.features?.cycleAnalyticsForGroups ? METRICS_REQUESTS : SUMMARY_METRICS_REQUEST;
- },
- query() {
- return {
- created_after: toYmd(this.createdAfter),
- created_before: toYmd(this.createdBefore),
- stage_id: this.selectedStage?.id || null,
- sort: this.pagination?.sort || null,
- direction: this.pagination?.direction || null,
- page: this.pagination?.page || null,
- };
- },
- },
- methods: {
- ...mapActions([
- 'fetchStageData',
- 'setSelectedStage',
- 'setDateRange',
- 'updateStageTablePagination',
- ]),
- onSetDateRange({ startDate, endDate }) {
- this.setDateRange({
- createdAfter: new Date(startDate),
- createdBefore: new Date(endDate),
- });
- },
- onSelectStage(stage) {
- this.setSelectedStage(stage);
- this.updateStageTablePagination({ ...this.pagination, page: 1 });
- },
- dismissOverviewDialog() {
- this.isOverviewDialogDismissed = true;
- setCookie(OVERVIEW_DIALOG_COOKIE, '1');
- },
- onHandleUpdatePagination(data) {
- this.updateStageTablePagination(data);
- },
- },
- dayRangeOptions: [7, 30, 90],
- i18n: {
- dropdownText: __('Last %{days} days'),
- pageTitle: __('Value Stream Analytics'),
- recentActivity: __('Recent Project Activity'),
- },
- VSA_METRICS_GROUPS,
-};
-</script>
-<template>
- <div>
- <h3>{{ $options.i18n.pageTitle }}</h3>
- <value-stream-filters
- :group-id="endpoints.groupId"
- :group-path="endpoints.groupPath"
- :has-project-filter="false"
- :start-date="createdAfter"
- :end-date="createdBefore"
- @setDateRange="onSetDateRange"
- />
- <div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row">
- <path-navigation
- v-if="displayPathNavigation"
- data-testid="vsa-path-navigation"
- class="gl-w-full gl-mt-4"
- :loading="isLoading || isLoadingStage"
- :stages="pathNavigationData"
- :selected-stage="selectedStage"
- @selected="onSelectStage"
- />
- </div>
- <value-stream-metrics
- :request-path="endpoints.fullPath"
- :request-params="filterParams"
- :requests="metricsRequests"
- :group-by="$options.VSA_METRICS_GROUPS"
- />
- <gl-loading-icon v-if="isLoading" size="lg" />
- <stage-table
- v-else
- :is-loading="isLoading || isLoadingStage"
- :stage-events="selectedStageEvents"
- :selected-stage="selectedStage"
- :stage-count="selectedStageCount"
- :empty-state-title="emptyStageTitle"
- :empty-state-message="emptyStageText"
- :no-data-svg-path="noDataSvgPath"
- :pagination="pagination"
- @handleUpdatePagination="onHandleUpdatePagination"
- />
- <url-sync v-if="isLoaded" :query="query" />
- </div>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/filter_bar.vue b/app/assets/javascripts/cycle_analytics/components/filter_bar.vue
deleted file mode 100644
index 0ad325a8523..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/filter_bar.vue
+++ /dev/null
@@ -1,144 +0,0 @@
-<script>
-import { mapActions, mapState } from 'vuex';
-import {
- OPERATOR_IS_ONLY,
- DEFAULT_NONE_ANY,
- TOKEN_TITLE_ASSIGNEE,
- TOKEN_TITLE_AUTHOR,
- TOKEN_TITLE_LABEL,
- TOKEN_TITLE_MILESTONE,
-} from '~/vue_shared/components/filtered_search_bar/constants';
-import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-import {
- prepareTokens,
- processFilters,
- filterToQueryObject,
-} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
-import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
-import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
-import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
-import UrlSync from '~/vue_shared/components/url_sync.vue';
-
-export default {
- name: 'FilterBar',
- components: {
- FilteredSearchBar,
- UrlSync,
- },
- props: {
- groupPath: {
- type: String,
- required: true,
- },
- },
- computed: {
- ...mapState('filters', {
- selectedMilestone: (state) => state.milestones.selected,
- selectedAuthor: (state) => state.authors.selected,
- selectedLabelList: (state) => state.labels.selectedList,
- selectedAssigneeList: (state) => state.assignees.selectedList,
- milestonesData: (state) => state.milestones.data,
- labelsData: (state) => state.labels.data,
- authorsData: (state) => state.authors.data,
- assigneesData: (state) => state.assignees.data,
- }),
- tokens() {
- return [
- {
- icon: 'clock',
- title: TOKEN_TITLE_MILESTONE,
- type: 'milestone',
- token: MilestoneToken,
- initialMilestones: this.milestonesData,
- unique: true,
- symbol: '%',
- operators: OPERATOR_IS_ONLY,
- fetchMilestones: this.fetchMilestones,
- },
- {
- icon: 'labels',
- title: TOKEN_TITLE_LABEL,
- type: 'labels',
- token: LabelToken,
- defaultLabels: DEFAULT_NONE_ANY,
- initialLabels: this.labelsData,
- unique: false,
- symbol: '~',
- operators: OPERATOR_IS_ONLY,
- fetchLabels: this.fetchLabels,
- },
- {
- icon: 'pencil',
- title: TOKEN_TITLE_AUTHOR,
- type: 'author',
- token: AuthorToken,
- initialAuthors: this.authorsData,
- unique: true,
- operators: OPERATOR_IS_ONLY,
- fetchAuthors: this.fetchAuthors,
- },
- {
- icon: 'user',
- title: TOKEN_TITLE_ASSIGNEE,
- type: 'assignees',
- token: AuthorToken,
- initialAuthors: this.assigneesData,
- unique: false,
- operators: OPERATOR_IS_ONLY,
- fetchAuthors: this.fetchAssignees,
- },
- ];
- },
- query() {
- return filterToQueryObject({
- milestone_title: this.selectedMilestone,
- author_username: this.selectedAuthor,
- label_name: this.selectedLabelList,
- assignee_username: this.selectedAssigneeList,
- });
- },
- },
- methods: {
- ...mapActions('filters', [
- 'setFilters',
- 'fetchMilestones',
- 'fetchLabels',
- 'fetchAuthors',
- 'fetchAssignees',
- ]),
- initialFilterValue() {
- return prepareTokens({
- milestone: this.selectedMilestone,
- author: this.selectedAuthor,
- assignees: this.selectedAssigneeList,
- labels: this.selectedLabelList,
- });
- },
- handleFilter(filters) {
- const { labels, milestone, author, assignees } = processFilters(filters);
-
- this.setFilters({
- selectedAuthor: author ? author[0] : null,
- selectedMilestone: milestone ? milestone[0] : null,
- selectedAssigneeList: assignees || [],
- selectedLabelList: labels || [],
- });
- },
- },
-};
-</script>
-
-<template>
- <div>
- <filtered-search-bar
- class="gl-flex-grow-1"
- :namespace="groupPath"
- recent-searches-storage-key="value-stream-analytics"
- :search-input-placeholder="__('Filter results')"
- :tokens="tokens"
- :initial-filter-value="initialFilterValue()"
- @onFilter="handleFilter"
- />
- <url-sync :query="query" />
- </div>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/formatted_stage_count.vue b/app/assets/javascripts/cycle_analytics/components/formatted_stage_count.vue
deleted file mode 100644
index b622b0441e2..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/formatted_stage_count.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<script>
-import { s__, n__, sprintf, formatNumber } from '~/locale';
-
-export default {
- props: {
- stageCount: {
- type: Number,
- required: false,
- default: null,
- },
- },
- computed: {
- formattedStageCount() {
- if (!this.stageCount) {
- return '-';
- } else if (this.stageCount > 1000) {
- return sprintf(s__('ValueStreamAnalytics|%{stageCount}+ items'), {
- stageCount: formatNumber(1000),
- });
- }
-
- return sprintf(n__('%{count} item', '%{count} items', this.stageCount), {
- count: formatNumber(this.stageCount),
- });
- },
- },
-};
-</script>
-
-<template>
- <span>{{ formattedStageCount }}</span>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/metric_tile.vue b/app/assets/javascripts/cycle_analytics/components/metric_tile.vue
deleted file mode 100644
index a5c20b237b3..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/metric_tile.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script>
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
-import { redirectTo } from '~/lib/utils/url_utility';
-import MetricPopover from '~/analytics/shared/components/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>
diff --git a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
deleted file mode 100644
index 72a7659aac0..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<script>
-import { GlPath, GlPopover, GlSkeletonLoader, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import Tracking from '~/tracking';
-import { OVERVIEW_STAGE_ID } from '../constants';
-import FormattedStageCount from './formatted_stage_count.vue';
-
-export default {
- name: 'PathNavigation',
- components: {
- GlPath,
- GlSkeletonLoader,
- GlPopover,
- FormattedStageCount,
- },
- directives: {
- SafeHtml,
- },
- mixins: [Tracking.mixin()],
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
- stages: {
- type: Array,
- required: true,
- },
- selectedStage: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- },
- methods: {
- showPopover({ id }) {
- return id && id !== OVERVIEW_STAGE_ID;
- },
- onSelectStage($event) {
- this.$emit('selected', $event);
- this.track('click_path_navigation', {
- extra: {
- stage_id: $event.id,
- },
- });
- },
- },
- popoverOptions: {
- triggers: 'hover',
- placement: 'bottom',
- },
-};
-</script>
-<template>
- <gl-skeleton-loader v-if="loading" :width="235" :lines="2" />
- <gl-path v-else :key="selectedStage.id" :items="stages" @selected="onSelectStage">
- <template #default="{ pathItem, pathId }">
- <gl-popover
- v-if="showPopover(pathItem)"
- v-bind="$options.popoverOptions"
- :target="pathId"
- :css-classes="['stage-item-popover']"
- data-testid="stage-item-popover"
- >
- <template #title>{{ pathItem.title }}</template>
- <div class="gl-px-4">
- <div class="gl-display-flex gl-justify-content-space-between">
- <div class="gl-pr-4 gl-pb-4">
- {{ s__('ValueStreamEvent|Stage time (median)') }}
- </div>
- <div class="gl-pb-4 gl-font-weight-bold">{{ pathItem.metric }}</div>
- </div>
- </div>
- <div class="gl-px-4">
- <div class="gl-display-flex gl-justify-content-space-between">
- <div class="gl-pr-4 gl-pb-4">
- {{ s__('ValueStreamEvent|Items in stage') }}
- </div>
- <div class="gl-pb-4 gl-font-weight-bold">
- <formatted-stage-count :stage-count="pathItem.stageCount" />
- </div>
- </div>
- </div>
- <div class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50">
- <div
- v-if="pathItem.startEventHtmlDescription"
- class="gl-display-flex gl-flex-direction-row"
- >
- <div class="gl-display-flex gl-flex-direction-column gl-pr-4 gl-pb-4 metric-label">
- {{ s__('ValueStreamEvent|Start') }}
- </div>
- <div
- v-safe-html="pathItem.startEventHtmlDescription"
- class="gl-display-flex gl-flex-direction-column gl-pb-4 stage-event-description"
- ></div>
- </div>
- <div
- v-if="pathItem.endEventHtmlDescription"
- class="gl-display-flex gl-flex-direction-row"
- >
- <div class="gl-display-flex gl-flex-direction-column gl-pr-4 metric-label">
- {{ s__('ValueStreamEvent|Stop') }}
- </div>
- <div
- v-safe-html="pathItem.endEventHtmlDescription"
- class="gl-display-flex gl-flex-direction-column stage-event-description"
- ></div>
- </div>
- </div>
- </gl-popover>
- </template>
- </gl-path>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
deleted file mode 100644
index f1fdffd4b72..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue
+++ /dev/null
@@ -1,305 +0,0 @@
-<script>
-import {
- GlEmptyState,
- GlIcon,
- GlLink,
- GlLoadingIcon,
- GlPagination,
- GlTable,
- GlBadge,
-} from '@gitlab/ui';
-import FormattedStageCount from '~/cycle_analytics/components/formatted_stage_count.vue';
-import { __ } from '~/locale';
-import Tracking from '~/tracking';
-import {
- NOT_ENOUGH_DATA_ERROR,
- FIELD_KEY_TITLE,
- PAGINATION_SORT_FIELD_END_EVENT,
- PAGINATION_SORT_FIELD_DURATION,
- PAGINATION_SORT_DIRECTION_ASC,
- PAGINATION_SORT_DIRECTION_DESC,
-} from '../constants';
-import TotalTime from './total_time.vue';
-
-const DEFAULT_WORKFLOW_TITLE_PROPERTIES = {
- thClass: 'gl-w-half',
- key: FIELD_KEY_TITLE,
- sortable: false,
-};
-
-const WORKFLOW_COLUMN_TITLES = {
- issues: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Issues') },
- jobs: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Jobs') },
- deployments: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Deployments') },
- mergeRequests: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Merge requests') },
-};
-
-const fullProjectPath = ({ namespaceFullPath = '', projectPath }) =>
- namespaceFullPath.split('/').length > 1 ? `${namespaceFullPath}/${projectPath}` : projectPath;
-
-export default {
- name: 'StageTable',
- components: {
- GlEmptyState,
- GlIcon,
- GlLink,
- GlLoadingIcon,
- GlPagination,
- GlTable,
- GlBadge,
- TotalTime,
- FormattedStageCount,
- },
- mixins: [Tracking.mixin()],
- props: {
- selectedStage: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- isLoading: {
- type: Boolean,
- required: true,
- },
- stageEvents: {
- type: Array,
- required: true,
- },
- stageCount: {
- type: Number,
- required: false,
- default: null,
- },
- noDataSvgPath: {
- type: String,
- required: true,
- },
- emptyStateTitle: {
- type: String,
- required: false,
- default: null,
- },
- emptyStateMessage: {
- type: String,
- required: false,
- default: '',
- },
- pagination: {
- type: Object,
- required: false,
- default: null,
- },
- sortable: {
- type: Boolean,
- required: false,
- default: true,
- },
- includeProjectName: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- if (this.pagination) {
- const {
- pagination: { sort, direction },
- } = this;
- return {
- sort,
- direction,
- sortDesc: direction === PAGINATION_SORT_DIRECTION_DESC,
- };
- }
- return { sort: null, direction: null, sortDesc: null };
- },
- computed: {
- isEmptyStage() {
- return !this.selectedStage || !this.stageEvents.length;
- },
- emptyStateTitleText() {
- return this.emptyStateTitle || NOT_ENOUGH_DATA_ERROR;
- },
- isMergeRequestStage() {
- const [firstEvent] = this.stageEvents;
- return this.isMrLink(firstEvent.url);
- },
- workflowTitle() {
- if (this.isMergeRequestStage) {
- return WORKFLOW_COLUMN_TITLES.mergeRequests;
- }
- return WORKFLOW_COLUMN_TITLES.issues;
- },
- fields() {
- return [
- this.workflowTitle,
- {
- key: PAGINATION_SORT_FIELD_END_EVENT,
- label: __('Last event'),
- sortable: this.sortable,
- },
- {
- key: PAGINATION_SORT_FIELD_DURATION,
- label: __('Duration'),
- sortable: this.sortable,
- },
- ];
- },
- prevPage() {
- return Math.max(this.pagination.page - 1, 0);
- },
- nextPage() {
- return this.pagination.hasNextPage ? this.pagination.page + 1 : null;
- },
- },
- methods: {
- isMrLink(url = '') {
- return url.includes('/merge_request');
- },
- itemId({ iid, projectPath, namespaceFullPath = '' }, separator = '#') {
- const prefix = this.includeProjectName
- ? fullProjectPath({ namespaceFullPath, projectPath })
- : '';
- return `${prefix}${separator}${iid}`;
- },
- itemDisplayName(item) {
- const separator = this.isMrLink(item.url) ? '!' : '#';
- return this.itemId(item, separator);
- },
- itemTitle(item) {
- return item.title || item.name;
- },
- onSelectPage(page) {
- const { sort, direction } = this.pagination;
- this.track('click_button', { label: 'pagination' });
- this.$emit('handleUpdatePagination', { sort, direction, page });
- },
- onSort({ sortBy, sortDesc }) {
- const direction = sortDesc ? PAGINATION_SORT_DIRECTION_DESC : PAGINATION_SORT_DIRECTION_ASC;
- this.sort = sortBy;
- this.sortDesc = sortDesc;
- this.$emit('handleUpdatePagination', { sort: sortBy, direction });
- this.track('click_button', { label: `sort_${sortBy}_${direction}` });
- },
- },
-};
-</script>
-<template>
- <div data-testid="vsa-stage-table">
- <gl-loading-icon v-if="isLoading" class="gl-mt-4" size="lg" />
- <gl-empty-state
- v-else-if="isEmptyStage"
- :title="emptyStateTitleText"
- :description="emptyStateMessage"
- :svg-path="noDataSvgPath"
- />
- <gl-table
- v-else
- stacked="lg"
- show-empty
- :sort-by.sync="sort"
- :sort-direction.sync="direction"
- :sort-desc.sync="sortDesc"
- :fields="fields"
- :items="stageEvents"
- :empty-text="emptyStateMessage"
- @sort-changed="onSort"
- >
- <template v-if="stageCount" #head(title)="data">
- <span>{{ data.label }}</span
- ><gl-badge class="gl-ml-2" size="sm"
- ><formatted-stage-count :stage-count="stageCount"
- /></gl-badge>
- </template>
- <template #head(duration)="data">
- <span data-testid="vsa-stage-header-duration">{{ data.label }}</span>
- </template>
- <template #head(end_event)="data">
- <span data-testid="vsa-stage-header-last-event">{{ data.label }}</span>
- </template>
- <template #cell(title)="{ item }">
- <div data-testid="vsa-stage-event">
- <div v-if="item.id" data-testid="vsa-stage-content">
- <p class="gl-m-0">
- <gl-link
- data-testid="vsa-stage-event-link"
- class="gl-text-black-normal"
- :href="item.url"
- >{{ itemId(item.id, '#') }}</gl-link
- >
- <gl-icon :size="16" name="fork" />
- <gl-link
- v-if="item.branch"
- :href="item.branch.url"
- class="gl-text-black-normal ref-name"
- >{{ item.branch.name }}</gl-link
- >
- <span class="icon-branch gl-text-gray-400">
- <gl-icon name="commit" :size="14" />
- </span>
- <gl-link
- class="commit-sha"
- :href="item.commitUrl"
- data-testid="vsa-stage-event-build-sha"
- >{{ item.shortSha }}</gl-link
- >
- </p>
- <p class="gl-m-0">
- <span data-testid="vsa-stage-event-build-author-and-date">
- <gl-link class="gl-text-black-normal" :href="item.url">{{ item.date }}</gl-link>
- {{ s__('ByAuthor|by') }}
- <gl-link
- class="gl-text-black-normal issue-author-link"
- :href="item.author.webUrl"
- >{{ item.author.name }}</gl-link
- >
- </span>
- </p>
- </div>
- <div v-else data-testid="vsa-stage-content">
- <h5 class="gl-font-weight-bold gl-my-1" data-testid="vsa-stage-event-title">
- <gl-link class="gl-text-black-normal" :href="item.url">{{ itemTitle(item) }}</gl-link>
- </h5>
- <p class="gl-m-0">
- <gl-link
- data-testid="vsa-stage-event-link"
- class="gl-text-black-normal"
- :href="item.url"
- >{{ itemDisplayName(item) }}</gl-link
- >
- <span class="gl-font-lg">&middot;</span>
- <span data-testid="vsa-stage-event-date">
- {{ s__('OpenedNDaysAgo|Created') }}
- <gl-link class="gl-text-black-normal" :href="item.url">{{
- item.createdAt
- }}</gl-link>
- </span>
- <span data-testid="vsa-stage-event-author">
- {{ s__('ByAuthor|by') }}
- <gl-link class="gl-text-black-normal" :href="item.author.webUrl">{{
- item.author.name
- }}</gl-link>
- </span>
- </p>
- </div>
- </div>
- </template>
- <template #cell(duration)="{ item }">
- <total-time :time="item.totalTime" data-testid="vsa-stage-event-time" />
- </template>
- <template #cell(end_event)="{ item }">
- <span data-testid="vsa-stage-last-event">{{ item.endEventTimestamp }}</span>
- </template>
- </gl-table>
- <gl-pagination
- v-if="pagination && !isLoading && !isEmptyStage"
- :value="pagination.page"
- :prev-page="prevPage"
- :next-page="nextPage"
- align="center"
- class="gl-mt-3"
- data-testid="vsa-stage-pagination"
- @input="onSelectPage"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time.vue b/app/assets/javascripts/cycle_analytics/components/total_time.vue
deleted file mode 100644
index 725952c3518..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/total_time.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script>
-import { n__, s__ } from '~/locale';
-
-export default {
- props: {
- time: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- },
- computed: {
- hasData() {
- return Object.keys(this.time).length;
- },
- calculatedTime() {
- const {
- time: { days = null, mins = null, hours = null, seconds = null },
- } = this;
-
- if (days) {
- return {
- duration: days,
- units: n__('day', 'days', days),
- };
- }
-
- if (hours) {
- return {
- duration: hours,
- units: n__('Time|hr', 'Time|hrs', hours),
- };
- }
-
- if (mins && !days) {
- return {
- duration: mins,
- units: n__('Time|min', 'Time|mins', mins),
- };
- }
-
- if ((seconds && this.hasData === 1) || seconds === 0) {
- return {
- duration: seconds,
- units: s__('Time|s'),
- };
- }
-
- return { duration: null, units: null };
- },
- },
-};
-</script>
-<template>
- <span>
- <template v-if="hasData">
- {{ calculatedTime.duration }} <span>{{ calculatedTime.units }}</span>
- </template>
- <template v-else> -- </template>
- </span>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
deleted file mode 100644
index 17decb6b448..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-<script>
-import { GlTooltipDirective } from '@gitlab/ui';
-import DateRange from '~/analytics/shared/components/daterange.vue';
-import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
-import { DATE_RANGE_LIMIT, PROJECTS_PER_PAGE } from '~/analytics/shared/constants';
-import FilterBar from './filter_bar.vue';
-
-export default {
- name: 'ValueStreamFilters',
- components: {
- DateRange,
- ProjectsDropdownFilter,
- FilterBar,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- selectedProjects: {
- type: Array,
- required: false,
- default: () => [],
- },
- hasProjectFilter: {
- type: Boolean,
- required: false,
- default: true,
- },
- hasDateRangeFilter: {
- type: Boolean,
- required: false,
- default: true,
- },
- groupId: {
- type: Number,
- required: true,
- },
- groupPath: {
- type: String,
- required: true,
- },
- startDate: {
- type: Date,
- required: false,
- default: null,
- },
- endDate: {
- type: Date,
- required: false,
- default: null,
- },
- },
- computed: {
- projectsQueryParams() {
- return {
- first: PROJECTS_PER_PAGE,
- includeSubgroups: true,
- };
- },
- currentDate() {
- const now = new Date();
- return new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
- },
- },
- multiProjectSelect: true,
- maxDateRange: DATE_RANGE_LIMIT,
-};
-</script>
-<template>
- <div
- class="gl-mt-3 gl-py-2 gl-px-3 gl-bg-gray-10 gl-border-b-1 gl-border-b-solid gl-border-t-1 gl-border-t-solid gl-border-gray-100"
- >
- <filter-bar
- data-testid="vsa-filter-bar"
- class="filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none"
- :group-path="groupPath"
- />
- <div
- v-if="hasDateRangeFilter || hasProjectFilter"
- class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between"
- >
- <div>
- <projects-dropdown-filter
- v-if="hasProjectFilter"
- :key="groupId"
- class="js-projects-dropdown-filter project-select gl-mb-2 gl-lg-mb-0"
- :group-id="groupId"
- :group-namespace="groupPath"
- :query-params="projectsQueryParams"
- :multi-select="$options.multiProjectSelect"
- :default-projects="selectedProjects"
- @selected="$emit('selectProject', $event)"
- />
- </div>
- <div class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row">
- <date-range
- v-if="hasDateRangeFilter"
- :start-date="startDate"
- :end-date="endDate"
- :max-date="currentDate"
- :max-date-range="$options.maxDateRange"
- :include-selected-date="true"
- class="js-daterange-picker"
- @change="$emit('setDateRange', $event)"
- />
- </div>
- </div>
- </div>
-</template>