Commit cb05fa54 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Stan Hu

[FE] VSA - Switch to table layout for individual stages

parent 3bc07e26
...@@ -14,6 +14,7 @@ import Metrics from './metrics.vue'; ...@@ -14,6 +14,7 @@ import Metrics from './metrics.vue';
import PathNavigation from './path_navigation.vue'; import PathNavigation from './path_navigation.vue';
import StageTable from './stage_table.vue'; import StageTable from './stage_table.vue';
import StageTableNav from './stage_table_nav.vue'; import StageTableNav from './stage_table_nav.vue';
import StageTableNew from './stage_table_new.vue';
import TypeOfWorkCharts from './type_of_work_charts.vue'; import TypeOfWorkCharts from './type_of_work_charts.vue';
import ValueStreamSelect from './value_stream_select.vue'; import ValueStreamSelect from './value_stream_select.vue';
...@@ -28,6 +29,7 @@ export default { ...@@ -28,6 +29,7 @@ export default {
TypeOfWorkCharts, TypeOfWorkCharts,
CustomStageForm, CustomStageForm,
StageTableNav, StageTableNav,
StageTableNew,
PathNavigation, PathNavigation,
FilterBar, FilterBar,
ValueStreamSelect, ValueStreamSelect,
...@@ -53,6 +55,7 @@ export default { ...@@ -53,6 +55,7 @@ export default {
'featureFlags', 'featureFlags',
'isLoading', 'isLoading',
'isLoadingStage', 'isLoadingStage',
// NOTE: we can remove the `isEmptyStage` field when we remove the existing stage table
'isEmptyStage', 'isEmptyStage',
'currentGroup', 'currentGroup',
'selectedProjects', 'selectedProjects',
...@@ -263,14 +266,24 @@ export default { ...@@ -263,14 +266,24 @@ export default {
) )
" "
/> />
<div v-else> <template v-else>
<metrics <metrics
v-if="!featureFlags.hasPathNavigation || isOverviewStageSelected" v-if="!featureFlags.hasPathNavigation || isOverviewStageSelected"
:group-path="currentGroupPath" :group-path="currentGroupPath"
:request-params="cycleAnalyticsRequestParams" :request-params="cycleAnalyticsRequestParams"
/> />
<template v-if="featureFlags.hasPathNavigation">
<stage-table-new
v-if="!isLoading && !isOverviewStageSelected"
:is-loading="isLoading || isLoadingStage"
:stage-events="currentStageEvents"
:current-stage="selectedStage"
:empty-state-message="selectedStageError"
:no-data-svg-path="noDataSvgPath"
/>
</template>
<stage-table <stage-table
v-if="!featureFlags.hasPathNavigation || !isOverviewStageSelected" v-else
:key="stageCount" :key="stageCount"
class="js-stage-table" class="js-stage-table"
:current-stage="selectedStage" :current-stage="selectedStage"
...@@ -308,7 +321,7 @@ export default { ...@@ -308,7 +321,7 @@ export default {
</template> </template>
</stage-table> </stage-table>
<url-sync :query="query" /> <url-sync :query="query" />
</div> </template>
<duration-chart v-if="shouldDisplayDurationChart" class="gl-mt-3" :stages="activeStages" /> <duration-chart v-if="shouldDisplayDurationChart" class="gl-mt-3" :stages="activeStages" />
<type-of-work-charts v-if="shouldDisplayTypeOfWorkCharts" /> <type-of-work-charts v-if="shouldDisplayTypeOfWorkCharts" />
</div> </div>
......
---
title: Updated VSA stage table with simpler layout
merge_request: 57792
author:
type: changed
...@@ -33,6 +33,7 @@ RSpec.describe 'Value stream analytics charts', :js do ...@@ -33,6 +33,7 @@ RSpec.describe 'Value stream analytics charts', :js do
context 'Duration chart' do context 'Duration chart' do
duration_stage_selector = '.js-dropdown-stages' duration_stage_selector = '.js-dropdown-stages'
stage_nav_selector = '.stage-nav' stage_nav_selector = '.stage-nav'
stage_table_selector = '.js-stage-table'
let(:duration_chart_dropdown) { page.find(duration_stage_selector) } let(:duration_chart_dropdown) { page.find(duration_stage_selector) }
let(:first_default_stage) { page.find('.stage-nav-item-cell', text: 'Issue').ancestor('.stage-nav-item') } let(:first_default_stage) { page.find('.stage-nav-item-cell', text: 'Issue').ancestor('.stage-nav-item') }
...@@ -55,7 +56,7 @@ RSpec.describe 'Value stream analytics charts', :js do ...@@ -55,7 +56,7 @@ RSpec.describe 'Value stream analytics charts', :js do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, stage_table_selector)
end end
it 'has all the default stages' do it 'has all the default stages' do
......
...@@ -41,6 +41,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -41,6 +41,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
start_field_label = 'custom-stage-start-event-label-0' start_field_label = 'custom-stage-start-event-label-0'
end_field_label = 'custom-stage-end-event-label-0' end_field_label = 'custom-stage-end-event-label-0'
name_field = 'custom-stage-name-0' name_field = 'custom-stage-name-0'
stage_table_selector = '.js-stage-table'
let(:add_stage_button) { '.js-add-stage-button' } let(:add_stage_button) { '.js-add-stage-button' }
let(:params) { { name: custom_stage_name, start_event_identifier: start_event_identifier, end_event_identifier: end_event_identifier } } let(:params) { { name: custom_stage_name, start_event_identifier: start_event_identifier, end_event_identifier: end_event_identifier } }
...@@ -86,7 +87,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -86,7 +87,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) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, stage_table_selector)
end end
let(:default_stage_order) { %w[Issue Plan Code Test Review Staging].freeze } let(:default_stage_order) { %w[Issue Plan Code Test Review Staging].freeze }
...@@ -299,7 +300,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -299,7 +300,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'with a group' do context 'with a group' do
context 'selected' do context 'selected' do
before do before do
select_group(group) select_group(group, stage_table_selector)
end end
it_behaves_like 'can create custom stages' do it_behaves_like 'can create custom stages' do
...@@ -312,7 +313,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -312,7 +313,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do context 'with a custom stage created', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/273045' do
before do before do
create_custom_stage create_custom_stage
select_group(group) select_group(group, stage_table_selector)
expect(page).to have_text custom_stage_name expect(page).to have_text custom_stage_name
end end
...@@ -324,7 +325,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -324,7 +325,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'with a sub group' do context 'with a sub group' do
context 'selected' do context 'selected' do
before do before do
select_group(sub_group) select_group(sub_group, stage_table_selector)
end end
it_behaves_like 'can create custom stages' do it_behaves_like 'can create custom stages' do
...@@ -337,7 +338,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -337,7 +338,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'with a custom stage created' do context 'with a custom stage created' do
before do before do
create_custom_stage(sub_group) create_custom_stage(sub_group)
select_group(sub_group) select_group(sub_group, stage_table_selector)
expect(page).to have_text custom_stage_name expect(page).to have_text custom_stage_name
end end
...@@ -349,7 +350,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -349,7 +350,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
context 'Add a stage button' do context 'Add a stage button' do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, stage_table_selector)
end end
it 'displays the custom stage form when clicked' do it 'displays the custom stage form when clicked' do
...@@ -378,7 +379,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -378,7 +379,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, stage_table_selector)
toggle_more_options(first_default_stage) toggle_more_options(first_default_stage)
end end
...@@ -431,7 +432,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -431,7 +432,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false) stub_feature_flags(value_stream_analytics_path_navigation: false)
create_custom_stage create_custom_stage
select_group(group) select_group(group, stage_table_selector)
expect(page).to have_text custom_stage_name expect(page).to have_text custom_stage_name
...@@ -487,7 +488,7 @@ RSpec.describe 'Customizable Group Value Stream Analytics', :js do ...@@ -487,7 +488,7 @@ 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) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, stage_table_selector)
toggle_more_options(first_default_stage) toggle_more_options(first_default_stage)
click_button(_('Hide stage')) click_button(_('Hide stage'))
......
...@@ -121,14 +121,13 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -121,14 +121,13 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
end end
shared_examples 'group value stream analytics' do shared_examples 'group value stream analytics' do
context 'stage panel' do context 'stage table' do
before do before do
select_stage("Issue") select_stage("Issue")
end end
it 'displays the stage table headers' do it 'displays the stage table' do
expect(page).to have_selector('.event-header', visible: true) expect(page).to have_selector('[data-testid="vsa-stage-table"]')
expect(page).to have_selector('.total-time-header', visible: true)
end end
end end
...@@ -176,7 +175,7 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -176,7 +175,7 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
before do before do
stub_feature_flags(value_stream_analytics_path_navigation: false) stub_feature_flags(value_stream_analytics_path_navigation: false)
select_group(group) select_group(group, '.js-stage-table')
end end
it 'does not show the path navigation' do it 'does not show the path navigation' do
...@@ -323,28 +322,16 @@ RSpec.describe 'Group value stream analytics filters and data', :js do ...@@ -323,28 +322,16 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
{ title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, time: "-" } { title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, time: "-" }
] ]
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])
expect(page).not_to have_selector('.stage-events .events-description')
end
stages_with_data.each do |stage|
select_stage(stage[:title])
expect(page.find('.stage-events .events-description').text).to have_text(_(stage[:description]))
end
end
it 'each stage with events will display the stage events list when selected', :sidekiq_might_not_need_inline do it 'each stage with events will display the stage events list 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])
expect(page).not_to have_selector('.stage-events .stage-event-item') expect(page).not_to have_selector('[data-testid="vsa-stage-event"]')
end end
stages_with_data.each do |stage| stages_with_data.each do |stage|
select_stage(stage[:title]) select_stage(stage[:title])
expect(page).to have_selector('.stage-events .stage-event-list') expect(page).to have_selector('[data-testid="vsa-stage-table"]')
expect(page.all('.stage-events .stage-event-item').length).to eq(stage[:events_count]) expect(page.all('[data-testid="vsa-stage-event"]').length).to eq(stage[:events_count])
end end
end end
......
...@@ -13,6 +13,7 @@ import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigat ...@@ -13,6 +13,7 @@ import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigat
import StageNavItem from 'ee/analytics/cycle_analytics/components/stage_nav_item.vue'; import StageNavItem from 'ee/analytics/cycle_analytics/components/stage_nav_item.vue';
import StageTable from 'ee/analytics/cycle_analytics/components/stage_table.vue'; import StageTable from 'ee/analytics/cycle_analytics/components/stage_table.vue';
import StageTableNav from 'ee/analytics/cycle_analytics/components/stage_table_nav.vue'; import StageTableNav from 'ee/analytics/cycle_analytics/components/stage_table_nav.vue';
import StageTableNew from 'ee/analytics/cycle_analytics/components/stage_table_new.vue';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import createStore from 'ee/analytics/cycle_analytics/store'; import createStore from 'ee/analytics/cycle_analytics/store';
...@@ -166,8 +167,8 @@ describe('Value Stream Analytics component', () => { ...@@ -166,8 +167,8 @@ describe('Value Stream Analytics component', () => {
expect(wrapper.find(Metrics).exists()).toBe(flag); expect(wrapper.find(Metrics).exists()).toBe(flag);
}; };
const displaysStageTable = (flag) => { const displaysStageTable = (flag, component = StageTable) => {
expect(wrapper.find(StageTable).exists()).toBe(flag); expect(wrapper.find(component).exists()).toBe(flag);
}; };
const displaysDurationChart = (flag) => { const displaysDurationChart = (flag) => {
...@@ -233,6 +234,7 @@ describe('Value Stream Analytics component', () => { ...@@ -233,6 +234,7 @@ describe('Value Stream Analytics component', () => {
it('does not display the stage table', () => { it('does not display the stage table', () => {
displaysStageTable(false); displaysStageTable(false);
displaysStageTable(false, StageTableNew);
}); });
it('does not display the duration chart', () => { it('does not display the duration chart', () => {
...@@ -289,6 +291,7 @@ describe('Value Stream Analytics component', () => { ...@@ -289,6 +291,7 @@ describe('Value Stream Analytics component', () => {
it('does not display the stage table', () => { it('does not display the stage table', () => {
displaysStageTable(false); displaysStageTable(false);
displaysStageTable(false, StageTableNew);
}); });
it('does not display the add stage button', () => { it('does not display the add stage button', () => {
...@@ -394,6 +397,7 @@ describe('Value Stream Analytics component', () => { ...@@ -394,6 +397,7 @@ describe('Value Stream Analytics component', () => {
it('hides the stage table', () => { it('hides the stage table', () => {
displaysStageTable(false); displaysStageTable(false);
displaysStageTable(false, StageTableNew);
}); });
it('hides the add stage button', () => { it('hides the add stage button', () => {
...@@ -402,28 +406,25 @@ describe('Value Stream Analytics component', () => { ...@@ -402,28 +406,25 @@ describe('Value Stream Analytics component', () => {
describe('Without the overview stage selected', () => { describe('Without the overview stage selected', () => {
beforeEach(async () => { beforeEach(async () => {
mock = new MockAdapter(axios);
mockRequiredRoutes(mock);
wrapper = await createComponent({
withStageSelected: true,
featureFlags: {
hasPathNavigation: true,
},
});
await store.dispatch('setSelectedStage', mockData.issueStage); await store.dispatch('setSelectedStage', mockData.issueStage);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
}); });
it('displays the stage table', () => { it('displays the stage table', () => {
displaysStageTable(true); displaysStageTable(true, StageTableNew);
}); });
it('displays the add stage button', async () => { it('does not display the add stage button', () => {
wrapper = await createComponent({ displaysAddStageButton(false);
opts: {
stubs: {
StageTable,
StageTableNav,
AddStageButton,
},
},
withStageSelected: true,
});
await wrapper.vm.$nextTick();
displaysAddStageButton(true);
}); });
}); });
...@@ -441,79 +442,79 @@ describe('Value Stream Analytics component', () => { ...@@ -441,79 +442,79 @@ describe('Value Stream Analytics component', () => {
it('does not display the path navigation', () => { it('does not display the path navigation', () => {
displaysPathNavigation(false); displaysPathNavigation(false);
}); });
});
describe('enabled', () => { describe('StageTable', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = await createComponent({ mock = new MockAdapter(axios);
withStageSelected: true, mockRequiredRoutes(mock);
featureFlags: {
hasPathNavigation: true, wrapper = await createComponent({
}, opts: {
stubs: {
StageTable,
StageTableNav,
StageNavItem,
},
},
withStageSelected: true,
});
}); });
});
it('displays the path navigation', () => { it('has the first stage selected by default', () => {
displaysPathNavigation(true); const first = findStageNavItemAtIndex(0);
}); const second = findStageNavItemAtIndex(1);
});
});
describe('StageTable', () => {
beforeEach(async () => {
mock = new MockAdapter(axios);
mockRequiredRoutes(mock);
wrapper = await createComponent({
opts: {
stubs: {
StageTable,
StageTableNav,
StageNavItem,
},
},
withStageSelected: true,
});
});
it('has the first stage selected by default', () => { expect(first.props('isActive')).toBe(true);
const first = findStageNavItemAtIndex(0); expect(second.props('isActive')).toBe(false);
const second = findStageNavItemAtIndex(1); });
expect(first.props('isActive')).toBe(true); it('can navigate to different stages', async () => {
expect(second.props('isActive')).toBe(false); findStageNavItemAtIndex(2).trigger('click');
});
it('can navigate to different stages', async () => { await wrapper.vm.$nextTick();
findStageNavItemAtIndex(2).trigger('click'); const first = findStageNavItemAtIndex(0);
const third = findStageNavItemAtIndex(2);
expect(third.props('isActive')).toBe(true);
expect(first.props('isActive')).toBe(false);
});
await wrapper.vm.$nextTick(); describe('Add stage button', () => {
const first = findStageNavItemAtIndex(0); beforeEach(async () => {
const third = findStageNavItemAtIndex(2); wrapper = await createComponent({
expect(third.props('isActive')).toBe(true); opts: {
expect(first.props('isActive')).toBe(false); stubs: {
StageTable,
StageTableNav,
AddStageButton,
},
},
withStageSelected: true,
});
});
it('can navigate to the custom stage form', async () => {
expect(wrapper.find(CustomStageForm).exists()).toBe(false);
findAddStageButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find(CustomStageForm).exists()).toBe(true);
});
});
});
}); });
describe('Add stage button', () => { describe('enabled', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = await createComponent({ wrapper = await createComponent({
opts: {
stubs: {
StageTable,
StageTableNav,
AddStageButton,
},
},
withStageSelected: true, withStageSelected: true,
featureFlags: {
hasPathNavigation: true,
},
}); });
}); });
it('can navigate to the custom stage form', async () => { it('displays the path navigation', () => {
expect(wrapper.find(CustomStageForm).exists()).toBe(false); displaysPathNavigation(true);
findAddStageButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find(CustomStageForm).exists()).toBe(true);
}); });
}); });
}); });
......
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
module CycleAnalyticsHelpers module CycleAnalyticsHelpers
include GitHelpers include GitHelpers
def wait_for_stages_to_load def wait_for_stages_to_load(selector = '.js-path-navigation')
expect(page).to have_selector '.js-stage-table' expect(page).to have_selector selector
wait_for_requests wait_for_requests
end end
def select_group(target_group) def select_group(target_group, ready_selector = '.js-path-navigation')
visit group_analytics_cycle_analytics_path(target_group) visit group_analytics_cycle_analytics_path(target_group)
wait_for_stages_to_load wait_for_stages_to_load(ready_selector)
end end
def toggle_dropdown(field) def toggle_dropdown(field)
......
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