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 {
shouldDisplayTypeOfWorkCharts() {
return !this.hasNoAccessError;
},
selectedStageReady() {
return !this.hasNoAccessError && this.selectedStage;
},
shouldDisplayPathNavigation() {
return this.featureFlags.hasPathNavigation && !this.hasNoAccessError && this.selectedStage;
return this.featureFlags.hasPathNavigation && !this.hasNoAccessError;
},
shouldDisplayVerticalNavigation() {
return !this.featureFlags.hasPathNavigation && this.selectedStageReady;
},
shouldDisplayCreateMultipleValueStreams() {
return Boolean(!this.shouldRenderEmptyState && !this.isLoadingValueStreams);
......@@ -189,17 +195,21 @@ export default {
:svg-path="emptyStateSvgPath"
/>
<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 v-if="shouldDisplayPathNavigation" class="gl-w-full gl-pb-2">
<path-navigation
:key="`path_navigation_key_${pathNavigationData.length}`"
class="js-path-navigation"
:loading="isLoading"
:stages="pathNavigationData"
:selected-stage="selectedStage"
@selected="onStageSelect"
/>
</div>
<filter-bar
v-if="shouldDisplayFilters"
class="js-filter-bar filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none"
:group-path="currentGroupPath"
/>
<div
v-if="shouldDisplayFilters"
class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between"
......@@ -223,11 +233,6 @@ export default {
@change="setDateRange"
/>
</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 v-if="!shouldRenderEmptyState" class="cycle-analytics gl-mt-0">
......@@ -255,8 +260,9 @@ export default {
:current-stage-events="currentStageEvents"
:no-data-svg-path="noDataSvgPath"
:empty-state-message="selectedStageError"
:has-path-navigation="featureFlags.hasPathNavigation"
>
<template #nav>
<template v-if="shouldDisplayVerticalNavigation" #nav>
<stage-table-nav
:current-stage="selectedStage"
:stages="activeStages"
......
<script>
import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { PATH_BACKGROUND_COLOR } from '../constants';
export default {
name: 'PathNavigation',
......@@ -24,16 +23,9 @@ export default {
default: () => {},
},
},
backgroundColor: PATH_BACKGROUND_COLOR,
};
</script>
<template>
<gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" />
<gl-path
v-else
:key="selectedStage.id"
:items="stages"
:background-color="$options.backgroundColor"
@selected="$emit('selected', $event)"
/>
<gl-path v-else :key="selectedStage.id" :items="stages" @selected="$emit('selected', $event)" />
</template>
......@@ -55,6 +55,11 @@ export default {
required: false,
default: '',
},
hasPathNavigation: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -73,29 +78,38 @@ export default {
return currentStageEvents.length && !isLoading && !isEmptyStage;
},
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 [
{
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',
},
...verticalNavHeaders,
{
title: this.stageName,
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,
},
{
title: __('Time'),
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,
},
];
......@@ -124,7 +138,11 @@ export default {
<div v-else class="card stage-panel">
<div class="card-header gl-border-b-0">
<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
v-for="({ title, description, classes, displayHeader = true }, i) in stageHeaders"
v-show="displayHeader"
......@@ -137,10 +155,14 @@ export default {
</nav>
</div>
<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>
</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">
<gl-loading-icon v-if="isLoadingStage" class="gl-mt-4" size="md" />
<template v-else>
......
import { gray10 } from '@gitlab/ui/scss_to_js/scss_variables';
import { __ } from '~/locale';
export const PROJECTS_PER_PAGE = 50;
......@@ -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_MERGE_REQUEST = 'MergeRequest';
export const TASKS_BY_TYPE_MAX_LABELS = 15;
export const PATH_BACKGROUND_COLOR = gray10;
export const TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS = {
[TASKS_BY_TYPE_SUBJECT_ISSUE]: __('Issues'),
......
......@@ -54,6 +54,7 @@ RSpec.describe 'Value stream analytics charts', :js do
end
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
end
......
......@@ -85,6 +85,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'Manual ordering' do
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
end
......@@ -153,25 +154,6 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
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|
it 'custom stage is saved with confirmation message' do
fill_in name_field, with: stage_name
......@@ -309,57 +291,81 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
end
end
context 'with a group' do
context 'selected' do
before do
select_group(group)
context 'With the path navigation feature flag disabled' do
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
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
it_behaves_like 'can create custom stages' do
let(:first_label) { group_label1 }
let(:second_label) { group_label2 }
let(:other_label) { label }
context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do
before do
create_custom_stage
select_group(group)
expect(page).to have_text custom_stage_name
end
it_behaves_like 'can edit custom stages'
end
end
context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do
before do
create_custom_stage
select_group(group)
context 'with a sub group' do
context 'selected' do
before do
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
it_behaves_like 'can edit custom stages'
end
end
context 'with a custom stage created' do
before do
create_custom_stage(sub_group)
select_group(sub_group)
context 'with a sub group' do
context 'selected' do
before do
select_group(sub_group)
end
expect(page).to have_text custom_stage_name
end
it_behaves_like 'can create custom stages' do
let(:first_label) { sub_group_label1 }
let(:second_label) { sub_group_label2 }
let(:other_label) { label }
it_behaves_like 'can edit custom stages'
end
end
context 'with a custom stage created' do
context 'Add a stage button' do
before do
create_custom_stage(sub_group)
select_group(sub_group)
expect(page).to have_text custom_stage_name
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
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
context 'Stage table' do
context 'default stages' do
def open_recover_stage_dropdown
find(add_stage_button).click
......@@ -371,6 +377,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
end
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
toggle_more_options(first_default_stage)
......@@ -422,6 +429,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'custom stages' do
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
create_custom_stage
select_group(group)
......@@ -478,6 +486,8 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'hidden stage' do
before do
stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group)
toggle_more_options(first_default_stage)
click_button(_('Hide stage'))
......
......@@ -29,7 +29,9 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
def select_stage(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
end
......@@ -121,32 +123,19 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
shared_examples 'group value stream analytics' do
context 'stage panel' 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('.total-time-header', visible: true)
end
end
context 'stage nav' do
it 'displays the list of stages' 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
context 'vertical navigation' do
it 'does not show the vertical stage navigation' do
expect(page).not_to have_selector(stage_nav_selector)
end
end
context 'path nav' do
context 'navigation' do
before do
stub_feature_flags(value_stream_analytics_path_navigation: true)
select_group(selected_group)
end
......@@ -190,9 +179,35 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
select_group(group)
end
it 'shows the path navigation' do
it 'does not show the path navigation' do
expect(page).not_to have_selector(path_nav_selector)
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
context 'without valid query parameters set' 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' }
]
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
stages_without_data.each do |stage|
select_stage(stage[:title])
......@@ -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|
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
......@@ -363,12 +372,6 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
wait_for_stages_to_load
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
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