diff options
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
17 files changed, 0 insertions, 1683 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> diff --git a/app/assets/javascripts/cycle_analytics/constants.js b/app/assets/javascripts/cycle_analytics/constants.js deleted file mode 100644 index 2758d686fb1..00000000000 --- a/app/assets/javascripts/cycle_analytics/constants.js +++ /dev/null @@ -1,47 +0,0 @@ -import { - getValueStreamMetrics, - METRIC_TYPE_SUMMARY, - METRIC_TYPE_TIME_SUMMARY, -} from '~/api/analytics_api'; -import { __, s__ } from '~/locale'; - -export const OVERVIEW_STAGE_ID = 'overview'; - -export const DEFAULT_VALUE_STREAM = { - id: 'default', - slug: 'default', - name: 'default', -}; - -export const NOT_ENOUGH_DATA_ERROR = s__( - "ValueStreamAnalyticsStage|We don't have enough data to show this stage.", -); - -export const PAGINATION_TYPE = 'keyset'; -export const PAGINATION_SORT_FIELD_END_EVENT = 'end_event'; -export const PAGINATION_SORT_FIELD_DURATION = 'duration'; -export const PAGINATION_SORT_DIRECTION_DESC = 'desc'; -export const PAGINATION_SORT_DIRECTION_ASC = 'asc'; -export const FIELD_KEY_TITLE = 'title'; - -export const I18N_VSA_ERROR_STAGES = __( - 'There was an error fetching value stream analytics stages.', -); -export const I18N_VSA_ERROR_STAGE_MEDIAN = __('There was an error fetching median data for stages'); -export const I18N_VSA_ERROR_SELECTED_STAGE = __( - 'There was an error fetching data for the selected stage', -); - -export const OVERVIEW_METRICS = { - TIME_SUMMARY: 'TIME_SUMMARY', - RECENT_ACTIVITY: 'RECENT_ACTIVITY', -}; - -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, -]; diff --git a/app/assets/javascripts/cycle_analytics/index.js b/app/assets/javascripts/cycle_analytics/index.js deleted file mode 100644 index 3da8696edeb..00000000000 --- a/app/assets/javascripts/cycle_analytics/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import Vue from 'vue'; -import { - extractFilterQueryParameters, - extractPaginationQueryParameters, -} from '~/analytics/shared/utils'; -import Translate from '../vue_shared/translate'; -import CycleAnalytics from './components/base.vue'; -import createStore from './store'; -import { buildCycleAnalyticsInitialData } from './utils'; - -Vue.use(Translate); - -export default () => { - const store = createStore(); - const el = document.querySelector('#js-cycle-analytics'); - const { noAccessSvgPath, noDataSvgPath } = el.dataset; - const initialData = buildCycleAnalyticsInitialData({ ...el.dataset, gon }); - - const pagination = extractPaginationQueryParameters(window.location.search); - const { - selectedAuthor, - selectedMilestone, - selectedAssigneeList, - selectedLabelList, - } = extractFilterQueryParameters(window.location.search); - - store.dispatch('initializeVsa', { - ...initialData, - selectedAuthor, - selectedMilestone, - selectedAssigneeList, - selectedLabelList, - pagination, - }); - - // eslint-disable-next-line no-new - new Vue({ - el, - name: 'CycleAnalytics', - apolloProvider: {}, - store, - render: (createElement) => - createElement(CycleAnalytics, { - props: { - noDataSvgPath, - noAccessSvgPath, - }, - }), - }); -}; diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js deleted file mode 100644 index 4a201e00582..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/actions.js +++ /dev/null @@ -1,209 +0,0 @@ -import { - getProjectValueStreamStages, - getProjectValueStreams, - getValueStreamStageMedian, - getValueStreamStageRecords, - getValueStreamStageCounts, -} from '~/api/analytics_api'; -import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils'; -import { createAlert } from '~/flash'; -import { __ } from '~/locale'; -import { DEFAULT_VALUE_STREAM, I18N_VSA_ERROR_STAGE_MEDIAN } from '../constants'; -import * as types from './mutation_types'; - -export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => { - commit(types.SET_SELECTED_VALUE_STREAM, valueStream); - return dispatch('fetchValueStreamStages'); -}; - -export const fetchValueStreamStages = ({ commit, state }) => { - const { - endpoints: { fullPath }, - selectedValueStream: { id }, - } = state; - commit(types.REQUEST_VALUE_STREAM_STAGES); - - return getProjectValueStreamStages(fullPath, id) - .then(({ data }) => commit(types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS, data)) - .catch(({ response: { status } }) => { - commit(types.RECEIVE_VALUE_STREAM_STAGES_ERROR, status); - }); -}; - -export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => { - commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data); - if (data.length) { - const [firstStream] = data; - return dispatch('setSelectedValueStream', firstStream); - } - return dispatch('setSelectedValueStream', DEFAULT_VALUE_STREAM); -}; - -export const fetchValueStreams = ({ commit, dispatch, state }) => { - const { - endpoints: { fullPath }, - } = state; - commit(types.REQUEST_VALUE_STREAMS); - - return getProjectValueStreams(fullPath) - .then(({ data }) => dispatch('receiveValueStreamsSuccess', data)) - .catch(({ response: { status } }) => { - commit(types.RECEIVE_VALUE_STREAMS_ERROR, status); - }); -}; - -export const fetchStageData = ({ - getters: { requestParams, filterParams, paginationParams }, - commit, -}) => { - commit(types.REQUEST_STAGE_DATA); - - return getValueStreamStageRecords(requestParams, { ...filterParams, ...paginationParams }) - .then(({ data, headers }) => { - // when there's a query timeout, the request succeeds but the error is encoded in the response data - if (data?.error) { - commit(types.RECEIVE_STAGE_DATA_ERROR, data.error); - } else { - commit(types.RECEIVE_STAGE_DATA_SUCCESS, data); - const { page = null, nextPage = null } = parseIntPagination(normalizeHeaders(headers)); - commit(types.SET_PAGINATION, { ...paginationParams, page, hasNextPage: Boolean(nextPage) }); - } - }) - .catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR)); -}; - -const getStageMedians = ({ stageId, vsaParams, filterParams = {} }) => { - return getValueStreamStageMedian({ ...vsaParams, stageId }, filterParams).then(({ data }) => ({ - id: stageId, - value: data?.value || null, - })); -}; - -export const fetchStageMedians = ({ - state: { stages }, - getters: { requestParams: vsaParams, filterParams }, - commit, -}) => { - commit(types.REQUEST_STAGE_MEDIANS); - return Promise.all( - stages.map(({ id: stageId }) => - getStageMedians({ - vsaParams, - stageId, - filterParams, - }), - ), - ) - .then((data) => commit(types.RECEIVE_STAGE_MEDIANS_SUCCESS, data)) - .catch((error) => { - commit(types.RECEIVE_STAGE_MEDIANS_ERROR, error); - createAlert({ message: I18N_VSA_ERROR_STAGE_MEDIAN }); - }); -}; - -const getStageCounts = ({ stageId, vsaParams, filterParams = {} }) => { - return getValueStreamStageCounts({ ...vsaParams, stageId }, filterParams).then(({ data }) => ({ - id: stageId, - ...data, - })); -}; - -export const fetchStageCountValues = ({ - state: { stages }, - getters: { requestParams: vsaParams, filterParams }, - commit, -}) => { - commit(types.REQUEST_STAGE_COUNTS); - return Promise.all( - stages.map(({ id: stageId }) => - getStageCounts({ - vsaParams, - stageId, - filterParams, - }), - ), - ) - .then((data) => commit(types.RECEIVE_STAGE_COUNTS_SUCCESS, data)) - .catch((error) => { - commit(types.RECEIVE_STAGE_COUNTS_ERROR, error); - createAlert({ - message: __('There was an error fetching stage total counts'), - }); - }); -}; - -export const fetchValueStreamStageData = ({ dispatch }) => - Promise.all([ - dispatch('fetchStageData'), - dispatch('fetchStageMedians'), - dispatch('fetchStageCountValues'), - ]); - -export const refetchStageData = async ({ dispatch, commit }) => { - commit(types.SET_LOADING, true); - await dispatch('fetchValueStreamStageData'); - commit(types.SET_LOADING, false); -}; - -export const setSelectedStage = ({ dispatch, commit }, selectedStage = null) => { - commit(types.SET_SELECTED_STAGE, selectedStage); - return dispatch('refetchStageData'); -}; - -export const setFilters = ({ dispatch }) => dispatch('refetchStageData'); - -export const setDateRange = ({ dispatch, commit }, { createdAfter, createdBefore }) => { - commit(types.SET_DATE_RANGE, { createdAfter, createdBefore }); - return dispatch('refetchStageData'); -}; - -export const setInitialStage = ({ dispatch, commit, state: { stages } }, stage) => { - if (!stages.length && !stage) { - commit(types.SET_NO_ACCESS_ERROR); - return null; - } - - const selectedStage = stage || stages[0]; - commit(types.SET_SELECTED_STAGE, selectedStage); - return dispatch('fetchValueStreamStageData'); -}; - -export const updateStageTablePagination = ( - { commit, dispatch, state: { selectedStage } }, - paginationParams, -) => { - commit(types.SET_PAGINATION, paginationParams); - return dispatch('fetchStageData', selectedStage.id); -}; - -export const initializeVsa = async ({ commit, dispatch }, initialData = {}) => { - commit(types.INITIALIZE_VSA, initialData); - - const { - endpoints: { fullPath, groupPath, milestonesPath = '', labelsPath = '' }, - selectedAuthor, - selectedMilestone, - selectedAssigneeList, - selectedLabelList, - selectedStage = null, - } = initialData; - - dispatch('filters/setEndpoints', { - labelsEndpoint: labelsPath, - milestonesEndpoint: milestonesPath, - groupEndpoint: groupPath, - projectEndpoint: fullPath, - }); - - dispatch('filters/initialize', { - selectedAuthor, - selectedMilestone, - selectedAssigneeList, - selectedLabelList, - }); - - commit(types.SET_LOADING, true); - await dispatch('fetchValueStreams'); - await dispatch('setInitialStage', selectedStage); - commit(types.SET_LOADING, false); -}; diff --git a/app/assets/javascripts/cycle_analytics/store/getters.js b/app/assets/javascripts/cycle_analytics/store/getters.js deleted file mode 100644 index 83068cabf0f..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/getters.js +++ /dev/null @@ -1,57 +0,0 @@ -import { dateFormats } from '~/analytics/shared/constants'; -import dateFormat from '~/lib/dateformat'; -import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; -import { PAGINATION_TYPE } from '../constants'; -import { transformStagesForPathNavigation, filterStagesByHiddenStatus } from '../utils'; - -export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage }) => { - return transformStagesForPathNavigation({ - stages: filterStagesByHiddenStatus(stages, false), - medians, - stageCounts, - selectedStage, - }); -}; - -export const requestParams = (state) => { - const { - endpoints: { fullPath }, - selectedValueStream: { id: valueStreamId }, - selectedStage: { id: stageId = null }, - } = state; - return { requestPath: fullPath, valueStreamId, stageId }; -}; - -export const paginationParams = ({ pagination: { page, sort, direction } }) => ({ - pagination: PAGINATION_TYPE, - sort, - direction, - page, -}); - -const filterBarParams = ({ filters }) => { - const { - authors: { selected: selectedAuthor }, - milestones: { selected: selectedMilestone }, - assignees: { selectedList: selectedAssigneeList }, - labels: { selectedList: selectedLabelList }, - } = filters; - return filterToQueryObject({ - milestone_title: selectedMilestone, - author_username: selectedAuthor, - label_name: selectedLabelList, - assignee_username: selectedAssigneeList, - }); -}; - -const dateRangeParams = ({ createdAfter, createdBefore }) => ({ - created_after: createdAfter ? dateFormat(createdAfter, dateFormats.isoDate) : null, - created_before: createdBefore ? dateFormat(createdBefore, dateFormats.isoDate) : null, -}); - -export const filterParams = (state) => { - return { - ...filterBarParams(state), - ...dateRangeParams(state), - }; -}; diff --git a/app/assets/javascripts/cycle_analytics/store/index.js b/app/assets/javascripts/cycle_analytics/store/index.js deleted file mode 100644 index 76e3e835016..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * While we are in the process implementing group level features at the project level - * we will use a simplified vuex store for the project level, eventually this can be - * replaced with the store at ee/app/assets/javascripts/analytics/cycle_analytics/store/index.js - * once we have enough of the same features implemented across the project and group level - */ - -import Vue from 'vue'; -import Vuex from 'vuex'; -import filters from '~/vue_shared/components/filtered_search_bar/store/modules/filters'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import state from './state'; - -Vue.use(Vuex); - -export default () => - new Vuex.Store({ - actions, - getters, - mutations, - state, - modules: { filters }, - }); diff --git a/app/assets/javascripts/cycle_analytics/store/mutation_types.js b/app/assets/javascripts/cycle_analytics/store/mutation_types.js deleted file mode 100644 index 9376d81f317..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/mutation_types.js +++ /dev/null @@ -1,28 +0,0 @@ -export const INITIALIZE_VSA = 'INITIALIZE_VSA'; - -export const SET_LOADING = 'SET_LOADING'; -export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM'; -export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE'; -export const SET_DATE_RANGE = 'SET_DATE_RANGE'; -export const SET_PAGINATION = 'SET_PAGINATION'; -export const SET_NO_ACCESS_ERROR = 'SET_NO_ACCESS_ERROR'; - -export const REQUEST_VALUE_STREAMS = 'REQUEST_VALUE_STREAMS'; -export const RECEIVE_VALUE_STREAMS_SUCCESS = 'RECEIVE_VALUE_STREAMS_SUCCESS'; -export const RECEIVE_VALUE_STREAMS_ERROR = 'RECEIVE_VALUE_STREAMS_ERROR'; - -export const REQUEST_VALUE_STREAM_STAGES = 'REQUEST_VALUE_STREAM_STAGES'; -export const RECEIVE_VALUE_STREAM_STAGES_SUCCESS = 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS'; -export const RECEIVE_VALUE_STREAM_STAGES_ERROR = 'RECEIVE_VALUE_STREAM_STAGES_ERROR'; - -export const REQUEST_STAGE_DATA = 'REQUEST_STAGE_DATA'; -export const RECEIVE_STAGE_DATA_SUCCESS = 'RECEIVE_STAGE_DATA_SUCCESS'; -export const RECEIVE_STAGE_DATA_ERROR = 'RECEIVE_STAGE_DATA_ERROR'; - -export const REQUEST_STAGE_MEDIANS = 'REQUEST_STAGE_MEDIANS'; -export const RECEIVE_STAGE_MEDIANS_SUCCESS = 'RECEIVE_STAGE_MEDIANS_SUCCESS'; -export const RECEIVE_STAGE_MEDIANS_ERROR = 'RECEIVE_STAGE_MEDIANS_ERROR'; - -export const REQUEST_STAGE_COUNTS = 'REQUEST_STAGE_COUNTS'; -export const RECEIVE_STAGE_COUNTS_SUCCESS = 'RECEIVE_STAGE_COUNTS_SUCCESS'; -export const RECEIVE_STAGE_COUNTS_ERROR = 'RECEIVE_STAGE_COUNTS_ERROR'; diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js deleted file mode 100644 index 8567529caf2..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/mutations.js +++ /dev/null @@ -1,112 +0,0 @@ -import Vue from 'vue'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { PAGINATION_SORT_FIELD_END_EVENT, PAGINATION_SORT_DIRECTION_DESC } from '../constants'; -import { formatMedianValues } from '../utils'; -import * as types from './mutation_types'; - -export default { - [types.INITIALIZE_VSA]( - state, - { endpoints, features, createdBefore, createdAfter, pagination = {} }, - ) { - state.endpoints = endpoints; - state.createdBefore = createdBefore; - state.createdAfter = createdAfter; - state.features = features; - - Vue.set(state, 'pagination', { - page: pagination.page ?? state.pagination.page, - sort: pagination.sort ?? state.pagination.sort, - direction: pagination.direction ?? state.pagination.direction, - }); - }, - [types.SET_LOADING](state, loadingState) { - state.isLoading = loadingState; - }, - [types.SET_SELECTED_VALUE_STREAM](state, selectedValueStream = {}) { - state.selectedValueStream = convertObjectPropsToCamelCase(selectedValueStream, { deep: true }); - }, - [types.SET_SELECTED_STAGE](state, stage) { - state.selectedStage = stage; - }, - [types.SET_DATE_RANGE](state, { createdAfter, createdBefore }) { - state.createdBefore = createdBefore; - state.createdAfter = createdAfter; - }, - [types.SET_PAGINATION](state, { page, hasNextPage, sort, direction }) { - Vue.set(state, 'pagination', { - page, - hasNextPage, - sort: sort || PAGINATION_SORT_FIELD_END_EVENT, - direction: direction || PAGINATION_SORT_DIRECTION_DESC, - }); - }, - [types.SET_NO_ACCESS_ERROR](state) { - state.hasNoAccessError = true; - }, - [types.REQUEST_VALUE_STREAMS](state) { - state.valueStreams = []; - }, - [types.RECEIVE_VALUE_STREAMS_SUCCESS](state, valueStreams = []) { - state.valueStreams = valueStreams; - }, - [types.RECEIVE_VALUE_STREAMS_ERROR](state) { - state.valueStreams = []; - }, - [types.REQUEST_VALUE_STREAM_STAGES](state) { - state.stages = []; - }, - [types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS](state, { stages = [] }) { - state.stages = stages.map((s) => convertObjectPropsToCamelCase(s, { deep: true })); - }, - [types.RECEIVE_VALUE_STREAM_STAGES_ERROR](state) { - state.stages = []; - }, - [types.REQUEST_STAGE_DATA](state) { - state.isLoadingStage = true; - state.isEmptyStage = false; - state.selectedStageEvents = []; - - state.hasNoAccessError = false; - }, - [types.RECEIVE_STAGE_DATA_SUCCESS](state, events = []) { - state.isLoadingStage = false; - state.isEmptyStage = !events.length; - state.selectedStageEvents = events.map((ev) => - convertObjectPropsToCamelCase(ev, { deep: true }), - ); - - state.hasNoAccessError = false; - }, - [types.RECEIVE_STAGE_DATA_ERROR](state, error) { - state.isLoadingStage = false; - state.isEmptyStage = true; - state.selectedStageEvents = []; - - state.selectedStageError = error; - }, - [types.REQUEST_STAGE_MEDIANS](state) { - state.medians = {}; - }, - [types.RECEIVE_STAGE_MEDIANS_SUCCESS](state, medians) { - state.medians = formatMedianValues(medians); - }, - [types.RECEIVE_STAGE_MEDIANS_ERROR](state) { - state.medians = {}; - }, - [types.REQUEST_STAGE_COUNTS](state) { - state.stageCounts = {}; - }, - [types.RECEIVE_STAGE_COUNTS_SUCCESS](state, stageCounts = []) { - state.stageCounts = stageCounts.reduce( - (acc, { id, count }) => ({ - ...acc, - [id]: count, - }), - {}, - ); - }, - [types.RECEIVE_STAGE_COUNTS_ERROR](state) { - state.stageCounts = {}; - }, -}; diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js deleted file mode 100644 index 8d662333afa..00000000000 --- a/app/assets/javascripts/cycle_analytics/store/state.js +++ /dev/null @@ -1,31 +0,0 @@ -import { - PAGINATION_SORT_FIELD_END_EVENT, - PAGINATION_SORT_DIRECTION_DESC, -} from '~/cycle_analytics/constants'; - -export default () => ({ - id: null, - features: {}, - endpoints: {}, - createdAfter: null, - createdBefore: null, - stages: [], - analytics: [], - valueStreams: [], - selectedValueStream: {}, - selectedStage: {}, - selectedStageEvents: [], - selectedStageError: '', - medians: {}, - stageCounts: {}, - hasNoAccessError: false, - isLoading: false, - isLoadingStage: false, - isEmptyStage: false, - pagination: { - page: null, - hasNextPage: false, - sort: PAGINATION_SORT_FIELD_END_EVENT, - direction: PAGINATION_SORT_DIRECTION_DESC, - }, -}); diff --git a/app/assets/javascripts/cycle_analytics/utils.js b/app/assets/javascripts/cycle_analytics/utils.js deleted file mode 100644 index 428bb11b950..00000000000 --- a/app/assets/javascripts/cycle_analytics/utils.js +++ /dev/null @@ -1,117 +0,0 @@ -import { parseSeconds } from '~/lib/utils/datetime_utility'; -import { formatTimeAsSummary } from '~/lib/utils/datetime/date_format_utility'; - -/** - * Takes the stages and median data, combined with the selected stage, to build an - * array which is formatted to proivde the data required for the path navigation. - * - * @param {Array} stages - The stages available to the group / project - * @param {Object} medians - The median values for the stages available to the group / project - * @param {Object} stageCounts - The total item count for the stages available - * @param {Object} selectedStage - The currently selected stage - * @returns {Array} An array of stages formatted with data required for the path navigation - */ -export const transformStagesForPathNavigation = ({ - stages, - medians, - stageCounts = {}, - selectedStage, -}) => { - const formattedStages = stages.map((stage) => { - return { - metric: medians[stage?.id], - selected: stage?.id === selectedStage?.id, // Also could null === null cause an issue here? - stageCount: stageCounts && stageCounts[stage?.id], - icon: null, - ...stage, - }; - }); - - return formattedStages; -}; - -/** - * Takes a raw median value in seconds and converts it to a string representation - * ie. converts 172800 => 2d (2 days) - * - * @param {Number} Median - The number of seconds for the median calculation - * @returns {String} String representation ie 2w - */ -export const medianTimeToParsedSeconds = (value) => - formatTimeAsSummary({ - ...parseSeconds(value, { daysPerWeek: 7, hoursPerDay: 24 }), - seconds: value, - }); - -/** - * Takes the raw median value arrays and converts them into a useful object - * containing the string for display in the path navigation - * ie. converts [{ id: 'test', value: 172800 }] => { 'test': '2d' } - * - * @param {Array} Medians - Array of stage median objects, each contains a `id`, `value` and `error` - * @returns {Object} Returns key value pair with the stage name and its display median value - */ -export const formatMedianValues = (medians = []) => - medians.reduce((acc, { id, value = 0 }) => { - return { - ...acc, - [id]: value ? medianTimeToParsedSeconds(value) : '-', - }; - }, {}); - -export const filterStagesByHiddenStatus = (stages = [], isHidden = true) => - stages.filter(({ hidden = false }) => hidden === isHidden); - -/** - * @typedef {Object} MetricData - * @property {String} title - Title of the metric measured - * @property {String} value - String representing the decimal point value, e.g '1.5' - * @property {String} [unit] - String representing the decimal point value, e.g '1.5' - * - * @typedef {Object} TransformedMetricData - * @property {String} label - Title of the metric measured - * @property {String} value - String representing the decimal point value, e.g '1.5' - * @property {String} identifier - Slugified string based on the 'title' or the provided 'identifier' attribute - * @property {String} description - String to display for a description - * @property {String} unit - String representing the decimal point value, e.g '1.5' - */ - -const extractFeatures = (gon) => ({ - cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups), -}); - -/** - * Builds the initial data object for Value Stream Analytics with data loaded from the backend - * - * @param {Object} dataset - dataset object paseed to the frontend via data-* properties - * @returns {Object} - The initial data to load the app with - */ -export const buildCycleAnalyticsInitialData = ({ - fullPath, - requestPath, - projectId, - groupId, - groupPath, - labelsPath, - milestonesPath, - stage, - createdAfter, - createdBefore, - gon, -} = {}) => { - return { - projectId: parseInt(projectId, 10), - endpoints: { - requestPath, - fullPath, - labelsPath, - milestonesPath, - groupId: parseInt(groupId, 10), - groupPath, - }, - createdAfter: new Date(createdAfter), - createdBefore: new Date(createdBefore), - selectedStage: stage ? JSON.parse(stage) : null, - features: extractFeatures(gon), - }; -}; |