diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue index 659341999a4798344357622547e2b85e50906608..daef88bdd876eb267debdf46753b1227f148cc7b 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue @@ -15,7 +15,6 @@ import { METRICS_REQUESTS } from '../constants'; import DurationChart from './duration_chart.vue'; import TypeOfWorkCharts from './type_of_work_charts.vue'; import ValueStreamAggregationStatus from './value_stream_aggregation_status.vue'; -import ValueStreamEmptyState from './value_stream_empty_state.vue'; import ValueStreamSelect from './value_stream_select.vue'; export default { @@ -27,7 +26,6 @@ export default { StageTable, PathNavigation, ValueStreamAggregationStatus, - ValueStreamEmptyState, ValueStreamFilters, ValueStreamMetrics, ValueStreamSelect, @@ -76,10 +74,9 @@ export default { 'pathNavigationData', 'isOverviewStageSelected', 'selectedStageCount', - 'hasValueStreams', ]), shouldRenderEmptyState() { - return this.isLoadingValueStreams || !this.hasValueStreams; + return !this.currentGroup && !this.isLoading; }, shouldDisplayFilters() { return !this.errorCode && !this.hasNoAccessError; @@ -201,25 +198,24 @@ export default { </script> <template> <div> - <value-stream-empty-state + <div + class="gl-mb-3 gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row gl-justify-content-space-between" + > + <h3>{{ __('Value Stream Analytics') }}</h3> + <div class="gl-display-flex gl-flex-direction-row gl-align-items-center gl-mt-0 gl-sm-mt-5"> + <value-stream-aggregation-status v-if="isAggregationStatusAvailable" :data="aggregation" /> + <value-stream-select v-if="shouldDisplayCreateMultipleValueStreams" /> + </div> + </div> + <gl-empty-state v-if="shouldRenderEmptyState" - :is-loading="isLoadingValueStreams" - :empty-state-svg-path="emptyStateSvgPath" - :has-date-range-error="!hasDateRangeSet" + :title="__('Value Stream Analytics can help you determine your team’s velocity')" + :description=" + __('Filter parameters are not valid. Make sure that the end date is after the start date.') + " + :svg-path="emptyStateSvgPath" /> <div v-else class="gl-max-w-full"> - <div - class="gl-mb-3 gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row gl-justify-content-space-between" - > - <h3>{{ __('Value Stream Analytics') }}</h3> - <div class="gl-display-flex gl-flex-direction-row gl-align-items-center gl-mt-0 gl-sm-mt-5"> - <value-stream-aggregation-status - v-if="isAggregationStatusAvailable" - :data="aggregation" - /> - <value-stream-select v-if="shouldDisplayCreateMultipleValueStreams" /> - </div> - </div> <path-navigation v-if="selectedStageReady" data-testid="vsa-path-navigation" diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_empty_state.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_empty_state.vue index 636f569524e0aa7a1ddf87d56173846452428689..a0e31b58fb0914632b8383c2412ee97810cebe2b 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_empty_state.vue +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_empty_state.vue @@ -76,7 +76,6 @@ export default { :svg-path="emptyStateSvgPath" :title="title" :description="description" - data-testid="vsa-empty-state" > <template v-if="!hasDateRangeError" #actions> <gl-button diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js b/ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js index 2bddfad72d8ffc66747e0662c5f519dbc9437308..52640f28c49f4bfab2d50b20e98bf3c7f048acd4 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js @@ -13,8 +13,6 @@ import { DEFAULT_VALUE_STREAM_ID, OVERVIEW_STAGE_CONFIG } from '../constants'; export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN; -export const hasValueStreams = ({ valueStreams }) => Boolean(valueStreams?.length); - export const currentValueStreamId = ({ selectedValueStream }) => selectedValueStream?.id || DEFAULT_VALUE_STREAM_ID; diff --git a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb index 98803bd60f73ec9496c3b62545df32ad4ec804a7..02497749d3cfd2efd06856cced4fb830a821a2dc 100644 --- a/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb +++ b/ee/app/controllers/groups/analytics/cycle_analytics/value_streams_controller.rb @@ -97,13 +97,7 @@ def stage_update_params end def value_streams - value_streams = @group.value_streams.preload_associated_models - - if Feature.enabled?(:use_vsa_aggregated_tables, @group, default_enabled: :yaml) - value_streams - else - value_streams.presence || [in_memory_default_value_stream] - end + @group.value_streams.preload_associated_models.presence || [in_memory_default_value_stream] end def in_memory_default_value_stream diff --git a/ee/app/services/ee/analytics/cycle_analytics/stages/list_service.rb b/ee/app/services/ee/analytics/cycle_analytics/stages/list_service.rb index d6dd1225b1cdbc74c0962c93999e535bba08251e..228b818c925fd701aa8883c95ef714bc60dc8574 100644 --- a/ee/app/services/ee/analytics/cycle_analytics/stages/list_service.rb +++ b/ee/app/services/ee/analytics/cycle_analytics/stages/list_service.rb @@ -11,11 +11,7 @@ module ListService def execute return forbidden unless allowed? - if parent.is_a?(Group) && ::Feature.enabled?(:use_vsa_aggregated_tables, parent, default_enabled: :yaml) - success(persisted_stages) - else - success(persisted_stages.presence || build_default_stages) - end + success(persisted_stages.presence || build_default_stages) end private diff --git a/ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb b/ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb index 10d945d30a62a8c16b3d055ca18707de8f9c8094..56b0170047611e045115e62cc437f40932e992a2 100644 --- a/ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb +++ b/ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb @@ -5,8 +5,6 @@ RSpec.describe Groups::Analytics::CycleAnalytics::StagesController do let_it_be(:user) { create(:user) } let_it_be(:group, refind: true) { create(:group) } - let_it_be(:stages) { [] } - let_it_be(:value_stream) { create(:cycle_analytics_group_value_stream, group: group, name: 'No stage value stream', stages: stages) } context 'when params have only group_id' do let(:params) { { group_id: group } } @@ -21,8 +19,6 @@ end context 'when use_vsa_aggregated_tables FF is disabled' do - let_it_be(:stages) { Gitlab::Analytics::CycleAnalytics::DefaultStages.all } - it_behaves_like 'Value Stream Analytics Stages controller' do before do stub_feature_flags(use_vsa_aggregated_tables: false) @@ -32,14 +28,7 @@ end context 'when params have group_id and value_stream_id' do - let_it_be(:stages) do - [ - create(:cycle_analytics_group_stage, group: group, name: "Issue", relative_position: 1), - create(:cycle_analytics_group_stage, group: group, name: "Code", relative_position: 2) - ] - end - - let_it_be(:value_stream) { create(:cycle_analytics_group_value_stream, group: group, name: 'First value stream', stages: stages) } + let_it_be(:value_stream) { create(:cycle_analytics_group_value_stream, group: group) } let(:params) { { group_id: group, value_stream_id: value_stream.id } } let(:parent) { group } diff --git a/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb b/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb index d49e06175757ca1652b229ae63135533364ed8c7..6bafd4b3388d3e9ef5e365df29ac0fd980226e6a 100644 --- a/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb +++ b/ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb @@ -16,34 +16,14 @@ end describe 'GET #index' do - context 'when the use_vsa_aggregated_tables feature flag is off' do - before do - stub_feature_flags(use_vsa_aggregated_tables: false) - end - - it 'returns an in-memory default value stream' do - get :index, params: params - - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME) - expect(json_response.first['name']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME) - end - end - - context 'when the use_vsa_aggregated_tables feature flag is on' do - before do - stub_feature_flags(use_vsa_aggregated_tables: true) - end + it 'returns an in-memory default value stream' do + get :index, params: params - it 'returns an empty array' do - get :index, params: params + expect(response).to have_gitlab_http_status(:ok) - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response.size).to eq(0) - end + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME) + expect(json_response.first['name']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME) end context 'when persisted value streams present' do diff --git a/ee/spec/features/groups/analytics/cycle_analytics/charts_spec.rb b/ee/spec/features/groups/analytics/cycle_analytics/charts_spec.rb index 6a6bb16bc7f06216320818ea548176bff64f312d..ee42821fdb5f8420bd4cf358b0a5da1d5fd33619 100644 --- a/ee/spec/features/groups/analytics/cycle_analytics/charts_spec.rb +++ b/ee/spec/features/groups/analytics/cycle_analytics/charts_spec.rb @@ -6,18 +6,11 @@ let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, name: 'CA-test-group') } + let_it_be(:group2) { create(:group, name: 'CA-bad-test-group') } let_it_be(:project) { create(:project, :repository, namespace: group, group: group, name: 'Cool fun project') } - let_it_be(:group_with_value_stream) { create(:group, name: 'CA-vsa-test-group') } - let_it_be(:vsa_group_project) { create(:project, :repository, namespace: group_with_value_stream, group: group_with_value_stream) } - let_it_be(:value_stream) { create(:cycle_analytics_group_value_stream, group: group_with_value_stream, name: 'First value stream') } - let_it_be(:vsa_stages) do - [ - create(:cycle_analytics_group_stage, group: group_with_value_stream, name: "Issue", relative_position: 1, value_stream: value_stream), - create(:cycle_analytics_group_stage, group: group_with_value_stream, name: "Code", relative_position: 2, value_stream: value_stream) - ] - end - - empty_state_selector = '[data-testid="vsa-empty-state"]' + let_it_be(:group_label1) { create(:group_label, group: group) } + let_it_be(:group_label2) { create(:group_label, group: group) } + let_it_be(:label) { create(:group_label, group: group2) } 3.times do |i| let_it_be("issue_#{i}".to_sym) { create(:issue, title: "New Issue #{i}", project: project, created_at: 2.days.ago) } @@ -31,7 +24,6 @@ def toggle_more_options(stage) before_all do group.add_owner(user) - group_with_value_stream.add_owner(user) end before do @@ -40,14 +32,15 @@ def toggle_more_options(stage) sign_in(user) end - shared_examples 'has the empty state' do - it 'renders the empty state' do - expect(page).to have_selector(empty_state_selector) - expect(page).to have_text(s_('CycleAnalytics|Custom value streams to measure your DevSecOps lifecycle')) + context 'Duration chart' do + let(:custom_value_stream_name) { "New created value stream" } + + before do + select_group(group) + + create_custom_value_stream(custom_value_stream_name) end - end - shared_examples 'has the duration chart' do it 'displays data for all stages on the overview' do page.within('[data-testid="vsa-path-navigation"]') do click_button "Overview" @@ -65,134 +58,73 @@ def toggle_more_options(stage) end end - shared_examples 'has the tasks by type chart' do - context 'with data available' do - filters_selector = '.js-tasks-by-type-chart-filters' - - before do - group_label1 = create(:group_label, group: selected_group) - group_label2 = create(:group_label, group: selected_group) - - mr_issue = create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: selected_group), labels: [group_label2]) - create(:merge_request, iid: mr_issue.id, created_at: 3.days.ago, source_project: selected_project, labels: [group_label1, group_label2]) - - 3.times do |i| - create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: selected_group), labels: [group_label1]) - create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: selected_group), labels: [group_label2]) - end + describe 'Tasks by type chart', :js do + filters_selector = '.js-tasks-by-type-chart-filters' - select_group(selected_group) - end + before do + stub_licensed_features(cycle_analytics_for_groups: true, type_of_work_analytics: true) - it 'displays the chart' do - expect(page).to have_text(s_('CycleAnalytics|Type of work')) + project.add_maintainer(user) - expect(page).to have_text(s_('CycleAnalytics|Tasks by type')) - end + sign_in(user) + end - it 'has 2 labels selected' do - expect(page).to have_text('Showing Issues and 2 labels') - end + context 'enabled' do + context 'with data available' do + before do + mr_issue = create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: group), labels: [group_label2]) + create(:merge_request, iid: mr_issue.id, created_at: 3.days.ago, source_project: project, labels: [group_label1, group_label2]) - it 'has chart filters' do - expect(page).to have_css(filters_selector) - end + 3.times do |i| + create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: group), labels: [group_label1]) + create(:labeled_issue, created_at: i.days.ago, project: create(:project, group: group), labels: [group_label2]) + end - it 'can update the filters' do - page.within filters_selector do - find('.dropdown-toggle').click - first_selected_label = all('[data-testid="type-of-work-filters-label"] .dropdown-item.active').first - first_selected_label.click + select_group(group) end - expect(page).to have_text('Showing Issues and 1 label') + it 'displays the chart' do + expect(page).to have_text(s_('CycleAnalytics|Type of work')) - page.within filters_selector do - find('.dropdown-toggle').click - find('[data-testid="type-of-work-filters-subject"] label', text: 'Merge Requests').click + expect(page).to have_text(s_('CycleAnalytics|Tasks by type')) end - expect(page).to have_text('Showing Merge Requests and 1 label') - end - end - - context 'no data available' do - before do - select_group(selected_group) - end - - it 'shows the no data available message' do - expect(page).to have_text(s_('CycleAnalytics|Type of work')) - - expect(page).to have_text(_('There is no data available. Please change your selection.')) - end - end - end - - describe 'Duration chart', :js do - context 'use_vsa_aggregated_tables feature flag off' do - before do - stub_feature_flags(use_vsa_aggregated_tables: false) - - select_group(group) - end - - it_behaves_like 'has the duration chart' - end - - context 'use_vsa_aggregated_tables feature flag on' do - context 'with no value streams' do - before do - select_group(group, empty_state_selector) + it 'has 2 labels selected' do + expect(page).to have_text('Showing Issues and 2 labels') end - it_behaves_like 'has the empty state' - end - - context 'with a value stream' do - before do - select_group(group_with_value_stream) + it 'has chart filters' do + expect(page).to have_css(filters_selector) end - it_behaves_like 'has the duration chart' - end - end - end - - describe 'Tasks by type chart', :js do - before do - stub_licensed_features(cycle_analytics_for_groups: true, type_of_work_analytics: true) + it 'can update the filters' do + page.within filters_selector do + find('.dropdown-toggle').click + first_selected_label = all('[data-testid="type-of-work-filters-label"] .dropdown-item.active').first + first_selected_label.click + end - project.add_maintainer(user) - - sign_in(user) - end + expect(page).to have_text('Showing Issues and 1 label') - context 'use_vsa_aggregated_tables feature flag off' do - let(:selected_group) { group } - let(:selected_project) { project } + page.within filters_selector do + find('.dropdown-toggle').click + find('[data-testid="type-of-work-filters-subject"] label', text: 'Merge Requests').click + end - before do - stub_feature_flags(use_vsa_aggregated_tables: false) + expect(page).to have_text('Showing Merge Requests and 1 label') + end end - it_behaves_like 'has the tasks by type chart' - end - - context 'use_vsa_aggregated_tables feature flag on' do - context 'with no value streams' do + context 'no data available' do before do - select_group(group, empty_state_selector) + select_group(group) end - it_behaves_like 'has the empty state' - end - - context 'with a value stream' do - let(:selected_group) { group_with_value_stream } - let(:selected_project) { vsa_group_project } + it 'shows the no data available message' do + expect(page).to have_text(s_('CycleAnalytics|Type of work')) - it_behaves_like 'has the tasks by type chart' + expect(page).to have_text(_('There is no data available. Please change your selection.')) + end end end end diff --git a/ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb b/ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb index a076740a11fe4cf70c81ee1dde3253eb6945e395..45a0ff42f0a385682a18604c8dc7afd97ef22a22 100644 --- a/ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb +++ b/ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb @@ -20,8 +20,6 @@ path_nav_selector = '[data-testid="vsa-path-navigation"]' filter_bar_selector = '[data-testid="vsa-filter-bar"]' card_metric_selector = '[data-testid="vsa-metrics"] .gl-single-stat' - empty_state_selector = '[data-testid="vsa-empty-state"]' - empty_stage_state_selector = '.empty-state' new_issues_count = 3 new_issues_count.times do |i| @@ -59,9 +57,9 @@ def create_merge_request(id, extra_params = {}) sign_in(user) end - shared_examples 'empty value stream stage' do + shared_examples 'empty state' do it 'displays an empty state' do - element = page.find(empty_stage_state_selector) + element = page.find('.empty-state') expect(element).to have_content(_("We don't have enough data to show this stage.")) expect(element.find('.svg-content img')['src']).to have_content('illustrations/analytics/cycle-analytics-empty-chart') @@ -172,219 +170,176 @@ def create_merge_request(id, extra_params = {}) end end - context 'use_vsa_aggregated_tables feature flag off' do - before do - stub_feature_flags(use_vsa_aggregated_tables: false) - - select_group(group) - end - - it 'does not render the VSA empty state' do - expect(page).not_to have_selector(empty_state_selector) - expect(page).not_to have_text(s_('CycleAnalytics|Custom value streams to measure your DevSecOps lifecycle')) - end - end - - context 'use_vsa_aggregated_tables feature flag on' do - context 'without a value stream' do + context 'without valid query parameters set' do + context 'with created_after date > created_before date' do before do - select_group(group, empty_state_selector) + visit "#{group_analytics_cycle_analytics_path(group)}?created_after=2019-12-31&created_before=2019-11-01" end - it 'renders the empty value stream state' do - expect(page).to have_text(s_('CycleAnalytics|Custom value streams to measure your DevSecOps lifecycle')) - end + it_behaves_like 'no group available' end - context 'with a value stream' do + context 'with fake parameters' do before do - value_stream = create(:cycle_analytics_group_value_stream, group: group, name: 'First value stream') - sub_value_stream = create(:cycle_analytics_group_value_stream, group: sub_group, name: 'First sub group value stream') + visit "#{group_analytics_cycle_analytics_path(group)}?beans=not-cool" - Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map do |stage_params| - group.cycle_analytics_stages.create!(stage_params.merge(value_stream: value_stream)) - sub_group.cycle_analytics_stages.create!(stage_params.merge(value_stream: sub_value_stream)) - end + select_stage("Issue") end - context 'without valid query parameters set' do - context 'with created_after date > created_before date' do - before do - visit "#{group_analytics_cycle_analytics_path(group)}?created_after=2019-12-31&created_before=2019-11-01" - end - - it_behaves_like 'no group available' - end - - context 'with fake parameters' do - before do - visit "#{group_analytics_cycle_analytics_path(group)}?beans=not-cool" + it_behaves_like 'empty state' + end + end - select_stage("Issue") - end + context 'with valid query parameters set' do + projects_dropdown = '.js-projects-dropdown-filter' - it_behaves_like 'empty value stream stage' - end + context 'with project_ids set' do + before do + visit "#{group_analytics_cycle_analytics_path(group)}?project_ids[]=#{project.id}" end - context 'with valid query parameters set' do - projects_dropdown = '.js-projects-dropdown-filter' - - context 'with project_ids set' do - before do - visit "#{group_analytics_cycle_analytics_path(group)}?project_ids[]=#{project.id}" - end - - it 'has the projects dropdown prepopulated' do - element = page.find(projects_dropdown) - - expect(element).to have_content project.name - end - end - - context 'with created_before and created_after set' do - let(:created_after) { '2019-11-01' } - let(:created_before) { '2019-12-31' } + it 'has the projects dropdown prepopulated' do + element = page.find(projects_dropdown) - date_range = '.js-daterange-picker' + expect(element).to have_content project.name + end + end - before do - visit "#{group_analytics_cycle_analytics_path(group)}?created_before=#{created_before}&created_after=#{created_after}" - end + context 'with created_before and created_after set' do + date_range = '.js-daterange-picker' - it 'has the date range prepopulated' do - element = page.find(date_range) + before do + visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01" + end - expect(element.find('.js-daterange-picker-from input').value).to eq created_after - expect(element.find('.js-daterange-picker-to input').value).to eq created_before + it 'has the date range prepopulated' do + element = page.find(date_range) - expect(page.find('.js-tasks-by-type-chart')).to have_text(_("Showing data for group '%{group_name}' from Nov 1, 2019 to Dec 31, 2019") % { group_name: group.name }) - end - end + expect(element.find('.js-daterange-picker-from input').value).to eq '2019-11-01' + expect(element.find('.js-daterange-picker-to input').value).to eq '2019-12-31' + expect(page.find('.js-tasks-by-type-chart')).to have_text(_("Showing data for group '%{group_name}' from Nov 1, 2019 to Dec 31, 2019") % { group_name: group.name }) end + end + end - context 'with a group' do - let(:selected_group) { group } + context 'with a group' do + let(:selected_group) { group } - before do - select_group(group) - end + before do + select_group(group) + end - it_behaves_like 'group value stream analytics' + it_behaves_like 'group value stream analytics' - it_behaves_like 'has overview metrics' + it_behaves_like 'has overview metrics' - it_behaves_like 'has default filters' - end + it_behaves_like 'has default filters' + end - context 'with a sub group' do - let(:selected_group) { sub_group } + context 'with a sub group' do + let(:selected_group) { sub_group } - before do - select_group(sub_group) - end + before do + select_group(sub_group) + end - it_behaves_like 'group value stream analytics' + it_behaves_like 'group value stream analytics' - it_behaves_like 'has overview metrics' + it_behaves_like 'has overview metrics' - it_behaves_like 'has default filters' - end + it_behaves_like 'has default filters' + end - context 'with lots of data', :js do - let_it_be(:issue) { create(:issue, project: project) } + context 'with lots of data', :js do + let_it_be(:issue) { create(:issue, project: project) } - around do |example| - freeze_time { example.run } - end + around do |example| + freeze_time { example.run } + end - before do - issue.update!(created_at: 5.days.ago) - create_cycle(user, project, issue, mr, milestone, pipeline) - create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: group), labels: [group_label1]) - create(:labeled_issue, created_at: 3.days.ago, project: create(:project, group: group), labels: [group_label2]) + before do + stub_feature_flags(use_vsa_aggregated_tables: false) + issue.update!(created_at: 5.days.ago) + create_cycle(user, project, issue, mr, milestone, pipeline) + create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: group), labels: [group_label1]) + create(:labeled_issue, created_at: 3.days.ago, project: create(:project, group: group), labels: [group_label2]) - issue.metrics.update!(first_mentioned_in_commit_at: mr.created_at - 5.hours) - mr.metrics.update!(first_deployed_to_production_at: mr.created_at + 2.hours, merged_at: mr.created_at + 1.hour) + issue.metrics.update!(first_mentioned_in_commit_at: mr.created_at - 5.hours) + mr.metrics.update!(first_deployed_to_production_at: mr.created_at + 2.hours, merged_at: mr.created_at + 1.hour) - deploy_master(user, project, environment: 'staging') - deploy_master(user, project) + deploy_master(user, project, environment: 'staging') + deploy_master(user, project) - aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group) - Analytics::CycleAnalytics::AggregatorService.new(aggregation: aggregation).execute + select_group(group) + end - select_group(group) - end + stages_with_data = [ + { title: 'Issue', description: 'Time before an issue gets scheduled', events_count: 1, time: '5d' }, + { title: 'Code', description: 'Time until first merge request', events_count: 1, time: '5h' }, + { title: 'Review', description: 'Time between merge request creation and merge/close', events_count: 1, time: '1h' }, + { title: 'Staging', description: 'From merge request merge until deploy to production', events_count: 1, time: '1h' } + ] + + stages_without_data = [ + { title: 'Plan', description: 'Time before an issue starts implementation', events_count: 0, time: "-" }, + { title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, time: "-" } + ] + + 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| + select_stage(stage[:title]) + expect(page).not_to have_selector('[data-testid="vsa-stage-event"]') + end - stages_with_data = [ - { title: 'Issue', description: 'Time before an issue gets scheduled', events_count: 1, time: '5d' }, - { title: 'Code', description: 'Time until first merge request', events_count: 1, time: '5h' }, - { title: 'Review', description: 'Time between merge request creation and merge/close', events_count: 1, time: '1h' }, - { title: 'Staging', description: 'From merge request merge until deploy to production', events_count: 1, time: '1h' } - ] - - stages_without_data = [ - { title: 'Plan', description: 'Time before an issue starts implementation', events_count: 0, time: "-" }, - { title: 'Test', description: 'Total test time for all commits/merges', events_count: 0, time: "-" } - ] - - 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| - select_stage(stage[:title]) - expect(page).not_to have_selector('[data-testid="vsa-stage-event"]') - end - - stages_with_data.each do |stage| - select_stage(stage[:title]) - expect(page).to have_selector('[data-testid="vsa-stage-table"]') - expect(page.all('[data-testid="vsa-stage-event"]').length).to eq(stage[:events_count]) - end - end + stages_with_data.each do |stage| + select_stage(stage[:title]) + expect(page).to have_selector('[data-testid="vsa-stage-table"]') + expect(page.all('[data-testid="vsa-stage-event"]').length).to eq(stage[:events_count]) + end + end - it 'each stage will be selectable' do - [].concat(stages_without_data, stages_with_data).each do |stage| - select_stage(stage[:title]) + it 'each stage will be selectable' do + [].concat(stages_without_data, stages_with_data).each do |stage| + select_stage(stage[:title]) - stage_name = page.find("#{path_nav_selector} .gl-path-active-item-indigo").text - expect(stage_name).to include(stage[:title]) - expect(stage_name).to include(stage[:time]) + stage_name = page.find("#{path_nav_selector} .gl-path-active-item-indigo").text + expect(stage_name).to include(stage[:title]) + expect(stage_name).to include(stage[:time]) - expect(page).to have_selector('[data-testid="vsa-duration-chart"]') - end - end + expect(page).to have_selector('[data-testid="vsa-duration-chart"]') + end + end - it 'will not display the stage table on the overview stage' do - expect(page).not_to have_selector('[data-testid="vsa-stage-table"]') + it 'will not display the stage table on the overview stage' do + expect(page).not_to have_selector('[data-testid="vsa-stage-table"]') - select_stage("Issue") - expect(page).to have_selector('[data-testid="vsa-stage-table"]') - end + select_stage("Issue") + expect(page).to have_selector('[data-testid="vsa-stage-table"]') + end - it 'will have data available' do - duration_chart_content = page.find('[data-testid="vsa-duration-chart"]') - expect(duration_chart_content).not_to have_text(_("There is no data available. Please change your selection.")) - expect(duration_chart_content).to have_text(s_('CycleAnalytics|Average time to completion')) + it 'will have data available' do + duration_chart_content = page.find('[data-testid="vsa-duration-chart"]') + expect(duration_chart_content).not_to have_text(_("There is no data available. Please change your selection.")) + expect(duration_chart_content).to have_text(s_('CycleAnalytics|Average time to completion')) - tasks_by_type_chart_content = page.find('.js-tasks-by-type-chart') - expect(tasks_by_type_chart_content).not_to have_text(_("There is no data available. Please change your selection.")) - end + tasks_by_type_chart_content = page.find('.js-tasks-by-type-chart') + expect(tasks_by_type_chart_content).not_to have_text(_("There is no data available. Please change your selection.")) + end - context 'with filters applied' do - before do - visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01" + context 'with filters applied' do + before do + visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01" - wait_for_stages_to_load - end + wait_for_stages_to_load + end - it 'will filter the data' do - duration_chart_content = page.find('[data-testid="vsa-duration-chart"]') - expect(duration_chart_content).not_to have_text(s_('CycleAnalytics|Average time to completion')) - expect(duration_chart_content).to have_text(s_("CycleAnalytics|There is no data for 'Total time' available. Adjust the current filters.")) + it 'will filter the data' do + duration_chart_content = page.find('[data-testid="vsa-duration-chart"]') + expect(duration_chart_content).not_to have_text(s_('CycleAnalytics|Average time to completion')) + expect(duration_chart_content).to have_text(s_("CycleAnalytics|There is no data for 'Total time' available. Adjust the current filters.")) - tasks_by_type_chart_content = page.find('.js-tasks-by-type-chart') - expect(tasks_by_type_chart_content).to have_text(_("There is no data available. Please change your selection.")) - end - end + tasks_by_type_chart_content = page.find('.js-tasks-by-type-chart') + expect(tasks_by_type_chart_content).to have_text(_("There is no data available. Please change your selection.")) end end end diff --git a/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb b/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb index ccc984f65de01d1be4d8193ef7377d27aea6d8c4..15b86f8cb67f5c736772a348465d2e5e69784226 100644 --- a/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb +++ b/ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb @@ -18,7 +18,7 @@ let(:extended_form_fields_selector) { '[data-testid="extended-form-fields"]' } let(:preset_selector) { '[data-testid="vsa-preset-selector"]' } - let(:empty_state_selector) { '[data-testid="vsa-empty-state"]' } + let!(:default_value_stream) { create(:cycle_analytics_group_value_stream, group: group, name: 'default') } 3.times do |i| let_it_be("issue_#{i}".to_sym) { create(:issue, title: "New Issue #{i}", project: project, created_at: 2.days.ago) } @@ -198,7 +198,7 @@ def click_action_button(action, index) end end - shared_examples 'create group value streams' do + describe 'With a group' do name = 'group value stream' before do @@ -210,7 +210,7 @@ def click_action_button(action, index) it_behaves_like 'delete a value stream', name end - shared_examples 'create sub group value streams' do + describe 'With a sub group' do name = 'sub group value stream' before do @@ -221,42 +221,4 @@ def click_action_button(action, index) it_behaves_like 'update a value stream', name it_behaves_like 'delete a value stream', name end - - context 'use_vsa_aggregated_tables feature flag off' do - before do - stub_feature_flags(use_vsa_aggregated_tables: false) - end - - it_behaves_like 'create group value streams' - it_behaves_like 'create sub group value streams' - end - - context 'use_vsa_aggregated_tables feature flag on' do - context 'without a value stream' do - before do - select_group(group, empty_state_selector) - end - - it 'renders the empty state' do - expect(page).to have_text(s_('CycleAnalytics|Custom value streams to measure your DevSecOps lifecycle')) - end - - it 'can navigate to the create value stream form' do - page.find('[data-testid="create-value-stream-button"]').click - - expect(page).to have_selector('[data-testid="value-stream-form-modal"]') - end - end - - context 'with a value stream' do - before do - # ensure we have a value stream already available - create(:cycle_analytics_group_value_stream, group: group, name: 'default') - create(:cycle_analytics_group_value_stream, group: sub_group, name: 'default') - end - - it_behaves_like 'create group value streams' - it_behaves_like 'create sub group value streams' - end - end end diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js index fa708c2e37c50f21380868371322143396577284..b74b16946b329eae67e55f51e8389429146cc4d0 100644 --- a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js +++ b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js @@ -9,7 +9,6 @@ import DurationChart from 'ee/analytics/cycle_analytics/components/duration_char 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 ValueStreamAggregationStatus from 'ee/analytics/cycle_analytics/components/value_stream_aggregation_status.vue'; -import ValueStreamEmptyState from 'ee/analytics/cycle_analytics/components/value_stream_empty_state.vue'; import createStore from 'ee/analytics/cycle_analytics/store'; import waitForPromises from 'helpers/wait_for_promises'; import { @@ -196,12 +195,11 @@ describe('EE Value Stream Analytics component', () => { expect(wrapper.findComponent(ValueStreamSelect).exists()).toBe(flag); }; - describe('with no value streams', () => { + describe('without a group', () => { beforeEach(async () => { + const { group, ...stateWithoutGroup } = initialCycleAnalyticsState; mock = new MockAdapter(axios); - wrapper = await createComponent({ - initialState: { ...initialCycleAnalyticsState, valueStreams: [] }, - }); + wrapper = await createComponent({ initialState: stateWithoutGroup }); }); afterEach(() => { @@ -211,10 +209,10 @@ describe('EE Value Stream Analytics component', () => { }); it('displays an empty state', () => { - const emptyState = wrapper.findComponent(ValueStreamEmptyState); + const emptyState = wrapper.findComponent(GlEmptyState); expect(emptyState.exists()).toBe(true); - expect(emptyState.props('emptyStateSvgPath')).toBe(emptyStateSvgPath); + expect(emptyState.props('svgPath')).toBe(emptyStateSvgPath); }); it('does not display the metrics cards', () => { diff --git a/ee/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb b/ee/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb index ae3436ffa7ae38738fd94022eea598fbbf14ae1a..554540ffbee21013fd355e820b091a52c76eed25 100644 --- a/ee/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb +++ b/ee/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb @@ -21,24 +21,8 @@ it_behaves_like 'permission check for Value Stream Analytics Stage services', :cycle_analytics_for_groups - context 'when the use_vsa_aggregated_tables feature is enabled' do - before do - stub_feature_flags(use_vsa_aggregated_tables: true) - end - - it 'returns empty array' do - expect(stages.size).to eq(0) - end - end - - context 'when the use_vsa_aggregated_tables feature is disabled' do - before do - stub_feature_flags(use_vsa_aggregated_tables: false) - end - - it 'returns the default stages' do - expect(stages.size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size) - end + it 'returns only the default stages' do + expect(stages.size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size) end it 'provides the default stages as non-persisted objects' do diff --git a/ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb b/ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb index db9e2bd4f84267fd78ecffb83ce218f9f8727bb9..4622cf46aa42aa8b2cc43aada5d749285f0a2d5c 100644 --- a/ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb +++ b/ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb @@ -22,7 +22,7 @@ subject response_start_events = json_response['stages'].map { |s| s['start_event_identifier'] } - start_events = stages.map { |s| s['start_event_identifier'] } + start_events = Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map { |s| s['start_event_identifier'] } expect(response_start_events).to eq(start_events) end