Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.markdownlint.yml1
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml25
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue4
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_overview.vue20
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_pods.vue24
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_status_bar.vue39
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_summary.vue19
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_tabs.vue7
-rw-r--r--app/assets/javascripts/environments/constants.js19
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue165
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue17
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql5
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql6
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/index.js11
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_details_header.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue27
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue69
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue34
-rw-r--r--app/assets/javascripts/whats_new/components/app.vue5
-rw-r--r--app/assets/stylesheets/components/whats_new.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb13
-rw-r--r--app/graphql/types/audit_events/definition_type.rb50
-rw-r--r--app/graphql/types/query_type.rb6
-rw-r--r--app/helpers/ci/catalog/resources_helper.rb4
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/policies/audit_events/definition_policy.rb11
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml5
-rw-r--r--config/feature_flags/development/super_sidebar_nav.yml8
-rw-r--r--config/initializers/00_deprecations.rb2
-rw-r--r--doc/.vale/gitlab/SubstitutionWarning.yml2
-rw-r--r--doc/.vale/gitlab/Substitutions.yml2
-rw-r--r--doc/api/graphql/reference/index.md131
-rw-r--r--doc/ci/components/index.md19
-rw-r--r--doc/ci/runners/saas/linux_saas_runner.md2
-rw-r--r--doc/development/ai_features.md4
-rw-r--r--doc/development/code_review.md33
-rw-r--r--doc/development/navigation_sidebar.md5
-rw-r--r--doc/development/pipelines/index.md44
-rw-r--r--doc/security/token_overview.md2
-rw-r--r--doc/topics/gitlab_flow.md22
-rw-r--r--doc/user/admin_area/license_file.md4
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md2
-rw-r--r--doc/user/admin_area/settings/external_authorization.md6
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md4
-rw-r--r--doc/user/clusters/agent/ci_cd_workflow.md2
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--doc/user/profile/account/two_factor_authentication.md22
-rw-r--r--doc/user/profile/user_passwords.md15
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md8
-rw-r--r--doc/user/project/service_desk.md11
-rw-r--r--lib/gitlab/audit/type/definition.rb4
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml36
-rw-r--r--locale/gitlab.pot51
-rw-r--r--qa/lib/gitlab/page/group/settings/billing.rb9
-rw-r--r--qa/lib/gitlab/page/group/settings/usage_quotas.rb28
-rw-r--r--qa/qa/page/merge_request/show.rb2
-rw-r--r--spec/features/commit_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb52
-rw-r--r--spec/features/nav/new_nav_toggle_spec.rb82
-rw-r--r--spec/features/projects/compare_spec.rb1
-rw-r--r--spec/features/projects/new_project_spec.rb1
-rw-r--r--spec/frontend/environments/kubernetes_overview_spec.js45
-rw-r--r--spec/frontend/environments/kubernetes_pods_spec.js15
-rw-r--r--spec/frontend/environments/kubernetes_status_bar_spec.js42
-rw-r--r--spec/frontend/environments/kubernetes_summary_spec.js12
-rw-r--r--spec/frontend/environments/kubernetes_tabs_spec.js19
-rw-r--r--spec/frontend/fixtures/startup_css.rb3
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js147
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/mock_data.js7
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js16
-rw-r--r--spec/frontend/pipelines/pipeline_details_header_spec.js14
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js59
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js17
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js151
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_mock_data.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/mock_data.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js1
-rw-r--r--spec/graphql/resolvers/audit_events/audit_event_definitions_resolver_spec.rb22
-rw-r--r--spec/graphql/types/audit_events/definition_type_spec.rb15
-rw-r--r--spec/helpers/ci/catalog/resources_helper_spec.rb12
-rw-r--r--spec/helpers/nav_helper_spec.rb53
-rw-r--r--spec/helpers/projects_helper_spec.rb6
-rw-r--r--spec/requests/api/graphql/audit_events/definitions_spec.rb24
-rw-r--r--spec/support/capybara.rb6
-rw-r--r--spec/support/helpers/login_helpers.rb10
-rw-r--r--spec/support/shared_contexts/graphql/types/query_type_shared_context.rb3
-rw-r--r--spec/support/stub_dot_com_check.rb2
92 files changed, 1539 insertions, 389 deletions
diff --git a/.markdownlint.yml b/.markdownlint.yml
index e1143511b64..6e6cd235092 100644
--- a/.markdownlint.yml
+++ b/.markdownlint.yml
@@ -103,7 +103,6 @@ proper-names:
"OAuth",
"OAuth 2",
"OmniAuth",
- "Omnibus GitLab",
"OpenID",
"OpenShift",
"PgBouncer",
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index bb5327cc9ce..c2fe47363ad 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1065,7 +1065,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/features/groups/settings/protected_environments_spec.rb'
- 'ee/spec/features/groups/usage_quotas/pipelines_tab_spec.rb'
- 'ee/spec/features/markdown/metrics_spec.rb'
- - 'ee/spec/features/merge_request/user_merges_with_namespace_storage_limits_spec.rb'
- 'ee/spec/features/merge_request/user_sees_approval_widget_spec.rb'
- 'ee/spec/features/namespace_user_cap_reached_alert_spec.rb'
- 'ee/spec/features/projects/environments/environment_spec.rb'
@@ -1665,30 +1664,6 @@ Layout/ArgumentAlignment:
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/user_registration_billing_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/saas_user_limit_experience_spec.rb'
- 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/utilization/free_namespace_storage_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/database_delete_replication_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_ci_job_log_artifacts_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_maven_package_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_npm_registry_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/geo_replication_project_snippets_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/http_push_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/http_push_to_secondary_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/ssh_push_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/ssh_push_to_secondary_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_http_push_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_http_push_to_secondary_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/12_systems/geo/wiki_ssh_push_to_secondary_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/13_secure/merge_request_license_widget_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/15_growth/free_trial_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_enforced_sso_git_access_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_non_enforced_sso_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/restrict_by_ip_address_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/group_wiki/delete_group_wiki_page_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/4_verify/job_trace_archival_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/5_package/dependency_proxy_sso_spec.rb'
- 'qa/qa/vendor/jira/jira_api.rb'
- 'qa/spec/support/loglinking_spec.rb'
- 'rubocop/cop/gitlab/finder_with_find_by.rb'
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 4cc60df0ecb..b9bfceee6b4 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -170,7 +170,7 @@ export default {
>
<div class="gl-pb-3 position-relative tree-list-search d-flex">
<div class="flex-fill d-flex">
- <gl-icon name="search" class="gl-absolute gl-top-5 tree-list-icon" />
+ <gl-icon name="search" class="gl-absolute gl-top-3 gl-left-3 tree-list-icon" />
<label for="diff-tree-search" class="sr-only">{{ $options.searchPlaceholder }}</label>
<input
id="diff-tree-search"
@@ -189,7 +189,7 @@ export default {
class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
@click="clearSearch"
>
- <gl-icon name="close" />
+ <gl-icon name="close" class="gl-absolute gl-top-3 gl-right-1 tree-list-icon" />
</button>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index a849adfc755..b9e6548f640 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -6,6 +6,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import KubernetesAgentInfo from './kubernetes_agent_info.vue';
import KubernetesPods from './kubernetes_pods.vue';
import KubernetesTabs from './kubernetes_tabs.vue';
+import KubernetesStatusBar from './kubernetes_status_bar.vue';
export default {
components: {
@@ -15,6 +16,7 @@ export default {
KubernetesAgentInfo,
KubernetesPods,
KubernetesTabs,
+ KubernetesStatusBar,
},
inject: ['kasTunnelUrl'],
props: {
@@ -32,6 +34,9 @@ export default {
return {
isVisible: false,
error: '',
+ hasFailedState: false,
+ podsLoading: false,
+ workloadTypesLoading: false,
};
},
computed: {
@@ -52,6 +57,14 @@ export default {
},
};
},
+ clusterHealthStatus() {
+ const clusterDataLoading = this.podsLoading || this.workloadTypesLoading;
+ if (clusterDataLoading) {
+ return '';
+ }
+
+ return this.hasFailedState ? 'error' : 'success';
+ },
},
methods: {
toggleCollapse() {
@@ -82,6 +95,7 @@ export default {
</p>
<gl-collapse :visible="isVisible" class="gl-md-pl-7 gl-md-pr-5 gl-mt-4">
<template v-if="isVisible">
+ <kubernetes-status-bar :cluster-health-status="clusterHealthStatus" class="gl-mb-4" />
<kubernetes-agent-info :cluster-agent="clusterAgent" class="gl-mb-5" />
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5">
@@ -92,12 +106,16 @@ export default {
:configuration="k8sAccessConfiguration"
:namespace="namespace"
class="gl-mb-5"
- @cluster-error="onClusterError" />
+ @cluster-error="onClusterError"
+ @loading="podsLoading = $event"
+ @failed="hasFailedState = true" />
<kubernetes-tabs
:configuration="k8sAccessConfiguration"
:namespace="namespace"
class="gl-mb-5"
@cluster-error="onClusterError"
+ @loading="workloadTypesLoading = $event"
+ @failed="hasFailedState = true"
/></template>
</gl-collapse>
</div>
diff --git a/app/assets/javascripts/environments/components/kubernetes_pods.vue b/app/assets/javascripts/environments/components/kubernetes_pods.vue
index a153331ee58..aded3a4d0c4 100644
--- a/app/assets/javascripts/environments/components/kubernetes_pods.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_pods.vue
@@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { s__ } from '~/locale';
import k8sPodsQuery from '../graphql/queries/k8s_pods.query.graphql';
+import { PHASE_RUNNING, PHASE_PENDING, PHASE_SUCCEEDED, PHASE_FAILED } from '../constants';
export default {
components: {
@@ -25,6 +26,9 @@ export default {
this.error = error;
this.$emit('cluster-error', this.error);
},
+ watchLoading(isLoading) {
+ this.$emit('loading', isLoading);
+ },
},
},
props: {
@@ -42,41 +46,39 @@ export default {
error: '',
};
},
-
computed: {
podStats() {
if (!this.k8sPods) return null;
return [
{
- // eslint-disable-next-line @gitlab/require-i18n-strings
- value: this.getPodsByPhase('Running'),
+ value: this.countPodsByPhase(PHASE_RUNNING),
title: this.$options.i18n.runningPods,
},
{
- // eslint-disable-next-line @gitlab/require-i18n-strings
- value: this.getPodsByPhase('Pending'),
+ value: this.countPodsByPhase(PHASE_PENDING),
title: this.$options.i18n.pendingPods,
},
{
- // eslint-disable-next-line @gitlab/require-i18n-strings
- value: this.getPodsByPhase('Succeeded'),
+ value: this.countPodsByPhase(PHASE_SUCCEEDED),
title: this.$options.i18n.succeededPods,
},
{
- // eslint-disable-next-line @gitlab/require-i18n-strings
- value: this.getPodsByPhase('Failed'),
+ value: this.countPodsByPhase(PHASE_FAILED),
title: this.$options.i18n.failedPods,
},
];
},
loading() {
- return this.$apollo.queries.k8sPods.loading;
+ return this.$apollo?.queries?.k8sPods?.loading;
},
},
methods: {
- getPodsByPhase(phase) {
+ countPodsByPhase(phase) {
const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase);
+ if (phase === PHASE_FAILED && filteredPods.length) {
+ this.$emit('failed');
+ }
return filteredPods.length;
},
},
diff --git a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
new file mode 100644
index 00000000000..94cd7438e46
--- /dev/null
+++ b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
@@ -0,0 +1,39 @@
+<script>
+import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { HEALTH_BADGES } from '../constants';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlBadge,
+ },
+ props: {
+ clusterHealthStatus: {
+ required: false,
+ type: String,
+ default: '',
+ validator(val) {
+ return ['error', 'success', ''].includes(val);
+ },
+ },
+ },
+ computed: {
+ healthBadge() {
+ return HEALTH_BADGES[this.clusterHealthStatus];
+ },
+ },
+ i18n: {
+ healthLabel: s__('Environment|Environment health'),
+ },
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-mb-2">
+ <span class="gl-font-sm gl-font-monospace gl-mr-3">{{ $options.i18n.healthLabel }}</span>
+ <gl-loading-icon v-if="!clusterHealthStatus" size="sm" inline />
+ <gl-badge v-else-if="healthBadge" :variant="healthBadge.variant">
+ {{ healthBadge.text }}
+ </gl-badge>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/kubernetes_summary.vue b/app/assets/javascripts/environments/components/kubernetes_summary.vue
index 85fc1c1a07d..b00e82809f6 100644
--- a/app/assets/javascripts/environments/components/kubernetes_summary.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_summary.vue
@@ -32,6 +32,12 @@ export default {
error(error) {
this.$emit('cluster-error', error);
},
+ result() {
+ this.checkFailed();
+ },
+ watchLoading(isLoading) {
+ this.$emit('loading', isLoading);
+ },
},
},
props: {
@@ -46,7 +52,7 @@ export default {
},
computed: {
summaryLoading() {
- return this.$apollo.queries.k8sWorkloads.loading;
+ return this.$apollo?.queries?.k8sWorkloads?.loading;
},
summaryCount() {
return this.k8sWorkloads ? Object.values(this.k8sWorkloads).flat().length : 0;
@@ -128,6 +134,17 @@ export default {
};
},
},
+ methods: {
+ checkFailed() {
+ const failed = this.summaryObjects.some((workloadType) => {
+ return workloadType.items?.failed?.length > 0;
+ });
+
+ if (failed) {
+ this.$emit('failed');
+ }
+ },
+ },
i18n: {
summaryTitle: s__('Environment|Summary'),
deployments: s__('Environment|Deployments'),
diff --git a/app/assets/javascripts/environments/components/kubernetes_tabs.vue b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
index b900c23b2b7..4492d209e3b 100644
--- a/app/assets/javascripts/environments/components/kubernetes_tabs.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
@@ -134,7 +134,12 @@ export default {
</script>
<template>
<gl-tabs>
- <kubernetes-summary :namespace="namespace" :configuration="configuration" />
+ <kubernetes-summary
+ :namespace="namespace"
+ :configuration="configuration"
+ @loading="$emit('loading', $event)"
+ @failed="$emit('failed')"
+ />
<gl-tab>
<template #title>
diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js
index 448cee530f6..2b178964c37 100644
--- a/app/assets/javascripts/environments/constants.js
+++ b/app/assets/javascripts/environments/constants.js
@@ -89,3 +89,22 @@ export const ENVIRONMENT_NEW_HELP_TEXT = __(
export const ENVIRONMENT_EDIT_HELP_TEXT = ENVIRONMENT_NEW_HELP_TEXT;
export const SERVICES_LIMIT_PER_PAGE = 10;
+
+export const CLUSTER_STATUS_HEALTHY_TEXT = s__('Environment|Healthy');
+export const CLUSTER_STATUS_UNHEALTHY_TEXT = s__('Environment|Unhealthy');
+
+export const HEALTH_BADGES = {
+ success: {
+ variant: 'success',
+ text: CLUSTER_STATUS_HEALTHY_TEXT,
+ },
+ error: {
+ variant: 'danger',
+ text: CLUSTER_STATUS_UNHEALTHY_TEXT,
+ },
+};
+
+export const PHASE_RUNNING = 'Running';
+export const PHASE_PENDING = 'Pending';
+export const PHASE_SUCCEEDED = 'Succeeded';
+export const PHASE_FAILED = 'Failed';
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue
new file mode 100644
index 00000000000..ed5ba3c2653
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue
@@ -0,0 +1,165 @@
+<script>
+import { GlBadge, GlLink, GlLoadingIcon, GlModal, GlSprintf, GlToggle } from '@gitlab/ui';
+import { createAlert, VARIANT_INFO } from '~/alert';
+import { __, s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+import getCiCatalogSettingsQuery from '../graphql/queries/get_ci_catalog_settings.query.graphql';
+import catalogResourcesCreate from '../graphql/mutations/catalog_resources_create.mutation.graphql';
+
+export const i18n = {
+ badgeText: __('Experiment'),
+ catalogResourceQueryError: s__(
+ 'CiCatalog|There was a problem fetching the CI/CD Catalog setting.',
+ ),
+ catalogResourceMutationError: s__(
+ 'CiCatalog|There was a problem marking the project as a CI/CD Catalog resource.',
+ ),
+ catalogResourceMutationSuccess: s__('CiCatalog|This project is now a CI/CD Catalog resource.'),
+ ciCatalogLabel: s__('CiCatalog|CI/CD Catalog resource'),
+ ciCatalogHelpText: s__(
+ 'CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}',
+ ),
+ modal: {
+ actionPrimary: {
+ text: s__('CiCatalog|Mark project as a CI/CD Catalog resource'),
+ },
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ body: s__(
+ 'CiCatalog|This project will be marked as a CI/CD Catalog resource and will be visible in the CI/CD Catalog. This action is not reversible.',
+ ),
+ title: s__('CiCatalog|Mark project as a CI/CD Catalog resource'),
+ },
+ readMeHelpText: s__(
+ 'CiCatalog|The project must contain a README.md file and a template.yml file. When enabled, the repository is available in the CI/CD Catalog.',
+ ),
+};
+
+export const ciCatalogHelpPath = helpPagePath('ci/components/index', {
+ anchor: 'components-catalog',
+});
+
+export default {
+ i18n,
+ components: {
+ GlBadge,
+ GlLink,
+ GlLoadingIcon,
+ GlModal,
+ GlSprintf,
+ GlToggle,
+ },
+ props: {
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ ciCatalogHelpPath,
+ isCatalogResource: false,
+ showCatalogResourceModal: false,
+ };
+ },
+ apollo: {
+ isCatalogResource: {
+ query: getCiCatalogSettingsQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ };
+ },
+ update({ project }) {
+ return project?.isCatalogResource || false;
+ },
+ error() {
+ createAlert({ message: this.$options.i18n.catalogResourceQueryError });
+ },
+ },
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.isCatalogResource.loading;
+ },
+ },
+ methods: {
+ async markProjectAsCatalogResource() {
+ try {
+ const {
+ data: {
+ catalogResourcesCreate: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: catalogResourcesCreate,
+ variables: { input: { projectPath: this.fullPath } },
+ });
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+
+ this.isCatalogResource = true;
+ createAlert({
+ message: this.$options.i18n.catalogResourceMutationSuccess,
+ variant: VARIANT_INFO,
+ });
+ } catch (error) {
+ const message = error.message || this.$options.i18n.catalogResourceMutationError;
+ createAlert({ message });
+ }
+ },
+ onCatalogResourceEnabledToggled() {
+ this.showCatalogResourceModal = true;
+ },
+ onModalCanceled() {
+ this.showCatalogResourceModal = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="isLoading" />
+ <div v-else data-testid="ci-catalog-settings">
+ <div>
+ <label class="gl-mb-1 gl-mr-2">
+ {{ $options.i18n.ciCatalogLabel }}
+ </label>
+ <gl-badge size="sm" variant="info"> {{ $options.i18n.badgeText }} </gl-badge>
+ </div>
+ <gl-sprintf :message="$options.i18n.ciCatalogHelpText">
+ <template #link="{ content }">
+ <gl-link :href="ciCatalogHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ <gl-toggle
+ class="gl-my-2"
+ :disabled="isCatalogResource"
+ :value="isCatalogResource"
+ :label="$options.i18n.ciCatalogLabel"
+ label-position="hidden"
+ name="ci_resource_enabled"
+ @change="onCatalogResourceEnabledToggled"
+ />
+ <div class="gl-text-secondary">
+ {{ $options.i18n.readMeHelpText }}
+ </div>
+ <gl-modal
+ :visible="showCatalogResourceModal"
+ modal-id="mark-as-catalog-resource"
+ size="sm"
+ :title="$options.i18n.modal.title"
+ :action-cancel="$options.i18n.modal.actionCancel"
+ :action-primary="$options.i18n.modal.actionPrimary"
+ @canceled="onModalCanceled"
+ @primary="markProjectAsCatalogResource"
+ >
+ {{ $options.i18n.modal.body }}
+ </gl-modal>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 84a5d980d39..c54596488af 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -20,6 +20,7 @@ import {
import { toggleHiddenClassBySelector } from '../external';
import ProjectFeatureSetting from './project_feature_setting.vue';
import ProjectSettingRow from './project_setting_row.vue';
+import CiCatalogSettings from './ci_catalog_settings.vue';
const FEATURE_ACCESS_LEVEL_ANONYMOUS = [30, s__('ProjectSettings|Everyone')];
@@ -34,6 +35,7 @@ export default {
...CVE_ID_REQUEST_BUTTON_I18N,
analyticsLabel: s__('ProjectSettings|Analytics'),
containerRegistryLabel: s__('ProjectSettings|Container registry'),
+ ciCdLabel: __('CI/CD'),
forksLabel: s__('ProjectSettings|Forks'),
issuesLabel: s__('ProjectSettings|Issues'),
lfsLabel: s__('ProjectSettings|Git Large File Storage (LFS)'),
@@ -62,7 +64,6 @@ export default {
'ProjectSettings|Track machine learning model experiments and artifacts.',
),
pagesLabel: s__('ProjectSettings|Pages'),
- ciCdLabel: __('CI/CD'),
repositoryLabel: s__('ProjectSettings|Repository'),
requirementsLabel: s__('ProjectSettings|Requirements'),
releasesLabel: s__('ProjectSettings|Releases'),
@@ -84,6 +85,7 @@ export default {
modelExperimentsHelpPath,
components: {
+ CiCatalogSettings,
ProjectFeatureSetting,
ProjectSettingRow,
GlButton,
@@ -106,6 +108,11 @@ export default {
required: false,
default: false,
},
+ canAddCatalogResource: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
currentSettings: {
type: Object,
required: true,
@@ -365,6 +372,9 @@ export default {
packageRegistryApiForEveryoneEnabledShown() {
return this.visibilityLevel !== VISIBILITY_LEVEL_PUBLIC_INTEGER;
},
+ monitorOperationsFeatureAccessLevelOptions() {
+ return this.featureAccessLevelOptions.filter(([value]) => value <= this.monitorAccessLevel);
+ },
},
watch: {
@@ -985,6 +995,11 @@ export default {
/>
</project-setting-row>
</div>
+ <ci-catalog-settings
+ v-if="canAddCatalogResource"
+ class="gl-mb-5"
+ :full-path="confirmationPhrase"
+ />
<project-setting-row v-if="canDisableEmails" ref="email-settings" class="mb-3">
<label class="js-emails-disabled">
<input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" />
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql
new file mode 100644
index 00000000000..c3b73ebf248
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql
@@ -0,0 +1,5 @@
+mutation catalogResourcesCreate($input: CatalogResourcesCreateInput!) {
+ catalogResourcesCreate(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql b/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql
new file mode 100644
index 00000000000..0de06028386
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql
@@ -0,0 +1,6 @@
+query getCiCatalogSettings($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ isCatalogResource
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/index.js b/app/assets/javascripts/pages/projects/shared/permissions/index.js
index de8b1cc400e..4b4589dc46c 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/index.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/index.js
@@ -1,8 +1,17 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
import { parseBoolean } from '~/lib/utils/common_utils';
import settingsPanel from './components/settings_panel.vue';
+Vue.use(VueApollo);
+
export default function initProjectPermissionsSettings() {
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
const mountPoint = document.querySelector('.js-project-permissions-form');
const componentPropsEl = document.querySelector('.js-project-permissions-form-data');
const componentProps = JSON.parse(componentPropsEl.innerHTML);
@@ -19,6 +28,8 @@ export default function initProjectPermissionsSettings() {
return new Vue({
el: mountPoint,
+ name: 'ProjectPermissionsRoot',
+ apolloProvider,
provide: {
additionalInformation,
confirmDangerMessage,
diff --git a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
index 3030a14d1d5..3c10b766311 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
@@ -259,6 +259,9 @@ export default {
commitPath() {
return this.pipeline?.commit?.webPath || '';
},
+ commitTitle() {
+ return this.pipeline?.commit?.title || '';
+ },
totalJobsText() {
return sprintf(__('%{jobs} Jobs'), {
jobs: this.totalJobs,
@@ -371,6 +374,9 @@ export default {
<div v-else class="gl-display-flex gl-justify-content-space-between">
<div>
<h3 v-if="name" class="gl-mt-0 gl-mb-2" data-testid="pipeline-name">{{ name }}</h3>
+ <h3 v-else class="gl-mt-0 gl-mb-2" data-testid="pipeline-commit-title">
+ {{ commitTitle }}
+ </h3>
<div>
<ci-badge-link :status="detailedStatus" />
<div class="gl-ml-2 gl-mb-2 gl-display-inline-block gl-h-6">
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index b7c812162b1..5fd7b8251b2 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -36,38 +36,11 @@ export default {
finishedTime() {
return this.pipeline?.details?.finished_at || this.pipeline?.finishedAt;
},
- showInProgress() {
- return !this.duration && !this.finishedTime && !this.skipped;
- },
- showSkipped() {
- return !this.duration && !this.finishedTime && this.skipped;
- },
- skipped() {
- return this.pipeline?.details?.status?.label === 'skipped';
- },
- stuck() {
- return this.pipeline?.flags?.stuck;
- },
},
};
</script>
<template>
<div class="gl-display-flex gl-flex-direction-column time-ago" :class="fontSize">
- <span
- v-if="showInProgress"
- class="gl-display-inline-flex gl-align-items-center"
- data-testid="pipeline-in-progress"
- >
- <gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
- <gl-icon v-else name="hourglass" class="gl-mr-2" :size="12" data-testid="hourglass-icon" />
- {{ s__('Pipeline|In progress') }}
- </span>
-
- <span v-if="showSkipped" data-testid="pipeline-skipped">
- <gl-icon name="status_skipped_borderless" />
- {{ s__('Pipeline|Skipped') }}
- </span>
-
<p v-if="duration" class="duration gl-display-inline-flex gl-align-items-center">
<gl-icon name="timer" class="gl-mr-2" :size="12" />
{{ durationFormatted }}
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
index a47df2c0197..312d71e6e84 100644
--- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
@@ -35,6 +35,7 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {
commit {
id
shortId
+ title
webPath
}
finishedAt
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue
index bdd46d6a656..a3d5a6bed11 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue
@@ -1,7 +1,6 @@
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
-import { RUNNING } from './constants';
+import { RUNNING, WILL_DEPLOY } from './constants';
export default {
name: 'DeploymentActionButton',
@@ -42,40 +41,50 @@ export default {
},
computed: {
isActionInProgress() {
- return Boolean(this.computedDeploymentStatus === RUNNING || this.actionInProgress);
- },
- actionInProgressTooltip() {
- switch (this.actionInProgress) {
- case this.actionsConfiguration.actionName:
- return this.actionsConfiguration.busyText;
- case null:
- return '';
- default:
- return __('Another action is currently in progress');
- }
+ return Boolean(
+ this.computedDeploymentStatus === RUNNING ||
+ this.computedDeploymentStatus === WILL_DEPLOY ||
+ this.actionInProgress,
+ );
},
isLoading() {
- return this.actionInProgress === this.actionsConfiguration.actionName;
+ return (
+ this.actionInProgress === this.actionsConfiguration.actionName ||
+ this.computedDeploymentStatus === WILL_DEPLOY
+ );
},
},
};
</script>
<template>
- <span v-gl-tooltip :title="actionInProgressTooltip" class="gl-display-inline-block" tabindex="0">
- <gl-button
- v-gl-tooltip
- category="primary"
- size="small"
- :title="buttonTitle"
- :aria-label="buttonTitle"
- :loading="isLoading"
- :disabled="isActionInProgress"
- :class="`inline gl-ml-3 ${containerClasses}`"
- :icon="icon"
- @click="$emit('click')"
- >
- <slot> </slot>
- </gl-button>
- </span>
+ <gl-button
+ v-if="isLoading || isActionInProgress"
+ category="primary"
+ size="small"
+ :title="buttonTitle"
+ :aria-label="buttonTitle"
+ :loading="isLoading"
+ :disabled="isActionInProgress"
+ :class="`inline gl-ml-3 ${containerClasses}`"
+ :icon="icon"
+ @click="$emit('click')"
+ >
+ <slot> </slot>
+ </gl-button>
+ <gl-button
+ v-else
+ v-gl-tooltip.hover
+ category="primary"
+ size="small"
+ :title="buttonTitle"
+ :aria-label="buttonTitle"
+ :loading="isLoading"
+ :disabled="isActionInProgress"
+ :class="`inline gl-ml-3 ${containerClasses}`"
+ :icon="icon"
+ @click="$emit('click')"
+ >
+ <slot> </slot>
+ </gl-button>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue
index 306ed664326..e79d2db4b5a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue
@@ -71,11 +71,25 @@ export default {
return this.deployment.details?.playable_build?.play_path;
},
redeployPath() {
+ if (this.redeployMrWidgetFeatureFlagEnabled) {
+ return this.deployment.retry_url;
+ }
return this.deployment.details?.playable_build?.retry_path;
},
stopUrl() {
return this.deployment.stop_url;
},
+ environmentAvailable() {
+ return Boolean(this.deployment.environment_available);
+ },
+ redeployMrWidgetFeatureFlagEnabled() {
+ return this.glFeatures.reviewAppsRedeployMrWidget;
+ },
+ showDeploymentActionButton() {
+ return (
+ this.redeployPath && !this.environmentAvailable && this.redeployMrWidgetFeatureFlagEnabled
+ );
+ },
},
actionsConfiguration: {
[STOPPING]: {
@@ -124,6 +138,10 @@ export default {
MRWidgetService.executeInlineAction(endpoint)
.then((resp) => {
+ if (this.redeployMrWidgetFeatureFlagEnabled) {
+ return;
+ }
+
const redirectUrl = resp?.data?.redirect_url;
if (redirectUrl) {
visitUrl(redirectUrl);
@@ -167,7 +185,7 @@ export default {
<span>{{ $options.actionsConfiguration[constants.DEPLOYING].buttonText }}</span>
</deployment-action-button>
<deployment-action-button
- v-if="canBeManuallyRedeployed"
+ v-if="canBeManuallyRedeployed && !redeployMrWidgetFeatureFlagEnabled"
:action-in-progress="actionInProgress"
:actions-configuration="$options.actionsConfiguration[constants.REDEPLOYING]"
:computed-deployment-status="computedDeploymentStatus"
@@ -178,12 +196,12 @@ export default {
<span>{{ $options.actionsConfiguration[constants.REDEPLOYING].buttonText }}</span>
</deployment-action-button>
<deployment-view-button
- v-if="hasExternalUrls"
+ v-if="hasExternalUrls && environmentAvailable"
:app-button-text="appButtonText"
:deployment="deployment"
/>
<deployment-action-button
- v-if="stopUrl"
+ v-if="stopUrl && environmentAvailable"
:action-in-progress="actionInProgress"
:computed-deployment-status="computedDeploymentStatus"
:actions-configuration="$options.actionsConfiguration[constants.STOPPING]"
@@ -192,5 +210,15 @@ export default {
container-classes="js-stop-env"
@click="stopEnvironment"
/>
+ <deployment-action-button
+ v-if="showDeploymentActionButton"
+ :action-in-progress="actionInProgress"
+ :computed-deployment-status="computedDeploymentStatus"
+ :actions-configuration="$options.actionsConfiguration[constants.REDEPLOYING]"
+ :button-title="$options.actionsConfiguration[constants.REDEPLOYING].buttonText"
+ :icon="$options.btnIcons.repeat"
+ container-classes="js-redeploy-action"
+ @click="redeploy"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/whats_new/components/app.vue b/app/assets/javascripts/whats_new/components/app.vue
index a439675d467..dd5d4edda59 100644
--- a/app/assets/javascripts/whats_new/components/app.vue
+++ b/app/assets/javascripts/whats_new/components/app.vue
@@ -2,6 +2,7 @@
import { GlDrawer, GlInfiniteScroll, GlResizeObserverDirective } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import Tracking from '~/tracking';
+import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { getDrawerBodyHeight } from '../utils/get_drawer_body_height';
import Feature from './feature.vue';
import SkeletonLoader from './skeleton_loader.vue';
@@ -28,6 +29,9 @@ export default {
},
computed: {
...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight', 'fetching']),
+ getDrawerHeaderHeight() {
+ return getContentWrapperHeight();
+ },
},
mounted() {
this.openDrawer(this.versionDigest);
@@ -69,6 +73,7 @@ export default {
ref="drawer"
v-gl-resize-observer="handleResize"
class="whats-new-drawer gl-reset-line-height"
+ :header-height="getDrawerHeaderHeight"
:z-index="700"
:open="open"
@close="closeDrawer"
diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss
index 35c619a2e2f..f8160c04031 100644
--- a/app/assets/stylesheets/components/whats_new.scss
+++ b/app/assets/stylesheets/components/whats_new.scss
@@ -1,5 +1,4 @@
.whats-new-drawer {
- margin-top: calc(#{$header-height} + #{$calc-application-bars-height});
@include gl-shadow-none;
overflow-y: hidden;
width: 500px;
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index b7d9650ab27..c59bdd70ba8 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -301,10 +301,6 @@ $tabs-holder-z-index: 250;
}
.tree-list-icon {
- top: 50%;
- left: 10px;
- transform: translateY(-50%);
-
&,
svg {
fill: var(--gray-400, $gray-400);
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 0ce52f75913..2f37382da73 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -52,6 +52,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:auto_merge_labels_mr_widget, project)
push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?)
push_frontend_feature_flag(:mr_activity_filters, current_user)
+ push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions]
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 910b894675d..e925e16c004 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -41,6 +41,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:synchronize_fork, @project&.fork_source)
push_frontend_feature_flag(:remove_monitor_metrics, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
+ push_frontend_feature_flag(:ci_namespace_catalog_experimental, @project)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
push_licensed_feature(:security_orchestration_policies) if @project.present? && @project.licensed_feature_available?(:security_orchestration_policies)
push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
diff --git a/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb b/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb
new file mode 100644
index 00000000000..230301ca5da
--- /dev/null
+++ b/app/graphql/resolvers/audit_events/audit_event_definitions_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module AuditEvents
+ class AuditEventDefinitionsResolver < BaseResolver
+ type [Types::AuditEvents::DefinitionType], null: false
+
+ def resolve
+ Gitlab::Audit::Type::Definition.definitions.values
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/audit_events/definition_type.rb b/app/graphql/types/audit_events/definition_type.rb
new file mode 100644
index 00000000000..94b88733798
--- /dev/null
+++ b/app/graphql/types/audit_events/definition_type.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Types
+ module AuditEvents
+ class DefinitionType < ::Types::BaseObject
+ graphql_name 'AuditEventDefinition'
+ description 'Represents the YAML definitions for audit events defined ' \
+ 'under ee/config/audit_events/types/<event-type-name>.yml ' \
+ 'and config/audit_events/types/<event-type-name>.yml.'
+
+ authorize :audit_event_definitions
+
+ field :name, GraphQL::Types::String,
+ null: false,
+ description: 'Key name of the audit event.'
+
+ field :description, GraphQL::Types::String,
+ null: false,
+ description: 'Description of what action the audit event tracks.'
+
+ field :introduced_by_issue, GraphQL::Types::String,
+ null: true,
+ description: 'Link to the issue introducing the event. For older' \
+ 'audit events, it can be a commit URL rather than a' \
+ 'merge request URL.'
+
+ field :introduced_by_mr, GraphQL::Types::String,
+ null: true,
+ description: 'Link to the merge request introducing the event. For' \
+ 'older audit events, it can be a commit URL rather than' \
+ 'a merge request URL.'
+
+ field :feature_category, GraphQL::Types::String,
+ null: false,
+ description: 'Feature category associated with the event.'
+
+ field :milestone, GraphQL::Types::String,
+ null: false,
+ description: 'Milestone the event was introduced in.'
+
+ field :saved_to_database, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Indicates if the event is saved to PostgreSQL database.'
+
+ field :streamed, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Indicates if the event is streamed to an external destination.'
+ end
+ end
+end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 20dce54d740..b26e447f622 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -165,6 +165,12 @@ module Types
alpha: { milestone: '15.1' },
description: 'Find a work item.'
+ field :audit_event_definitions,
+ Types::AuditEvents::DefinitionType.connection_type,
+ null: false,
+ description: 'Definitions for all audit events available on the instance.',
+ resolver: Resolvers::AuditEvents::AuditEventDefinitionsResolver
+
def design_management
DesignManagementObject.new(nil)
end
diff --git a/app/helpers/ci/catalog/resources_helper.rb b/app/helpers/ci/catalog/resources_helper.rb
index 9f70410f17f..bc77e0cd33a 100644
--- a/app/helpers/ci/catalog/resources_helper.rb
+++ b/app/helpers/ci/catalog/resources_helper.rb
@@ -3,6 +3,10 @@
module Ci
module Catalog
module ResourcesHelper
+ def can_add_catalog_resource?(_project)
+ false
+ end
+
def can_view_namespace_catalog?(_project)
false
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 4f30b555ba0..c7864c1d45f 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -87,8 +87,6 @@ module NavHelper
end
def show_super_sidebar?(user = current_user)
- return false unless Feature.enabled?(:super_sidebar_nav, user)
-
# The new sidebar is not enabled for anonymous use
# Once we enable the new sidebar by default, this
# should return true
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7445d91ec04..6f675e1120c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -421,8 +421,9 @@ module ProjectsHelper
packagesAvailable: ::Gitlab.config.packages.enabled,
packagesHelpPath: help_page_path('user/packages/index'),
currentSettings: project_permissions_settings(project),
- canDisableEmails: can_disable_emails?(project, current_user),
+ canAddCatalogResource: can_add_catalog_resource?(project),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
+ canDisableEmails: can_disable_emails?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('user/public_access'),
registryAvailable: Gitlab.config.registry.enabled,
diff --git a/app/policies/audit_events/definition_policy.rb b/app/policies/audit_events/definition_policy.rb
new file mode 100644
index 00000000000..4109c59fb77
--- /dev/null
+++ b/app/policies/audit_events/definition_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module AuditEvents
+ class DefinitionPolicy < ::BasePolicy
+ condition(:read_audit_events_definitions_enabled) do
+ true
+ end
+
+ rule { read_audit_events_definitions_enabled }.enable :audit_event_definitions
+ end
+end
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 1739dee1511..65dbafc19da 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -42,9 +42,8 @@
%li.d-md-none
= link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url, data: { track_action: "click_link", track_label: "switch_to_canary", track_property: "navigation_top" }
- - if Feature.enabled?(:super_sidebar_nav, current_user)
- %li.divider
- .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_url} }
+ %li.divider
+ .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_url} }
- if current_user_menu?(:sign_out)
%li.divider
diff --git a/config/feature_flags/development/super_sidebar_nav.yml b/config/feature_flags/development/super_sidebar_nav.yml
deleted file mode 100644
index 81dd4a7842a..00000000000
--- a/config/feature_flags/development/super_sidebar_nav.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: super_sidebar_nav
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101910
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381456
-milestone: '15.7'
-type: development
-group: group::foundations
-default_enabled: true
diff --git a/config/initializers/00_deprecations.rb b/config/initializers/00_deprecations.rb
index 39398367c1e..0acb920fa9d 100644
--- a/config/initializers/00_deprecations.rb
+++ b/config/initializers/00_deprecations.rb
@@ -18,7 +18,7 @@ if Rails.env.production?
else
ActiveSupport::Deprecation.silenced = false
ActiveSupport::Deprecation.behavior = [:stderr, :notify]
- ActiveSupport::Deprecation.disallowed_behavior = :raise
+ ActiveSupport::Deprecation.disallowed_behavior = [:stderr, :raise]
rails7_deprecation_warnings = [
# https://gitlab.com/gitlab-org/gitlab/-/issues/366910
diff --git a/doc/.vale/gitlab/SubstitutionWarning.yml b/doc/.vale/gitlab/SubstitutionWarning.yml
index 8f3d3330271..fa4a0960d9c 100644
--- a/doc/.vale/gitlab/SubstitutionWarning.yml
+++ b/doc/.vale/gitlab/SubstitutionWarning.yml
@@ -33,6 +33,8 @@ swap:
n/a: "not applicable"
navigate to: "go to"
OAuth2: "OAuth 2.0"
+ omnibus gitlab: "Linux package"
+ 'omnibus(?!\))': "Linux package"
once that: "after that"
once the: "after the"
once you: "after you"
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index 225e4e74880..10369669ca5 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -18,7 +18,7 @@ swap:
GitLabber: GitLab team member
GitLabbers: GitLab team members
GitLab-shell: GitLab Shell
- gitlab omnibus: Omnibus GitLab
+ gitlab omnibus: Linux package
param: parameter
params: parameters
pg: PostgreSQL
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 76bf554c0d8..5ee56680756 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -57,6 +57,16 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="queryaimessagesrequestids"></a>`requestIds` | [`[ID!]`](#id) | Array of request IDs to fetch. |
| <a id="queryaimessagesroles"></a>`roles` | [`[AiCachedMessageRole!]`](#aicachedmessagerole) | Array of roles to fetch. |
+### `Query.auditEventDefinitions`
+
+Definitions for all audit events available on the instance.
+
+Returns [`AuditEventDefinitionConnection!`](#auditeventdefinitionconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
### `Query.boardList`
Find an issue board list.
@@ -7411,6 +7421,29 @@ The edge type for [`ApprovalProjectRule`](#approvalprojectrule).
| <a id="approvalprojectruleedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="approvalprojectruleedgenode"></a>`node` | [`ApprovalProjectRule`](#approvalprojectrule) | The item at the end of the edge. |
+#### `AuditEventDefinitionConnection`
+
+The connection type for [`AuditEventDefinition`](#auditeventdefinition).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="auditeventdefinitionconnectionedges"></a>`edges` | [`[AuditEventDefinitionEdge]`](#auditeventdefinitionedge) | A list of edges. |
+| <a id="auditeventdefinitionconnectionnodes"></a>`nodes` | [`[AuditEventDefinition]`](#auditeventdefinition) | A list of nodes. |
+| <a id="auditeventdefinitionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `AuditEventDefinitionEdge`
+
+The edge type for [`AuditEventDefinition`](#auditeventdefinition).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="auditeventdefinitionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="auditeventdefinitionedgenode"></a>`node` | [`AuditEventDefinition`](#auditeventdefinition) | The item at the end of the edge. |
+
#### `AuditEventStreamingHeaderConnection`
The connection type for [`AuditEventStreamingHeader`](#auditeventstreamingheader).
@@ -12073,6 +12106,23 @@ Represents a vulnerability asset type.
| <a id="assettypetype"></a>`type` | [`String!`](#string) | Type of the asset. |
| <a id="assettypeurl"></a>`url` | [`String!`](#string) | URL of the asset. |
+### `AuditEventDefinition`
+
+Represents the YAML definitions for audit events defined under ee/config/audit_events/types/<event-type-name>.yml and config/audit_events/types/<event-type-name>.yml.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="auditeventdefinitiondescription"></a>`description` | [`String!`](#string) | Description of what action the audit event tracks. |
+| <a id="auditeventdefinitionfeaturecategory"></a>`featureCategory` | [`String!`](#string) | Feature category associated with the event. |
+| <a id="auditeventdefinitionintroducedbyissue"></a>`introducedByIssue` | [`String`](#string) | Link to the issue introducing the event. For olderaudit events, it can be a commit URL rather than amerge request URL. |
+| <a id="auditeventdefinitionintroducedbymr"></a>`introducedByMr` | [`String`](#string) | Link to the merge request introducing the event. Forolder audit events, it can be a commit URL rather thana merge request URL. |
+| <a id="auditeventdefinitionmilestone"></a>`milestone` | [`String!`](#string) | Milestone the event was introduced in. |
+| <a id="auditeventdefinitionname"></a>`name` | [`String!`](#string) | Key name of the audit event. |
+| <a id="auditeventdefinitionsavedtodatabase"></a>`savedToDatabase` | [`Boolean!`](#boolean) | Indicates if the event is saved to PostgreSQL database. |
+| <a id="auditeventdefinitionstreamed"></a>`streamed` | [`Boolean!`](#boolean) | Indicates if the event is streamed to an external destination. |
+
### `AuditEventStreamingHeader`
Represents a HTTP header key/value that belongs to an audit streaming destination.
@@ -13244,6 +13294,35 @@ Returns [`CommitParentNames`](#commitparentnames).
| ---- | ---- | ----------- |
| <a id="commitreferencestippingtagslimit"></a>`limit` | [`Int!`](#int) | Number of ref names to return. |
+### `ComparedSecurityReport`
+
+Represents compared security report.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="comparedsecurityreportadded"></a>`added` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. New vulnerability findings. |
+| <a id="comparedsecurityreportbasereportcreatedat"></a>`baseReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
+| <a id="comparedsecurityreportbasereportoutofdate"></a>`baseReportOutOfDate` | [`Boolean`](#boolean) | Indicates whether the base report out of date. |
+| <a id="comparedsecurityreportfixed"></a>`fixed` **{warning-solid}** | [`[ComparedSecurityReportFinding!]`](#comparedsecurityreportfinding) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Fixed vulnerability findings. |
+| <a id="comparedsecurityreportheadreportcreatedat"></a>`headReportCreatedAt` | [`Time`](#time) | Time of the base report creation. |
+
+### `ComparedSecurityReportFinding`
+
+Represents finding.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="comparedsecurityreportfindingdescription"></a>`description` | [`String`](#string) | Description of the vulnerability finding. |
+| <a id="comparedsecurityreportfindingfoundbypipelineiid"></a>`foundByPipelineIid` | [`String`](#string) | IID of the pipeline. |
+| <a id="comparedsecurityreportfindingseverity"></a>`severity` | [`VulnerabilitySeverity`](#vulnerabilityseverity) | Severity of the vulnerability finding. |
+| <a id="comparedsecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
+| <a id="comparedsecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
+| <a id="comparedsecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
+
### `ComplianceFramework`
Represents a ComplianceFramework associated with a Project.
@@ -15054,6 +15133,18 @@ Describes an external status check.
| <a id="fileuploadpath"></a>`path` | [`String!`](#string) | Path of the upload. |
| <a id="fileuploadsize"></a>`size` | [`Int!`](#int) | Size of the upload in bytes. |
+### `FindingReportsComparer`
+
+Represents security reports comparison for vulnerability findings.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="findingreportscomparerreport"></a>`report` **{warning-solid}** | [`ComparedSecurityReport`](#comparedsecurityreport) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Compared security report. |
+| <a id="findingreportscomparerstatus"></a>`status` | [`FindingReportsComparerStatus`](#findingreportscomparerstatus) | Comparison status. |
+| <a id="findingreportscomparerstatusreason"></a>`statusReason` | [`String`](#string) | Text explaining the status. |
+
### `Forecast`
Information about specific forecast created.
@@ -17314,6 +17405,22 @@ Returns [`[DiffStats!]`](#diffstats).
| ---- | ---- | ----------- |
| <a id="mergerequestdiffstatspath"></a>`path` | [`String`](#string) | Specific file path. |
+##### `MergeRequest.findingReportsComparer`
+
+Vulnerability finding reports comparison reported on the merge request.
+
+WARNING:
+**Introduced** in 16.1.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`FindingReportsComparer`](#findingreportscomparer).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mergerequestfindingreportscomparerreporttype"></a>`reportType` | [`ComparableSecurityReportType!`](#comparablesecurityreporttype) | Filter vulnerability findings by report type. |
+
##### `MergeRequest.pipelines`
Pipelines for the merge request. Note: for performance reasons, no more than the most recent 500 pipelines will be returned.
@@ -24450,6 +24557,20 @@ Mode of a commit action.
| <a id="commitencodingbase64"></a>`BASE64` | Base64 encoding. |
| <a id="commitencodingtext"></a>`TEXT` | Text encoding. |
+### `ComparableSecurityReportType`
+
+Comparable security report type.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="comparablesecurityreporttypeapi_fuzzing"></a>`API_FUZZING` | API Fuzzing report. |
+| <a id="comparablesecurityreporttypecontainer_scanning"></a>`CONTAINER_SCANNING` | Container Scanning report. |
+| <a id="comparablesecurityreporttypecoverage_fuzzing"></a>`COVERAGE_FUZZING` | Coverage Fuzzing report. |
+| <a id="comparablesecurityreporttypedast"></a>`DAST` | DAST report. |
+| <a id="comparablesecurityreporttypedependency_scanning"></a>`DEPENDENCY_SCANNING` | Dependency Scanning report. |
+| <a id="comparablesecurityreporttypesast"></a>`SAST` | SAST report. |
+| <a id="comparablesecurityreporttypesecret_detection"></a>`SECRET_DETECTION` | Secret Detection report. |
+
### `ComplianceFrameworkPresenceFilter`
ComplianceFramework of a project for filtering.
@@ -24965,6 +25086,16 @@ Event action.
| <a id="eventactionreopened"></a>`REOPENED` | Reopened action. |
| <a id="eventactionupdated"></a>`UPDATED` | Updated action. |
+### `FindingReportsComparerStatus`
+
+Report comparison status.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="findingreportscomparerstatuserror"></a>`ERROR` | An error happened while generating the report. |
+| <a id="findingreportscomparerstatusparsed"></a>`PARSED` | Report is generated. |
+| <a id="findingreportscomparerstatusparsing"></a>`PARSING` | Report is being generated. |
+
### `ForecastStatus`
List of statuses for forecasting model.
diff --git a/doc/ci/components/index.md b/doc/ci/components/index.md
index 1b5f1c91c94..883624aeda2 100644
--- a/doc/ci/components/index.md
+++ b/doc/ci/components/index.md
@@ -190,13 +190,12 @@ After components are added to a components repository, they can immediately be [
However, this repository is not discoverable. You must mark this project as a catalog resource to allow it to be visible in the CI Catalog
so other users can discover it.
-To mark a project as a catalog resource, run the following [graphQL](../../api/graphql/index.md)
-mutation:
-
-```graphql
-mutation {
- catalogResourcesCreate(input: { projectPath: "path-to-project"}) {
- errors
- }
-}
-```
+To mark a project as a catalog resource:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Visibility, project features, permissions**.
+1. Scroll down to **CI/CD Catalog resource** and select the toggle to mark the project as a catalog resource.
+
+NOTE:
+This action is not reversible.
diff --git a/doc/ci/runners/saas/linux_saas_runner.md b/doc/ci/runners/saas/linux_saas_runner.md
index d0ddbdfcaf0..be28197e681 100644
--- a/doc/ci/runners/saas/linux_saas_runner.md
+++ b/doc/ci/runners/saas/linux_saas_runner.md
@@ -180,7 +180,7 @@ sentry_dsn = "X"
MachineOptions = [
"google-project=PROJECT",
"google-disk-size=25",
- "google-machine-type=n1-standard-1",
+ "google-machine-type=n2d-standard-2",
"google-username=core",
"google-tags=gitlab-com,srm",
"google-use-internal-ip",
diff --git a/doc/development/ai_features.md b/doc/development/ai_features.md
index 6d7c0b9072f..c46117c4afd 100644
--- a/doc/development/ai_features.md
+++ b/doc/development/ai_features.md
@@ -68,7 +68,7 @@ All AI features are experimental.
1. Enable the specific feature flag for the feature you want to test
1. Set the required access token. To receive an access token:
1. For Vertex, follow the [instructions below](#configure-gcp-vertex-access).
- 1. For all other providers, create an access request where `@m_gill`, `@wayne`, and `@timzallmann` are the tech stack owners.
+ 1. For all other providers, like Anthropic or OpenAI, create an access request where `@m_gill`, `@wayne`, and `@timzallmann` are the tech stack owners.
### Set up the embedding database
@@ -84,7 +84,7 @@ For features that use the embedding database, additional setup is needed.
1. Run `gdk reconfigure`
1. Run database migrations to create the embedding database
-### Setup for GitLab chat
+### Setup for GitLab documentation chat (legacy chat)
To populate the embedding database for GitLab chat:
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 2abd6034fc6..daa1b51b252 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -28,12 +28,23 @@ The reviewer can:
- Give you a second opinion on the chosen solution and implementation.
- Help look for bugs, logic problems, or uncovered edge cases.
-If the merge request is trivial to review (for example, fixing a typo or a tiny refactor that doesn't change the behavior or any data),
-you can skip the reviewer step and directly ask a [maintainer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer).
-Otherwise, a merge request should always be first reviewed by a reviewer in each
-[category (e.g. backend, database)](#approval-guidelines)
-the MR touches, as maintainers may not have the relevant domain knowledge, and
-also to spread the workload.
+If the merge request is small and straightforward to review, you can skip the reviewer step and
+directly ask a
+[maintainer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer).
+
+What constitutes "small and straightforward" is a gray area. Here are
+some examples of small and straightforward changes:
+
+- Fixing a typo or making small copy changes ([example](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121337#note_1399406719)).
+- A tiny refactor that doesn't change any behavior or data.
+- Removing references to a feature flag that has been default enabled for > 1 month.
+- Removing unused methods or classes.
+- A well-understood logic change that requires changes to < 5 lines of code.
+
+Otherwise, a merge request should be first reviewed by a reviewer in each
+[category (for example: backend, database)](#approval-guidelines)
+the MR touches, as maintainers may not have the relevant domain knowledge. This
+also helps to spread the workload.
For assistance with security scans or comments, include the Application Security Team (`@gitlab-com/gl-security/appsec`).
@@ -51,7 +62,9 @@ Some domain areas (like `Verify`) require an approval from a domain expert, base
CODEOWNERS rules. Because CODEOWNERS sections are independent approval rules, we could have certain
rules (for example `Verify`) that may be a subset of other more generic approval rules (for example `backend`).
For a more efficient process, authors should look for domain-specific approvals before generic approvals.
-Domain-specific approvers may also be maintainers, and if so they should review the domain specifics and broader change at the same time and approve once for both roles.
+Domain-specific approvers may also be maintainers, and if so they should review
+the domain specifics and broader change at the same time and approve once for
+both roles.
Read more about [author responsibilities](#the-responsibility-of-the-merge-request-author) below.
@@ -180,14 +193,14 @@ by a reviewer before passing it to a maintainer as described in the
| A new service to GitLab (Puma, Sidekiq, Gitaly are examples) | [Product manager](https://about.gitlab.com/company/team/). See the [process for adding a service component to GitLab](adding_service_component.md) for details. |
| Changes related to authentication or authorization | [Manage:Authentication and Authorization team member](https://about.gitlab.com/company/team/). Check the [code review section on the group page](https://about.gitlab.com/handbook/engineering/development/dev/manage/authentication-and-authorization/#additional-considerations) for more details. Patterns for files known to require review from the team are listed in the in the `Authentication and Authorization` section of the [`CODEOWNERS`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/CODEOWNERS) file, and the team will be listed in the approvers section of all merge requests that modify these files. |
-- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code within Haml templates is considered `~backend` code. When in doubt, request both a frontend and backend review.
+- (*1*): Specs other than JavaScript specs are considered `~backend` code. Haml markup is considered `~frontend` code. However, Ruby code in Haml templates is considered `~backend` code. When in doubt, request both a frontend and backend review.
- (*2*): We encourage you to seek guidance from a database maintainer if your merge
request is potentially introducing expensive queries. It is most efficient to comment
on the line of code in question with the SQL queries so they can give their advice.
- (*3*): User-facing changes include both visual changes (regardless of how minor),
and changes to the rendered DOM which impact how a screen reader may announce
the content.
-- (*4*): End-to-end changes include all files within the `qa` directory.
+- (*4*): End-to-end changes include all files in the `qa` directory.
#### Acceptance checklist
@@ -382,7 +395,7 @@ codebase, and not that of any specific domain, they can review, approve, and mer
merge requests from any team and in any product area.
Maintainers are the DRI of assuring that the acceptance criteria of a merge request are reasonably met.
-In general, [quality is everyone’s responsibility](https://about.gitlab.com/handbook/engineering/quality/),
+In general, [quality is everyone's responsibility](https://about.gitlab.com/handbook/engineering/quality/),
but maintainers of an MR are held responsible for **ensuring** that an MR meets those general quality standards.
If a maintainer feels that an MR is substantial enough, or requires a [domain expert](#domain-experts),
diff --git a/doc/development/navigation_sidebar.md b/doc/development/navigation_sidebar.md
index cd543012ddd..f7ab75b62f4 100644
--- a/doc/development/navigation_sidebar.md
+++ b/doc/development/navigation_sidebar.md
@@ -15,10 +15,7 @@ the sidebar is a work in progress, and so is this documentation.
## Enable the new navigation sidebar
-To enable the new navigation sidebar:
-
-- Enable the `super_sidebar_nav` feature flag.
-- Select your avatar, then turn on the **New navigation** toggle.
+To enable the new navigation sidebar, select your avatar, then turn on the **New navigation** toggle.
## Adding items to the sidebar
diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md
index 610e68dd2fb..383bc338dc5 100644
--- a/doc/development/pipelines/index.md
+++ b/doc/development/pipelines/index.md
@@ -481,6 +481,50 @@ for how it works.
committed. More context can be found at:
[Setting it to `false` to skip it](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118938#note_1374688877)
+##### Why do we have both the mirror project and validation project?
+
+We have separate projects for a several reasons.
+
+- **Security**: Previously, we had the mirror project only. However, to fully
+ mitigate a [security issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369898),
+ we had to make the mirror project private.
+- **Isolation**: We want to run JH code in a completely isolated and standalone project.
+ We should not run it under the `gitlab-org` group, which is where the mirror
+ project is. The validation project is completely isolated.
+- **Cost**: We don't want to connect to JiHuLab.com from each merge request.
+ It is more cost effective to mirror the code from JiHuLab.com to
+ somewhere at GitLab.com, and have our merge requests fetch code from there.
+ This means that the validation project can fetch code from the mirror, rather
+ than from JiHuLab.com. The mirror project will periodically fetch from
+ JiHuLab.com.
+- **Branch separation/security/efficiency**: We want to mirror all branches,
+ so that we can fetch the corresponding JH branch from JiHuLab.com. However,
+ we don't want to overwrite the `as-if-jh-code-sync` branch in the validation project,
+ because we use it to control the validation pipeline and it has access to
+ `AS_IF_JH_TOKEN`. However, we cannot mirror all branches except a single
+ one. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/413032) for details.
+
+ Given this issue, the validation project is set to only mirror `master` and
+ `main-jh`. Technically, we don't even need those branches, but we do want to
+ keep the repository up-to-date with all the default branches so that when
+ we push changes from the merge request, we only need to push changes from
+ the merge request, which can be more efficient.
+
+- Separation of concerns:
+ - Validation project only has the following branches:
+ - `master` and `main-jh` to keep changes up-to-date.
+ - `as-if-jh-code-sync` for dependency synchronization.
+ We should never mirror this.
+ - `as-if-jh/*` branches from the merge requests.
+ We should never mirror these.
+ - All branches from the mirror project are all coming from JiHuLab.com.
+ We never push anything to the mirror project, nor does it run any
+ pipelines. CI/CD is disabled in the mirror project.
+
+We can consider merging the two projects to simplify the
+setup and process, but we need to make sure that all of these reasons
+are no longer concerns.
+
### `rspec:undercoverage` job
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74859) in GitLab 14.6.
diff --git a/doc/security/token_overview.md b/doc/security/token_overview.md
index 40c05ddc53a..5a772106562 100644
--- a/doc/security/token_overview.md
+++ b/doc/security/token_overview.md
@@ -70,7 +70,7 @@ Bot users for groups are service accounts and do not count as licensed seats.
You can use the [group access tokens API](../api/group_access_tokens.md) to
programmatically take action, such as
-[rotating a project access token](../api/group_access_tokens.md#rotate-a-group-access-token).
+[rotating a group access token](../api/group_access_tokens.md#rotate-a-group-access-token).
## Deploy tokens
diff --git a/doc/topics/gitlab_flow.md b/doc/topics/gitlab_flow.md
index f40d59ad7e4..7dd35419c76 100644
--- a/doc/topics/gitlab_flow.md
+++ b/doc/topics/gitlab_flow.md
@@ -444,7 +444,7 @@ However, as discussed in [the section about rebasing](#squashing-commits-with-re
Rebasing could create more work, as every time you rebase, you may need to resolve the same conflicts.
Sometimes you can reuse recorded resolutions (`rerere`), but merging is better, because you only have to resolve conflicts once.
-You can read a more thorough explanation of the tradeoffs between merging and rebasing [here](https://git-scm.com/book/en/v2/Git-Branching-Rebasing#:~:text=Final%20commit%20history-,The,-Perils%20of%20Rebasing).
+The Git documentation has a thorough explanation of the [tradeoffs between merging and rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing#:~:text=Final%20commit%20history-,The,-Perils%20of%20Rebasing).
A good way to prevent creating many merge commits is to not frequently merge `main` into the feature branch.
Three reasons to merge in `main`:
@@ -543,9 +543,17 @@ In GitLab Flow, your can include automated CI tests in your branch or merge requ
## Working with feature branches
-When creating a feature branch, always branch from an up-to-date `main`.
-If you know before you start that your work depends on another branch, you can also branch from there.
-If you need to merge in another branch after starting, explain the reason in the merge commit.
-If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on `main` or another feature branch.
-Do not merge from upstream again if your code can work and merge cleanly without doing so.
-Merging only when needed prevents creating merge commits in your feature branch that later end up littering the `main` history.
+Some tips for working with feature branches:
+
+- When you create a feature branch locally, always update your local copy of `main` before
+ branching off from it.
+- When creating a feature branch, always branch from `main` unless you know your work
+ depends on some other branch. For example, to create `feature-x-update`, branch from
+ `feature-x` instead of `main`.
+- If you merge in another branch after starting, explain the reason in the merge commit.
+- If you have not pushed your branch upstream yet, you can still pull in new changes
+ by rebasing your local feature branch against your local copy of its parent branch.
+- Do not merge recent changes from other branches into your local feature branch if your code
+ can work and merge cleanly without those extra changes. Each time you merge commits into your
+ feature branch, you add a merge commit to your feature branch. These merge commits
+ later end up littering the history in your `main` branch.
diff --git a/doc/user/admin_area/license_file.md b/doc/user/admin_area/license_file.md
index 01d2c31dd10..13807d53fea 100644
--- a/doc/user/admin_area/license_file.md
+++ b/doc/user/admin_area/license_file.md
@@ -42,7 +42,7 @@ export GITLAB_ACTIVATION_CODE=your_activation_code
If you have a license, you can also import it when you install GitLab.
-- **For installations from source**
+- For self-compiled installations:
- Place the `Gitlab.gitlab-license` file in the `config/` directory.
- To specify a custom location and filename for the license, set the
`GITLAB_LICENSE_FILE` environment variable with the path to the file:
@@ -51,7 +51,7 @@ If you have a license, you can also import it when you install GitLab.
export GITLAB_LICENSE_FILE="/path/to/license/file"
```
-- **For Omnibus package**
+- For Linux package installations:
- Place the `Gitlab.gitlab-license` file in the `/etc/gitlab/` directory.
- To specify a custom location and filename for the license, add this entry to `gitlab.rb`:
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 5c730375f98..f0fe55f25c9 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -352,7 +352,7 @@ error, the [max attachment size](#max-attachment-size)
is probably larger than the web server's allowed value.
To increase the max attachment size to 200 MB in a
-[Omnibus GitLab](https://docs.gitlab.com/omnibus/) install, you may need to
+[Linux package](https://docs.gitlab.com/omnibus/) install, you may need to
add the line below to `/etc/gitlab/gitlab.rb` before increasing the max attachment size:
```ruby
diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md
index 072873ba7f6..2e62670fa00 100644
--- a/doc/user/admin_area/settings/external_authorization.md
+++ b/doc/user/admin_area/settings/external_authorization.md
@@ -32,12 +32,12 @@ authorization service.
Whenever access is granted or denied this is logged in a log file called
`external-policy-access-control.log`. Read more about the logs GitLab keeps in
-the [Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/logs.html).
+the [Linux package documentation](https://docs.gitlab.com/omnibus/settings/logs.html).
When using TLS Authentication with a self signed certificate, the CA certificate
needs to be trusted by the OpenSSL installation. When using GitLab installed
-using Omnibus, learn to install a custom CA in the
-[Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
+using the Linux package, learn to install a custom CA in the
+[Linux package documentation](https://docs.gitlab.com/omnibus/settings/ssl/index.html).
Alternatively, learn where to install custom certificates by using
`openssl version -d`.
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
index 9b8718fc336..4fc44c94b4a 100644
--- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -131,7 +131,7 @@ GitLab. For example:
- Set `Gitlab-Bypass-Rate-Limiting` to a value other than `1` on all requests that
should be affected by rate limiting.
1. Set the environment variable `GITLAB_THROTTLE_BYPASS_HEADER`.
- - For [Omnibus](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
+ - For [Linux package installations](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
set `'GITLAB_THROTTLE_BYPASS_HEADER' => 'Gitlab-Bypass-Rate-Limiting'` in `gitlab_rails['env']`.
- For source installations, set `export GITLAB_THROTTLE_BYPASS_HEADER=Gitlab-Bypass-Rate-Limiting`
in `/etc/default/gitlab`.
@@ -163,7 +163,7 @@ the `GITLAB_THROTTLE_USER_ALLOWLIST` environment variable. If you want
users 1, 53 and 217 to bypass the authenticated request rate limiter,
the allowlist configuration would be `1,53,217`.
-- For [Omnibus](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
+- For [Linux package installations](https://docs.gitlab.com/omnibus/settings/environment-variables.html),
set `'GITLAB_THROTTLE_USER_ALLOWLIST' => '1,53,217'` in `gitlab_rails['env']`.
- For source installations, set `export GITLAB_THROTTLE_USER_ALLOWLIST=1,53,217`
in `/etc/default/gitlab`.
diff --git a/doc/user/clusters/agent/ci_cd_workflow.md b/doc/user/clusters/agent/ci_cd_workflow.md
index 7a3f09f50ca..effbd5be7ec 100644
--- a/doc/user/clusters/agent/ci_cd_workflow.md
+++ b/doc/user/clusters/agent/ci_cd_workflow.md
@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) the `ci_access` attribute in GitLab 14.3.
> - The ability to authorize groups was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) in GitLab 14.3.
> - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) to GitLab Free in 14.5.
-> - Support for Omnibus installations was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5686) in GitLab 14.5.
+> - Support for Linux package installations was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5686) in GitLab 14.5.
> - The ability to switch between certificate-based clusters and agents was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335089) in GitLab 14.9. The certificate-based cluster context is always called `gitlab-deploy`.
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80508) from _CI/CD tunnel_ to _CI/CD workflow_ in GitLab 14.9.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 2fa77878cb0..82d0bfb035a 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -468,7 +468,7 @@ and can't be configured on GitLab.com to expire. You can erase job logs
## GitLab.com at scale
-In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
+In addition to the GitLab Enterprise Edition Linux package install, GitLab.com uses
the following applications and settings to achieve scale. All settings are
publicly available, as [Kubernetes configuration](https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com)
or [Chef cookbooks](https://gitlab.com/gitlab-cookbooks).
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 8827e1e27c5..57852d3e6ac 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -108,7 +108,7 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
1. Open the configuration file.
- For Omnibus GitLab:
+ For Linux package installations:
```shell
sudo editor /etc/gitlab/gitlab.rb
@@ -123,7 +123,7 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
1. Add the provider configuration:
- For Omnibus package:
+ For Linux package installations:
```ruby
gitlab_rails['forti_authenticator_enabled'] = true
@@ -145,8 +145,8 @@ Configure FortiAuthenticator in GitLab. On your GitLab server:
```
1. Save the configuration file.
-1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Omnibus GitLab) or
- [restart](../../../administration/restart_gitlab.md#installations-from-source) (GitLab installed from source).
+1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Linux package) or
+ [restart](../../../administration/restart_gitlab.md#installations-from-source) (self-compiled installations).
### Enable one-time password using Duo
@@ -174,7 +174,7 @@ On your GitLab server:
1. Open the configuration file.
- For Omnibus GitLab:
+ For Linux package installations:
```shell
sudo editor /etc/gitlab/gitlab.rb
@@ -189,7 +189,7 @@ On your GitLab server:
1. Add the provider configuration:
- For Omnibus package:
+ For Linux package installations:
```ruby
gitlab_rails['duo_auth_enabled'] = false
@@ -209,7 +209,7 @@ On your GitLab server:
```
1. Save the configuration file.
-1. For Omnibus GitLab, [reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. For Linux package installations, [reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
For installations from source, [restart GitLab](../../../administration/restart_gitlab.md#installations-from-source).
### Enable one-time password using FortiToken Cloud
@@ -233,7 +233,7 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
1. Open the configuration file.
- For Omnibus GitLab:
+ For Linux package installations:
```shell
sudo editor /etc/gitlab/gitlab.rb
@@ -248,7 +248,7 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
1. Add the provider configuration:
- For Omnibus package:
+ For Linux package installations:
```ruby
gitlab_rails['forti_token_cloud_enabled'] = true
@@ -266,8 +266,8 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
```
1. Save the configuration file.
-1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Omnibus GitLab) or
- [restart](../../../administration/restart_gitlab.md#installations-from-source) (GitLab installed from source).
+1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) (Linux package) or
+ [restart](../../../administration/restart_gitlab.md#installations-from-source) (self-compiled installations).
### Set up a WebAuthn device
diff --git a/doc/user/profile/user_passwords.md b/doc/user/profile/user_passwords.md
index eac3db3c71c..f8cbdd54e77 100644
--- a/doc/user/profile/user_passwords.md
+++ b/doc/user/profile/user_passwords.md
@@ -23,6 +23,8 @@ authorization provider, you do not need to choose a password. GitLab
## Change your password
+> Password reset emails sent to any verified email address [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16311) in GitLab 16.1.
+
You can change your password. GitLab enforces [password requirements](#password-requirements) when you choose your new
password.
@@ -33,8 +35,17 @@ password.
1. In the **New password** and **Password confirmation** text box, enter your new password.
1. Select **Save password**.
-If you don't know your current password, select the **I forgot my password** link. A password reset email is sent to the
-account's **primary** email address.
+If you do not know your current password, select **I forgot my password**
+and complete the form. A password reset email is sent to the email address you
+enter into this form, provided that the email address is verified. If you enter an
+unverified email address into this form, no email is sent, and you see the following
+message:
+
+> "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
+
+NOTE:
+Your account can have more than one verified email address, and any email address
+associated with your account can be verified.
## Password requirements
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index 3b5e4030487..4de334a0535 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -17,9 +17,9 @@ separately configured [Mattermost notifications](mattermost.md).
GitLab provides different ways to configure Mattermost slash commands. For any of these options,
you must have Mattermost [3.4 or later](https://mattermost.com/blog/category/platform/releases/).
-- **Omnibus GitLab installations**: Mattermost is bundled with
- [Omnibus GitLab](https://docs.gitlab.com/omnibus/). To configure Mattermost for Omnibus GitLab,
- read the [Omnibus GitLab Mattermost documentation](../../../integration/mattermost/index.md).
+- **Linux package installations**: Mattermost is bundled with
+ [Linux package](https://docs.gitlab.com/omnibus/). To configure Mattermost for Linux package
+ installations, read the [Linux package Mattermost documentation](../../../integration/mattermost/index.md).
- **If Mattermost is installed on the same server as GitLab**, use the
[automated configuration](#configure-automatically).
- **For all other installations**, use the [manual configuration](#configure-manually).
@@ -133,7 +133,7 @@ The available slash commands for Mattermost are:
## Related topics
- [Mattermost slash commands](https://developers.mattermost.com/integrate/slash-commands/)
-- [Omnibus GitLab Mattermost](../../../integration/mattermost/index.md)
+- [Linux package Mattermost](../../../integration/mattermost/index.md)
## Troubleshooting
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
index 085f3305ddb..b777110d670 100644
--- a/doc/user/project/service_desk.md
+++ b/doc/user/project/service_desk.md
@@ -238,9 +238,10 @@ To configure a custom mailbox for Service Desk with IMAP, add the following snip
NOTE:
In GitLab 15.3 and later, Service Desk uses `webhook` (internal API call) by default instead of enqueuing a Sidekiq job.
-To use `webhook` on an Omnibus installation running GitLab 15.3, you must generate a secret file.
+To use `webhook` on a Linux package installation running GitLab 15.3, you must generate a secret file.
For more information, see [merge request 5927](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5927).
-In GitLab 15.4, reconfiguring an Omnibus installation generates this secret file automatically, so no secret file configuration setting is needed.
+In GitLab 15.4, reconfiguring a Linux package installation generates this secret file automatically, so no
+secret file configuration setting is needed.
For more information, see [issue 1462](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1462).
```ruby
@@ -419,7 +420,7 @@ Service Desk can be configured to read Microsoft Exchange Online mailboxes with
Graph API instead of IMAP. Set up an OAuth 2.0 application for Microsoft Graph
[the same way as for incoming email](../../administration/incoming_email.md#microsoft-graph).
-- Example for Omnibus GitLab installations:
+- Example for Linux package installations:
```ruby
gitlab_rails['service_desk_email_enabled'] = true
@@ -502,8 +503,8 @@ and the [common settings for `service_desk_email`](https://docs.gitlab.com/chart
#### Linux package (Omnibus)
-In multi-node Linux package (Omnibus) environments, run `mail_room` only on one node. Run it either on a single
-`rails` node (for example, [Omnibus role](https://docs.gitlab.com/omnibus/roles/index.html) `application_role`)
+In multi-node Linux package installation environments, run `mail_room` only on one node. Run it either on a single
+`rails` node (for example, `application_role`)
or completely separately.
##### Set up all nodes
diff --git a/lib/gitlab/audit/type/definition.rb b/lib/gitlab/audit/type/definition.rb
index 81c88a3a0ae..f9a5ec40adf 100644
--- a/lib/gitlab/audit/type/definition.rb
+++ b/lib/gitlab/audit/type/definition.rb
@@ -13,6 +13,10 @@ module Gitlab
validate :validate_schema
validate :validate_file_name
+ def self.declarative_policy_class
+ 'AuditEvents::DefinitionPolicy'
+ end
+
InvalidAuditEventTypeError = Class.new(StandardError)
AUDIT_EVENT_TYPE_SCHEMA_PATH = Rails.root.join('config', 'audit_events', 'types', 'type_schema.json')
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index b8a4c59c233..95cdf9b9953 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -6,53 +6,51 @@
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
-image: openjdk:8-jdk
+image: eclipse-temurin:17-jdk-jammy
variables:
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
# It should match compileSdkVersion.
- ANDROID_COMPILE_SDK: "29"
+ ANDROID_COMPILE_SDK: "33"
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
# It should match buildToolsVersion.
- ANDROID_BUILD_TOOLS: "29.0.3"
+ ANDROID_BUILD_TOOLS: "33.0.2"
# It's what version of the command line tools we're going to download from the official site.
# Official Site-> https://developer.android.com/studio/index.html
# There, look down below at the cli tools only, sdk tools package is of format:
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
# when the script was last modified for latest compileSdkVersion, it was which is written down below
- ANDROID_SDK_TOOLS: "6514223"
+ ANDROID_SDK_TOOLS: "9477386"
# Packages installation before running script
before_script:
- apt-get --quiet update --yes
- - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
+ - apt-get --quiet install --yes wget unzip
# Setup path as android_home for moving/exporting the downloaded sdk into it
- - export ANDROID_HOME="${PWD}/android-home"
+ - export ANDROID_HOME="${PWD}/android-sdk-root"
# Create a new directory at specified location
- install -d $ANDROID_HOME
# Here we are installing androidSDK tools from official source,
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
# after that unzipping those tools and
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
- - wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
- # move to the archive at ANDROID_HOME
- - pushd $ANDROID_HOME
- - unzip -d cmdline-tools cmdline-tools.zip
- - popd
- - export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
+ - wget --no-verbose --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
+ - unzip -q -d "$ANDROID_HOME/cmdline-tools" "$ANDROID_HOME/cmdline-tools.zip"
+ - mv -T "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/tools"
+ - export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin
# Nothing fancy here, just checking sdkManager version
- sdkmanager --version
# use yes to accept all licenses
- - yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
- - sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
- - sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
- - sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
+ - yes | sdkmanager --licenses > /dev/null || true
+ - sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
+ - sdkmanager "platform-tools"
+ - sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
# Not necessary, but just for surity
- chmod +x ./gradlew
@@ -64,6 +62,11 @@ lintDebug:
stage: build
script:
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
+ artifacts:
+ paths:
+ - app/lint/reports/lint-results-debug.html
+ expose_as: "lint-report"
+ when: always
# Make Project
assembleDebug:
@@ -77,6 +80,7 @@ assembleDebug:
# Run all tests, if any fails, interrupt the pipeline(fail it)
debugTests:
+ needs: [lintDebug, assembleDebug]
interruptible: true
stage: test
script:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6e0e0454dd2..5b59e702170 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5376,9 +5376,6 @@ msgstr ""
msgid "Anonymous"
msgstr ""
-msgid "Another action is currently in progress"
-msgstr ""
-
msgid "Another issue tracker is already in use. Only one issue tracker service can be active at a time"
msgstr ""
@@ -9577,6 +9574,9 @@ msgstr ""
msgid "Ci config already present"
msgstr ""
+msgid "CiCatalog|CI/CD Catalog resource"
+msgstr ""
+
msgid "CiCatalog|CI/CD catalog"
msgstr ""
@@ -9589,6 +9589,12 @@ msgstr ""
msgid "CiCatalog|Learn more"
msgstr ""
+msgid "CiCatalog|Mark project as a CI/CD Catalog resource"
+msgstr ""
+
+msgid "CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}"
+msgstr ""
+
msgid "CiCatalog|Page %{currentPage} of %{totalPage}"
msgstr ""
@@ -9598,9 +9604,24 @@ msgstr ""
msgid "CiCatalog|Repositories of pipeline components available in this namespace."
msgstr ""
+msgid "CiCatalog|The project must contain a README.md file and a template.yml file. When enabled, the repository is available in the CI/CD Catalog."
+msgstr ""
+
+msgid "CiCatalog|There was a problem fetching the CI/CD Catalog setting."
+msgstr ""
+
+msgid "CiCatalog|There was a problem marking the project as a CI/CD Catalog resource."
+msgstr ""
+
msgid "CiCatalog|There was an error fetching CI/CD Catalog resources."
msgstr ""
+msgid "CiCatalog|This project is now a CI/CD Catalog resource."
+msgstr ""
+
+msgid "CiCatalog|This project will be marked as a CI/CD Catalog resource and will be visible in the CI/CD Catalog. This action is not reversible."
+msgstr ""
+
msgid "CiCatalog|We want to help you create and manage pipeline component repositories, while also making it easier to reuse pipeline configurations. Let us know how we're doing!"
msgstr ""
@@ -17400,12 +17421,18 @@ msgstr ""
msgid "Environment|Deployments"
msgstr ""
+msgid "Environment|Environment health"
+msgstr ""
+
msgid "Environment|External IP"
msgstr ""
msgid "Environment|Failed"
msgstr ""
+msgid "Environment|Healthy"
+msgstr ""
+
msgid "Environment|Jobs"
msgstr ""
@@ -17439,6 +17466,9 @@ msgstr ""
msgid "Environment|Summary"
msgstr ""
+msgid "Environment|Unhealthy"
+msgstr ""
+
msgid "Epic"
msgstr ""
@@ -33678,9 +33708,6 @@ msgstr ""
msgid "Pipeline|Failed"
msgstr ""
-msgid "Pipeline|In progress"
-msgstr ""
-
msgid "Pipeline|Manual"
msgstr ""
@@ -48932,21 +48959,9 @@ msgstr ""
msgid "UsageQuota|User settings &gt; Usage quotas"
msgstr ""
-msgid "UsageQuota|When you purchase additional storage, we automatically unlock projects that were locked if the storage limit was reached."
-msgstr ""
-
msgid "UsageQuota|Wiki content."
msgstr ""
-msgid "UsageQuota|You have consumed all of your additional storage. Purchase more to unlock projects over the limit."
-msgstr ""
-
-msgid "UsageQuota|You have reached the free storage limit on %{projectsLockedText}. To unlock them, purchase additional storage."
-msgstr ""
-
-msgid "UsageQuota|Your purchased storage is running low. To avoid locked projects, purchase more storage."
-msgstr ""
-
msgid "UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again."
msgstr ""
diff --git a/qa/lib/gitlab/page/group/settings/billing.rb b/qa/lib/gitlab/page/group/settings/billing.rb
index 45a67526682..0d25a012db3 100644
--- a/qa/lib/gitlab/page/group/settings/billing.rb
+++ b/qa/lib/gitlab/page/group/settings/billing.rb
@@ -33,10 +33,13 @@ module Gitlab
# Waits for subscription to be synced and UI to be updated
#
# @param subscription_plan [String]
- def wait_for_subscription(subscription_plan, page:)
+ def wait_for_subscription(subscription_plan)
::QA::Support::Waiter.wait_until(
- max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT, sleep_interval: 2, reload_page: page,
- message: "Subscription plan '#{subscription_plan}' failed to appear") do
+ max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
+ sleep_interval: 2,
+ reload_page: Chemlab.configuration.browser.session,
+ message: "Subscription plan '#{subscription_plan}' failed to appear"
+ ) do
billing_plan_header.match?(/currently using the #{subscription_plan} saas plan/i)
end
end
diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
index ce540b5ee56..5fc40a64722 100644
--- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb
+++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
@@ -82,6 +82,34 @@ module Gitlab
purchased_usage_total[/(\d+){2}.\d+/].to_f
end
+
+ # Waits for additional CI minutes to be available on the page
+ def wait_for_additional_ci_minutes_available
+ ::QA::Support::Waiter.wait_until(
+ max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
+ sleep_interval: 2,
+ reload_page: Chemlab.configuration.browser.session,
+ message: 'Expected additional CI minutes but they did not appear.'
+ ) do
+ additional_ci_minutes_added?
+ end
+ end
+
+ # Waits for additional CI minutes amount to match the expected number of minutes
+ #
+ # @param [String] minutes
+ def wait_for_additional_ci_minute_limits(minutes)
+ wait_for_additional_ci_minutes_available
+
+ ::QA::Support::Waiter.wait_until(
+ max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT,
+ sleep_interval: 2,
+ reload_page: Chemlab.configuration.browser.session,
+ message: "Expected additional CI minutes to equal #{minutes}"
+ ) do
+ additional_ci_limits == minutes
+ end
+ end
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 9eb55989ea8..53699d6bb21 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -211,7 +211,7 @@ module QA
# Click by JS is needed to bypass the Moved MR actions popover
# Change back to regular click_element when moved_mr_sidebar FF is removed
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
- click_by_javascript(find_element(:edit_title_button))
+ click_by_javascript(find_element(:edit_title_button, skip_finished_loading_check: true))
end
def fast_forward_not_possible?
diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb
index dd96b763e55..61792ea5a58 100644
--- a/spec/features/commit_spec.rb
+++ b/spec/features/commit_spec.rb
@@ -70,7 +70,6 @@ RSpec.describe 'Commit', feature_category: :source_code_management do
context "when super sidebar is enabled" do
before do
user.update!(use_new_navigation: true)
- stub_feature_flags(super_sidebar_nav: true)
end
it_behaves_like "single commit view"
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 40ab06937ff..6dcebad300c 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -120,6 +120,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js, feature_categ
end
before do
+ stub_feature_flags(review_apps_redeploy_mr_widget: false)
build.success!
deployment.update!(on_stop: manual.name)
visit project_merge_request_path(project, merge_request)
@@ -142,5 +143,56 @@ RSpec.describe 'Merge request > User sees deployment widget', :js, feature_categ
end
end
end
+
+ context 'with stop action with the review_apps_redeploy_mr_widget feature flag turned on' do
+ let(:manual) do
+ create(:ci_build, :manual, pipeline: pipeline,
+ name: 'close_app', environment: environment.name)
+ end
+
+ before do
+ stub_feature_flags(review_apps_redeploy_mr_widget: true)
+ build.success!
+ deployment.update!(on_stop: manual.name)
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+ end
+
+ it 'displays the re-deploy button' do
+ accept_gl_confirm(button_text: 'Stop environment') do
+ find('.js-stop-env').click
+ end
+
+ expect(page).to have_selector('.js-redeploy-action')
+ end
+
+ context 'for reporter' do
+ let(:role) { :reporter }
+
+ it 'does not show stop button' do
+ expect(page).not_to have_selector('.js-stop-env')
+ end
+ end
+ end
+
+ context 'with redeploy action and with the review_apps_redeploy_mr_widget feature flag turned on' do
+ before do
+ stub_feature_flags(review_apps_redeploy_mr_widget: true)
+ build.success!
+ environment.update!(state: 'stopped')
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+ end
+
+ it 'begins redeploying the deployment' do
+ accept_gl_confirm(button_text: 'Re-deploy') do
+ find('.js-redeploy-action').click
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content('Will deploy to')
+ end
+ end
end
end
diff --git a/spec/features/nav/new_nav_toggle_spec.rb b/spec/features/nav/new_nav_toggle_spec.rb
index 2cdaf12bb15..6872058be8e 100644
--- a/spec/features/nav/new_nav_toggle_spec.rb
+++ b/spec/features/nav/new_nav_toggle_spec.rb
@@ -7,75 +7,53 @@ RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do
before do
user.update!(use_new_navigation: user_preference)
- stub_feature_flags(super_sidebar_nav: new_nav_ff)
sign_in(user)
visit explore_projects_path
end
- context 'with feature flag off' do
- let(:new_nav_ff) { false }
+ context 'when user has new nav disabled' do
+ let(:user_preference) { false }
- where(:user_preference) do
- [true, false]
- end
+ it 'allows to enable new nav', :aggregate_failures do
+ within '.js-nav-user-dropdown' do
+ find('a[data-toggle="dropdown"]').click
+ expect(page).to have_content('Navigation redesign')
- with_them do
- it 'shows old topbar user dropdown with no way to toggle to new nav' do
- within '.js-header-content .js-nav-user-dropdown' do
- find('a[data-toggle="dropdown"]').click
- expect(page).not_to have_content('Navigation redesign')
- end
+ toggle = page.find('.gl-toggle:not(.is-checked)')
+ toggle.click # reloads the page
end
- end
- end
-
- context 'with feature flag on' do
- let(:new_nav_ff) { true }
- context 'when user has new nav disabled' do
- let(:user_preference) { false }
+ wait_for_requests
- it 'allows to enable new nav', :aggregate_failures do
- within '.js-nav-user-dropdown' do
- find('a[data-toggle="dropdown"]').click
- expect(page).to have_content('Navigation redesign')
-
- toggle = page.find('.gl-toggle:not(.is-checked)')
- toggle.click # reloads the page
- end
-
- wait_for_requests
-
- expect(user.reload.use_new_navigation).to eq true
- end
+ expect(user.reload.use_new_navigation).to eq true
+ end
- it 'shows the old navigation' do
- expect(page).to have_selector('.js-navbar')
- expect(page).not_to have_selector('[data-testid="super-sidebar"]')
- end
+ it 'shows the old navigation' do
+ expect(page).to have_selector('.js-navbar')
+ expect(page).not_to have_selector('[data-testid="super-sidebar"]')
end
+ end
- context 'when user has new nav enabled' do
- let(:user_preference) { true }
+ context 'when user has new nav enabled' do
+ let(:user_preference) { true }
- it 'allows to disable new nav', :aggregate_failures do
- within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do
- click_button "#{user.name} user’s menu"
- expect(page).to have_content('Navigation redesign')
+ it 'allows to disable new nav', :aggregate_failures do
+ within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do
+ click_button "#{user.name} user’s menu"
+ expect(page).to have_content('Navigation redesign')
- toggle = page.find('.gl-toggle.is-checked')
- toggle.click # reloads the page
- end
+ toggle = page.find('.gl-toggle.is-checked')
+ toggle.click # reloads the page
+ end
- wait_for_requests
+ wait_for_requests
- expect(user.reload.use_new_navigation).to eq false
- end
+ expect(user.reload.use_new_navigation).to eq false
+ end
- it 'shows the new navigation' do
- expect(page).not_to have_selector('.js-navbar')
- expect(page).to have_selector('[data-testid="super-sidebar"]')
- end
+ it 'shows the new navigation' do
+ expect(page).not_to have_selector('.js-navbar')
+ expect(page).to have_selector('[data-testid="super-sidebar"]')
end
end
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 3313f6ef3b7..beb5fa7822b 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -189,7 +189,6 @@ RSpec.describe "Compare", :js, feature_category: :groups_and_projects do
context "when super sidebar is enabled" do
before do
user.update!(use_new_navigation: true)
- stub_feature_flags(super_sidebar_nav: true)
end
it_behaves_like "compare view of branches"
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 9f56c2c1cb0..d05b7649f94 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -99,7 +99,6 @@ RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
context 'when the new navigation is enabled' do
before do
user.update!(use_new_navigation: true)
- stub_feature_flags(super_sidebar_nav: true)
end
include_examples '"New project" page'
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
index d4ba7323aaf..b9eff2f79c5 100644
--- a/spec/frontend/environments/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -5,6 +5,7 @@ import KubernetesOverview from '~/environments/components/kubernetes_overview.vu
import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
import KubernetesTabs from '~/environments/components/kubernetes_tabs.vue';
+import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue';
import { agent, kubernetesNamespace } from './graphql/mock_data';
import { mockKasTunnelUrl } from './mock_data';
@@ -32,6 +33,7 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
const findAgentInfo = () => wrapper.findComponent(KubernetesAgentInfo);
const findKubernetesPods = () => wrapper.findComponent(KubernetesPods);
const findKubernetesTabs = () => wrapper.findComponent(KubernetesTabs);
+ const findKubernetesStatusBar = () => wrapper.findComponent(KubernetesStatusBar);
const findAlert = () => wrapper.findComponent(GlAlert);
const createWrapper = () => {
@@ -105,6 +107,49 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
configuration,
});
});
+
+ it('renders kubernetes status bar', () => {
+ expect(findKubernetesStatusBar().exists()).toBe(true);
+ });
+ });
+
+ describe('Kubernetes health status', () => {
+ beforeEach(() => {
+ createWrapper();
+ toggleCollapse();
+ });
+
+ it("doesn't set `clusterHealthStatus` when pods are still loading", async () => {
+ findKubernetesPods().vm.$emit('loading', true);
+ await nextTick();
+
+ expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('');
+ });
+
+ it("doesn't set `clusterHealthStatus` when workload types are still loading", async () => {
+ findKubernetesTabs().vm.$emit('loading', true);
+ await nextTick();
+
+ expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('');
+ });
+
+ it('sets `clusterHealthStatus` as error when pods emitted a failure', async () => {
+ findKubernetesPods().vm.$emit('failed');
+ await nextTick();
+
+ expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
+ });
+
+ it('sets `clusterHealthStatus` as error when workload types emitted a failure', async () => {
+ findKubernetesTabs().vm.$emit('failed');
+ await nextTick();
+
+ expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
+ });
+
+ it('sets `clusterHealthStatus` as success when data is loaded and no failures where emitted', () => {
+ expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
+ });
});
describe('on cluster error', () => {
diff --git a/spec/frontend/environments/kubernetes_pods_spec.js b/spec/frontend/environments/kubernetes_pods_spec.js
index 137309d7853..0420d8df1a9 100644
--- a/spec/frontend/environments/kubernetes_pods_spec.js
+++ b/spec/frontend/environments/kubernetes_pods_spec.js
@@ -50,6 +50,14 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
+ it('emits loading state', async () => {
+ createWrapper();
+ expect(wrapper.emitted('loading')[0]).toEqual([true]);
+
+ await waitForPromises();
+ expect(wrapper.emitted('loading')[1]).toEqual([false]);
+ });
+
it('hides the loading icon when the list of pods loaded', async () => {
createWrapper();
await waitForPromises();
@@ -84,6 +92,13 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
});
},
);
+
+ it('emits a failed event when there are failed pods', async () => {
+ createWrapper();
+ await waitForPromises();
+
+ expect(wrapper.emitted('failed')).toHaveLength(1);
+ });
});
describe('when gets an error from the cluster_client API', () => {
diff --git a/spec/frontend/environments/kubernetes_status_bar_spec.js b/spec/frontend/environments/kubernetes_status_bar_spec.js
new file mode 100644
index 00000000000..2ebb30e2766
--- /dev/null
+++ b/spec/frontend/environments/kubernetes_status_bar_spec.js
@@ -0,0 +1,42 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
+import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue';
+import {
+ CLUSTER_STATUS_HEALTHY_TEXT,
+ CLUSTER_STATUS_UNHEALTHY_TEXT,
+} from '~/environments/constants';
+
+describe('~/environments/components/kubernetes_status_bar.vue', () => {
+ let wrapper;
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findHealthBadge = () => wrapper.findComponent(GlBadge);
+
+ const createWrapper = ({ clusterHealthStatus = '' } = {}) => {
+ wrapper = shallowMount(KubernetesStatusBar, {
+ propsData: { clusterHealthStatus },
+ });
+ };
+
+ describe('health badge', () => {
+ it('shows loading icon when cluster health is not present', () => {
+ createWrapper();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it.each([
+ ['success', 'success', CLUSTER_STATUS_HEALTHY_TEXT],
+ ['error', 'danger', CLUSTER_STATUS_UNHEALTHY_TEXT],
+ ])(
+ 'when clusterHealthStatus is %s shows health badge with variant %s and text %s',
+ (status, variant, text) => {
+ createWrapper({ clusterHealthStatus: status });
+
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findHealthBadge().props('variant')).toBe(variant);
+ expect(findHealthBadge().text()).toBe(text);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/environments/kubernetes_summary_spec.js b/spec/frontend/environments/kubernetes_summary_spec.js
index 53b83079486..22c81f29f64 100644
--- a/spec/frontend/environments/kubernetes_summary_spec.js
+++ b/spec/frontend/environments/kubernetes_summary_spec.js
@@ -59,6 +59,14 @@ describe('~/environments/components/kubernetes_summary.vue', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
+ it('emits loading state', async () => {
+ createWrapper();
+ expect(wrapper.emitted('loading')[0]).toEqual([true]);
+
+ await waitForPromises();
+ expect(wrapper.emitted('loading')[1]).toEqual([false]);
+ });
+
describe('when workloads data is loaded', () => {
beforeEach(async () => {
await createWrapper();
@@ -94,6 +102,10 @@ describe('~/environments/components/kubernetes_summary.vue', () => {
);
});
+ it('emits a failed event when there are failed workload types', () => {
+ expect(wrapper.emitted('failed')).toHaveLength(1);
+ });
+
it('emits an error message when gets an error from the cluster_client API', async () => {
const error = new Error('Error from the cluster_client API');
const createErroredApolloProvider = () => {
diff --git a/spec/frontend/environments/kubernetes_tabs_spec.js b/spec/frontend/environments/kubernetes_tabs_spec.js
index 429f267347b..81b0bb86e0e 100644
--- a/spec/frontend/environments/kubernetes_tabs_spec.js
+++ b/spec/frontend/environments/kubernetes_tabs_spec.js
@@ -165,4 +165,23 @@ describe('~/environments/components/kubernetes_tabs.vue', () => {
expect(wrapper.emitted('cluster-error')).toEqual([[error]]);
});
});
+
+ describe('summary tab', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('emits loading event when gets it from the component', () => {
+ findKubernetesSummary().vm.$emit('loading', true);
+ expect(wrapper.emitted('loading')[0]).toEqual([true]);
+
+ findKubernetesSummary().vm.$emit('loading', false);
+ expect(wrapper.emitted('loading')[1]).toEqual([false]);
+ });
+
+ it('emits a failed event when gets it from the component', () => {
+ findKubernetesSummary().vm.$emit('failed');
+ expect(wrapper.emitted('failed')).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/fixtures/startup_css.rb b/spec/frontend/fixtures/startup_css.rb
index 5b09e1c9495..83e02470321 100644
--- a/spec/frontend/fixtures/startup_css.rb
+++ b/spec/frontend/fixtures/startup_css.rb
@@ -40,11 +40,8 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
expect(response).to be_successful
end
- # This Feature Flag is off by default
# This ensures that the correct css is generated for super sidebar
- # When the feature flag is off, the general startup will capture it
it "startup_css/project-#{type}-super-sidebar.html" do
- stub_feature_flags(super_sidebar_nav: true)
user.update!(use_new_navigation: true)
get :show, params: {
diff --git a/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js b/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js
new file mode 100644
index 00000000000..4ac3a511fa2
--- /dev/null
+++ b/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js
@@ -0,0 +1,147 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlBadge, GlLoadingIcon, GlModal, GlSprintf, GlToggle } from '@gitlab/ui';
+
+import { createAlert } from '~/alert';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+
+import catalogResourcesCreate from '~/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql';
+import getCiCatalogSettingsQuery from '~/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql';
+import CiCatalogSettings, {
+ i18n,
+} from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue';
+
+import { mockCiCatalogSettingsResponse } from './mock_data';
+
+Vue.use(VueApollo);
+jest.mock('~/alert');
+
+describe('CiCatalogSettings', () => {
+ let wrapper;
+ let ciCatalogSettingsResponse;
+ let catalogResourcesCreateResponse;
+
+ const fullPath = 'gitlab-org/gitlab';
+
+ const createComponent = ({ ciCatalogSettingsHandler = ciCatalogSettingsResponse } = {}) => {
+ const handlers = [
+ [getCiCatalogSettingsQuery, ciCatalogSettingsHandler],
+ [catalogResourcesCreate, catalogResourcesCreateResponse],
+ ];
+ const mockApollo = createMockApollo(handlers);
+
+ wrapper = shallowMountExtended(CiCatalogSettings, {
+ propsData: {
+ fullPath,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ apolloProvider: mockApollo,
+ });
+
+ return waitForPromises();
+ };
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findBadge = () => wrapper.findComponent(GlBadge);
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findToggle = () => wrapper.findComponent(GlToggle);
+
+ const findCiCatalogSettings = () => wrapper.findByTestId('ci-catalog-settings');
+
+ beforeEach(() => {
+ ciCatalogSettingsResponse = jest.fn().mockResolvedValue(mockCiCatalogSettingsResponse);
+ catalogResourcesCreateResponse = jest.fn();
+ });
+
+ describe('when initial queries are loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows a loading icon and no CI catalog settings', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findCiCatalogSettings().exists()).toBe(false);
+ });
+ });
+
+ describe('when queries have loaded', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
+ it('does not show a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('renders the CI Catalog settings', () => {
+ expect(findCiCatalogSettings().exists()).toBe(true);
+ });
+
+ it('renders the experiment badge', () => {
+ expect(findBadge().exists()).toBe(true);
+ });
+
+ it('renders the toggle', () => {
+ expect(findToggle().exists()).toBe(true);
+ });
+
+ it('renders the modal', () => {
+ expect(findModal().exists()).toBe(true);
+ expect(findModal().attributes('title')).toBe(i18n.modal.title);
+ });
+
+ describe('when queries have loaded', () => {
+ beforeEach(() => {
+ catalogResourcesCreateResponse.mockResolvedValue(mockCiCatalogSettingsResponse);
+ });
+
+ it('shows the modal when the toggle is clicked', async () => {
+ expect(findModal().props('visible')).toBe(false);
+
+ await findToggle().vm.$emit('change', true);
+
+ expect(findModal().props('visible')).toBe(true);
+ expect(findModal().props('actionPrimary').text).toBe(i18n.modal.actionPrimary.text);
+ });
+
+ it('hides the modal when cancel is clicked', () => {
+ findToggle().vm.$emit('change', true);
+ findModal().vm.$emit('canceled');
+
+ expect(findModal().props('visible')).toBe(false);
+ expect(catalogResourcesCreateResponse).not.toHaveBeenCalled();
+ });
+
+ it('calls the mutation with the correct input from the modal click', async () => {
+ expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(0);
+
+ findToggle().vm.$emit('change', true);
+ findModal().vm.$emit('primary');
+ await waitForPromises();
+
+ expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(1);
+ expect(catalogResourcesCreateResponse).toHaveBeenCalledWith({
+ input: {
+ projectPath: fullPath,
+ },
+ });
+ });
+ });
+ });
+
+ describe('when the query is unsuccessful', () => {
+ const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
+
+ it('throws an error', async () => {
+ await createComponent({ ciCatalogSettingsHandler: failedHandler });
+
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({ message: i18n.catalogResourceQueryError });
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/mock_data.js b/spec/frontend/pages/projects/shared/permissions/components/mock_data.js
new file mode 100644
index 00000000000..44bbf2a5eb2
--- /dev/null
+++ b/spec/frontend/pages/projects/shared/permissions/components/mock_data.js
@@ -0,0 +1,7 @@
+export const mockCiCatalogSettingsResponse = {
+ data: {
+ catalogResourcesCreate: {
+ errors: [],
+ },
+ },
+};
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 16e23472eb9..02e510c9541 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -1,6 +1,7 @@
import { GlSprintf, GlToggle } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import ProjectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
+import CiCatalogSettings from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue';
import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
import {
featureAccessLevel,
@@ -34,6 +35,7 @@ const defaultProps = {
warnAboutPotentiallyUnwantedCharacters: true,
},
isGitlabCom: true,
+ canAddCatalogResource: false,
canDisableEmails: true,
canChangeVisibilityLevel: true,
allowedVisibilityOptions: [0, 10, 20],
@@ -118,6 +120,7 @@ describe('Settings Panel', () => {
const findPagesSettings = () => wrapper.findComponent({ ref: 'pages-settings' });
const findPagesAccessLevels = () =>
wrapper.find('[name="project[project_feature_attributes][pages_access_level]"]');
+ const findCiCatalogSettings = () => wrapper.findComponent(CiCatalogSettings);
const findEmailSettings = () => wrapper.findComponent({ ref: 'email-settings' });
const findShowDefaultAwardEmojis = () =>
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
@@ -647,6 +650,19 @@ describe('Settings Panel', () => {
});
});
+ describe('CI Catalog Settings', () => {
+ it('should show the CI Catalog settings if user has permission', () => {
+ wrapper = mountComponent({ canAddCatalogResource: true });
+
+ expect(findCiCatalogSettings().exists()).toBe(true);
+ });
+ it('should not show the CI Catalog settings if user does not have permission', () => {
+ wrapper = mountComponent();
+
+ expect(findCiCatalogSettings().exists()).toBe(false);
+ });
+ });
+
describe('Email notifications', () => {
it('should show the disable email notifications input if emails an be disabled', () => {
wrapper = mountComponent({ canDisableEmails: true });
diff --git a/spec/frontend/pipelines/pipeline_details_header_spec.js b/spec/frontend/pipelines/pipeline_details_header_spec.js
index 7141e10fb17..828c90211ec 100644
--- a/spec/frontend/pipelines/pipeline_details_header_spec.js
+++ b/spec/frontend/pipelines/pipeline_details_header_spec.js
@@ -59,6 +59,7 @@ describe('Pipeline details header', () => {
const findTimeAgo = () => wrapper.findComponent(TimeAgo);
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
const findPipelineName = () => wrapper.findByTestId('pipeline-name');
+ const findCommitTitle = () => wrapper.findByTestId('pipeline-commit-title');
const findTotalJobs = () => wrapper.findByTestId('total-jobs');
const findComputeCredits = () => wrapper.findByTestId('compute-credits');
const findCommitLink = () => wrapper.findByTestId('commit-link');
@@ -178,6 +179,19 @@ describe('Pipeline details header', () => {
});
});
+ describe('without pipeline name', () => {
+ it('displays commit title', async () => {
+ createComponent(defaultHandlers, { ...defaultProps, name: '' });
+
+ await waitForPromises();
+
+ const expectedTitle = pipelineHeaderSuccess.data.project.pipeline.commit.title;
+
+ expect(findPipelineName().exists()).toBe(false);
+ expect(findCommitTitle().text()).toBe(expectedTitle);
+ });
+ });
+
describe('finished pipeline', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index ccc07d9b1a8..5afe91c4784 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -8,7 +8,7 @@ describe('Timeago component', () => {
const defaultProps = { duration: 0, finished_at: '' };
- const createComponent = (props = defaultProps, stuck = false, extraProps) => {
+ const createComponent = (props = defaultProps, extraProps) => {
wrapper = extendedWrapper(
shallowMount(TimeAgo, {
propsData: {
@@ -16,9 +16,6 @@ describe('Timeago component', () => {
details: {
...props,
},
- flags: {
- stuck,
- },
},
...extraProps,
},
@@ -33,10 +30,6 @@ describe('Timeago component', () => {
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
- const findInProgress = () => wrapper.findByTestId('pipeline-in-progress');
- const findSkipped = () => wrapper.findByTestId('pipeline-skipped');
- const findHourGlassIcon = () => wrapper.findByTestId('hourglass-icon');
- const findWarningIcon = () => wrapper.findByTestId('warning-icon');
const findCalendarIcon = () => wrapper.findByTestId('calendar-icon');
describe('with duration', () => {
@@ -79,9 +72,12 @@ describe('Timeago component', () => {
});
it('should hide calendar icon if correct prop is passed', () => {
- createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' }, false, {
- displayCalendarIcon: false,
- });
+ createComponent(
+ { duration: 0, finished_at: '2017-04-26T12:40:23.277Z' },
+ {
+ displayCalendarIcon: false,
+ },
+ );
expect(findCalendarIcon().exists()).toBe(false);
});
@@ -97,45 +93,4 @@ describe('Timeago component', () => {
expect(findCalendarIcon().exists()).toBe(false);
});
});
-
- describe('in progress', () => {
- it.each`
- durationTime | finishedAtTime | shouldShow
- ${10} | ${'2017-04-26T12:40:23.277Z'} | ${false}
- ${10} | ${''} | ${false}
- ${0} | ${'2017-04-26T12:40:23.277Z'} | ${false}
- ${0} | ${''} | ${true}
- `(
- 'progress state shown: $shouldShow when pipeline duration is $durationTime and finished_at is $finishedAtTime',
- ({ durationTime, finishedAtTime, shouldShow }) => {
- createComponent({
- duration: durationTime,
- finished_at: finishedAtTime,
- });
-
- expect(findInProgress().exists()).toBe(shouldShow);
- expect(findSkipped().exists()).toBe(false);
- },
- );
-
- it('should show warning icon beside in progress if pipeline is stuck', () => {
- const stuck = true;
-
- createComponent(defaultProps, stuck);
-
- expect(findWarningIcon().exists()).toBe(true);
- expect(findHourGlassIcon().exists()).toBe(false);
- });
- });
-
- describe('skipped', () => {
- it('should show skipped if pipeline was skipped', () => {
- createComponent({
- status: { label: 'skipped' },
- });
-
- expect(findSkipped().exists()).toBe(true);
- expect(findInProgress().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
index 785515ae846..2aa4e7c4841 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
@@ -5,6 +5,7 @@ import {
RUNNING,
DEPLOYING,
REDEPLOYING,
+ WILL_DEPLOY,
} from '~/vue_merge_request_widget/components/deployment/constants';
import DeploymentActionButton from '~/vue_merge_request_widget/components/deployment/deployment_action_button.vue';
import { actionButtonMocks } from './deployment_mock_data';
@@ -118,4 +119,20 @@ describe('Deployment action button', () => {
expect(wrapper.findComponent(GlButton).props('disabled')).toBe(false);
});
});
+
+ describe('when the deployment status is will_deploy', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ ...baseProps,
+ actionInProgress: actionButtonMocks[REDEPLOYING].actionName,
+ computedDeploymentStatus: WILL_DEPLOY,
+ },
+ });
+ });
+ it('is disabled and shows the loading icon', () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlButton).props('disabled')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
index f2b78dedf3a..b901b80e8bf 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
@@ -9,6 +9,7 @@ import {
FAILED,
DEPLOYING,
REDEPLOYING,
+ SUCCESS,
STOPPING,
} from '~/vue_merge_request_widget/components/deployment/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
@@ -35,7 +36,8 @@ describe('DeploymentAction component', () => {
const findStopButton = () => wrapper.find('.js-stop-env');
const findDeployButton = () => wrapper.find('.js-manual-deploy-action');
- const findRedeployButton = () => wrapper.find('.js-manual-redeploy-action');
+ const findManualRedeployButton = () => wrapper.find('.js-manual-redeploy-action');
+ const findRedeployButton = () => wrapper.find('.js-redeploy-action');
beforeEach(() => {
executeActionSpy = jest.spyOn(MRWidgetService, 'executeInlineAction');
@@ -79,17 +81,17 @@ describe('DeploymentAction component', () => {
describe('when there is no retry_path in details', () => {
it('the manual redeploy button does not appear', () => {
- expect(findRedeployButton().exists()).toBe(false);
+ expect(findManualRedeployButton().exists()).toBe(false);
});
});
});
describe('when conditions are met', () => {
describe.each`
- configConst | computedDeploymentStatus | displayConditionChanges | finderFn | endpoint
- ${STOPPING} | ${CREATED} | ${{}} | ${findStopButton} | ${deploymentMockData.stop_url}
- ${DEPLOYING} | ${MANUAL_DEPLOY} | ${playDetails} | ${findDeployButton} | ${playDetails.playable_build.play_path}
- ${REDEPLOYING} | ${FAILED} | ${retryDetails} | ${findRedeployButton} | ${retryDetails.playable_build.retry_path}
+ configConst | computedDeploymentStatus | displayConditionChanges | finderFn | endpoint
+ ${STOPPING} | ${CREATED} | ${{}} | ${findStopButton} | ${deploymentMockData.stop_url}
+ ${DEPLOYING} | ${MANUAL_DEPLOY} | ${playDetails} | ${findDeployButton} | ${playDetails.playable_build.play_path}
+ ${REDEPLOYING} | ${FAILED} | ${retryDetails} | ${findManualRedeployButton} | ${retryDetails.playable_build.retry_path}
`(
'$configConst action',
({ configConst, computedDeploymentStatus, displayConditionChanges, finderFn, endpoint }) => {
@@ -231,4 +233,141 @@ describe('DeploymentAction component', () => {
},
);
});
+
+ describe('with the reviewAppsRedeployMrWidget feature flag turned on', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ computedDeploymentStatus: SUCCESS,
+ deployment: {
+ ...deploymentMockData,
+ details: undefined,
+ retry_url: retryDetails.playable_build.retry_path,
+ environment_available: false,
+ },
+ },
+ provide: {
+ glFeatures: {
+ reviewAppsRedeployMrWidget: true,
+ },
+ },
+ });
+ });
+
+ it('should display the redeploy button', () => {
+ expect(findRedeployButton().exists()).toBe(true);
+ });
+
+ describe('when the redeploy button is clicked', () => {
+ describe('should show a confirm dialog but not call executeInlineAction when declined', () => {
+ beforeEach(() => {
+ executeActionSpy.mockResolvedValueOnce();
+ confirmAction.mockResolvedValueOnce(false);
+ findRedeployButton().trigger('click');
+ });
+
+ it('should show the confirm dialog', () => {
+ expect(confirmAction).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalledWith(
+ actionButtonMocks[REDEPLOYING].confirmMessage,
+ {
+ primaryBtnVariant: actionButtonMocks[REDEPLOYING].buttonVariant,
+ primaryBtnText: actionButtonMocks[REDEPLOYING].buttonText,
+ },
+ );
+ });
+
+ it('should not execute the action', () => {
+ expect(MRWidgetService.executeInlineAction).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('should show a confirm dialog and call executeInlineAction when accepted', () => {
+ beforeEach(() => {
+ executeActionSpy.mockResolvedValueOnce();
+ confirmAction.mockResolvedValueOnce(true);
+ findRedeployButton().trigger('click');
+ });
+
+ it('should show the confirm dialog', () => {
+ expect(confirmAction).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalledWith(
+ actionButtonMocks[REDEPLOYING].confirmMessage,
+ {
+ primaryBtnVariant: actionButtonMocks[REDEPLOYING].buttonVariant,
+ primaryBtnText: actionButtonMocks[REDEPLOYING].buttonText,
+ },
+ );
+ });
+
+ it('should not throw an error', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
+ describe('response includes redirect_url', () => {
+ const url = '/root/example';
+ beforeEach(async () => {
+ executeActionSpy.mockResolvedValueOnce({
+ data: { redirect_url: url },
+ });
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ findRedeployButton().trigger('click');
+ });
+
+ it('does not call visit url', () => {
+ expect(visitUrl).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('it should call the executeAction method', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm, 'executeAction').mockImplementation();
+ jest.spyOn(eventHub, '$emit');
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ findRedeployButton().trigger('click');
+ });
+
+ it('calls with the expected arguments', () => {
+ expect(wrapper.vm.executeAction).toHaveBeenCalled();
+ expect(wrapper.vm.executeAction).toHaveBeenCalledWith(
+ retryDetails.playable_build.retry_path,
+ actionButtonMocks[REDEPLOYING],
+ );
+ });
+
+ it('emits the FetchDeployments event', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('FetchDeployments');
+ });
+ });
+
+ describe('when executeInlineAction errors', () => {
+ beforeEach(async () => {
+ executeActionSpy.mockRejectedValueOnce();
+ jest.spyOn(eventHub, '$emit');
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ findRedeployButton().trigger('click');
+ });
+
+ it('should call createAlert with error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: actionButtonMocks[REDEPLOYING].errorMessage,
+ });
+ });
+
+ it('emits the FetchDeployments event', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('FetchDeployments');
+ });
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_mock_data.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_mock_data.js
index e98b1160ae4..374fe4e1b95 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_mock_data.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_mock_data.js
@@ -43,6 +43,7 @@ const deploymentMockData = {
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ environment_available: true,
details: {},
status: SUCCESS,
changes: [
diff --git a/spec/frontend/vue_merge_request_widget/mock_data.js b/spec/frontend/vue_merge_request_widget/mock_data.js
index 46e1919b0ea..47143bb2bb8 100644
--- a/spec/frontend/vue_merge_request_widget/mock_data.js
+++ b/spec/frontend/vue_merge_request_widget/mock_data.js
@@ -427,6 +427,7 @@ export const mockStore = {
external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com',
status: SUCCESS,
+ environment_available: true,
},
{
id: 1,
@@ -434,6 +435,7 @@ export const mockStore = {
external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com',
status: SUCCESS,
+ environment_available: true,
},
],
postMergeDeployments: [
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 64fb2806447..266f69c8f2e 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -626,6 +626,7 @@ describe('MrWidgetOptions', () => {
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes,
status: SUCCESS,
+ environment_available: true,
};
beforeEach(() => {
diff --git a/spec/graphql/resolvers/audit_events/audit_event_definitions_resolver_spec.rb b/spec/graphql/resolvers/audit_events/audit_event_definitions_resolver_spec.rb
new file mode 100644
index 00000000000..0febd90d8e8
--- /dev/null
+++ b/spec/graphql/resolvers/audit_events/audit_event_definitions_resolver_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::AuditEvents::AuditEventDefinitionsResolver, feature_category: :audit_events do
+ using RSpec::Parameterized::TableSyntax
+
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+
+ describe '#resolve' do
+ let(:args) { {} }
+
+ subject(:audit_event_definitions) { resolve(described_class, args: args, ctx: { current_user: current_user }) }
+
+ it 'returns an array of audit event definitions' do
+ expect(audit_event_definitions).to be_an(Array)
+ expect(audit_event_definitions).to match_array(Gitlab::Audit::Type::Definition.definitions.values)
+ end
+ end
+end
diff --git a/spec/graphql/types/audit_events/definition_type_spec.rb b/spec/graphql/types/audit_events/definition_type_spec.rb
new file mode 100644
index 00000000000..250c0661c6a
--- /dev/null
+++ b/spec/graphql/types/audit_events/definition_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AuditEventDefinition'], feature_category: :audit_events do
+ let(:fields) do
+ %i[
+ name description introduced_by_issue introduced_by_mr
+ feature_category milestone saved_to_database streamed
+ ]
+ end
+
+ specify { expect(described_class.graphql_name).to eq('AuditEventDefinition') }
+ specify { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/helpers/ci/catalog/resources_helper_spec.rb b/spec/helpers/ci/catalog/resources_helper_spec.rb
index e873b9379fe..3b29e6f292b 100644
--- a/spec/helpers/ci/catalog/resources_helper_spec.rb
+++ b/spec/helpers/ci/catalog/resources_helper_spec.rb
@@ -7,6 +7,18 @@ RSpec.describe Ci::Catalog::ResourcesHelper, feature_category: :pipeline_composi
let_it_be(:project) { build(:project) }
+ describe '#can_add_catalog_resource?' do
+ subject { helper.can_add_catalog_resource?(project) }
+
+ before do
+ stub_licensed_features(ci_namespace_catalog: false)
+ end
+
+ it 'user cannot add a catalog resource in CE regardless of permissions' do
+ expect(subject).to be false
+ end
+ end
+
describe '#can_view_namespace_catalog?' do
subject { helper.can_view_namespace_catalog?(project) }
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index 17d28b07763..4a02b184522 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -138,55 +138,34 @@ RSpec.describe NavHelper, feature_category: :navigation do
describe '#show_super_sidebar?' do
shared_examples 'show_super_sidebar is supposed to' do
before do
- stub_feature_flags(super_sidebar_nav: new_nav_ff)
user.update!(use_new_navigation: user_preference)
end
- context 'with feature flag off' do
- let(:new_nav_ff) { false }
+ context 'when user has not interacted with the new nav toggle yet' do
+ let(:user_preference) { nil }
- context 'when user has new nav disabled' do
- let(:user_preference) { false }
+ specify { expect(subject).to eq false }
- specify { expect(subject).to eq false }
- end
-
- context 'when user has new nav enabled' do
- let(:user_preference) { true }
+ context 'when the user was enrolled into the new nav via a special feature flag' do
+ before do
+ # this ff is disabled in globally to keep tests of the old nav working
+ stub_feature_flags(super_sidebar_nav_enrolled: true)
+ end
- specify { expect(subject).to eq false }
+ specify { expect(subject).to eq true }
end
end
- context 'with feature flag on' do
- let(:new_nav_ff) { true }
-
- context 'when user has not interacted with the new nav toggle yet' do
- let(:user_preference) { nil }
-
- specify { expect(subject).to eq false }
-
- context 'when the user was enrolled into the new nav via a special feature flag' do
- before do
- # this ff is disabled in globally to keep tests of the old nav working
- stub_feature_flags(super_sidebar_nav_enrolled: true)
- end
-
- specify { expect(subject).to eq true }
- end
- end
-
- context 'when user has new nav disabled' do
- let(:user_preference) { false }
+ context 'when user has new nav disabled' do
+ let(:user_preference) { false }
- specify { expect(subject).to eq false }
- end
+ specify { expect(subject).to eq false }
+ end
- context 'when user has new nav enabled' do
- let(:user_preference) { true }
+ context 'when user has new nav enabled' do
+ let(:user_preference) { true }
- specify { expect(subject).to eq true }
- end
+ specify { expect(subject).to eq true }
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 75fb4b1d1e6..cde7fc0e272 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1052,6 +1052,12 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
it 'includes membersPagePath' do
expect(subject).to include(membersPagePath: project_project_members_path(project))
end
+
+ it 'includes canAddCatalogResource' do
+ allow(helper).to receive(:can?) { false }
+
+ expect(subject).to include(canAddCatalogResource: false)
+ end
end
describe '#project_classes' do
diff --git a/spec/requests/api/graphql/audit_events/definitions_spec.rb b/spec/requests/api/graphql/audit_events/definitions_spec.rb
new file mode 100644
index 00000000000..4e0f4dcfae1
--- /dev/null
+++ b/spec/requests/api/graphql/audit_events/definitions_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a list of audit event definitions', feature_category: :audit_events do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+
+ let(:path) { %i[audit_event_definitions nodes] }
+ let(:audit_event_definition_keys) do
+ Gitlab::Audit::Type::Definition.definitions.keys
+ end
+
+ let(:query) { graphql_query_for(:audit_event_definitions, {}, 'nodes { name }') }
+
+ it 'returns the audit event definitions' do
+ post_graphql(query, current_user: current_user)
+
+ returned_names = graphql_data_at(*path).map { |v| v['name'].to_sym }
+
+ expect(returned_names).to all be_in(audit_event_definition_keys)
+ end
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index e600cbd7004..c7b2a03fde2 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -53,7 +53,11 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options|
# In cases of multiple installations of chromedriver, prioritize the version installed by SeleniumManager
# selenium-manager doesn't work with Linux arm64 yet:
# https://github.com/SeleniumHQ/selenium/issues/11357
- if RUBY_PLATFORM =~ /x86_64-linux|darwin/
+ if RUBY_PLATFORM.include?('x86_64-linux') ||
+ # Rosetta is required on macOS because the selenium-manager
+ # binaries (https://github.com/SeleniumHQ/selenium/tree/trunk/common/manager/macos)
+ # are only compiled for macOS x86.
+ (RUBY_PLATFORM.include?('darwin') && system('/usr/bin/pgrep -q oahd'))
chrome_options = Selenium::WebDriver::Chrome::Options.chrome
chromedriver_path = File.dirname(Selenium::WebDriver::SeleniumManager.driver_path(chrome_options))
ENV['PATH'] = "#{chromedriver_path}:#{ENV['PATH']}" # rubocop:disable RSpec/EnvAssignment
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 67315b9d81e..acd654bc96d 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -149,7 +149,7 @@ module LoginHelpers
mock_auth_hash(provider, uid, email, response_object: response_object)
end
- def configure_mock_auth(provider, uid, email, response_object: nil, additional_info: {})
+ def configure_mock_auth(provider, uid, email, response_object: nil, additional_info: {}, name: 'mockuser')
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
@@ -157,7 +157,7 @@ module LoginHelpers
provider: provider,
uid: uid,
info: {
- name: 'mockuser',
+ name: name,
email: email,
image: 'mock_user_thumbnail_url'
},
@@ -180,8 +180,10 @@ module LoginHelpers
}).merge(additional_info) { |_, old_hash, new_hash| old_hash.merge(new_hash) }
end
- def mock_auth_hash(provider, uid, email, additional_info: {}, response_object: nil)
- configure_mock_auth(provider, uid, email, additional_info: additional_info, response_object: response_object)
+ def mock_auth_hash(provider, uid, email, additional_info: {}, response_object: nil, name: 'mockuser')
+ configure_mock_auth(
+ provider, uid, email, additional_info: additional_info, response_object: response_object, name: name
+ )
original_env_config_omniauth_auth = Rails.application.env_config['omniauth.auth']
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
index 095c8639d15..26f550b9b40 100644
--- a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
+++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
@@ -40,7 +40,8 @@ RSpec.shared_context 'with FOSS query type fields' do
:usage_trends_measurements,
:user,
:users,
- :work_item
+ :work_item,
+ :audit_event_definitions
]
end
end
diff --git a/spec/support/stub_dot_com_check.rb b/spec/support/stub_dot_com_check.rb
index 8134e15cb12..6934b33d111 100644
--- a/spec/support/stub_dot_com_check.rb
+++ b/spec/support/stub_dot_com_check.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.configure do |config|
- %i[saas saas_registration saas_sso_registration saas_subscription_registration].each do |metadata|
+ %i[saas saas_registration].each do |metadata|
config.before(:context, metadata) do
# Ensure Gitlab.com? returns true during context.
# This is needed for let_it_be which is shared across examples,