diff options
Diffstat (limited to 'app/assets/javascripts/cycle_analytics/components')
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">·</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> |