Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									5bc7b9357e
								
							
						
					
					
						commit
						1b8bee4713
					
				|  | @ -1 +1 @@ | ||||||
| 2d62ba38be14d081d99e91d4c44e6371f9deddde | 7d35879381a67a26332ecdd66392a2ad30bf64ca | ||||||
|  |  | ||||||
|  | @ -1,31 +1,19 @@ | ||||||
| <script> | <script> | ||||||
| import { GlTabs, GlTab } from '@gitlab/ui'; | import { GlTabs, GlTab } from '@gitlab/ui'; | ||||||
| import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; | import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; | ||||||
|  | import { __, s__ } from '~/locale'; | ||||||
| import { InternalEvents } from '~/tracking'; | import { InternalEvents } from '~/tracking'; | ||||||
| import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; | import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; | ||||||
| import PipelineCharts from './pipeline_charts.vue'; | import PipelinesDashboard from './pipelines_dashboard.vue'; | ||||||
| import PipelineChartsNew from './pipeline_charts_new.vue'; | import PipelinesDashboardClickhouse from './pipelines_dashboard_clickhouse.vue'; | ||||||
|  | 
 | ||||||
|  | const URL_PARAM_KEY = 'chart'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     GlTabs, |     GlTabs, | ||||||
|     GlTab, |     GlTab, | ||||||
|     PipelineCharts, |  | ||||||
|     PipelineChartsNew, |  | ||||||
|     DeploymentFrequencyCharts: () => |  | ||||||
|       import('ee_component/dora/components/deployment_frequency_charts.vue'), |  | ||||||
|     LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'), |  | ||||||
|     TimeToRestoreServiceCharts: () => |  | ||||||
|       import('ee_component/dora/components/time_to_restore_service_charts.vue'), |  | ||||||
|     ChangeFailureRateCharts: () => |  | ||||||
|       import('ee_component/dora/components/change_failure_rate_charts.vue'), |  | ||||||
|     ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'), |  | ||||||
|   }, |   }, | ||||||
|   pipelinesTabEvent: 'p_analytics_ci_cd_pipelines', |  | ||||||
|   deploymentFrequencyTabEvent: 'p_analytics_ci_cd_deployment_frequency', |  | ||||||
|   leadTimeTabEvent: 'p_analytics_ci_cd_lead_time', |  | ||||||
|   timeToRestoreServiceTabEvent: 'visit_ci_cd_time_to_restore_service_tab', |  | ||||||
|   changeFailureRateTabEvent: 'visit_ci_cd_failure_rate_tab', |  | ||||||
|   mixins: [InternalEvents.mixin(), glFeatureFlagsMixin()], |   mixins: [InternalEvents.mixin(), glFeatureFlagsMixin()], | ||||||
|   inject: { |   inject: { | ||||||
|     shouldRenderDoraCharts: { |     shouldRenderDoraCharts: { | ||||||
|  | @ -38,50 +26,83 @@ export default { | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     const tabs = [ | ||||||
|       selectedTab: 0, |       { | ||||||
|     }; |         key: 'pipelines', | ||||||
|  |         event: 'p_analytics_ci_cd_pipelines', | ||||||
|  |         title: __('Pipelines'), | ||||||
|  |         componentIs: this.glFeatures?.ciImprovedProjectPipelineAnalytics | ||||||
|  |           ? PipelinesDashboardClickhouse | ||||||
|  |           : PipelinesDashboard, | ||||||
|  |         lazy: true, | ||||||
|       }, |       }, | ||||||
|   computed: { |     ]; | ||||||
|     charts() { |  | ||||||
|       const chartsToShow = ['pipelines']; |  | ||||||
| 
 | 
 | ||||||
|     if (this.shouldRenderDoraCharts) { |     if (this.shouldRenderDoraCharts) { | ||||||
|         chartsToShow.push( |       tabs.push( | ||||||
|           'deployment-frequency', |         { | ||||||
|           'lead-time', |           key: 'deployment-frequency', | ||||||
|           'time-to-restore-service', |           event: 'p_analytics_ci_cd_deployment_frequency', | ||||||
|           'change-failure-rate', |           title: __('Deployment frequency'), | ||||||
|  |           componentIs: () => import('ee_component/dora/components/deployment_frequency_charts.vue'), | ||||||
|  |           lazy: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           key: 'lead-time', | ||||||
|  |           event: 'p_analytics_ci_cd_lead_time', | ||||||
|  |           title: __('Lead time'), | ||||||
|  |           componentIs: () => import('ee_component/dora/components/lead_time_charts.vue'), | ||||||
|  |           lazy: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           key: 'time-to-restore-service', | ||||||
|  |           event: 'visit_ci_cd_time_to_restore_service_tab', | ||||||
|  |           title: s__('DORA4Metrics|Time to restore service'), | ||||||
|  |           componentIs: () => | ||||||
|  |             import('ee_component/dora/components/time_to_restore_service_charts.vue'), | ||||||
|  |           lazy: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           key: 'change-failure-rate', | ||||||
|  |           event: 'visit_ci_cd_failure_rate_tab', | ||||||
|  |           title: s__('DORA4Metrics|Change failure rate'), | ||||||
|  |           componentIs: () => import('ee_component/dora/components/change_failure_rate_charts.vue'), | ||||||
|  |           lazy: true, | ||||||
|  |         }, | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.shouldRenderQualitySummary) { |     if (this.shouldRenderQualitySummary) { | ||||||
|         chartsToShow.push('project-quality'); |       tabs.push({ | ||||||
|  |         key: 'project-quality', | ||||||
|  |         title: s__('QualitySummary|Project quality'), | ||||||
|  |         componentIs: () => import('ee_component/project_quality_summary/app.vue'), | ||||||
|  |         lazy: true, | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|       return chartsToShow; |     return { | ||||||
|     }, |       activeTabIndex: 0, | ||||||
|     pipelineChartsComponent() { |       tabs, | ||||||
|       if (this.glFeatures?.ciImprovedProjectPipelineAnalytics) { |     }; | ||||||
|         return PipelineChartsNew; |  | ||||||
|       } |  | ||||||
|       return PipelineCharts; |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
|   created() { |   created() { | ||||||
|     this.selectTab(); |     this.syncActiveTab(); | ||||||
|     window.addEventListener('popstate', this.selectTab); |     window.addEventListener('popstate', this.syncActiveTab); | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     selectTab() { |     syncActiveTab() { | ||||||
|       const [chart] = getParameterValues('chart') || this.charts; |       const paramValue = getParameterValues(URL_PARAM_KEY)?.[0]; | ||||||
|       const tab = this.charts.indexOf(chart); |       const selectedIndex = this.tabs.map((tab) => tab.key).indexOf(paramValue); | ||||||
|       this.selectedTab = tab >= 0 ? tab : 0; |       this.activeTabIndex = selectedIndex >= 0 ? selectedIndex : 0; | ||||||
|     }, |     }, | ||||||
|     onTabChange(index) { |     onTabInput(index) { | ||||||
|       if (index !== this.selectedTab) { |       if (index !== this.activeTabIndex) { | ||||||
|         this.selectedTab = index; |         const tab = this.tabs[index]; | ||||||
|         const path = mergeUrlParams({ chart: this.charts[index] }, window.location.pathname); |         const path = mergeUrlParams({ [URL_PARAM_KEY]: tab.key }, window.location.pathname); | ||||||
|  | 
 | ||||||
|  |         this.activeTabIndex = index; | ||||||
|  |         tab.lazy = false; // mount the tab permanently after it is shown | ||||||
|         updateHistory({ url: path, title: window.title }); |         updateHistory({ url: path, title: window.title }); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  | @ -90,48 +111,17 @@ export default { | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <gl-tabs v-if="charts.length > 1" :value="selectedTab" @input="onTabChange"> |     <gl-tabs v-if="tabs.length > 1" :value="activeTabIndex" @input="onTabInput"> | ||||||
|       <gl-tab |       <gl-tab | ||||||
|         :title="__('Pipelines')" |         v-for="tab in tabs" | ||||||
|         data-testid="pipelines-tab" |         :key="tab.key" | ||||||
|         @click="trackEvent($options.pipelinesTabEvent)" |         :title="tab.title" | ||||||
|  |         :lazy="tab.lazy" | ||||||
|  |         @click="tab.event && trackEvent(tab.event)" | ||||||
|       > |       > | ||||||
|         <component :is="pipelineChartsComponent" /> |         <component :is="tab.componentIs" /> | ||||||
|       </gl-tab> |  | ||||||
|       <template v-if="shouldRenderDoraCharts"> |  | ||||||
|         <gl-tab |  | ||||||
|           :title="__('Deployment frequency')" |  | ||||||
|           data-testid="deployment-frequency-tab" |  | ||||||
|           @click="trackEvent($options.deploymentFrequencyTabEvent)" |  | ||||||
|         > |  | ||||||
|           <deployment-frequency-charts /> |  | ||||||
|         </gl-tab> |  | ||||||
|         <gl-tab |  | ||||||
|           :title="__('Lead time')" |  | ||||||
|           data-testid="lead-time-tab" |  | ||||||
|           @click="trackEvent($options.leadTimeTabEvent)" |  | ||||||
|         > |  | ||||||
|           <lead-time-charts /> |  | ||||||
|         </gl-tab> |  | ||||||
|         <gl-tab |  | ||||||
|           :title="s__('DORA4Metrics|Time to restore service')" |  | ||||||
|           data-testid="time-to-restore-service-tab" |  | ||||||
|           @click="trackEvent($options.timeToRestoreServiceTabEvent)" |  | ||||||
|         > |  | ||||||
|           <time-to-restore-service-charts /> |  | ||||||
|         </gl-tab> |  | ||||||
|         <gl-tab |  | ||||||
|           :title="s__('DORA4Metrics|Change failure rate')" |  | ||||||
|           data-testid="change-failure-rate-tab" |  | ||||||
|           @click="trackEvent($options.changeFailureRateTabEvent)" |  | ||||||
|         > |  | ||||||
|           <change-failure-rate-charts /> |  | ||||||
|         </gl-tab> |  | ||||||
|       </template> |  | ||||||
|       <gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')"> |  | ||||||
|         <project-quality-summary /> |  | ||||||
|       </gl-tab> |       </gl-tab> | ||||||
|     </gl-tabs> |     </gl-tabs> | ||||||
|     <component :is="pipelineChartsComponent" v-else /> |     <component :is="tabs[0].componentIs" v-else /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -47,6 +47,7 @@ const defaultCountValues = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  |   name: 'PipelinesDashboard', | ||||||
|   components: { |   components: { | ||||||
|     GlAlert, |     GlAlert, | ||||||
|     GlColumnChart, |     GlColumnChart, | ||||||
|  | @ -16,6 +16,7 @@ import PipelineDurationChart from './pipeline_duration_chart.vue'; | ||||||
| import PipelineStatusChart from './pipeline_status_chart.vue'; | import PipelineStatusChart from './pipeline_status_chart.vue'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  |   name: 'PipelinesDashboardClickhouse', | ||||||
|   components: { |   components: { | ||||||
|     GlCollapsibleListbox, |     GlCollapsibleListbox, | ||||||
|     GlFormGroup, |     GlFormGroup, | ||||||
|  | @ -8,15 +8,6 @@ description: TODO | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/9d6fe7bfdf9ff3f68ee73baa0e3d0aa7df13c351 | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/9d6fe7bfdf9ff3f68ee73baa0e3d0aa7df13c351 | ||||||
| milestone: '10.8' | milestone: '10.8' | ||||||
| gitlab_schema: gitlab_ci | gitlab_schema: gitlab_ci | ||||||
| desired_sharding_key: |  | ||||||
|   project_id: |  | ||||||
|     references: projects |  | ||||||
|     backfill_via: |  | ||||||
|       parent: |  | ||||||
|         foreign_key: build_id |  | ||||||
|         table: p_ci_builds |  | ||||||
|         sharding_key: project_id |  | ||||||
|         belongs_to: build |  | ||||||
|         foreign_key_name: fk_89e29fa5ee_p |  | ||||||
| desired_sharding_key_migration_job_name: BackfillCiBuildTraceChunksProjectId |  | ||||||
| table_size: small | table_size: small | ||||||
|  | sharding_key: | ||||||
|  |   project_id: projects | ||||||
|  |  | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class AddCiBuildTraceChunksProjectIdNotNull < Gitlab::Database::Migration[2.2] | ||||||
|  |   milestone '17.11' | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     add_not_null_constraint :ci_build_trace_chunks, :project_id | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     remove_not_null_constraint :ci_build_trace_chunks, :project_id | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 739b48db5ebc3b2e03dff587bd1e4661764e06dc4025300a16a364271e7313e6 | ||||||
|  | @ -10460,7 +10460,8 @@ CREATE TABLE ci_build_trace_chunks ( | ||||||
|     lock_version integer DEFAULT 0 NOT NULL, |     lock_version integer DEFAULT 0 NOT NULL, | ||||||
|     build_id bigint NOT NULL, |     build_id bigint NOT NULL, | ||||||
|     partition_id bigint NOT NULL, |     partition_id bigint NOT NULL, | ||||||
|     project_id bigint |     project_id bigint, | ||||||
|  |     CONSTRAINT check_b374316678 CHECK ((project_id IS NOT NULL)) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| CREATE SEQUENCE ci_build_trace_chunks_id_seq | CREATE SEQUENCE ci_build_trace_chunks_id_seq | ||||||
|  |  | ||||||
|  | @ -5650,7 +5650,11 @@ trigger-multi-project-pipeline: | ||||||
| Use `trigger:include` to declare that a job is a "trigger job" which starts a | Use `trigger:include` to declare that a job is a "trigger job" which starts a | ||||||
| [child pipeline](../pipelines/downstream_pipelines.md#parent-child-pipelines). | [child pipeline](../pipelines/downstream_pipelines.md#parent-child-pipelines). | ||||||
| 
 | 
 | ||||||
| Use `trigger:include:artifact` to trigger a [dynamic child pipeline](../pipelines/downstream_pipelines.md#dynamic-child-pipelines). | Additionally, use: | ||||||
|  | 
 | ||||||
|  | - `trigger:include:artifact` to trigger a [dynamic child pipeline](../pipelines/downstream_pipelines.md#dynamic-child-pipelines). | ||||||
|  | - `trigger:include:inputs` to set the [inputs](inputs.md) when the downstream pipeline configuration | ||||||
|  |   uses [`spec:inputs`](#specinputs). | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
|  | @ -5744,6 +5748,26 @@ successfully complete before starting. | ||||||
| - If the downstream pipeline has a failed job, but the job uses [`allow_failure: true`](#allow_failure), | - If the downstream pipeline has a failed job, but the job uses [`allow_failure: true`](#allow_failure), | ||||||
|   the downstream pipeline is considered successful and the trigger job shows **success**. |   the downstream pipeline is considered successful and the trigger job shows **success**. | ||||||
| 
 | 
 | ||||||
|  | #### `trigger:inputs` | ||||||
|  | 
 | ||||||
|  | {{< history >}} | ||||||
|  | 
 | ||||||
|  | - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/519963) in GitLab 17.11 [with a flag](../../administration/feature_flags.md) named `ci_inputs_for_pipelines`. Disabled by default. | ||||||
|  | 
 | ||||||
|  | {{</history >}} | ||||||
|  | 
 | ||||||
|  | Use `trigger:inputs` to set the [inputs](inputs.md) when the downstream pipeline configuration | ||||||
|  | uses [`spec:inputs`](#specinputs). | ||||||
|  | 
 | ||||||
|  | **Example of `trigger:inputs`**: | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | trigger: | ||||||
|  |   - project: 'my-group/my-project' | ||||||
|  |     inputs: | ||||||
|  |       website: "My website" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| #### `trigger:forward` | #### `trigger:forward` | ||||||
| 
 | 
 | ||||||
| {{< history >}} | {{< history >}} | ||||||
|  |  | ||||||
|  | @ -1213,6 +1213,7 @@ ee: | ||||||
|             - :iterations_cadence |             - :iterations_cadence | ||||||
|       - protected_branches: |       - protected_branches: | ||||||
|         - :unprotect_access_levels |         - :unprotect_access_levels | ||||||
|  |         - :squash_option | ||||||
|       - protected_environments: |       - protected_environments: | ||||||
|         - :deploy_access_levels |         - :deploy_access_levels | ||||||
|       - :security_setting |       - :security_setting | ||||||
|  | @ -1357,6 +1358,9 @@ ee: | ||||||
|       - :iid |       - :iid | ||||||
|     vulnerability_read: |     vulnerability_read: | ||||||
|       - :project_id |       - :project_id | ||||||
|  |     squash_option: | ||||||
|  |       - :squash_option | ||||||
|  |       - :project_id | ||||||
|   excluded_attributes: |   excluded_attributes: | ||||||
|     project: |     project: | ||||||
|       - :vulnerability_hooks_integrations |       - :vulnerability_hooks_integrations | ||||||
|  |  | ||||||
|  | @ -42,7 +42,8 @@ module Gitlab | ||||||
|                       committer: 'MergeRequest::DiffCommitUser', |                       committer: 'MergeRequest::DiffCommitUser', | ||||||
|                       merge_request_diff_commits: 'MergeRequestDiffCommit', |                       merge_request_diff_commits: 'MergeRequestDiffCommit', | ||||||
|                       work_item_type: 'WorkItems::Type', |                       work_item_type: 'WorkItems::Type', | ||||||
|                       user_contributions: 'User' }.freeze |                       user_contributions: 'User', | ||||||
|  |                       squash_option: 'Projects::BranchRules::SquashOption' }.freeze | ||||||
| 
 | 
 | ||||||
|         BUILD_MODELS = %i[Ci::Build Ci::Bridge commit_status generic_commit_status].freeze |         BUILD_MODELS = %i[Ci::Build Ci::Bridge commit_status generic_commit_status].freeze | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,13 +10,8 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|   describe GraphQL::Query, type: :request do |   describe GraphQL::Query, type: :request do | ||||||
|     let_it_be(:group) { create(:group, path: 'container-registry-group') } |     let_it_be(:group) { create(:group, path: 'container-registry-group') } | ||||||
|     let_it_be(:project) { create(:project, group: group, path: 'container-registry-project') } |     let_it_be(:project) { create(:project, group: group, path: 'container-registry-project') } | ||||||
|     let_it_be(:owner) { create(:user) } |  | ||||||
|     let_it_be(:user) { create(:user) } |     let_it_be(:user) { create(:user) } | ||||||
| 
 | 
 | ||||||
|     before_all do |  | ||||||
|       project.add_owner(owner) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     describe 'Protected container image tags' do |     describe 'Protected container image tags' do | ||||||
|       base_path = 'packages_and_registries/settings/project/graphql' |       base_path = 'packages_and_registries/settings/project/graphql' | ||||||
|       project_container_protection_tag_rules_query_path = |       project_container_protection_tag_rules_query_path = | ||||||
|  | @ -28,26 +23,35 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|       update_container_protection_tag_rule_mutation_path = |       update_container_protection_tag_rule_mutation_path = | ||||||
|         "#{base_path}/mutations/update_container_protection_tag_rule.mutation.graphql" |         "#{base_path}/mutations/update_container_protection_tag_rule.mutation.graphql" | ||||||
| 
 | 
 | ||||||
|  |       let(:query) { get_graphql_query_as_string(project_container_protection_tag_rules_query_path) } | ||||||
|  | 
 | ||||||
|  |       let(:variables) do | ||||||
|  |         { | ||||||
|  |           projectPath: project.full_path, | ||||||
|  |           first: 5 | ||||||
|  |         } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       before do |       before do | ||||||
|         stub_gitlab_api_client_to_support_gitlab_api(supported: true) |         stub_gitlab_api_client_to_support_gitlab_api(supported: true) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'when user does not have access to the project' do |       context 'when user does not have access to the project' do | ||||||
|         it "graphql/#{project_container_protection_tag_rules_query_path}.null_project.json" do |         it "graphql/#{project_container_protection_tag_rules_query_path}.null_project.json" do | ||||||
|           query = get_graphql_query_as_string(project_container_protection_tag_rules_query_path) |           post_graphql(query, current_user: user, variables: variables) | ||||||
| 
 |  | ||||||
|           post_graphql(query, current_user: user, variables: { projectPath: project.full_path, first: 5 }) |  | ||||||
| 
 | 
 | ||||||
|           expect_graphql_errors_to_be_empty |           expect_graphql_errors_to_be_empty | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'when user has access to the project &' do |       context 'when user has access to the project &' do | ||||||
|  |         before_all do | ||||||
|  |           project.add_owner(user) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|         context 'with no tag protection rules' do |         context 'with no tag protection rules' do | ||||||
|           it "graphql/#{project_container_protection_tag_rules_query_path}.empty_rules.json" do |           it "graphql/#{project_container_protection_tag_rules_query_path}.empty_rules.json" do | ||||||
|             query = get_graphql_query_as_string(project_container_protection_tag_rules_query_path) |             post_graphql(query, current_user: user, variables: variables) | ||||||
| 
 |  | ||||||
|             post_graphql(query, current_user: owner, variables: { projectPath: project.full_path, first: 5 }) |  | ||||||
| 
 | 
 | ||||||
|             expect_graphql_errors_to_be_empty |             expect_graphql_errors_to_be_empty | ||||||
|           end |           end | ||||||
|  | @ -63,9 +67,7 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           it "graphql/#{project_container_protection_tag_rules_query_path}.json" do |           it "graphql/#{project_container_protection_tag_rules_query_path}.json" do | ||||||
|             query = get_graphql_query_as_string(project_container_protection_tag_rules_query_path) |             post_graphql(query, current_user: user, variables: variables) | ||||||
| 
 |  | ||||||
|             post_graphql(query, current_user: owner, variables: { projectPath: project.full_path, first: 5 }) |  | ||||||
| 
 | 
 | ||||||
|             expect_graphql_errors_to_be_empty |             expect_graphql_errors_to_be_empty | ||||||
|           end |           end | ||||||
|  | @ -81,15 +83,16 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           it "graphql/#{project_container_protection_tag_rules_query_path}.max_rules.json" do |           it "graphql/#{project_container_protection_tag_rules_query_path}.max_rules.json" do | ||||||
|             query = get_graphql_query_as_string(project_container_protection_tag_rules_query_path) |             post_graphql(query, current_user: user, variables: variables) | ||||||
| 
 |  | ||||||
|             post_graphql(query, current_user: owner, variables: { projectPath: project.full_path, first: 5 }) |  | ||||||
| 
 | 
 | ||||||
|             expect_graphql_errors_to_be_empty |             expect_graphql_errors_to_be_empty | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'when there are no errors deleting a rule' do |         describe 'deleting a rule' do | ||||||
|  |           let(:mutation) { get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path) } | ||||||
|  | 
 | ||||||
|  |           context 'when there are no errors' do | ||||||
|             let_it_be(:container_protection_tag_rule) do |             let_it_be(:container_protection_tag_rule) do | ||||||
|               create(:container_registry_protection_tag_rule, |               create(:container_registry_protection_tag_rule, | ||||||
|                 project: project, |                 project: project, | ||||||
|  | @ -99,11 +102,9 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|             it "graphql/#{delete_container_protection_tag_rule_mutation_path}.json" do |             it "graphql/#{delete_container_protection_tag_rule_mutation_path}.json" do | ||||||
|             mutation = get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path) |  | ||||||
| 
 |  | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|               current_user: owner, |                 current_user: user, | ||||||
|                 variables: { |                 variables: { | ||||||
|                   input: { |                   input: { | ||||||
|                     id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}" |                     id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}" | ||||||
|  | @ -115,13 +116,11 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|         context 'when there are errors deleting a rule' do |           context 'when there are errors' do | ||||||
|             it "graphql/#{delete_container_protection_tag_rule_mutation_path}.errors.json" do |             it "graphql/#{delete_container_protection_tag_rule_mutation_path}.errors.json" do | ||||||
|             mutation = get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path) |  | ||||||
| 
 |  | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|               current_user: owner, |                 current_user: user, | ||||||
|                 variables: { |                 variables: { | ||||||
|                   input: { |                   input: { | ||||||
|                     id: 'gid://gitlab/ContainerRegistry::Protection::TagRule/non-existent' |                     id: 'gid://gitlab/ContainerRegistry::Protection::TagRule/non-existent' | ||||||
|  | @ -135,15 +134,13 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|               ) |               ) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|  |         end | ||||||
| 
 | 
 | ||||||
|         context 'when there are no errors creating a rule' do |         describe 'creating a rule' do | ||||||
|           it "graphql/#{create_container_protection_tag_rule_mutation_path}.json" do |           let(:mutation) { get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path) } | ||||||
|             mutation = get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path) |  | ||||||
| 
 | 
 | ||||||
|             post_graphql( |           let(:variables) do | ||||||
|               mutation, |             { | ||||||
|               current_user: owner, |  | ||||||
|               variables: { |  | ||||||
|               input: { |               input: { | ||||||
|                 projectPath: project.full_path, |                 projectPath: project.full_path, | ||||||
|                 tagNamePattern: 'v.*', |                 tagNamePattern: 'v.*', | ||||||
|  | @ -151,27 +148,28 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|                 minimumAccessLevelForDelete: 'OWNER' |                 minimumAccessLevelForDelete: 'OWNER' | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           context 'when there are no errors' do | ||||||
|  |             it "graphql/#{create_container_protection_tag_rule_mutation_path}.json" do | ||||||
|  |               post_graphql( | ||||||
|  |                 mutation, | ||||||
|  |                 current_user: user, | ||||||
|  |                 variables: variables | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect_graphql_errors_to_be_empty |               expect_graphql_errors_to_be_empty | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|         context 'when there are field errors creating a rule' do |           context 'when there are field errors' do | ||||||
|             it "graphql/#{create_container_protection_tag_rule_mutation_path}.server_errors.json" do |             it "graphql/#{create_container_protection_tag_rule_mutation_path}.server_errors.json" do | ||||||
|             mutation = get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path) |               variables[:input][:tagNamePattern] = '' | ||||||
| 
 | 
 | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|               current_user: owner, |                 current_user: user, | ||||||
|               variables: { |                 variables: variables | ||||||
|                 input: { |  | ||||||
|                   project_path: project.full_path, |  | ||||||
|                   tagNamePattern: '', |  | ||||||
|                   minimumAccessLevelForPush: 'MAINTAINER', |  | ||||||
|                   minimumAccessLevelForDelete: 'OWNER' |  | ||||||
|                 } |  | ||||||
|               } |  | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect_graphql_errors_to_include( |               expect_graphql_errors_to_include( | ||||||
|  | @ -180,32 +178,24 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|         context 'when there are errors creating a rule' do |           context 'when there are errors' do | ||||||
|             before do |             before do | ||||||
|               create(:container_registry_protection_tag_rule, project: project, |               create(:container_registry_protection_tag_rule, project: project, | ||||||
|                 tag_name_pattern: "v.*") |                 tag_name_pattern: "v.*") | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|             it "graphql/#{create_container_protection_tag_rule_mutation_path}.errors.json" do |             it "graphql/#{create_container_protection_tag_rule_mutation_path}.errors.json" do | ||||||
|             mutation = get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path) |  | ||||||
| 
 |  | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|               current_user: owner, |                 current_user: user, | ||||||
|               variables: { |                 variables: variables | ||||||
|                 input: { |  | ||||||
|                   project_path: project.full_path, |  | ||||||
|                   tagNamePattern: 'v.*', |  | ||||||
|                   minimumAccessLevelForPush: 'MAINTAINER', |  | ||||||
|                   minimumAccessLevelForDelete: 'OWNER' |  | ||||||
|                 } |  | ||||||
|               } |  | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect(graphql_data_at('createContainerProtectionTagRule', 'errors')) |               expect(graphql_data_at('createContainerProtectionTagRule', 'errors')) | ||||||
|                 .to include('Tag name pattern has already been taken') |                 .to include('Tag name pattern has already been taken') | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|  |         end | ||||||
| 
 | 
 | ||||||
|         describe 'updating a rule' do |         describe 'updating a rule' do | ||||||
|           let_it_be(:container_protection_tag_rule) do |           let_it_be(:container_protection_tag_rule) do | ||||||
|  | @ -216,21 +206,26 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
|             ) |             ) | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|  |           let(:mutation) { get_graphql_query_as_string(update_container_protection_tag_rule_mutation_path) } | ||||||
|  |           let(:variables) do | ||||||
|  |             { | ||||||
|  |               input: { | ||||||
|  |                 id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}", | ||||||
|  |                 tagNamePattern: 'v.*', | ||||||
|  |                 minimumAccessLevelForPush: 'MAINTAINER', | ||||||
|  |                 minimumAccessLevelForDelete: 'OWNER' | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|           context 'when there are no errors' do |           context 'when there are no errors' do | ||||||
|             it "graphql/#{update_container_protection_tag_rule_mutation_path}.json" do |             it "graphql/#{update_container_protection_tag_rule_mutation_path}.json" do | ||||||
|               mutation = get_graphql_query_as_string(update_container_protection_tag_rule_mutation_path) |               mutation = get_graphql_query_as_string(update_container_protection_tag_rule_mutation_path) | ||||||
| 
 | 
 | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|                 current_user: owner, |                 current_user: user, | ||||||
|                 variables: { |                 variables: variables | ||||||
|                   input: { |  | ||||||
|                     id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}", |  | ||||||
|                     tagNamePattern: 'v.*', |  | ||||||
|                     minimumAccessLevelForPush: 'ADMIN', |  | ||||||
|                     minimumAccessLevelForDelete: 'ADMIN' |  | ||||||
|                   } |  | ||||||
|                 } |  | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect_graphql_errors_to_be_empty |               expect_graphql_errors_to_be_empty | ||||||
|  | @ -239,19 +234,12 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
| 
 | 
 | ||||||
|           context 'when there are field errors' do |           context 'when there are field errors' do | ||||||
|             it "graphql/#{update_container_protection_tag_rule_mutation_path}.server_errors.json" do |             it "graphql/#{update_container_protection_tag_rule_mutation_path}.server_errors.json" do | ||||||
|               mutation = get_graphql_query_as_string(update_container_protection_tag_rule_mutation_path) |               variables[:input][:tagNamePattern] = '' | ||||||
| 
 | 
 | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|                 current_user: owner, |                 current_user: user, | ||||||
|                 variables: { |                 variables: variables | ||||||
|                   input: { |  | ||||||
|                     id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}", |  | ||||||
|                     tagNamePattern: '', |  | ||||||
|                     minimumAccessLevelForPush: 'MAINTAINER', |  | ||||||
|                     minimumAccessLevelForDelete: 'OWNER' |  | ||||||
|                   } |  | ||||||
|                 } |  | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect_graphql_errors_to_include( |               expect_graphql_errors_to_include( | ||||||
|  | @ -271,15 +259,8 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co | ||||||
| 
 | 
 | ||||||
|               post_graphql( |               post_graphql( | ||||||
|                 mutation, |                 mutation, | ||||||
|                 current_user: owner, |                 current_user: user, | ||||||
|                 variables: { |                 variables: variables | ||||||
|                   input: { |  | ||||||
|                     id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}", |  | ||||||
|                     tagNamePattern: 'v.*', |  | ||||||
|                     minimumAccessLevelForPush: 'MAINTAINER', |  | ||||||
|                     minimumAccessLevelForDelete: 'OWNER' |  | ||||||
|                   } |  | ||||||
|                 } |  | ||||||
|               ) |               ) | ||||||
| 
 | 
 | ||||||
|               expect(graphql_data_at('updateContainerProtectionTagRule', 'errors')) |               expect(graphql_data_at('updateContainerProtectionTagRule', 'errors')) | ||||||
|  |  | ||||||
|  | @ -1,261 +1,52 @@ | ||||||
| import { GlTabs, GlTab } from '@gitlab/ui'; | import { GlTabs } from '@gitlab/ui'; | ||||||
| import { merge } from 'lodash'; | import { shallowMount } from '@vue/test-utils'; | ||||||
| import { nextTick } from 'vue'; |  | ||||||
| import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; |  | ||||||
| import setWindowLocation from 'helpers/set_window_location_helper'; |  | ||||||
| import { TEST_HOST } from 'helpers/test_constants'; |  | ||||||
| import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; |  | ||||||
| import Component from '~/projects/pipelines/charts/components/app.vue'; |  | ||||||
| import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue'; |  | ||||||
| import PipelineChartsNew from '~/projects/pipelines/charts/components/pipeline_charts_new.vue'; |  | ||||||
| import API from '~/api'; |  | ||||||
| import { mockTracking } from 'helpers/tracking_helper'; |  | ||||||
| import { SNOWPLOW_DATA_SOURCE, SNOWPLOW_SCHEMA } from '~/projects/pipelines/charts/constants'; |  | ||||||
| 
 | 
 | ||||||
| jest.mock('~/lib/utils/url_utility'); | import App from '~/projects/pipelines/charts/components/app.vue'; | ||||||
| 
 | 
 | ||||||
| const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} }; | import PipelinesDashboard from '~/projects/pipelines/charts/components/pipelines_dashboard.vue'; | ||||||
| const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} }; | import PipelinesDashboardClickhouse from '~/projects/pipelines/charts/components/pipelines_dashboard_clickhouse.vue'; | ||||||
| const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} }; |  | ||||||
| const ChangeFailureRateChartsStub = { name: 'ChangeFailureRateCharts', render: () => {} }; |  | ||||||
| const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} }; |  | ||||||
| 
 | 
 | ||||||
| describe('ProjectsPipelinesChartsApp', () => { | describe('ProjectsPipelinesChartsApp', () => { | ||||||
|   let wrapper; |   let wrapper; | ||||||
| 
 | 
 | ||||||
|   function createComponent(mountOptions = {}) { |   const createWrapper = ({ provide, ...options } = {}) => { | ||||||
|     wrapper = shallowMountExtended( |     wrapper = shallowMount(App, { | ||||||
|       Component, |  | ||||||
|       merge( |  | ||||||
|         {}, |  | ||||||
|         { |  | ||||||
|       provide: { |       provide: { | ||||||
|             shouldRenderDoraCharts: true, |         ...provide, | ||||||
|             shouldRenderQualitySummary: true, |  | ||||||
|       }, |       }, | ||||||
|           stubs: { |       ...options, | ||||||
|             DeploymentFrequencyCharts: DeploymentFrequencyChartsStub, |     }); | ||||||
|             LeadTimeCharts: LeadTimeChartsStub, |   }; | ||||||
|             TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub, |  | ||||||
|             ChangeFailureRateCharts: ChangeFailureRateChartsStub, |  | ||||||
|             ProjectQualitySummary: ProjectQualitySummaryStub, |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|         mountOptions, |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   const findGlTabs = () => wrapper.findComponent(GlTabs); |   const findGlTabs = () => wrapper.findComponent(GlTabs); | ||||||
|   const findAllGlTabs = () => wrapper.findAllComponents(GlTab); |  | ||||||
|   const findGlTabAtIndex = (index) => findAllGlTabs().at(index); |  | ||||||
|   const findLeadTimeCharts = () => wrapper.findComponent(LeadTimeChartsStub); |  | ||||||
|   const findTimeToRestoreServiceCharts = () => |  | ||||||
|     wrapper.findComponent(TimeToRestoreServiceChartsStub); |  | ||||||
|   const findChangeFailureRateCharts = () => wrapper.findComponent(ChangeFailureRateChartsStub); |  | ||||||
|   const findDeploymentFrequencyCharts = () => wrapper.findComponent(DeploymentFrequencyChartsStub); |  | ||||||
|   const findPipelineCharts = () => wrapper.findComponent(PipelineCharts); |  | ||||||
|   const findPipelineChartsNew = () => wrapper.findComponent(PipelineChartsNew); |  | ||||||
|   const findProjectQualitySummary = () => wrapper.findComponent(ProjectQualitySummaryStub); |  | ||||||
| 
 | 
 | ||||||
|   describe('when all charts are available', () => { |   const findPipelinesDashboard = () => wrapper.findComponent(PipelinesDashboard); | ||||||
|  |   const findPipelinesDashboardClickhouse = () => | ||||||
|  |     wrapper.findComponent(PipelinesDashboardClickhouse); | ||||||
|  | 
 | ||||||
|  |   describe('when showing only pipelines dashboard', () => { | ||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
|       createComponent(); |       createWrapper(); | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     describe.each` |  | ||||||
|       title                        | finderFn                          | index |  | ||||||
|       ${'Pipelines'}               | ${findPipelineCharts}             | ${0} |  | ||||||
|       ${'Deployment frequency'}    | ${findDeploymentFrequencyCharts}  | ${1} |  | ||||||
|       ${'Lead time'}               | ${findLeadTimeCharts}             | ${2} |  | ||||||
|       ${'Time to restore service'} | ${findTimeToRestoreServiceCharts} | ${3} |  | ||||||
|       ${'Change failure rate'}     | ${findChangeFailureRateCharts}    | ${4} |  | ||||||
|       ${'Project quality'}         | ${findProjectQualitySummary}      | ${5} |  | ||||||
|     `('Tabs', ({ title, finderFn, index }) => {
 |  | ||||||
|       it(`renders tab with a title ${title} at index ${index}`, () => { |  | ||||||
|         expect(findGlTabAtIndex(index).attributes('title')).toBe(title); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it(`renders the ${title} chart`, () => { |  | ||||||
|         expect(finderFn().exists()).toBe(true); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it(`updates the current tab and url when the ${title} tab is clicked`, async () => { |  | ||||||
|         let chartsPath; |  | ||||||
|         const tabName = title.toLowerCase().replace(/\s/g, '-'); |  | ||||||
| 
 |  | ||||||
|         setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`); |  | ||||||
| 
 |  | ||||||
|         mergeUrlParams.mockImplementation(({ chart }, path) => { |  | ||||||
|           expect(chart).toBe(tabName); |  | ||||||
|           expect(path).toBe(window.location.pathname); |  | ||||||
|           chartsPath = `${path}?chart=${chart}`; |  | ||||||
|           return chartsPath; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         updateHistory.mockImplementation(({ url }) => { |  | ||||||
|           expect(url).toBe(chartsPath); |  | ||||||
|         }); |  | ||||||
|         const tabs = findGlTabs(); |  | ||||||
| 
 |  | ||||||
|         expect(tabs.attributes('value')).toBe('0'); |  | ||||||
| 
 |  | ||||||
|         tabs.vm.$emit('input', index); |  | ||||||
| 
 |  | ||||||
|         await nextTick(); |  | ||||||
| 
 |  | ||||||
|         expect(tabs.attributes('value')).toBe(index.toString()); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('should not try to push history if the tab does not change', async () => { |  | ||||||
|       setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`); |  | ||||||
| 
 |  | ||||||
|       mergeUrlParams.mockImplementation(({ chart }, path) => `${path}?chart=${chart}`); |  | ||||||
| 
 |  | ||||||
|       const tabs = findGlTabs(); |  | ||||||
| 
 |  | ||||||
|       expect(tabs.attributes('value')).toBe('0'); |  | ||||||
| 
 |  | ||||||
|       tabs.vm.$emit('input', 0); |  | ||||||
| 
 |  | ||||||
|       await nextTick(); |  | ||||||
| 
 |  | ||||||
|       expect(updateHistory).not.toHaveBeenCalled(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     describe('event tracking', () => { |  | ||||||
|       describe('Internal Events RedisHLL events', () => { |  | ||||||
|         it.each` |  | ||||||
|           testId                           | event |  | ||||||
|           ${'pipelines-tab'}               | ${'p_analytics_ci_cd_pipelines'} |  | ||||||
|           ${'deployment-frequency-tab'}    | ${'p_analytics_ci_cd_deployment_frequency'} |  | ||||||
|           ${'lead-time-tab'}               | ${'p_analytics_ci_cd_lead_time'} |  | ||||||
|           ${'time-to-restore-service-tab'} | ${'visit_ci_cd_time_to_restore_service_tab'} |  | ||||||
|           ${'change-failure-rate-tab'}     | ${'visit_ci_cd_failure_rate_tab'} |  | ||||||
|         `('tracks the $event event when clicked', ({ testId, event }) => {
 |  | ||||||
|           const trackApiSpy = jest.spyOn(API, 'trackInternalEvent'); |  | ||||||
| 
 |  | ||||||
|           expect(trackApiSpy).not.toHaveBeenCalled(); |  | ||||||
| 
 |  | ||||||
|           wrapper.findByTestId(testId).vm.$emit('click'); |  | ||||||
| 
 |  | ||||||
|           expect(trackApiSpy).toHaveBeenCalledWith(event, {}); |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       describe('Snowplow events', () => { |  | ||||||
|         it.each` |  | ||||||
|           testId                        | event |  | ||||||
|           ${'pipelines-tab'}            | ${'p_analytics_ci_cd_pipelines'} |  | ||||||
|           ${'deployment-frequency-tab'} | ${'p_analytics_ci_cd_deployment_frequency'} |  | ||||||
|           ${'lead-time-tab'}            | ${'p_analytics_ci_cd_lead_time'} |  | ||||||
|         `('tracks the $event event when clicked', ({ testId, event }) => {
 |  | ||||||
|           const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); |  | ||||||
| 
 |  | ||||||
|           wrapper.findByTestId(testId).vm.$emit('click'); |  | ||||||
| 
 |  | ||||||
|           expect(trackingSpy).toHaveBeenCalledWith(undefined, event, { |  | ||||||
|             context: { |  | ||||||
|               schema: SNOWPLOW_SCHEMA, |  | ||||||
|               data: { |  | ||||||
|                 event_name: event, |  | ||||||
|                 data_source: SNOWPLOW_DATA_SOURCE, |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('when provided with a query param', () => { |  | ||||||
|     it.each` |  | ||||||
|       chart                        | tab |  | ||||||
|       ${'change-failure-rate'}     | ${'4'} |  | ||||||
|       ${'time-to-restore-service'} | ${'3'} |  | ||||||
|       ${'lead-time'}               | ${'2'} |  | ||||||
|       ${'deployment-frequency'}    | ${'1'} |  | ||||||
|       ${'pipelines'}               | ${'0'} |  | ||||||
|       ${'fake'}                    | ${'0'} |  | ||||||
|       ${''}                        | ${'0'} |  | ||||||
|     `('shows the correct tab for URL parameter "$chart"', ({ chart, tab }) => {
 |  | ||||||
|       setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts?chart=${chart}`); |  | ||||||
|       getParameterValues.mockImplementation((name) => { |  | ||||||
|         expect(name).toBe('chart'); |  | ||||||
|         return chart ? [chart] : []; |  | ||||||
|       }); |  | ||||||
|       createComponent(); |  | ||||||
|       expect(findGlTabs().attributes('value')).toBe(tab); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('should set the tab when the back button is clicked', async () => { |  | ||||||
|       let popstateHandler; |  | ||||||
| 
 |  | ||||||
|       window.addEventListener = jest.fn(); |  | ||||||
| 
 |  | ||||||
|       window.addEventListener.mockImplementation((event, handler) => { |  | ||||||
|         if (event === 'popstate') { |  | ||||||
|           popstateHandler = handler; |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       getParameterValues.mockImplementation((name) => { |  | ||||||
|         expect(name).toBe('chart'); |  | ||||||
|         return []; |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       createComponent(); |  | ||||||
| 
 |  | ||||||
|       expect(findGlTabs().attributes('value')).toBe('0'); |  | ||||||
| 
 |  | ||||||
|       getParameterValues.mockImplementationOnce((name) => { |  | ||||||
|         expect(name).toBe('chart'); |  | ||||||
|         return ['deployment-frequency']; |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       popstateHandler(); |  | ||||||
| 
 |  | ||||||
|       await nextTick(); |  | ||||||
| 
 |  | ||||||
|       expect(findGlTabs().attributes('value')).toBe('1'); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('when the dora charts are not available and project quality summary is not available', () => { |  | ||||||
|     beforeEach(() => { |  | ||||||
|       createComponent({ |  | ||||||
|         provide: { shouldRenderDoraCharts: false, shouldRenderQualitySummary: false }, |  | ||||||
|       }); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('does not render tabs', () => { |     it('does not render tabs', () => { | ||||||
|  |       // tabs are only shown in EE
 | ||||||
|       expect(findGlTabs().exists()).toBe(false); |       expect(findGlTabs().exists()).toBe(false); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('renders the pipeline charts', () => { |     it('shows pipelines dashboard', () => { | ||||||
|       expect(findPipelineCharts().exists()).toBe(true); |       expect(wrapper.findComponent(PipelinesDashboard).exists()).toBe(true); | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('when the project quality summary is not available', () => { |  | ||||||
|     beforeEach(() => { |  | ||||||
|       createComponent({ provide: { shouldRenderQualitySummary: false } }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('does not render the tab', () => { |  | ||||||
|       expect(findProjectQualitySummary().exists()).toBe(false); |  | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('ci_improved_project_pipeline_analytics feature flag', () => { |   describe('ci_improved_project_pipeline_analytics feature flag', () => { | ||||||
|     describe.each` |     describe.each` | ||||||
|       status   | finderFn |       status   | finderFn | ||||||
|       ${false} | ${findPipelineCharts} |       ${false} | ${findPipelinesDashboard} | ||||||
|       ${true}  | ${findPipelineChartsNew} |       ${true}  | ${findPipelinesDashboardClickhouse} | ||||||
|     `('when flag is $status', ({ status, finderFn }) => {
 |     `('when flag is $status', ({ status, finderFn }) => {
 | ||||||
|       it('renders component', () => { |       it('renders component', () => { | ||||||
|         createComponent({ |         createWrapper({ | ||||||
|           provide: { |           provide: { | ||||||
|             glFeatures: { |             glFeatures: { | ||||||
|               ciImprovedProjectPipelineAnalytics: status, |               ciImprovedProjectPipelineAnalytics: status, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import Vue from 'vue'; | ||||||
| import VueApollo from 'vue-apollo'; | import VueApollo from 'vue-apollo'; | ||||||
| import createMockApollo from 'helpers/mock_apollo_helper'; | import createMockApollo from 'helpers/mock_apollo_helper'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import PipelineChartsNew from '~/projects/pipelines/charts/components/pipeline_charts_new.vue'; | import PipelinesDashboardClickhouse from '~/projects/pipelines/charts/components/pipelines_dashboard_clickhouse.vue'; | ||||||
| import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; | import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; | ||||||
| import PipelineDurationChart from '~/projects/pipelines/charts/components/pipeline_duration_chart.vue'; | import PipelineDurationChart from '~/projects/pipelines/charts/components/pipeline_duration_chart.vue'; | ||||||
| import PipelineStatusChart from '~/projects/pipelines/charts/components/pipeline_status_chart.vue'; | import PipelineStatusChart from '~/projects/pipelines/charts/components/pipeline_status_chart.vue'; | ||||||
|  | @ -19,7 +19,7 @@ jest.mock('~/alert'); | ||||||
| 
 | 
 | ||||||
| const projectPath = 'gitlab-org/gitlab'; | const projectPath = 'gitlab-org/gitlab'; | ||||||
| 
 | 
 | ||||||
| describe('~/projects/pipelines/charts/components/pipeline_charts_new.vue', () => { | describe('PipelinesDashboardClickhouse', () => { | ||||||
|   useFakeDate('2022-02-15T08:30'); // a date with a time
 |   useFakeDate('2022-02-15T08:30'); // a date with a time
 | ||||||
| 
 | 
 | ||||||
|   let wrapper; |   let wrapper; | ||||||
|  | @ -32,7 +32,7 @@ describe('~/projects/pipelines/charts/components/pipeline_charts_new.vue', () => | ||||||
|   const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat); |   const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat); | ||||||
| 
 | 
 | ||||||
|   const createComponent = ({ mountFn = shallowMount } = {}) => { |   const createComponent = ({ mountFn = shallowMount } = {}) => { | ||||||
|     wrapper = mountFn(PipelineChartsNew, { |     wrapper = mountFn(PipelinesDashboardClickhouse, { | ||||||
|       provide: { |       provide: { | ||||||
|         projectPath, |         projectPath, | ||||||
|       }, |       }, | ||||||
|  | @ -4,7 +4,7 @@ import Vue from 'vue'; | ||||||
| import VueApollo from 'vue-apollo'; | import VueApollo from 'vue-apollo'; | ||||||
| import createMockApollo from 'helpers/mock_apollo_helper'; | import createMockApollo from 'helpers/mock_apollo_helper'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue'; | import PipelinesDashboard from '~/projects/pipelines/charts/components/pipelines_dashboard.vue'; | ||||||
| import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; | import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; | ||||||
| import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql'; | import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql'; | ||||||
| import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql'; | import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql'; | ||||||
|  | @ -14,7 +14,7 @@ import { mockPipelineCount, mockPipelineStatistics } from '../mock_data'; | ||||||
| const projectPath = 'gitlab-org/gitlab'; | const projectPath = 'gitlab-org/gitlab'; | ||||||
| Vue.use(VueApollo); | Vue.use(VueApollo); | ||||||
| 
 | 
 | ||||||
| describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => { | describe('PipelinesDashboard', () => { | ||||||
|   let wrapper; |   let wrapper; | ||||||
| 
 | 
 | ||||||
|   function createMockApolloProvider() { |   function createMockApolloProvider() { | ||||||
|  | @ -27,7 +27,7 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   beforeEach(async () => { |   beforeEach(async () => { | ||||||
|     wrapper = shallowMount(PipelineCharts, { |     wrapper = shallowMount(PipelinesDashboard, { | ||||||
|       provide: { |       provide: { | ||||||
|         projectPath, |         projectPath, | ||||||
|       }, |       }, | ||||||
|  | @ -61,6 +61,7 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do | ||||||
|       'ci_pipeline_messages.project_id', |       'ci_pipeline_messages.project_id', | ||||||
|       # LFK already present on ci_pipeline_schedules and cascade delete all ci resources. |       # LFK already present on ci_pipeline_schedules and cascade delete all ci resources. | ||||||
|       'ci_pipeline_schedule_variables.project_id', |       'ci_pipeline_schedule_variables.project_id', | ||||||
|  |       'ci_build_trace_chunks.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources | ||||||
|       'p_ci_job_annotations.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources |       'p_ci_job_annotations.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources | ||||||
|       'ci_builds_runner_session.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources |       'ci_builds_runner_session.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources | ||||||
|       'p_ci_pipelines_config.project_id', # LFK already present on p_ci_pipelines and cascade delete all ci resources |       'p_ci_pipelines_config.project_id', # LFK already present on p_ci_pipelines and cascade delete all ci resources | ||||||
|  |  | ||||||
|  | @ -569,6 +569,8 @@ create_access_levels: | ||||||
| - protected_tag | - protected_tag | ||||||
| - group | - group | ||||||
| - deploy_key | - deploy_key | ||||||
|  | squash_option: | ||||||
|  | - protected_branch | ||||||
| container_repositories: | container_repositories: | ||||||
| - project | - project | ||||||
| - name | - name | ||||||
|  |  | ||||||
|  | @ -47,6 +47,7 @@ RSpec.describe 'Test coverage of the Project Import', feature_category: :importe | ||||||
|       project.ci_pipelines.notes.events |       project.ci_pipelines.notes.events | ||||||
|       project.ci_pipelines.notes.events.push_event_payload |       project.ci_pipelines.notes.events.push_event_payload | ||||||
|       project.protected_branches.unprotect_access_levels |       project.protected_branches.unprotect_access_levels | ||||||
|  |       project.protected_branches.squash_option | ||||||
|       project.boards.lists.label.priorities |       project.boards.lists.label.priorities | ||||||
|       project.service_desk_setting |       project.service_desk_setting | ||||||
|       project.security_setting |       project.security_setting | ||||||
|  |  | ||||||
|  | @ -1129,3 +1129,6 @@ Vulnerabilities::Identifier: | ||||||
|   - url |   - url | ||||||
| Vulnerabilities::Read: | Vulnerabilities::Read: | ||||||
|   - project_id |   - project_id | ||||||
|  | Projects::BranchRules::SquashOption: | ||||||
|  |   - squash_option | ||||||
|  |   - project_id | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue