diff options
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
9 files changed, 108 insertions, 130 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue deleted file mode 100644 index cf4c35ef12b..00000000000 --- a/app/assets/javascripts/cycle_analytics/components/banner.vue +++ /dev/null @@ -1,54 +0,0 @@ -<script> -/* eslint-disable vue/no-v-html */ -import { GlIcon } from '@gitlab/ui'; -import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg'; - -export default { - components: { - GlIcon, - }, - props: { - documentationLink: { - type: String, - required: true, - }, - }, - computed: { - iconCycleAnalyticsSplash() { - return iconCycleAnalyticsSplash; - }, - }, - methods: { - dismissOverviewDialog() { - this.$emit('dismiss-overview-dialog'); - }, - }, -}; -</script> -<template> - <div class="landing content-block"> - <button - :aria-label="__('Dismiss Value Stream Analytics introduction box')" - class="js-ca-dismiss-button dismiss-button" - type="button" - @click="dismissOverviewDialog" - > - <gl-icon name="close" /> - </button> - <div class="svg-container" v-html="iconCycleAnalyticsSplash"></div> - <div class="inner-content"> - <h4>{{ __('Introducing Value Stream Analytics') }}</h4> - <p> - {{ - __(`Value Stream Analytics gives an overview -of how much time it takes to go from idea to production in your project.`) - }} - </p> - <p> - <a :href="documentationLink" target="_blank" rel="nofollow" class="btn"> - {{ __('Read more') }} - </a> - </p> - </div> - </div> -</template> diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue index c9ecac6829b..ae78ce33263 100644 --- a/app/assets/javascripts/cycle_analytics/components/base.vue +++ b/app/assets/javascripts/cycle_analytics/components/base.vue @@ -1,9 +1,10 @@ <script> -import { GlIcon, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; import Cookies from 'js-cookie'; import { mapActions, mapState, mapGetters } from 'vuex'; 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 ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue'; import { __ } from '~/locale'; import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants'; @@ -13,11 +14,10 @@ const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; export default { name: 'CycleAnalytics', components: { - GlIcon, GlLoadingIcon, - GlSprintf, PathNavigation, StageTable, + ValueStreamFilters, ValueStreamMetrics, }, props: { @@ -45,11 +45,12 @@ export default { 'selectedStageError', 'stages', 'summary', - 'daysInPast', 'permissions', 'stageCounts', 'endpoints', 'features', + 'createdBefore', + 'createdAfter', ]), ...mapGetters(['pathNavigationData', 'filterParams']), displayStageEvents() { @@ -98,14 +99,12 @@ export default { }, }, methods: { - ...mapActions([ - 'fetchCycleAnalyticsData', - 'fetchStageData', - 'setSelectedStage', - 'setDateRange', - ]), - handleDateSelect(daysInPast) { - this.setDateRange(daysInPast); + ...mapActions(['fetchStageData', 'setSelectedStage', 'setDateRange']), + onSetDateRange({ startDate, endDate }) { + this.setDateRange({ + createdAfter: new Date(startDate), + createdBefore: new Date(endDate), + }); }, onSelectStage(stage) { this.setSelectedStage(stage); @@ -133,35 +132,22 @@ export default { <div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row"> <path-navigation v-if="displayPathNavigation" - class="js-path-navigation gl-w-full gl-pb-2" + data-testid="vsa-path-navigation" + class="gl-w-full gl-pb-2" :loading="isLoading || isLoadingStage" :stages="pathNavigationData" :selected-stage="selectedStage" @selected="onSelectStage" /> - <div class="gl-flex-grow gl-align-self-end"> - <div class="js-ca-dropdown dropdown inline"> - <!-- eslint-disable-next-line @gitlab/vue-no-data-toggle --> - <button class="dropdown-menu-toggle" data-toggle="dropdown" type="button"> - <span class="dropdown-label"> - <gl-sprintf :message="$options.i18n.dropdownText"> - <template #days>{{ daysInPast }}</template> - </gl-sprintf> - <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon gl-top-3" /> - </span> - </button> - <ul class="dropdown-menu dropdown-menu-right"> - <li v-for="days in $options.dayRangeOptions" :key="`day-range-${days}`"> - <a href="#" @click.prevent="handleDateSelect(days)"> - <gl-sprintf :message="$options.i18n.dropdownText"> - <template #days>{{ days }}</template> - </gl-sprintf> - </a> - </li> - </ul> - </div> - </div> </div> + <value-stream-filters + :group-id="endpoints.groupId" + :group-path="endpoints.groupPath" + :has-project-filter="false" + :start-date="createdAfter" + :end-date="createdBefore" + @setDateRange="onSetDateRange" + /> <value-stream-metrics :request-path="endpoints.fullPath" :request-params="filterParams" @@ -178,6 +164,7 @@ export default { :empty-state-message="emptyStageText" :no-data-svg-path="noDataSvgPath" :pagination="null" + :sortable="false" /> </div> </template> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue index 0c47838c773..8a2667a4ab1 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue @@ -23,8 +23,8 @@ import TotalTime from './total_time_component.vue'; const DEFAULT_WORKFLOW_TITLE_PROPERTIES = { thClass: 'gl-w-half', key: PAGINATION_SORT_FIELD_END_EVENT, - sortable: true, }; + const WORKFLOW_COLUMN_TITLES = { issues: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Issues') }, jobs: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Jobs') }, @@ -84,6 +84,11 @@ export default { required: false, default: null, }, + sortable: { + type: Boolean, + required: false, + default: true, + }, }, data() { if (this.pagination) { @@ -122,9 +127,11 @@ export default { key: PAGINATION_SORT_FIELD_DURATION, label: __('Time'), thClass: 'gl-w-half', - sortable: true, }, - ]; + ].map((field) => ({ + ...field, + sortable: this.sortable, + })); }, prevPage() { return Math.max(this.pagination.page - 1, 0); diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue index 6b1e537dc77..8610dfc2b03 100644 --- a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue +++ b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue @@ -61,33 +61,38 @@ export default { <template> <div class="gl-mt-3 gl-py-2 gl-px-3 bg-gray-light border-top border-bottom"> <filter-bar - class="js-filter-bar filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none" + 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" > - <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)" - /> - <date-range - v-if="hasDateRangeFilter" - :start-date="startDate" - :end-date="endDate" - :max-date-range="$options.maxDateRange" - :include-selected-date="true" - class="js-daterange-picker" - @change="$emit('setDateRange', $event)" - /> + <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> + <date-range + v-if="hasDateRangeFilter" + :start-date="startDate" + :end-date="endDate" + :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/index.js b/app/assets/javascripts/cycle_analytics/index.js index 3827db4d9b2..620da0104e0 100644 --- a/app/assets/javascripts/cycle_analytics/index.js +++ b/app/assets/javascripts/cycle_analytics/index.js @@ -1,7 +1,9 @@ import Vue from 'vue'; import Translate from '../vue_shared/translate'; import CycleAnalytics from './components/base.vue'; +import { DEFAULT_DAYS_TO_DISPLAY } from './constants'; import createStore from './store'; +import { calculateFormattedDayInPast } from './utils'; Vue.use(Translate); @@ -14,19 +16,29 @@ export default () => { requestPath, fullPath, projectId, + groupId, groupPath, + labelsPath, + milestonesPath, } = el.dataset; + const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY); + store.dispatch('initializeVsa', { projectId: parseInt(projectId, 10), - groupPath, endpoints: { requestPath, fullPath, + labelsPath, + milestonesPath, + groupId: parseInt(groupId, 10), + groupPath, }, features: { cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups), }, + createdBefore: new Date(now), + createdAfter: new Date(past), }); // eslint-disable-next-line no-new diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js index a7a2c8ea9d3..e39cd224199 100644 --- a/app/assets/javascripts/cycle_analytics/store/actions.js +++ b/app/assets/javascripts/cycle_analytics/store/actions.js @@ -163,6 +163,7 @@ const refetchStageData = (dispatch) => { dispatch('fetchCycleAnalyticsData'), dispatch('fetchStageData'), dispatch('fetchStageMedians'), + dispatch('fetchStageCountValues'), ]), ) .finally(() => dispatch('setLoading', false)); @@ -170,14 +171,24 @@ const refetchStageData = (dispatch) => { export const setFilters = ({ dispatch }) => refetchStageData(dispatch); -export const setDateRange = ({ dispatch, commit }, daysInPast) => { - commit(types.SET_DATE_RANGE, daysInPast); +export const setDateRange = ({ dispatch, commit }, { createdAfter, createdBefore }) => { + commit(types.SET_DATE_RANGE, { createdAfter, createdBefore }); return refetchStageData(dispatch); }; export const initializeVsa = ({ commit, dispatch }, initialData = {}) => { commit(types.INITIALIZE_VSA, initialData); + const { + endpoints: { fullPath, groupPath, milestonesPath = '', labelsPath = '' }, + } = initialData; + dispatch('filters/setEndpoints', { + labelsEndpoint: labelsPath, + milestonesEndpoint: milestonesPath, + groupEndpoint: groupPath, + projectEndpoint: fullPath, + }); + return dispatch('setLoading', true) .then(() => dispatch('fetchValueStreams')) .finally(() => dispatch('setLoading', false)); diff --git a/app/assets/javascripts/cycle_analytics/store/getters.js b/app/assets/javascripts/cycle_analytics/store/getters.js index 9faccabcaad..77c285f5ce0 100644 --- a/app/assets/javascripts/cycle_analytics/store/getters.js +++ b/app/assets/javascripts/cycle_analytics/store/getters.js @@ -1,5 +1,6 @@ import dateFormat from 'dateformat'; import { dateFormats } from '~/analytics/shared/constants'; +import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { transformStagesForPathNavigation, filterStagesByHiddenStatus } from '../utils'; export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage }) => { @@ -20,6 +21,21 @@ export const requestParams = (state) => { return { requestPath: fullPath, valueStreamId, stageId }; }; +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, @@ -33,6 +49,7 @@ export const legacyFilterParams = ({ daysInPast }) => { export const filterParams = (state) => { return { + ...filterBarParams(state), ...dateRangeParams(state), }; }; diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js index e41de85c1fa..301e7d95f8c 100644 --- a/app/assets/javascripts/cycle_analytics/store/mutations.js +++ b/app/assets/javascripts/cycle_analytics/store/mutations.js @@ -1,14 +1,12 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { DEFAULT_DAYS_TO_DISPLAY } from '../constants'; -import { formatMedianValues, calculateFormattedDayInPast } from '../utils'; +import { formatMedianValues } from '../utils'; import * as types from './mutation_types'; export default { - [types.INITIALIZE_VSA](state, { endpoints, features }) { + [types.INITIALIZE_VSA](state, { endpoints, features, createdBefore, createdAfter }) { state.endpoints = endpoints; - const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY); - state.createdBefore = now; - state.createdAfter = past; + state.createdBefore = createdBefore; + state.createdAfter = createdAfter; state.features = features; }, [types.SET_LOADING](state, loadingState) { @@ -20,11 +18,9 @@ export default { [types.SET_SELECTED_STAGE](state, stage) { state.selectedStage = stage; }, - [types.SET_DATE_RANGE](state, daysInPast) { - state.daysInPast = daysInPast; - const { now, past } = calculateFormattedDayInPast(daysInPast); - state.createdBefore = now; - state.createdAfter = past; + [types.SET_DATE_RANGE](state, { createdAfter, createdBefore }) { + state.createdBefore = createdBefore; + state.createdAfter = createdAfter; }, [types.REQUEST_VALUE_STREAMS](state) { state.valueStreams = []; diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js index e6da3f609b2..0882db51218 100644 --- a/app/assets/javascripts/cycle_analytics/store/state.js +++ b/app/assets/javascripts/cycle_analytics/store/state.js @@ -1,10 +1,7 @@ -import { DEFAULT_DAYS_TO_DISPLAY } from '../constants'; - export default () => ({ id: null, features: {}, endpoints: {}, - daysInPast: DEFAULT_DAYS_TO_DISPLAY, createdAfter: null, createdBefore: null, stages: [], |