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() {
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
key: 'pipelines',
|
||||||
|
event: 'p_analytics_ci_cd_pipelines',
|
||||||
|
title: __('Pipelines'),
|
||||||
|
componentIs: this.glFeatures?.ciImprovedProjectPipelineAnalytics
|
||||||
|
? PipelinesDashboardClickhouse
|
||||||
|
: PipelinesDashboard,
|
||||||
|
lazy: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.shouldRenderDoraCharts) {
|
||||||
|
tabs.push(
|
||||||
|
{
|
||||||
|
key: 'deployment-frequency',
|
||||||
|
event: 'p_analytics_ci_cd_deployment_frequency',
|
||||||
|
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) {
|
||||||
|
tabs.push({
|
||||||
|
key: 'project-quality',
|
||||||
|
title: s__('QualitySummary|Project quality'),
|
||||||
|
componentIs: () => import('ee_component/project_quality_summary/app.vue'),
|
||||||
|
lazy: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedTab: 0,
|
activeTabIndex: 0,
|
||||||
|
tabs,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
charts() {
|
|
||||||
const chartsToShow = ['pipelines'];
|
|
||||||
|
|
||||||
if (this.shouldRenderDoraCharts) {
|
|
||||||
chartsToShow.push(
|
|
||||||
'deployment-frequency',
|
|
||||||
'lead-time',
|
|
||||||
'time-to-restore-service',
|
|
||||||
'change-failure-rate',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.shouldRenderQualitySummary) {
|
|
||||||
chartsToShow.push('project-quality');
|
|
||||||
}
|
|
||||||
|
|
||||||
return chartsToShow;
|
|
||||||
},
|
|
||||||
pipelineChartsComponent() {
|
|
||||||
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,129 +83,117 @@ 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_it_be(:container_protection_tag_rule) do
|
let(:mutation) { get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path) }
|
||||||
create(:container_registry_protection_tag_rule,
|
|
||||||
project: project,
|
context 'when there are no errors' do
|
||||||
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,
|
let_it_be(:container_protection_tag_rule) do
|
||||||
minimum_access_level_for_delete: Gitlab::Access::OWNER
|
create(:container_registry_protection_tag_rule,
|
||||||
)
|
project: project,
|
||||||
|
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,
|
||||||
|
minimum_access_level_for_delete: Gitlab::Access::OWNER
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "graphql/#{delete_container_protection_tag_rule_mutation_path}.json" do
|
||||||
|
post_graphql(
|
||||||
|
mutation,
|
||||||
|
current_user: user,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect_graphql_errors_to_be_empty
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "graphql/#{delete_container_protection_tag_rule_mutation_path}.json" do
|
context 'when there are errors' do
|
||||||
mutation = get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path)
|
it "graphql/#{delete_container_protection_tag_rule_mutation_path}.errors.json" do
|
||||||
|
post_graphql(
|
||||||
post_graphql(
|
mutation,
|
||||||
mutation,
|
current_user: user,
|
||||||
current_user: owner,
|
variables: {
|
||||||
variables: {
|
input: {
|
||||||
input: {
|
id: 'gid://gitlab/ContainerRegistry::Protection::TagRule/non-existent'
|
||||||
id: "gid://gitlab/ContainerRegistry::Protection::TagRule/#{container_protection_tag_rule.id}"
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
expect_graphql_errors_to_be_empty
|
expect_graphql_errors_to_include(
|
||||||
|
"The resource that you are attempting to access does not exist or " \
|
||||||
|
"you don't have permission to perform this action"
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when there are errors deleting a rule' do
|
describe 'creating a rule' do
|
||||||
it "graphql/#{delete_container_protection_tag_rule_mutation_path}.errors.json" do
|
let(:mutation) { get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path) }
|
||||||
mutation = get_graphql_query_as_string(delete_container_protection_tag_rule_mutation_path)
|
|
||||||
|
|
||||||
post_graphql(
|
let(:variables) do
|
||||||
mutation,
|
{
|
||||||
current_user: owner,
|
input: {
|
||||||
variables: {
|
projectPath: project.full_path,
|
||||||
input: {
|
tagNamePattern: 'v.*',
|
||||||
id: 'gid://gitlab/ContainerRegistry::Protection::TagRule/non-existent'
|
minimumAccessLevelForPush: 'MAINTAINER',
|
||||||
}
|
minimumAccessLevelForDelete: 'OWNER'
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
expect_graphql_errors_to_include(
|
|
||||||
"The resource that you are attempting to access does not exist or " \
|
|
||||||
"you don't have permission to perform this action"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are no errors creating a rule' do
|
|
||||||
it "graphql/#{create_container_protection_tag_rule_mutation_path}.json" do
|
|
||||||
mutation = get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path)
|
|
||||||
|
|
||||||
post_graphql(
|
|
||||||
mutation,
|
|
||||||
current_user: owner,
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
projectPath: project.full_path,
|
|
||||||
tagNamePattern: 'v.*',
|
|
||||||
minimumAccessLevelForPush: 'MAINTAINER',
|
|
||||||
minimumAccessLevelForDelete: 'OWNER'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect_graphql_errors_to_be_empty
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are field errors creating a rule' 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)
|
|
||||||
|
|
||||||
post_graphql(
|
|
||||||
mutation,
|
|
||||||
current_user: owner,
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
project_path: project.full_path,
|
|
||||||
tagNamePattern: '',
|
|
||||||
minimumAccessLevelForPush: 'MAINTAINER',
|
|
||||||
minimumAccessLevelForDelete: 'OWNER'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect_graphql_errors_to_include(
|
|
||||||
"tagNamePattern can't be blank"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there are errors creating a rule' do
|
|
||||||
before do
|
|
||||||
create(:container_registry_protection_tag_rule, project: project,
|
|
||||||
tag_name_pattern: "v.*")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "graphql/#{create_container_protection_tag_rule_mutation_path}.errors.json" do
|
context 'when there are no errors' do
|
||||||
mutation = get_graphql_query_as_string(create_container_protection_tag_rule_mutation_path)
|
it "graphql/#{create_container_protection_tag_rule_mutation_path}.json" do
|
||||||
|
post_graphql(
|
||||||
|
mutation,
|
||||||
|
current_user: user,
|
||||||
|
variables: variables
|
||||||
|
)
|
||||||
|
|
||||||
post_graphql(
|
expect_graphql_errors_to_be_empty
|
||||||
mutation,
|
end
|
||||||
current_user: owner,
|
end
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
project_path: project.full_path,
|
|
||||||
tagNamePattern: 'v.*',
|
|
||||||
minimumAccessLevelForPush: 'MAINTAINER',
|
|
||||||
minimumAccessLevelForDelete: 'OWNER'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(graphql_data_at('createContainerProtectionTagRule', 'errors'))
|
context 'when there are field errors' do
|
||||||
.to include('Tag name pattern has already been taken')
|
it "graphql/#{create_container_protection_tag_rule_mutation_path}.server_errors.json" do
|
||||||
|
variables[:input][:tagNamePattern] = ''
|
||||||
|
|
||||||
|
post_graphql(
|
||||||
|
mutation,
|
||||||
|
current_user: user,
|
||||||
|
variables: variables
|
||||||
|
)
|
||||||
|
|
||||||
|
expect_graphql_errors_to_include(
|
||||||
|
"tagNamePattern can't be blank"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are errors' do
|
||||||
|
before do
|
||||||
|
create(:container_registry_protection_tag_rule, project: project,
|
||||||
|
tag_name_pattern: "v.*")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "graphql/#{create_container_protection_tag_rule_mutation_path}.errors.json" do
|
||||||
|
post_graphql(
|
||||||
|
mutation,
|
||||||
|
current_user: user,
|
||||||
|
variables: variables
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(graphql_data_at('createContainerProtectionTagRule', 'errors'))
|
||||||
|
.to include('Tag name pattern has already been taken')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -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,
|
provide: {
|
||||||
merge(
|
...provide,
|
||||||
{},
|
},
|
||||||
{
|
...options,
|
||||||
provide: {
|
});
|
||||||
shouldRenderDoraCharts: true,
|
};
|
||||||
shouldRenderQualitySummary: true,
|
|
||||||
},
|
|
||||||
stubs: {
|
|
||||||
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