Commit 04b848f9 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Brandon Labuschagne

Move path navigation outside filters

Moves the path navigation above the filters
for VSA to match the designs
parent b8b97284
......@@ -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