Commit d956a648 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Update multiple value stream specs

Updates tests to include states when the
feature flag is on and also off.

Adds additional blocks with / without an existing
value stream when the feature flag is on.

Fix cycle_analytics charts specs

Update VSA chart specs to include blocks
for the aggregation feature flag.

Fix error message for invalid date ranges

Fix flash of loading state

Fix empty state selector

Fixes the empty state selector in the charts
filters and multiple value stream specs
parent d6c03eb1
......@@ -10652,13 +10652,13 @@ CREATE TABLE analytics_cycle_analytics_group_stages (
relative_position integer,
start_event_identifier integer NOT NULL,
end_event_identifier integer NOT NULL,
group_id bigint,
group_id bigint NOT NULL,
start_event_label_id bigint,
end_event_label_id bigint,
hidden boolean DEFAULT false NOT NULL,
custom boolean DEFAULT true NOT NULL,
name character varying(255) NOT NULL,
group_value_stream_id bigint,
group_value_stream_id bigint NOT NULL,
stage_event_hash_id bigint,
CONSTRAINT check_e6bd4271b5 CHECK ((stage_event_hash_id IS NOT NULL))
);
......@@ -10674,10 +10674,11 @@ ALTER SEQUENCE analytics_cycle_analytics_group_stages_id_seq OWNED BY analytics_
CREATE TABLE analytics_cycle_analytics_group_value_streams (
id bigint NOT NULL,
name character varying(255) NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
group_id bigint NOT NULL
group_id bigint NOT NULL,
name text NOT NULL,
CONSTRAINT check_bc1ed5f1f7 CHECK ((char_length(name) <= 100))
);
CREATE SEQUENCE analytics_cycle_analytics_group_value_streams_id_seq
......@@ -11125,6 +11126,8 @@ CREATE TABLE application_settings (
elasticsearch_indexed_file_size_limit_kb integer DEFAULT 1024 NOT NULL,
enforce_namespace_storage_limit boolean DEFAULT false NOT NULL,
container_registry_delete_tags_service_timeout integer DEFAULT 250 NOT NULL,
kroki_url character varying,
kroki_enabled boolean,
elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
gitpod_enabled boolean DEFAULT false NOT NULL,
gitpod_url text DEFAULT 'https://gitpod.io/'::text,
......@@ -11135,28 +11138,26 @@ CREATE TABLE application_settings (
encrypted_ci_jwt_signing_key text,
encrypted_ci_jwt_signing_key_iv text,
container_registry_expiration_policies_worker_capacity integer DEFAULT 4 NOT NULL,
secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_url text,
encrypted_secret_detection_token_revocation_token text,
encrypted_secret_detection_token_revocation_token_iv text,
elasticsearch_analyzers_smartcn_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_smartcn_search boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_search boolean DEFAULT false NOT NULL,
new_user_signups_cap integer,
secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_url text,
encrypted_secret_detection_token_revocation_token text,
encrypted_secret_detection_token_revocation_token_iv text,
domain_denylist_enabled boolean DEFAULT false,
domain_denylist text,
domain_allowlist text,
new_user_signups_cap integer,
encrypted_cloud_license_auth_token text,
encrypted_cloud_license_auth_token_iv text,
secret_detection_revocation_token_types_url text,
kroki_url text,
kroki_enabled boolean DEFAULT false NOT NULL,
disable_feed_token boolean DEFAULT false NOT NULL,
personal_access_token_prefix text DEFAULT 'glpat-'::text,
rate_limiting_response_text text,
container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL,
invisible_captcha_enabled boolean DEFAULT false NOT NULL,
container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL,
enforce_ssh_key_expiration boolean DEFAULT true NOT NULL,
git_two_factor_session_expiry integer DEFAULT 15 NOT NULL,
keep_latest_artifact boolean DEFAULT true NOT NULL,
......@@ -11211,10 +11212,10 @@ CREATE TABLE application_settings (
throttle_unauthenticated_api_enabled boolean DEFAULT false NOT NULL,
throttle_unauthenticated_api_requests_per_period integer DEFAULT 3600 NOT NULL,
throttle_unauthenticated_api_period_in_seconds integer DEFAULT 3600 NOT NULL,
jobs_per_stage_page_size integer DEFAULT 200 NOT NULL,
sidekiq_job_limiter_mode smallint DEFAULT 1 NOT NULL,
sidekiq_job_limiter_compression_threshold_bytes integer DEFAULT 100000 NOT NULL,
sidekiq_job_limiter_limit_bytes integer DEFAULT 0 NOT NULL,
jobs_per_stage_page_size integer DEFAULT 200 NOT NULL,
suggest_pipeline_enabled boolean DEFAULT true NOT NULL,
throttle_unauthenticated_deprecated_api_requests_per_period integer DEFAULT 1800 NOT NULL,
throttle_unauthenticated_deprecated_api_period_in_seconds integer DEFAULT 3600 NOT NULL,
......@@ -11231,20 +11232,20 @@ CREATE TABLE application_settings (
sentry_dsn text,
sentry_clientside_dsn text,
sentry_environment text,
static_objects_external_storage_auth_token_encrypted text,
max_ssh_key_lifetime integer,
static_objects_external_storage_auth_token_encrypted text,
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
user_email_lookup_limit integer DEFAULT 60 NOT NULL,
packages_cleanup_package_file_worker_capacity smallint DEFAULT 2 NOT NULL,
runner_token_expiration_interval integer,
group_runner_token_expiration_interval integer,
project_runner_token_expiration_interval integer,
container_registry_import_max_tags_count integer DEFAULT 100 NOT NULL,
container_registry_import_max_retries integer DEFAULT 3 NOT NULL,
container_registry_import_start_max_retries integer DEFAULT 50 NOT NULL,
container_registry_import_max_step_duration integer DEFAULT 300 NOT NULL,
container_registry_import_target_plan text DEFAULT 'free'::text NOT NULL,
container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 11:00:00+11'::timestamp with time zone NOT NULL,
container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 00:00:00+00'::timestamp with time zone NOT NULL,
runner_token_expiration_interval integer,
group_runner_token_expiration_interval integer,
project_runner_token_expiration_interval integer,
ecdsa_sk_key_restriction integer DEFAULT 0 NOT NULL,
ed25519_sk_key_restriction integer DEFAULT 0 NOT NULL,
users_get_by_id_limit integer DEFAULT 300 NOT NULL,
......@@ -11259,7 +11260,7 @@ CREATE TABLE application_settings (
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
CONSTRAINT app_settings_yaml_max_depth_positive CHECK ((max_yaml_depth > 0)),
CONSTRAINT app_settings_yaml_max_size_positive CHECK ((max_yaml_size_bytes > 0)),
CONSTRAINT check_17d9558205 CHECK ((char_length(kroki_url) <= 1024)),
CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)),
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
CONSTRAINT check_32710817e9 CHECK ((char_length(static_objects_external_storage_auth_token_encrypted) <= 255)),
CONSTRAINT check_3559645ae5 CHECK ((char_length(container_registry_import_target_plan) <= 255)),
......@@ -17414,8 +17415,7 @@ CREATE TABLE namespaces (
shared_runners_enabled boolean DEFAULT true NOT NULL,
allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL,
traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL,
tmp_project_id integer,
email_visibility_disabled boolean
tmp_project_id integer
);
CREATE SEQUENCE namespaces_id_seq
......@@ -18598,8 +18598,8 @@ CREATE TABLE plan_limits (
ci_max_artifact_size_api_fuzzing integer DEFAULT 0 NOT NULL,
ci_pipeline_deployments integer DEFAULT 500 NOT NULL,
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL,
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL,
daily_invites integer DEFAULT 0 NOT NULL,
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL,
terraform_module_max_file_size bigint DEFAULT 1073741824 NOT NULL,
helm_max_file_size bigint DEFAULT 5242880 NOT NULL,
ci_registered_group_runners integer DEFAULT 1000 NOT NULL,
......@@ -21989,8 +21989,8 @@ CREATE TABLE web_hooks (
encrypted_url character varying,
encrypted_url_iv character varying,
deployment_events boolean DEFAULT false NOT NULL,
feature_flag_events boolean DEFAULT false NOT NULL,
releases_events boolean DEFAULT false NOT NULL,
feature_flag_events boolean DEFAULT false NOT NULL,
member_events boolean DEFAULT false NOT NULL,
subgroup_events boolean DEFAULT false NOT NULL,
recent_failures smallint DEFAULT 0 NOT NULL,
......@@ -26631,8 +26631,6 @@ CREATE INDEX index_analytics_ca_group_stages_on_end_event_label_id ON analytics_
CREATE INDEX index_analytics_ca_group_stages_on_group_id ON analytics_cycle_analytics_group_stages USING btree (group_id);
CREATE UNIQUE INDEX index_analytics_ca_group_stages_on_group_id_vs_id_and_name ON analytics_cycle_analytics_group_stages USING btree (group_id, group_value_stream_id, name);
CREATE INDEX index_analytics_ca_group_stages_on_relative_position ON analytics_cycle_analytics_group_stages USING btree (relative_position);
CREATE INDEX index_analytics_ca_group_stages_on_start_event_label_id ON analytics_cycle_analytics_group_stages USING btree (start_event_label_id);
......@@ -27167,8 +27165,6 @@ CREATE INDEX index_cluster_agent_tokens_on_agent_id_status_last_used_at ON clust
CREATE INDEX index_cluster_agent_tokens_on_created_by_user_id ON cluster_agent_tokens USING btree (created_by_user_id);
CREATE INDEX index_cluster_agent_tokens_on_last_used_at ON cluster_agent_tokens USING btree (last_used_at DESC NULLS LAST);
CREATE UNIQUE INDEX index_cluster_agent_tokens_on_token_encrypted ON cluster_agent_tokens USING btree (token_encrypted);
CREATE INDEX index_cluster_agents_on_created_by_user_id ON cluster_agents USING btree (created_by_user_id);
......@@ -27545,7 +27541,7 @@ CREATE INDEX index_events_on_author_id_and_created_at_merge_requests ON events U
CREATE INDEX index_events_on_author_id_and_id ON events USING btree (author_id, id);
CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 10:00:00+10'::timestamp with time zone);
CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 00:00:00+00'::timestamp with time zone);
CREATE INDEX index_events_on_group_id_partial ON events USING btree (group_id) WHERE (group_id IS NOT NULL);
......@@ -27731,6 +27727,8 @@ CREATE INDEX index_group_import_states_on_user_id ON group_import_states USING b
CREATE INDEX index_group_repository_storage_moves_on_group_id ON group_repository_storage_moves USING btree (group_id);
CREATE UNIQUE INDEX index_group_stages_on_group_id_group_value_stream_id_and_name ON analytics_cycle_analytics_group_stages USING btree (group_id, group_value_stream_id, name);
CREATE INDEX index_group_stages_on_stage_event_hash_id ON analytics_cycle_analytics_group_stages USING btree (stage_event_hash_id);
CREATE UNIQUE INDEX index_group_user_callouts_feature ON user_group_callouts USING btree (user_id, feature_name, group_id);
......@@ -28287,8 +28285,6 @@ CREATE INDEX index_notes_on_discussion_id ON notes USING btree (discussion_id);
CREATE INDEX index_notes_on_line_code ON notes USING btree (line_code);
CREATE INDEX index_notes_on_note_gin_trigram ON notes USING gin (note gin_trgm_ops);
CREATE INDEX index_notes_on_noteable_id_and_noteable_type_and_system ON notes USING btree (noteable_id, noteable_type, system);
CREATE INDEX index_notes_on_project_id_and_id_and_system_false ON notes USING btree (project_id, id) WHERE (NOT system);
......@@ -29525,6 +29521,8 @@ CREATE UNIQUE INDEX issue_user_mentions_on_issue_id_index ON issue_user_mentions
CREATE UNIQUE INDEX kubernetes_namespaces_cluster_and_namespace ON clusters_kubernetes_namespaces USING btree (cluster_id, namespace);
CREATE INDEX merge_request_mentions_temp_index ON merge_requests USING btree (id) WHERE ((description ~~ '%@%'::text) OR ((title)::text ~~ '%@%'::text));
CREATE UNIQUE INDEX merge_request_user_mentions_on_mr_id_and_note_id_index ON merge_request_user_mentions USING btree (merge_request_id, note_id);
CREATE UNIQUE INDEX merge_request_user_mentions_on_mr_id_index ON merge_request_user_mentions USING btree (merge_request_id) WHERE (note_id IS NULL);
......@@ -29571,7 +29569,7 @@ CREATE INDEX tmp_gitlab_subscriptions_max_seats_used_migration_2 ON gitlab_subsc
CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
CREATE INDEX tmp_index_ci_job_artifacts_on_id_where_trace_and_expire_at ON ci_job_artifacts USING btree (id) WHERE ((file_type = 3) AND (expire_at = ANY (ARRAY['2021-04-22 10:00:00+10'::timestamp with time zone, '2021-05-22 10:00:00+10'::timestamp with time zone, '2021-06-22 10:00:00+10'::timestamp with time zone, '2022-01-22 11:00:00+11'::timestamp with time zone, '2022-02-22 11:00:00+11'::timestamp with time zone, '2022-03-22 11:00:00+11'::timestamp with time zone, '2022-04-22 10:00:00+10'::timestamp with time zone])));
CREATE INDEX tmp_index_ci_job_artifacts_on_id_where_trace_and_expire_at ON ci_job_artifacts USING btree (id) WHERE ((file_type = 3) AND (expire_at = ANY (ARRAY['2021-04-22 00:00:00+00'::timestamp with time zone, '2021-05-22 00:00:00+00'::timestamp with time zone, '2021-06-22 00:00:00+00'::timestamp with time zone, '2022-01-22 00:00:00+00'::timestamp with time zone, '2022-02-22 00:00:00+00'::timestamp with time zone, '2022-03-22 00:00:00+00'::timestamp with time zone, '2022-04-22 00:00:00+00'::timestamp with time zone])));
CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container_repositories USING btree (id, migration_state);
......@@ -31503,7 +31501,7 @@ ALTER TABLE ONLY merge_request_assignees
ADD CONSTRAINT fk_af036e3261 FOREIGN KEY (updated_state_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY analytics_cycle_analytics_group_stages
ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE SET NULL;
ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
ALTER TABLE ONLY fork_network_members
ADD CONSTRAINT fk_b01280dae4 FOREIGN KEY (forked_from_project_id) REFERENCES projects(id) ON DELETE SET NULL;
......@@ -79,7 +79,7 @@ export default {
'hasValueStreams',
]),
shouldRenderEmptyState() {
return !this.isLoadingValueStreams && !this.hasValueStreams;
return this.isLoadingValueStreams || !this.hasValueStreams;
},
shouldDisplayFilters() {
return !this.errorCode && !this.hasNoAccessError;
......@@ -203,7 +203,9 @@ export default {
<div>
<value-stream-empty-state
v-if="shouldRenderEmptyState"
:is-loading="isLoadingValueStreams"
:empty-state-svg-path="emptyStateSvgPath"
:has-date-range-error="!hasDateRangeSet"
/>
<div v-else class="gl-max-w-full">
<div
......
......@@ -76,6 +76,7 @@ export default {
:svg-path="emptyStateSvgPath"
:title="title"
:description="description"
data-testid="vsa-empty-state"
>
<template v-if="!hasDateRangeError" #actions>
<gl-button
......
......@@ -12,6 +12,8 @@ RSpec.describe 'Value stream analytics charts', :js do
let_it_be(:group_label2) { create(:group_label, group: group) }
let_it_be(:label) { create(:group_label, group: group2) }
empty_state_selector = '[data-testid="vsa-empty-state"]'
3.times do |i|
let_it_be("issue_#{i}".to_sym) { create(:issue, title: "New Issue #{i}", project: project, created_at: 2.days.ago) }
end
......@@ -32,15 +34,14 @@ RSpec.describe 'Value stream analytics charts', :js do
sign_in(user)
end
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)
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'))
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"
......@@ -58,72 +59,132 @@ RSpec.describe 'Value stream analytics charts', :js do
end
end
describe 'Tasks by type chart', :js do
filters_selector = '.js-tasks-by-type-chart-filters'
shared_examples 'has the tasks by type chart' do
context 'with data available' do
filters_selector = '.js-tasks-by-type-chart-filters'
before do
stub_licensed_features(cycle_analytics_for_groups: true, type_of_work_analytics: true)
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])
project.add_maintainer(user)
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
sign_in(user)
end
select_group(group)
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 'displays the chart' do
expect(page).to have_text(s_('CycleAnalytics|Type of work'))
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
expect(page).to have_text(s_('CycleAnalytics|Tasks by type'))
end
select_group(group)
end
it 'has 2 labels selected' do
expect(page).to have_text('Showing Issues and 2 labels')
end
it 'displays the chart' do
expect(page).to have_text(s_('CycleAnalytics|Type of work'))
it 'has chart filters' do
expect(page).to have_css(filters_selector)
end
expect(page).to have_text(s_('CycleAnalytics|Tasks by type'))
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
it 'has 2 labels selected' do
expect(page).to have_text('Showing Issues and 2 labels')
end
expect(page).to have_text('Showing Issues and 1 label')
it 'has chart filters' do
expect(page).to have_css(filters_selector)
page.within filters_selector do
find('.dropdown-toggle').click
find('[data-testid="type-of-work-filters-subject"] label', text: 'Merge Requests').click
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
end
expect(page).to have_text('Showing Merge Requests and 1 label')
end
end
context 'no data available' do
before do
select_group(group)
end
expect(page).to have_text('Showing Issues and 1 label')
it 'shows the no data available message' 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
end
expect(page).to have_text(_('There is no data available. Please change your selection.'))
end
end
end
expect(page).to have_text('Showing Merge Requests and 1 label')
context 'Duration chart' 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)
end
it_behaves_like 'has the empty state'
end
context 'no data available' do
context 'with a value stream' do
before do
create(:cycle_analytics_group_value_stream, group: group, name: 'First value stream')
select_group(group)
end
it 'shows the no data available message' do
expect(page).to have_text(s_('CycleAnalytics|Type of work'))
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)
project.add_maintainer(user)
sign_in(user)
end
context 'type_of_work_analytics enabled' do
context 'use_vsa_aggregated_tables feature flag off' do
before do
stub_feature_flags(use_vsa_aggregated_tables: false)
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
before do
select_group(group, empty_state_selector)
end
it_behaves_like 'has the empty state'
end
context 'with a value stream' do
before do
create(:cycle_analytics_group_value_stream, group: group, name: 'First value stream')
end
expect(page).to have_text(_('There is no data available. Please change your selection.'))
it_behaves_like 'has the tasks by type chart'
end
end
end
......
......@@ -20,6 +20,8 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
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|
......@@ -57,9 +59,9 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
sign_in(user)
end
shared_examples 'empty state' do
shared_examples 'empty value stream stage' do
it 'displays an empty state' do
element = page.find('.empty-state')
element = page.find(empty_stage_state_selector)
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')
......@@ -170,176 +172,207 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
end
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
context 'use_vsa_aggregated_tables feature flag off' do
before do
stub_feature_flags(use_vsa_aggregated_tables: false)
it_behaves_like 'no group available'
select_group(group)
end
context 'with fake parameters' do
before do
visit "#{group_analytics_cycle_analytics_path(group)}?beans=not-cool"
select_stage("Issue")
end
it_behaves_like 'empty state'
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 'with valid query parameters set' do
projects_dropdown = '.js-projects-dropdown-filter'
context 'with project_ids set' do
context 'use_vsa_aggregated_tables feature flag on' do
context 'without a value stream' do
before do
visit "#{group_analytics_cycle_analytics_path(group)}?project_ids[]=#{project.id}"
select_group(group, empty_state_selector)
end
it 'has the projects dropdown prepopulated' do
element = page.find(projects_dropdown)
expect(element).to have_content project.name
it 'renders the empty value stream state' do
expect(page).to have_text(s_('CycleAnalytics|Custom value streams to measure your DevSecOps lifecycle'))
end
end
context 'with created_before and created_after set' do
date_range = '.js-daterange-picker'
context 'with a value stream' do
before do
visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01"
create(:cycle_analytics_group_value_stream, group: group, name: 'First value stream')
create(:cycle_analytics_group_value_stream, group: sub_group, name: 'First sub group value stream')
end
it 'has the date range prepopulated' do
element = page.find(date_range)
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"
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 })
select_stage("Issue")
end
it_behaves_like 'empty value stream stage'
end
end
end
end
context 'with a group' do
let(:selected_group) { group }
context 'with valid query parameters set' do
projects_dropdown = '.js-projects-dropdown-filter'
before do
select_group(group)
end
context 'with project_ids set' do
before do
visit "#{group_analytics_cycle_analytics_path(group)}?project_ids[]=#{project.id}"
end
it_behaves_like 'group value stream analytics'
it 'has the projects dropdown prepopulated' do
element = page.find(projects_dropdown)
it_behaves_like 'has overview metrics'
expect(element).to have_content project.name
end
end
it_behaves_like 'has default filters'
end
context 'with created_before and created_after set' do
date_range = '.js-daterange-picker'
context 'with a sub group' do
let(:selected_group) { sub_group }
before do
visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01"
end
before do
select_group(sub_group)
end
it 'has the date range prepopulated' do
element = page.find(date_range)
it_behaves_like 'group value stream analytics'
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
it_behaves_like 'has overview metrics'
context 'with a group' do
let(:selected_group) { group }
it_behaves_like 'has default filters'
end
before do
select_group(group)
end
context 'with lots of data', :js do
let_it_be(:issue) { create(:issue, project: project) }
it_behaves_like 'group value stream analytics'
around do |example|
freeze_time { example.run }
end
it_behaves_like 'has overview metrics'
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])
it_behaves_like 'has default filters'
end
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)
context 'with a sub group' do
let(:selected_group) { sub_group }
deploy_master(user, project, environment: 'staging')
deploy_master(user, project)
before do
select_group(sub_group)
end
select_group(group)
end
it_behaves_like 'group value stream analytics'
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
it_behaves_like 'has overview metrics'
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])
it_behaves_like 'has default filters'
end
end
it 'each stage will be selectable' do
[].concat(stages_without_data, stages_with_data).each do |stage|
select_stage(stage[:title])
context 'with lots of data', :js do
let_it_be(:issue) { create(:issue, project: project) }
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])
around do |example|
freeze_time { example.run }
end
expect(page).to have_selector('[data-testid="vsa-duration-chart"]')
end
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])
it 'will not display the stage table on the overview stage' do
expect(page).not_to have_selector('[data-testid="vsa-stage-table"]')
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)
select_stage("Issue")
expect(page).to have_selector('[data-testid="vsa-stage-table"]')
end
deploy_master(user, project, environment: 'staging')
deploy_master(user, project)
select_group(group)
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'))
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
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
it 'each stage will be selectable' do
[].concat(stages_without_data, stages_with_data).each do |stage|
select_stage(stage[:title])
context 'with filters applied' do
before do
visit "#{group_analytics_cycle_analytics_path(group)}?created_before=2019-12-31&created_after=2019-11-01"
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])
wait_for_stages_to_load
end
expect(page).to have_selector('[data-testid="vsa-duration-chart"]')
end
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 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
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."))
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
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
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
end
end
end
......
......@@ -18,14 +18,13 @@ RSpec.describe 'Multiple value streams', :js do
let(:extended_form_fields_selector) { '[data-testid="extended-form-fields"]' }
let(:preset_selector) { '[data-testid="vsa-preset-selector"]' }
let!(:default_value_stream) { create(:cycle_analytics_group_value_stream, group: group, name: 'default') }
let(:empty_state_selector) { '[data-testid="vsa-empty-state"]' }
3.times do |i|
let_it_be("issue_#{i}".to_sym) { create(:issue, title: "New Issue #{i}", project: project, created_at: 2.days.ago) }
end
before do
stub_feature_flags(use_vsa_aggregated_tables: false)
stub_licensed_features(cycle_analytics_for_groups: true, type_of_work_analytics: true)
sign_in(user)
......@@ -199,7 +198,7 @@ RSpec.describe 'Multiple value streams', :js do
end
end
describe 'With a group' do
shared_examples 'create group value streams' do
name = 'group value stream'
before do
......@@ -211,7 +210,7 @@ RSpec.describe 'Multiple value streams', :js do
it_behaves_like 'delete a value stream', name
end
describe 'With a sub group' do
shared_examples 'create sub group value streams' do
name = 'sub group value stream'
before do
......@@ -222,4 +221,42 @@ RSpec.describe 'Multiple value streams', :js do
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
......@@ -9,6 +9,7 @@ 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 {
......@@ -195,11 +196,12 @@ describe('EE Value Stream Analytics component', () => {
expect(wrapper.findComponent(ValueStreamSelect).exists()).toBe(flag);
};
describe('without a group', () => {
describe('with no value streams', () => {
beforeEach(async () => {
const { group, ...stateWithoutGroup } = initialCycleAnalyticsState;
mock = new MockAdapter(axios);
wrapper = await createComponent({ initialState: stateWithoutGroup });
wrapper = await createComponent({
initialState: { ...initialCycleAnalyticsState, valueStreams: [] },
});
});
afterEach(() => {
......@@ -209,10 +211,10 @@ describe('EE Value Stream Analytics component', () => {
});
it('displays an empty state', () => {
const emptyState = wrapper.findComponent(GlEmptyState);
const emptyState = wrapper.findComponent(ValueStreamEmptyState);
expect(emptyState.exists()).toBe(true);
expect(emptyState.props('svgPath')).toBe(emptyStateSvgPath);
expect(emptyState.props('emptyStateSvgPath')).toBe(emptyStateSvgPath);
});
it('does not display the metrics cards', () => {
......
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