Commit 60444b92 authored by Brandon Labuschagne's avatar Brandon Labuschagne

Merge branch '277379-toggle-vsa-navigation' into 'master'

Toggle VSA navigation with feature flag

See merge request gitlab-org/gitlab!55984
parents 732f4abe 04b848f9
...@@ -92,8 +92,14 @@ export default { ...@@ -92,8 +92,14 @@ export default {
shouldDisplayTypeOfWorkCharts() { shouldDisplayTypeOfWorkCharts() {
return !this.hasNoAccessError; return !this.hasNoAccessError;
}, },
selectedStageReady() {
return !this.hasNoAccessError && this.selectedStage;
},
shouldDisplayPathNavigation() { shouldDisplayPathNavigation() {
return this.featureFlags.hasPathNavigation && !this.hasNoAccessError && this.selectedStage; return this.featureFlags.hasPathNavigation && !this.hasNoAccessError;
},
shouldDisplayVerticalNavigation() {
return !this.featureFlags.hasPathNavigation && this.selectedStageReady;
}, },
shouldDisplayCreateMultipleValueStreams() { shouldDisplayCreateMultipleValueStreams() {
return Boolean(!this.shouldRenderEmptyState && !this.isLoadingValueStreams); return Boolean(!this.shouldRenderEmptyState && !this.isLoadingValueStreams);
...@@ -189,17 +195,21 @@ export default { ...@@ -189,17 +195,21 @@ export default {
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
/> />
<div v-if="!shouldRenderEmptyState" class="gl-max-w-full"> <div v-if="!shouldRenderEmptyState" class="gl-max-w-full">
<path-navigation
v-if="shouldDisplayPathNavigation"
:key="`path_navigation_key_${pathNavigationData.length}`"
class="js-path-navigation gl-w-full gl-pb-2"
:loading="isLoading"
:stages="pathNavigationData"
:selected-stage="selectedStage"
@selected="onStageSelect"
/>
<div class="gl-mt-3 gl-py-2 gl-px-3 bg-gray-light border-top border-bottom"> <div class="gl-mt-3 gl-py-2 gl-px-3 bg-gray-light border-top border-bottom">
<div v-if="shouldDisplayPathNavigation" class="gl-w-full gl-pb-2"> <filter-bar
<path-navigation v-if="shouldDisplayFilters"
:key="`path_navigation_key_${pathNavigationData.length}`" class="js-filter-bar filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none"
class="js-path-navigation" :group-path="currentGroupPath"
:loading="isLoading" />
:stages="pathNavigationData"
:selected-stage="selectedStage"
@selected="onStageSelect"
/>
</div>
<div <div
v-if="shouldDisplayFilters" v-if="shouldDisplayFilters"
class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between" class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between"
...@@ -223,11 +233,6 @@ export default { ...@@ -223,11 +233,6 @@ export default {
@change="setDateRange" @change="setDateRange"
/> />
</div> </div>
<filter-bar
v-if="shouldDisplayFilters"
class="js-filter-bar filtered-search-box gl-display-flex gl-mt-3 gl-mr-3 gl-border-none"
:group-path="currentGroupPath"
/>
</div> </div>
</div> </div>
<div v-if="!shouldRenderEmptyState" class="cycle-analytics gl-mt-0"> <div v-if="!shouldRenderEmptyState" class="cycle-analytics gl-mt-0">
...@@ -255,8 +260,9 @@ export default { ...@@ -255,8 +260,9 @@ export default {
:current-stage-events="currentStageEvents" :current-stage-events="currentStageEvents"
:no-data-svg-path="noDataSvgPath" :no-data-svg-path="noDataSvgPath"
:empty-state-message="selectedStageError" :empty-state-message="selectedStageError"
:has-path-navigation="featureFlags.hasPathNavigation"
> >
<template #nav> <template v-if="shouldDisplayVerticalNavigation" #nav>
<stage-table-nav <stage-table-nav
:current-stage="selectedStage" :current-stage="selectedStage"
:stages="activeStages" :stages="activeStages"
......
<script> <script>
import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { PATH_BACKGROUND_COLOR } from '../constants';
export default { export default {
name: 'PathNavigation', name: 'PathNavigation',
...@@ -24,16 +23,9 @@ export default { ...@@ -24,16 +23,9 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
backgroundColor: PATH_BACKGROUND_COLOR,
}; };
</script> </script>
<template> <template>
<gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" /> <gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" />
<gl-path <gl-path v-else :key="selectedStage.id" :items="stages" @selected="$emit('selected', $event)" />
v-else
:key="selectedStage.id"
:items="stages"
:background-color="$options.backgroundColor"
@selected="$emit('selected', $event)"
/>
</template> </template>
...@@ -55,6 +55,11 @@ export default { ...@@ -55,6 +55,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
hasPathNavigation: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -73,29 +78,38 @@ export default { ...@@ -73,29 +78,38 @@ export default {
return currentStageEvents.length && !isLoading && !isEmptyStage; return currentStageEvents.length && !isLoading && !isEmptyStage;
}, },
stageHeaders() { stageHeaders() {
const verticalNavHeaders = !this.hasPathNavigation
? [
{
title: s__('ProjectLifecycle|Stage'),
description: __('The phase of the development lifecycle.'),
classes: 'stage-header pl-5',
},
{
title: __('Median'),
description: __(
'The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.',
),
classes: 'median-header',
},
]
: [];
return [ return [
{ ...verticalNavHeaders,
title: s__('ProjectLifecycle|Stage'),
description: __('The phase of the development lifecycle.'),
classes: 'stage-header pl-5',
},
{
title: __('Median'),
description: __(
'The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.',
),
classes: 'median-header',
},
{ {
title: this.stageName, title: this.stageName,
description: __('The collection of events added to the data gathered for that stage.'), description: __('The collection of events added to the data gathered for that stage.'),
classes: 'event-header pl-3', classes: !this.hasPathNavigation
? 'event-header pl-3'
: 'event-header gl-align-items-flex-start! gl-w-half!',
displayHeader: !this.customStageFormActive, displayHeader: !this.customStageFormActive,
}, },
{ {
title: __('Time'), title: __('Time'),
description: __('The time taken by each data entry gathered by that stage.'), description: __('The time taken by each data entry gathered by that stage.'),
classes: 'total-time-header pr-5 text-right', classes: !this.hasPathNavigation
? 'total-time-header pr-5 text-right'
: 'total-time-header gl-align-items-flex-end! gl-text-right! gl-w-half!',
displayHeader: !this.customStageFormActive, displayHeader: !this.customStageFormActive,
}, },
]; ];
...@@ -124,7 +138,11 @@ export default { ...@@ -124,7 +138,11 @@ export default {
<div v-else class="card stage-panel"> <div v-else class="card stage-panel">
<div class="card-header gl-border-b-0"> <div class="card-header gl-border-b-0">
<nav class="col-headers"> <nav class="col-headers">
<ul> <ul
:class="{
'gl-display-flex! gl-justify-content-space-between! gl-flex-direction-row! gl-px-5!': hasPathNavigation,
}"
>
<stage-table-header <stage-table-header
v-for="({ title, description, classes, displayHeader = true }, i) in stageHeaders" v-for="({ title, description, classes, displayHeader = true }, i) in stageHeaders"
v-show="displayHeader" v-show="displayHeader"
...@@ -137,10 +155,14 @@ export default { ...@@ -137,10 +155,14 @@ export default {
</nav> </nav>
</div> </div>
<div class="stage-panel-body"> <div class="stage-panel-body">
<nav ref="stageNav" class="stage-nav gl-pl-2"> <nav v-if="!hasPathNavigation" ref="stageNav" class="stage-nav gl-pl-2">
<slot name="nav"></slot> <slot name="nav"></slot>
</nav> </nav>
<div class="section stage-events overflow-auto" :style="{ height: stageEventsHeight }"> <div
class="section stage-events overflow-auto"
:class="{ 'w-100': hasPathNavigation }"
:style="{ height: stageEventsHeight }"
>
<slot name="content"> <slot name="content">
<gl-loading-icon v-if="isLoadingStage" class="gl-mt-4" size="md" /> <gl-loading-icon v-if="isLoadingStage" class="gl-mt-4" size="md" />
<template v-else> <template v-else>
......
import { gray10 } from '@gitlab/ui/scss_to_js/scss_variables';
import { __ } from '~/locale'; import { __ } from '~/locale';
export const PROJECTS_PER_PAGE = 50; export const PROJECTS_PER_PAGE = 50;
...@@ -33,7 +32,6 @@ export const DEFAULT_STAGE_NAMES = [...Object.keys(EMPTY_STAGE_TEXT)]; ...@@ -33,7 +32,6 @@ export const DEFAULT_STAGE_NAMES = [...Object.keys(EMPTY_STAGE_TEXT)];
export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'Issue'; export const TASKS_BY_TYPE_SUBJECT_ISSUE = 'Issue';
export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'MergeRequest'; export const TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST = 'MergeRequest';
export const TASKS_BY_TYPE_MAX_LABELS = 15; export const TASKS_BY_TYPE_MAX_LABELS = 15;
export const PATH_BACKGROUND_COLOR = gray10;
export const TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS = { export const TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS = {
[TASKS_BY_TYPE_SUBJECT_ISSUE]: __('Issues'), [TASKS_BY_TYPE_SUBJECT_ISSUE]: __('Issues'),
......
...@@ -54,6 +54,7 @@ RSpec.describe 'Value stream analytics charts', :js do ...@@ -54,6 +54,7 @@ RSpec.describe 'Value stream analytics charts', :js do
end end
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group)
end end
......
...@@ -85,6 +85,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -85,6 +85,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'Manual ordering' do context 'Manual ordering' do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group)
end end
...@@ -153,25 +154,6 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -153,25 +154,6 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
end end
end end
context 'Add a stage button' do
before do
select_group(group)
end
it 'displays the custom stage form when clicked' do
expect(page).not_to have_text(s_('CustomCycleAnalytics|New stage'))
expect(page).to have_selector(add_stage_button, visible: true)
expect(page).to have_text(s_('CustomCycleAnalytics|Add a stage'))
expect(page).not_to have_selector("#{add_stage_button}.active")
page.find(add_stage_button).click
expect(page).to have_selector("#{add_stage_button}.active")
expect(page).to have_text(s_('CustomCycleAnalytics|New stage'))
end
end
shared_examples 'submits custom stage form successfully' do |stage_name| shared_examples 'submits custom stage form successfully' do |stage_name|
it 'custom stage is saved with confirmation message' do it 'custom stage is saved with confirmation message' do
fill_in name_field, with: stage_name fill_in name_field, with: stage_name
...@@ -309,57 +291,81 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -309,57 +291,81 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
end end
end end
context 'with a group' do context 'With the path navigation feature flag disabled' do
context 'selected' do before do
before do stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) end
context 'with a group' do
context 'selected' do
before do
select_group(group)
end
it_behaves_like 'can create custom stages' do
let(:first_label) { group_label1 }
let(:second_label) { group_label2 }
let(:other_label) { label }
end
end end
it_behaves_like 'can create custom stages' do context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do
let(:first_label) { group_label1 } before do
let(:second_label) { group_label2 } create_custom_stage
let(:other_label) { label } select_group(group)
expect(page).to have_text custom_stage_name
end
it_behaves_like 'can edit custom stages'
end end
end end
context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do context 'with a sub group' do
before do context 'selected' do
create_custom_stage before do
select_group(group) select_group(sub_group)
end
expect(page).to have_text custom_stage_name it_behaves_like 'can create custom stages' do
let(:first_label) { sub_group_label1 }
let(:second_label) { sub_group_label2 }
let(:other_label) { label }
end
end end
it_behaves_like 'can edit custom stages' context 'with a custom stage created' do
end before do
end create_custom_stage(sub_group)
select_group(sub_group)
context 'with a sub group' do expect(page).to have_text custom_stage_name
context 'selected' do end
before do
select_group(sub_group)
end
it_behaves_like 'can create custom stages' do it_behaves_like 'can edit custom stages'
let(:first_label) { sub_group_label1 }
let(:second_label) { sub_group_label2 }
let(:other_label) { label }
end end
end end
context 'with a custom stage created' do context 'Add a stage button' do
before do before do
create_custom_stage(sub_group) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(sub_group) select_group(group)
expect(page).to have_text custom_stage_name
end end
it_behaves_like 'can edit custom stages' it 'displays the custom stage form when clicked' do
expect(page).not_to have_text(s_('CustomCycleAnalytics|New stage'))
expect(page).to have_selector(add_stage_button, visible: true)
expect(page).to have_text(s_('CustomCycleAnalytics|Add a stage'))
expect(page).not_to have_selector("#{add_stage_button}.active")
page.find(add_stage_button).click
expect(page).to have_selector("#{add_stage_button}.active")
expect(page).to have_text(s_('CustomCycleAnalytics|New stage'))
end
end end
end
context 'Stage table' do
context 'default stages' do context 'default stages' do
def open_recover_stage_dropdown def open_recover_stage_dropdown
find(add_stage_button).click find(add_stage_button).click
...@@ -371,6 +377,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -371,6 +377,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
end end
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group)
toggle_more_options(first_default_stage) toggle_more_options(first_default_stage)
...@@ -422,6 +429,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -422,6 +429,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'custom stages' do context 'custom stages' do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
create_custom_stage create_custom_stage
select_group(group) select_group(group)
...@@ -478,6 +486,8 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -478,6 +486,8 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'hidden stage' do context 'hidden stage' do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
toggle_more_options(first_default_stage) toggle_more_options(first_default_stage)
click_button(_('Hide stage')) click_button(_('Hide stage'))
......
...@@ -29,7 +29,9 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -29,7 +29,9 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
def select_stage(name) def select_stage(name)
string_id = "CycleAnalyticsStage|#{name}" string_id = "CycleAnalyticsStage|#{name}"
page.find('.stage-nav .stage-nav-item .stage-name', text: s_(string_id), match: :prefer_exact).click within '[data-testid="gl-path-nav"]' do
page.find('li', text: s_(string_id), match: :prefer_exact).click
end
wait_for_requests wait_for_requests
end end
...@@ -121,32 +123,19 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -121,32 +123,19 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
shared_examples 'group value stream analytics' do shared_examples 'group value stream analytics' do
context 'stage panel' do context 'stage panel' do
it 'displays the stage table headers' do it 'displays the stage table headers' do
expect(page).to have_selector('.stage-header', visible: true)
expect(page).to have_selector('.median-header', visible: true)
expect(page).to have_selector('.event-header', visible: true) expect(page).to have_selector('.event-header', visible: true)
expect(page).to have_selector('.total-time-header', visible: true) expect(page).to have_selector('.total-time-header', visible: true)
end end
end end
context 'stage nav' do context 'vertical navigation' do
it 'displays the list of stages' do it 'does not show the vertical stage navigation' do
expect(page).to have_selector(stage_nav_selector, visible: true) expect(page).not_to have_selector(stage_nav_selector)
end
it 'displays the default list of stages' do
stage_nav = page.find(stage_nav_selector)
%w[Issue Plan Code Test Review Staging].each do |item|
string_id = "CycleAnalytics|#{item}"
expect(stage_nav).to have_content(s_(string_id))
end
end end
end end
context 'path nav' do context 'navigation' do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: true)
select_group(selected_group) select_group(selected_group)
end end
...@@ -190,9 +179,35 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -190,9 +179,35 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
select_group(group) select_group(group)
end end
it 'shows the path navigation' do it 'does not show the path navigation' do
expect(page).not_to have_selector(path_nav_selector) expect(page).not_to have_selector(path_nav_selector)
end end
it 'shows the vertical stage navigation' do
expect(page).to have_selector(stage_nav_selector, visible: true)
end
it 'displays the default list of stages' do
stage_nav = page.find(stage_nav_selector)
%w[Issue Plan Code Test Review Staging].each do |item|
string_id = "CycleAnalytics|#{item}"
expect(stage_nav).to have_content(s_(string_id))
end
end
it 'each stage will have median values', :sidekiq_might_not_need_inline do
stage_medians = page.all('.stage-nav .stage-median').collect(&:text)
expect(stage_medians).to eq(["Not enough data"] * 6)
end
it 'displays the stage table headers' do
expect(page).to have_selector('.stage-header', visible: true)
expect(page).to have_selector('.median-header', visible: true)
expect(page).to have_selector('.event-header', visible: true)
expect(page).to have_selector('.total-time-header', visible: true)
end
end end
context 'without valid query parameters set' do context 'without valid query parameters set' do
...@@ -306,12 +321,6 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -306,12 +321,6 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
{ title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, median: 'Not enough data' } { title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, median: 'Not enough data' }
] ]
it 'each stage will have median values', :sidekiq_might_not_need_inline do
stage_medians = page.all('.stage-nav .stage-median').collect(&:text)
expect(stage_medians).to eq(["5 days", "Not enough data", "about 5 hours", "Not enough data", "about 1 hour", "about 1 hour"])
end
it 'each stage will display the events description when selected', :sidekiq_might_not_need_inline do it 'each stage will display the events description when selected', :sidekiq_might_not_need_inline do
stages_without_data.each do |stage| stages_without_data.each do |stage|
select_stage(stage[:title]) select_stage(stage[:title])
...@@ -341,7 +350,7 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -341,7 +350,7 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
[].concat(stages_without_data, stages_with_data).each do |stage| [].concat(stages_without_data, stages_with_data).each do |stage|
select_stage(stage[:title]) select_stage(stage[:title])
expect(page.find('.stage-nav .active .stage-name').text).to eq(stage[:title]) expect(page.find('.js-path-navigation .gl-path-active-item-indigo').text).to eq(stage[:title])
end end
end end
...@@ -363,12 +372,6 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -363,12 +372,6 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
wait_for_stages_to_load wait_for_stages_to_load
end end
it 'will filter the stage median values' do
stage_medians = page.all('.stage-nav .stage-median').collect(&:text)
expect(stage_medians).to eq([_("Not enough data")] * 6)
end
it 'will filter the data' do it 'will filter the data' do
expect(page.find('[data-testid="vsa-stage-table"]')).to have_text(_("We don't have enough data to show this stage.")) expect(page.find('[data-testid="vsa-stage-table"]')).to have_text(_("We don't have enough data to show this stage."))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment