summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-15 21:07:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-15 21:07:42 +0000
commit2b5079efdb7c4e7d5a607d95747ddeb0b8af9678 (patch)
tree6d226593a137e111c7d4075ec22d6c4d328c3b57
parent5ff1f808adf841bca979cb2fac6bdfa9c449d028 (diff)
downloadgitlab-ce-2b5079efdb7c4e7d5a607d95747ddeb0b8af9678.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml6
-rw-r--r--.rubocop_todo/gitlab/namespaced_class.yml1
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml9
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue8
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/index.js2
-rw-r--r--app/assets/javascripts/design_management/components/list/item.vue10
-rw-r--r--app/assets/javascripts/design_management/pages/index.vue12
-rw-r--r--app/assets/javascripts/super_sidebar/components/menu_section.vue2
-rw-r--r--app/assets/javascripts/super_sidebar/components/pinned_section.vue7
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_menu.vue49
-rw-r--r--app/assets/javascripts/work_items/components/widget_wrapper.vue2
-rw-r--r--app/assets/stylesheets/page_bundles/design_management.scss6
-rw-r--r--app/helpers/projects/ml/experiments_helper.rb21
-rw-r--r--app/models/organization.rb24
-rw-r--r--app/presenters/ml/candidate_details_presenter.rb88
-rw-r--r--app/views/projects/ml/candidates/show.html.haml3
-rw-r--r--config/feature_flags/development/realtime_approvals.yml2
-rw-r--r--db/docs/design_management_repository_states.yml10
-rw-r--r--db/migrate/20230322164031_create_design_management_repository_states.rb41
-rw-r--r--db/migrate/20230509085428_change_organizations_sequence.rb12
-rw-r--r--db/migrate/20230509115525_add_name_to_organization.rb20
-rw-r--r--db/migrate/20230509131736_add_default_organization.rb16
-rw-r--r--db/migrate/20230515111314_add_text_limit_on_organization_name.rb13
-rw-r--r--db/schema_migrations/202303221640311
-rw-r--r--db/schema_migrations/202305090854281
-rw-r--r--db/schema_migrations/202305091155251
-rw-r--r--db/schema_migrations/202305091317361
-rw-r--r--db/schema_migrations/202305151113141
-rw-r--r--db/structure.sql36
-rw-r--r--doc/topics/release_your_application.md1
-rw-r--r--doc/user/clusters/agent/gitops/example_repository_structure.md78
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml2
-rw-r--r--lib/sidebars/admin/menus/admin_settings_menu.rb5
-rw-r--r--lib/sidebars/groups/menus/settings_menu.rb5
-rw-r--r--lib/sidebars/menu.rb8
-rw-r--r--lib/sidebars/projects/menus/settings_menu.rb5
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb6
-rw-r--r--spec/factories/organizations.rb13
-rw-r--r--spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js38
-rw-r--r--spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js2
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js1
-rw-r--r--spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap8
-rw-r--r--spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap8
-rw-r--r--spec/frontend/super_sidebar/components/sidebar_menu_spec.js47
-rw-r--r--spec/helpers/ci/pipeline_editor_helper_spec.rb5
-rw-r--r--spec/helpers/projects/ml/experiments_helper_spec.rb35
-rw-r--r--spec/lib/sidebars/admin/menus/admin_settings_menu_spec.rb3
-rw-r--r--spec/lib/sidebars/groups/menus/settings_menu_spec.rb6
-rw-r--r--spec/lib/sidebars/menu_spec.rb2
-rw-r--r--spec/lib/sidebars/projects/menus/settings_menu_spec.rb6
-rw-r--r--spec/migrations/20230509131736_add_default_organization_spec.rb20
-rw-r--r--spec/models/organization_spec.rb96
-rw-r--r--spec/presenters/ml/candidate_details_presenter_spec.rb111
-rw-r--r--spec/support/helpers/test_env.rb1
-rw-r--r--spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb6
59 files changed, 798 insertions, 134 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 5f0b59af9d7..8fcea840cfe 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1463,6 +1463,12 @@
.qa:rules:package-and-test-sidebar:
rules:
+ - <<: *if-not-canonical-namespace
+ when: never
+ - <<: *if-not-ee
+ when: never
+ - <<: *if-merge-request-labels-pipeline-expedite
+ when: never
- <<: *if-merge-request
changes: *code-patterns
when: manual
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 925b5188c8b..3a81130a47c 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -244,6 +244,7 @@ Gitlab/NamespacedClass:
- 'app/models/notification_setting.rb'
- 'app/models/oauth_access_grant.rb'
- 'app/models/oauth_access_token.rb'
+ - 'app/models/organization.rb'
- 'app/models/out_of_context_discussion.rb'
- 'app/models/pages_deployment.rb'
- 'app/models/pages_domain.rb'
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 4e4d3537c9e..bdd3a15489d 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1451,15 +1451,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/services/vulnerabilities/user_notes_count_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
- 'ee/spec/services/vulnerability_merge_request_links/create_service_spec.rb'
- - 'ee/spec/support/shared_examples/audit/audit_event_type_stream_shared_examples.rb'
- - 'ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb'
- - 'ee/spec/support/shared_examples/features/credentials_inventory_shared_examples.rb'
- - 'ee/spec/support/shared_examples/features/password_complexity_shared_examples.rb'
- - 'ee/spec/support/shared_examples/graphql/dast/dast_profile_schedule_shared_examples.rb'
- - 'ee/spec/support/shared_examples/graphql/mutations/set_multiple_assignees_shared_examples.rb'
- - 'ee/spec/support/shared_examples/services/geo/geo_request_service_shared_examples.rb'
- - 'ee/spec/support/shared_examples/services/search_service_shared_examples.rb'
- - 'ee/spec/support/shared_examples/services/vulnerabilities/removes_dismissal_feedback_from_associated_findings_shared_example.rb'
- 'ee/spec/tasks/gitlab/elastic_rake_spec.rb'
- 'lib/api/access_requests.rb'
- 'lib/api/admin/plan_limits.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index abaf97ff662..a32468aaacf 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-d87e76a602d9a5400c80363c8cfa878a3409c292
+11825b3ad89525b194cc4095e581eef843377cdb
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
index 66957365261..eabf4749e9c 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue
@@ -17,6 +17,7 @@ export default {
GlButton,
},
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
+ inject: ['aiChatAvailable'],
props: {
showDrawer: {
type: Boolean,
@@ -31,6 +32,11 @@ export default {
required: true,
},
},
+ computed: {
+ isAiConfigChatAvailable() {
+ return this.glFeatures.aiCiConfigGenerator && this.aiChatAvailable;
+ },
+ },
methods: {
toggleDrawer() {
if (this.showDrawer) {
@@ -96,7 +102,7 @@ export default {
{{ $options.i18n.jobAssistant }}
</gl-button>
<gl-button
- v-if="glFeatures.aiCiConfigGenerator"
+ v-if="isAiConfigChatAvailable"
icon="bulb"
size="small"
data-testid="ai-assistant-drawer-toggle"
diff --git a/app/assets/javascripts/ci/pipeline_editor/index.js b/app/assets/javascripts/ci/pipeline_editor/index.js
index 09acd805410..b8d6c27435d 100644
--- a/app/assets/javascripts/ci/pipeline_editor/index.js
+++ b/app/assets/javascripts/ci/pipeline_editor/index.js
@@ -46,6 +46,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
usesExternalConfig,
validateTabIllustrationPath,
ymlHelpPagePath,
+ aiChatAvailable,
} = el.dataset;
const configurationPaths = Object.fromEntries(
@@ -115,6 +116,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
el,
apolloProvider,
provide: {
+ aiChatAvailable: parseBoolean(aiChatAvailable),
ciConfigPath,
ciExamplesHelpPagePath,
ciHelpPagePath,
diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue
index f52486f0629..9c1bcf5bf90 100644
--- a/app/assets/javascripts/design_management/components/list/item.vue
+++ b/app/assets/javascripts/design_management/components/list/item.vue
@@ -133,7 +133,11 @@ export default {
<div
class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative"
>
- <div v-if="icon.name" data-testid="design-event" class="gl-top-5 gl-right-5 gl-absolute">
+ <div
+ v-if="icon.name"
+ data-testid="design-event"
+ class="gl-absolute gl-top-3 gl-right-3 gl-mr-1"
+ >
<span :title="icon.tooltip" :aria-label="icon.tooltip">
<gl-icon
:name="icon.name"
@@ -165,11 +169,11 @@ export default {
/>
</gl-intersection-observer>
</div>
- <div class="card-footer gl-display-flex gl-w-full">
+ <div class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4">
<div class="gl-display-flex gl-flex-direction-column str-truncated-100">
<span
v-gl-tooltip
- class="gl-font-weight-bold str-truncated-100"
+ class="gl-font-weight-semibold str-truncated-100"
data-qa-selector="design_file_name"
:data-testid="`design-img-filename-${id}`"
:title="filename"
diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue
index e270613e4eb..dcc65c957fe 100644
--- a/app/assets/javascripts/design_management/pages/index.vue
+++ b/app/assets/javascripts/design_management/pages/index.vue
@@ -141,6 +141,12 @@ export default {
}
return 'col-12';
},
+ designContentWrapperClass() {
+ if (this.hasDesigns) {
+ return 'gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5';
+ }
+ return null;
+ },
},
mounted() {
if (this.$route.path === '/designs') {
@@ -364,7 +370,7 @@ export default {
</gl-alert>
<header
v-if="showToolbar"
- class="gl-display-flex gl-my-0 gl-text-gray-900"
+ class="gl-border gl-px-5 gl-py-4 gl-display-flex gl-justify-content-space-between gl-bg-white gl-rounded-base gl-rounded-bottom-left-none! gl-rounded-bottom-right-none!"
data-testid="design-toolbar-wrapper"
>
<div
@@ -418,7 +424,7 @@ export default {
</div>
</div>
</header>
- <div>
+ <div :class="designContentWrapperClass">
<gl-loading-icon v-if="isLoading" size="lg" />
<gl-alert v-else-if="error" variant="danger" :dismissible="false">
{{ __('An error occurred while loading designs. Please try again.') }}
@@ -483,7 +489,7 @@ export default {
v-if="canSelectDesign(design.filename)"
:checked="isDesignSelected(design.filename)"
type="checkbox"
- class="design-checkbox"
+ class="design-checkbox gl-absolute gl-top-4 gl-left-6 gl-ml-2"
data-qa-selector="design_checkbox"
:data-qa-design="design.filename"
@change="changeSelectedDesigns(design.filename)"
diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue
index 5de6e04d827..93c249dffeb 100644
--- a/app/assets/javascripts/super_sidebar/components/menu_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue
@@ -69,6 +69,7 @@ export default {
<template>
<component :is="tag">
+ <hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" />
<button
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus"
:class="computedLinkClasses"
@@ -117,6 +118,5 @@ export default {
/>
</slot>
</gl-collapse>
- <hr v-if="separated" aria-hidden="true" class="gl-mx-4 gl-my-2" />
</component>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
index 4fc86e41ef2..ccd739c8bb1 100644
--- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
@@ -23,6 +23,11 @@ export default {
required: false,
default: () => [],
},
+ separated: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -69,7 +74,7 @@ export default {
<menu-section
:item="sectionItem"
:expanded="expanded"
- :separated="true"
+ :separated="separated"
@collapse-toggle="expanded = !expanded"
>
<draggable
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
index 12abd727ef0..08af9232107 100644
--- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
@@ -89,6 +89,9 @@ export default {
supportsPins() {
return PANELS_WITH_PINS.includes(this.panelType);
},
+ hasStaticItems() {
+ return this.staticItems.length > 0;
+ },
},
methods: {
createPin(itemId) {
@@ -126,34 +129,50 @@ export default {
Sentry.captureException(e);
});
},
+ isSection(navItem) {
+ return navItem.items?.length;
+ },
},
};
</script>
<template>
<nav class="gl-p-2 gl-relative">
- <template v-if="staticItems.length > 0">
- <ul class="gl-p-0 gl-m-0">
- <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static />
- </ul>
- <hr aria-hidden="true" class="gl-my-2 gl-mx-4" />
- </template>
+ <ul v-if="hasStaticItems" class="gl-p-0 gl-m-0">
+ <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static />
+ </ul>
<pinned-section
v-if="supportsPins"
+ separated
:items="pinnedItems"
@pin-remove="destroyPin"
@pin-reorder="movePin"
/>
+ <hr
+ v-if="supportsPins"
+ aria-hidden="true"
+ class="gl-my-2 gl-mx-4"
+ data-testid="main-menu-separator"
+ />
<ul class="gl-p-0 gl-list-style-none">
- <component
- :is="item.items && item.items.length ? 'MenuSection' : 'NavItem'"
- v-for="item in nonStaticItems"
- :key="item.id"
- :item="item"
- tag="li"
- @pin-add="createPin"
- @pin-remove="destroyPin"
- />
+ <template v-for="item in nonStaticItems">
+ <menu-section
+ v-if="isSection(item)"
+ :key="item.id"
+ :item="item"
+ :separated="item.separated"
+ @pin-add="createPin"
+ @pin-remove="destroyPin"
+ />
+ <nav-item
+ v-else
+ :key="item.id"
+ :item="item"
+ tag="li"
+ @pin-add="createPin"
+ @pin-remove="destroyPin"
+ />
+ </template>
</ul>
</nav>
</template>
diff --git a/app/assets/javascripts/work_items/components/widget_wrapper.vue b/app/assets/javascripts/work_items/components/widget_wrapper.vue
index 3f5ff526e91..6ae30e9b084 100644
--- a/app/assets/javascripts/work_items/components/widget_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/widget_wrapper.vue
@@ -45,7 +45,7 @@ export default {
<template>
<div
id="tasks"
- class="gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-bg-gray-10 gl-mt-4"
+ class="gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100 gl-bg-gray-10 gl-mt-5"
>
<div
class="gl-pl-5 gl-pr-4 gl-py-4 gl-display-flex gl-justify-content-space-between gl-bg-white gl-rounded-base"
diff --git a/app/assets/stylesheets/page_bundles/design_management.scss b/app/assets/stylesheets/page_bundles/design_management.scss
index f56eb4ae6fb..b42e6fd85fa 100644
--- a/app/assets/stylesheets/page_bundles/design_management.scss
+++ b/app/assets/stylesheets/page_bundles/design_management.scss
@@ -8,12 +8,6 @@ $t-gray-a-16-design-pin: rgba($black, 0.16);
background: transparent;
}
-.design-checkbox {
- position: absolute;
- top: $gl-padding;
- left: 30px;
-}
-
.layout-page.design-detail-layout {
max-height: 100vh;
}
diff --git a/app/helpers/projects/ml/experiments_helper.rb b/app/helpers/projects/ml/experiments_helper.rb
index 6e5e13ef0b6..7aef208447a 100644
--- a/app/helpers/projects/ml/experiments_helper.rb
+++ b/app/helpers/projects/ml/experiments_helper.rb
@@ -5,27 +5,6 @@ module Projects
require 'json'
include ActionView::Helpers::NumberHelper
- def show_candidate_view_model(candidate)
- data = {
- candidate: {
- params: candidate.params,
- metrics: candidate.latest_metrics,
- info: {
- iid: candidate.iid,
- eid: candidate.eid,
- path_to_artifact: link_to_artifact(candidate),
- experiment_name: candidate.experiment.name,
- path_to_experiment: link_to_experiment(candidate.project, candidate.experiment),
- path: link_to_details(candidate),
- status: candidate.status
- },
- metadata: candidate.metadata
- }
- }
-
- Gitlab::Json.generate(data)
- end
-
def experiment_as_data(experiment)
data = {
name: experiment.name,
diff --git a/app/models/organization.rb b/app/models/organization.rb
index 73a7e84305f..cfbbbf1183e 100644
--- a/app/models/organization.rb
+++ b/app/models/organization.rb
@@ -1,6 +1,26 @@
# frozen_string_literal: true
-# rubocop: disable Gitlab/NamespacedClass
class Organization < ApplicationRecord
+ DEFAULT_ORGANIZATION_ID = 1
+
+ scope :without_default, -> { where.not(id: DEFAULT_ORGANIZATION_ID) }
+
+ before_destroy :check_if_default_organization
+
+ validates :name,
+ presence: true,
+ length: { maximum: 255 },
+ uniqueness: { case_sensitive: false }
+
+ def default?
+ id == DEFAULT_ORGANIZATION_ID
+ end
+
+ private
+
+ def check_if_default_organization
+ return unless default?
+
+ raise ActiveRecord::RecordNotDestroyed, _('Cannot delete the default organization')
+ end
end
-# rubocop: enable Gitlab/NamespacedClass
diff --git a/app/presenters/ml/candidate_details_presenter.rb b/app/presenters/ml/candidate_details_presenter.rb
new file mode 100644
index 00000000000..58ec2aee471
--- /dev/null
+++ b/app/presenters/ml/candidate_details_presenter.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module Ml
+ class CandidateDetailsPresenter
+ include Rails.application.routes.url_helpers
+
+ def initialize(candidate)
+ @candidate = candidate
+ end
+
+ def present
+ data = {
+ candidate: {
+ info: {
+ iid: candidate.iid,
+ eid: candidate.eid,
+ path_to_artifact: link_to_artifact,
+ experiment_name: candidate.experiment.name,
+ path_to_experiment: link_to_experiment,
+ path: link_to_details,
+ status: candidate.status,
+ ci_job: job_info
+ },
+ params: candidate.params,
+ metrics: candidate.latest_metrics,
+ metadata: candidate.metadata
+ }
+ }
+
+ Gitlab::Json.generate(data)
+ end
+
+ private
+
+ attr_reader :candidate
+
+ def job_info
+ return unless candidate.from_ci?
+
+ build = candidate.ci_build
+
+ {
+ path: project_job_path(build.project, build),
+ name: build.name,
+ **user_info(build.user) || {},
+ **mr_info(build.pipeline.merge_request) || {}
+ }
+ end
+
+ def user_info(user)
+ return unless user
+
+ {
+ user: {
+ path: user_path(user),
+ username: user.username
+ }
+ }
+ end
+
+ def mr_info(mr)
+ return unless mr
+
+ {
+ merge_request: {
+ path: project_merge_request_path(mr.project, mr),
+ title: mr.title
+ }
+ }
+ end
+
+ def link_to_artifact
+ artifact = candidate.artifact
+
+ return unless artifact.present?
+
+ project_package_path(candidate.project, artifact)
+ end
+
+ def link_to_details
+ project_ml_candidate_path(candidate.project, candidate.iid)
+ end
+
+ def link_to_experiment
+ project_ml_experiment_path(candidate.project, candidate.experiment.iid)
+ end
+ end
+end
diff --git a/app/views/projects/ml/candidates/show.html.haml b/app/views/projects/ml/candidates/show.html.haml
index aea74ecfb48..5d5059551d3 100644
--- a/app/views/projects/ml/candidates/show.html.haml
+++ b/app/views/projects/ml/candidates/show.html.haml
@@ -3,5 +3,6 @@
- add_to_breadcrumbs experiment.name, project_ml_experiment_path(@project, experiment.iid)
- breadcrumb_title "Candidate #{@candidate.iid}"
- add_page_specific_style 'page_bundles/ml_experiment_tracking'
+- presenter = ::Ml::CandidateDetailsPresenter.new(@candidate)
-#js-show-ml-candidate{ data: { view_model: show_candidate_view_model(@candidate) } }
+#js-show-ml-candidate{ data: { view_model: presenter.present } }
diff --git a/config/feature_flags/development/realtime_approvals.yml b/config/feature_flags/development/realtime_approvals.yml
index b2a4131b0f6..adb818e52bf 100644
--- a/config/feature_flags/development/realtime_approvals.yml
+++ b/config/feature_flags/development/realtime_approvals.yml
@@ -5,4 +5,4 @@ rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/399539'
milestone: '16.0'
type: development
group: group::code review
-default_enabled: false
+default_enabled: true
diff --git a/db/docs/design_management_repository_states.yml b/db/docs/design_management_repository_states.yml
new file mode 100644
index 00000000000..3a8fa59fb42
--- /dev/null
+++ b/db/docs/design_management_repository_states.yml
@@ -0,0 +1,10 @@
+---
+table_name: design_management_repository_states
+classes:
+classes: []
+feature_categories:
+- geo_replication
+description: Separate table for Design Repository verification states
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113958
+milestone: '16.0'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230322164031_create_design_management_repository_states.rb b/db/migrate/20230322164031_create_design_management_repository_states.rb
new file mode 100644
index 00000000000..0c906a45ae8
--- /dev/null
+++ b/db/migrate/20230322164031_create_design_management_repository_states.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class CreateDesignManagementRepositoryStates < Gitlab::Database::Migration[2.1]
+ VERIFICATION_STATE_INDEX_NAME = "index_design_management_repository_states_on_verification_state"
+ PENDING_VERIFICATION_INDEX_NAME = "index_design_management_repository_states_pending_verification"
+ FAILED_VERIFICATION_INDEX_NAME = "index_design_management_repository_states_failed_verification"
+ NEEDS_VERIFICATION_INDEX_NAME = "index_design_management_repository_states_needs_verification"
+
+ enable_lock_retries!
+
+ def up
+ create_table :design_management_repository_states, id: false do |t|
+ t.datetime_with_timezone :verification_started_at
+ t.datetime_with_timezone :verification_retry_at
+ t.datetime_with_timezone :verified_at
+ t.references :design_management_repository, primary_key: true, default: nil, index: false,
+ foreign_key: { on_delete: :cascade }
+ t.integer :verification_state, default: 0, limit: 2, null: false
+ t.integer :verification_retry_count, default: 0, limit: 2, null: false
+ t.binary :verification_checksum, using: 'verification_checksum::bytea'
+ t.text :verification_failure, limit: 255
+
+ t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME
+ t.index :verified_at,
+ where: "(verification_state = 0)",
+ order: { verified_at: 'ASC NULLS FIRST' },
+ name: PENDING_VERIFICATION_INDEX_NAME
+ t.index :verification_retry_at,
+ where: "(verification_state = 3)",
+ order: { verification_retry_at: 'ASC NULLS FIRST' },
+ name: FAILED_VERIFICATION_INDEX_NAME
+ t.index :verification_state,
+ where: "(verification_state = 0 OR verification_state = 3)",
+ name: NEEDS_VERIFICATION_INDEX_NAME
+ end
+ end
+
+ def down
+ drop_table :design_management_repository_states
+ end
+end
diff --git a/db/migrate/20230509085428_change_organizations_sequence.rb b/db/migrate/20230509085428_change_organizations_sequence.rb
new file mode 100644
index 00000000000..59ec8c6e1ea
--- /dev/null
+++ b/db/migrate/20230509085428_change_organizations_sequence.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class ChangeOrganizationsSequence < Gitlab::Database::Migration[2.1]
+ def up
+ # Modify sequence for organizations.id so id '1' is never automatically taken
+ execute "ALTER SEQUENCE organizations_id_seq START WITH 1000 MINVALUE 1000 RESTART"
+ end
+
+ def down
+ execute "ALTER SEQUENCE organizations_id_seq START WITH 1 MINVALUE 1"
+ end
+end
diff --git a/db/migrate/20230509115525_add_name_to_organization.rb b/db/migrate/20230509115525_add_name_to_organization.rb
new file mode 100644
index 00000000000..d77fa84a70c
--- /dev/null
+++ b/db/migrate/20230509115525_add_name_to_organization.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# rubocop:disable Migration/AddLimitToTextColumns, Migration/AddIndex
+# limit is added in 20230515111314_add_text_limit_on_organization_name.rb
+class AddNameToOrganization < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'unique_organizations_on_name_lower'
+
+ def up
+ add_column :organizations, :name, :text, null: false, default: ''
+
+ add_index :organizations, 'lower(name)', name: INDEX_NAME, unique: true
+ end
+
+ def down
+ remove_column :organizations, :name, if_exists: true
+ end
+end
+# rubocop:enable Migration/AddLimitToTextColumns, Migration/AddIndex
diff --git a/db/migrate/20230509131736_add_default_organization.rb b/db/migrate/20230509131736_add_default_organization.rb
new file mode 100644
index 00000000000..a63e7171f53
--- /dev/null
+++ b/db/migrate/20230509131736_add_default_organization.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddDefaultOrganization < Gitlab::Database::Migration[2.1]
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ class Organization < MigrationRecord
+ end
+
+ def up
+ Organization.create(id: 1, name: 'Default')
+ end
+
+ def down
+ Organization.where(id: 1).delete_all
+ end
+end
diff --git a/db/migrate/20230515111314_add_text_limit_on_organization_name.rb b/db/migrate/20230515111314_add_text_limit_on_organization_name.rb
new file mode 100644
index 00000000000..c0b687fab94
--- /dev/null
+++ b/db/migrate/20230515111314_add_text_limit_on_organization_name.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddTextLimitOnOrganizationName < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :organizations, :name, 255
+ end
+
+ def down
+ remove_text_limit :organizations, :name
+ end
+end
diff --git a/db/schema_migrations/20230322164031 b/db/schema_migrations/20230322164031
new file mode 100644
index 00000000000..57478bae5b3
--- /dev/null
+++ b/db/schema_migrations/20230322164031
@@ -0,0 +1 @@
+4610b001f48cc89d79a769231d73f7fe579ffe8753a276e32ddd4cc15ad5bf25 \ No newline at end of file
diff --git a/db/schema_migrations/20230509085428 b/db/schema_migrations/20230509085428
new file mode 100644
index 00000000000..cf7214ceadc
--- /dev/null
+++ b/db/schema_migrations/20230509085428
@@ -0,0 +1 @@
+6179fe3d8c419c58e028fc1fe5d554678976229eff88f087beec174cb669d4ce \ No newline at end of file
diff --git a/db/schema_migrations/20230509115525 b/db/schema_migrations/20230509115525
new file mode 100644
index 00000000000..e3c0ada40cd
--- /dev/null
+++ b/db/schema_migrations/20230509115525
@@ -0,0 +1 @@
+92b70129d19796653569fb730be43ea6eed7dacbce224e1323124fdf03b0a0b0 \ No newline at end of file
diff --git a/db/schema_migrations/20230509131736 b/db/schema_migrations/20230509131736
new file mode 100644
index 00000000000..593c9495eb2
--- /dev/null
+++ b/db/schema_migrations/20230509131736
@@ -0,0 +1 @@
+f9545a27756e5ca05220ffebcf89e8268e0231cbd8c7af0a89d13c70f5a070ec \ No newline at end of file
diff --git a/db/schema_migrations/20230515111314 b/db/schema_migrations/20230515111314
new file mode 100644
index 00000000000..d2d7d2c94c4
--- /dev/null
+++ b/db/schema_migrations/20230515111314
@@ -0,0 +1 @@
+2a011d12459e0c21832df777569a12f4f2bbdaa5f57da7dc3823147f948d7772 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 75087462e3a..8dea1331b6e 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -15321,6 +15321,18 @@ CREATE SEQUENCE design_management_repositories_id_seq
ALTER SEQUENCE design_management_repositories_id_seq OWNED BY design_management_repositories.id;
+CREATE TABLE design_management_repository_states (
+ verification_started_at timestamp with time zone,
+ verification_retry_at timestamp with time zone,
+ verified_at timestamp with time zone,
+ design_management_repository_id bigint NOT NULL,
+ verification_state smallint DEFAULT 0 NOT NULL,
+ verification_retry_count smallint DEFAULT 0 NOT NULL,
+ verification_checksum bytea,
+ verification_failure text,
+ CONSTRAINT check_bf1387c28b CHECK ((char_length(verification_failure) <= 255))
+);
+
CREATE TABLE design_management_versions (
id bigint NOT NULL,
sha bytea NOT NULL,
@@ -19179,13 +19191,15 @@ ALTER SEQUENCE operations_user_lists_id_seq OWNED BY operations_user_lists.id;
CREATE TABLE organizations (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL
+ updated_at timestamp with time zone NOT NULL,
+ name text DEFAULT ''::text NOT NULL,
+ CONSTRAINT check_d130d769e0 CHECK ((char_length(name) <= 255))
);
CREATE SEQUENCE organizations_id_seq
- START WITH 1
+ START WITH 1000
INCREMENT BY 1
- NO MINVALUE
+ MINVALUE 1000
NO MAXVALUE
CACHE 1;
@@ -27046,6 +27060,9 @@ ALTER TABLE ONLY design_management_designs_versions
ALTER TABLE ONLY design_management_repositories
ADD CONSTRAINT design_management_repositories_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY design_management_repository_states
+ ADD CONSTRAINT design_management_repository_states_pkey PRIMARY KEY (design_management_repository_id);
+
ALTER TABLE ONLY design_management_versions
ADD CONSTRAINT design_management_versions_pkey PRIMARY KEY (id);
@@ -30560,6 +30577,14 @@ CREATE INDEX index_design_management_designs_versions_on_version_id ON design_ma
CREATE UNIQUE INDEX index_design_management_repositories_on_project_id ON design_management_repositories USING btree (project_id);
+CREATE INDEX index_design_management_repository_states_failed_verification ON design_management_repository_states USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3);
+
+CREATE INDEX index_design_management_repository_states_needs_verification ON design_management_repository_states USING btree (verification_state) WHERE ((verification_state = 0) OR (verification_state = 3));
+
+CREATE INDEX index_design_management_repository_states_on_verification_state ON design_management_repository_states USING btree (verification_state);
+
+CREATE INDEX index_design_management_repository_states_pending_verification ON design_management_repository_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
+
CREATE INDEX index_design_management_versions_on_author_id ON design_management_versions USING btree (author_id) WHERE (author_id IS NOT NULL);
CREATE INDEX index_design_management_versions_on_issue_id ON design_management_versions USING btree (issue_id);
@@ -33170,6 +33195,8 @@ CREATE UNIQUE INDEX unique_index_on_system_note_metadata_id ON resource_link_eve
CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id);
+CREATE UNIQUE INDEX unique_organizations_on_name_lower ON organizations USING btree (lower(name));
+
CREATE UNIQUE INDEX unique_packages_project_id_and_name_and_version_when_debian ON packages_packages USING btree (project_id, name, version) WHERE ((package_type = 9) AND (status <> 4));
CREATE UNIQUE INDEX unique_postgres_async_fk_validations_name_and_table_name ON postgres_async_foreign_key_validations USING btree (name, table_name);
@@ -37021,6 +37048,9 @@ ALTER TABLE ONLY requirements_management_test_reports
ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT fk_rails_d2711daad4 FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE SET NULL;
+ALTER TABLE ONLY design_management_repository_states
+ ADD CONSTRAINT fk_rails_d2a258cc5a FOREIGN KEY (design_management_repository_id) REFERENCES design_management_repositories(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY web_hooks
ADD CONSTRAINT fk_rails_d35697648e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/topics/release_your_application.md b/doc/topics/release_your_application.md
index 819ed0a1224..e8dba553cab 100644
--- a/doc/topics/release_your_application.md
+++ b/doc/topics/release_your_application.md
@@ -26,6 +26,7 @@ release features incrementally.
- [Auto Deploy](autodevops/stages.md#auto-deploy) is the DevOps stage dedicated to software
deployment using GitLab CI/CD. Auto Deploy has built-in support for EC2 and ECS deployments.
- Deploy to Kubernetes clusters by using the [GitLab agent](../user/clusters/agent/install/index.md).
+- View an example of [how to structure a GitOps deployment repository](../user/clusters/agent/gitops/example_repository_structure.md).
- Use Docker images to run AWS commands from GitLab CI/CD, and a template to
facilitate [deployment to AWS](../ci/cloud_deployment).
- Use GitLab CI/CD to target any type of infrastructure accessible by GitLab Runner.
diff --git a/doc/user/clusters/agent/gitops/example_repository_structure.md b/doc/user/clusters/agent/gitops/example_repository_structure.md
new file mode 100644
index 00000000000..93c05e40bfd
--- /dev/null
+++ b/doc/user/clusters/agent/gitops/example_repository_structure.md
@@ -0,0 +1,78 @@
+---
+stage: Deploy
+group: Environments
+info: An example of how to structure a repository for GitOps deployments
+---
+
+# Example GitOps repository structure **(FREE)**
+
+This page describes an example structure for a project that builds and deploys an application
+to a Kubernetes cluster with [GitOps](https://about.gitlab.com/topics/gitops) and the
+[GitLab agent for Kubernetes](../../agent/gitops.md).
+
+You can find an example project that uses this structure
+[in this GitLab repository](https://gitlab.com/tigerwnz/minimal-gitops-app). You can use the example project
+as a starting point to create your own deployment project.
+
+## Deployment workflow
+
+The default branch is the single source of truth for your application and the
+Kubernetes manifests that deploy it. To be reflected in a Kubernetes cluster,
+a code or configuration change must exist in the default branch.
+
+A GitLab agent for Kubernetes is installed in every Kubernetes cluster. The agent
+is configured to sync manifests from a corresponding branch in the repository.
+These branches represent the state of each cluster, and contain only commits that
+exist in the default branch.
+
+Changes are deployed by merging the default branch into the branch of a cluster.
+The agent that watches the branch picks up the change and syncs it to the cluster.
+
+For the actual deployment, the example project uses the GitLab agent for Kubernetes,
+but you can also use other GitOps tools.
+
+### Review apps
+
+Ephemeral environments such as [review apps](../../../../ci/review_apps/index.md)
+are deployed differently. Their configuration does not exist on the default branch,
+and the changes are not meant to be deployed to a permanent environment. Review app
+manifests are generated and deployed in a merge request feature branch, which is removed
+when the MR is merged.
+
+## Example deployment
+
+The example project deploys to two permanent environments, staging and production,
+which each have a dedicated Kubernetes cluster. A third cluster is used for ephemeral
+review apps.
+
+Each cluster has a corresponding branch that represents the current state of the cluster:
+`_gitlab/agents/staging`, `_gitlab/agents/production` and `_gitlab/agents/review`. Each branch is
+[protected](../../../../user/project/protected_branches.md) and
+a [project access token](../../../../user/project/settings/project_access_tokens.md)
+is created for each branch with a configuration that allows only the corresponding token to push to the branch.
+This ensures that environment branches are updated only through the configured process.
+
+Deployment branches are updated by CI/CD jobs. The access token that allows pushing to each
+branch is configured as a [CI/CD variable](../../../../ci/variables/index.md). These variables
+are protected, and only available to pipelines running on a protected branch.
+The CI/CD job merges the default branch `main` into the deployment branch, and pushes
+the deployment branch back to the repository using the provided token. To preserve the
+commit history between both branches, the CI/CD job uses a fast-forward merge.
+
+Each cluster has an agent for Kubernetes, and each agent is configured to
+[sync manifests from the branch corresponding to its cluster](../gitops.md#gitops-configuration-reference).
+In your own project, you can different GitOps tool like Flux, or use the same configuration to deploy
+to virtual machines with GitLab CI/CD.
+
+### Application changes
+
+The example project follows this process to deploy an application change:
+
+1. A new feature branch is created with the desired changes. The pipeline builds an image,
+ runs the test suite, and deploy the changes to a review app in the `review` cluster.
+1. The feature branch is merged to `main` and the review app is removed.
+1. Manifests are updated on `main` (either directly or via merge request) to point to an updated
+ version of the deployed image. The pipeline automatically merges `main` into the `_gitlab/agents/staging`
+ branch, which updates the `staging` cluster.
+1. The `production` job is triggered manually, and merges `main` into the `_gitlab/agents/production` branch,
+ deploying to the `production` cluster.
diff --git a/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
index 733ba4e4954..1ed4cd86e82 100644
--- a/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
@@ -13,7 +13,7 @@ stages:
- dast
variables:
- DAST_VERSION: 3
+ DAST_VERSION: 4
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 6e1d96d4add..792bd7f666b 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -22,7 +22,7 @@
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
variables:
- DAST_VERSION: 3
+ DAST_VERSION: 4
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
index de8a21819cc..d1d1c4d7e52 100644
--- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
@@ -22,7 +22,7 @@
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
variables:
- DAST_VERSION: 3
+ DAST_VERSION: 4
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
diff --git a/lib/sidebars/admin/menus/admin_settings_menu.rb b/lib/sidebars/admin/menus/admin_settings_menu.rb
index 163c32ad0a9..4656e0f33e2 100644
--- a/lib/sidebars/admin/menus/admin_settings_menu.rb
+++ b/lib/sidebars/admin/menus/admin_settings_menu.rb
@@ -35,6 +35,11 @@ module Sidebars
{ 'data-qa-selector': 'admin_settings_menu_link' }
end
+ override :separated?
+ def separated?
+ true
+ end
+
private
def general_settings_menu_item
diff --git a/lib/sidebars/groups/menus/settings_menu.rb b/lib/sidebars/groups/menus/settings_menu.rb
index 76c5f9c16a5..b8f345c1ed5 100644
--- a/lib/sidebars/groups/menus/settings_menu.rb
+++ b/lib/sidebars/groups/menus/settings_menu.rb
@@ -52,6 +52,11 @@ module Sidebars
true
end
+ override :separated?
+ def separated?
+ true
+ end
+
private
def general_menu_item
diff --git a/lib/sidebars/menu.rb b/lib/sidebars/menu.rb
index 432cc6ebc8b..5f9255c06d0 100644
--- a/lib/sidebars/menu.rb
+++ b/lib/sidebars/menu.rb
@@ -66,6 +66,11 @@ module Sidebars
@renderable_items ||= @items.select(&:render?)
end
+ # Defines whether menu is separated from others with a top separator
+ def separated?
+ false
+ end
+
# Returns a tree-like representation of itself and all
# renderable menu entries, with additional information
# on whether the item(s) have an active route
@@ -79,7 +84,8 @@ module Sidebars
link: link,
is_active: is_active,
pill_count: has_pill? ? pill_count : nil,
- items: items
+ items: items,
+ separated: separated?
}
end
diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb
index 5138c1534b8..9ed142f7f60 100644
--- a/lib/sidebars/projects/menus/settings_menu.rb
+++ b/lib/sidebars/projects/menus/settings_menu.rb
@@ -49,6 +49,11 @@ module Sidebars
true
end
+ override :separated?
+ def separated?
+ true
+ end
+
private
def general_menu_item
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b2d1169af7b..99cda6ff71b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8751,6 +8751,9 @@ msgstr ""
msgid "Cannot delete the default framework"
msgstr ""
+msgid "Cannot delete the default organization"
+msgstr ""
+
msgid "Cannot have multiple Jira imports running at the same time"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
index d79befc6837..b39475b481d 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
@@ -1,10 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :orchestrated, :skip_live_env, product_group: :container_registry, quarantine: {
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/399556',
- type: :flaky
- } do
+ RSpec.describe 'Package', :orchestrated, :skip_live_env, product_group: :container_registry do
describe 'Self-managed Container Registry' do
include Support::Helpers::MaskToken
@@ -48,7 +45,6 @@ module QA
after do
runner.remove_via_api!
- project.remove_via_api!
end
context "when tls is disabled" do
diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb
index a6684a8f95f..7ff0493d140 100644
--- a/spec/factories/organizations.rb
+++ b/spec/factories/organizations.rb
@@ -1,5 +1,16 @@
# frozen_string_literal: true
FactoryBot.define do
- factory :organization
+ factory :organization do
+ sequence(:name) { |n| "Organization ##{n}" }
+
+ trait :default do
+ id { Organization::DEFAULT_ORGANIZATION_ID }
+ name { 'Default' }
+ initialize_with do
+ # Ensure we only use one default organization
+ Organization.find_by(id: Organization::DEFAULT_ORGANIZATION_ID) || new(**attributes)
+ end
+ end
+ end
end
diff --git a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
index 2861fc35342..f1a5c4169fb 100644
--- a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
@@ -11,12 +11,25 @@ describe('CI Editor Header', () => {
let wrapper;
let trackingSpy = null;
- const createComponent = ({ showDrawer = false, showJobAssistantDrawer = false } = {}) => {
+ const createComponent = ({
+ showDrawer = false,
+ showJobAssistantDrawer = false,
+ showAiAssistantDrawer = false,
+ aiChatAvailable = false,
+ aiCiConfigGenerator = false,
+ } = {}) => {
wrapper = extendedWrapper(
shallowMount(CiEditorHeader, {
+ provide: {
+ aiChatAvailable,
+ glFeatures: {
+ aiCiConfigGenerator,
+ },
+ },
propsData: {
showDrawer,
showJobAssistantDrawer,
+ showAiAssistantDrawer,
},
}),
);
@@ -24,6 +37,7 @@ describe('CI Editor Header', () => {
const findLinkBtn = () => wrapper.findByTestId('template-repo-link');
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
+ const findAiAssistnantBtn = () => wrapper.findByTestId('ai-assistant-drawer-toggle');
afterEach(() => {
unmockTracking();
@@ -39,7 +53,29 @@ describe('CI Editor Header', () => {
label,
});
};
+ describe('Ai Assistant toggle button', () => {
+ describe('when feature is unavailable', () => {
+ it('should not show ai button when feature toggle is off', () => {
+ createComponent({ aiChatAvailable: true });
+ mockTracking(undefined, wrapper.element, jest.spyOn);
+ expect(findAiAssistnantBtn().exists()).toBe(false);
+ });
+
+ it('should not show ai button when feature is unavailable', () => {
+ createComponent({ aiCiConfigGenerator: true });
+ mockTracking(undefined, wrapper.element, jest.spyOn);
+ expect(findAiAssistnantBtn().exists()).toBe(false);
+ });
+ });
+ describe('when feature is available', () => {
+ it('should show ai button', () => {
+ createComponent({ aiCiConfigGenerator: true, aiChatAvailable: true });
+ mockTracking(undefined, wrapper.element, jest.spyOn);
+ expect(findAiAssistnantBtn().exists()).toBe(true);
+ });
+ });
+ });
describe('link button', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
index cbdf01105c7..471b033913b 100644
--- a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -57,6 +57,7 @@ describe('Pipeline editor tabs component', () => {
isNewCiConfigFile: true,
showDrawer: false,
showJobAssistantDrawer: false,
+ showAiAssistantDrawer: false,
...props,
},
data() {
@@ -65,6 +66,7 @@ describe('Pipeline editor tabs component', () => {
};
},
provide: {
+ aiChatAvailable: false,
ciConfigPath: '/path/to/ci-config',
ciLintPath: mockCiLintPath,
currentBranch: 'main',
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
index 7ec6d4c6a01..576263d5418 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
@@ -41,6 +41,7 @@ describe('Pipeline editor home wrapper', () => {
...props,
},
provide: {
+ aiChatAvailable: false,
projectFullPath: '',
totalBranches: 19,
glFeatures: {
diff --git a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
index 7773950708f..9451f35ac5b 100644
--- a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
+++ b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap
@@ -38,13 +38,13 @@ exports[`Design management list item component with notes renders item with mult
</div>
<div
- class="card-footer gl-display-flex gl-w-full"
+ class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4"
>
<div
class="gl-display-flex gl-flex-direction-column str-truncated-100"
>
<span
- class="gl-font-weight-bold str-truncated-100"
+ class="gl-font-weight-semibold str-truncated-100"
data-qa-selector="design_file_name"
data-testid="design-img-filename-1"
title="test"
@@ -118,13 +118,13 @@ exports[`Design management list item component with notes renders item with sing
</div>
<div
- class="card-footer gl-display-flex gl-w-full"
+ class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4"
>
<div
class="gl-display-flex gl-flex-direction-column str-truncated-100"
>
<span
- class="gl-font-weight-bold str-truncated-100"
+ class="gl-font-weight-semibold str-truncated-100"
data-qa-selector="design_file_name"
data-testid="design-img-filename-1"
title="test"
diff --git a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
index ef1ed9bee51..7da0652faba 100644
--- a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap
@@ -9,7 +9,9 @@ exports[`Design management index page designs renders error 1`] = `
<!---->
- <div>
+ <div
+ class="gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5"
+ >
<gl-alert-stub
dismisslabel="Dismiss"
primarybuttonlink=""
@@ -41,7 +43,9 @@ exports[`Design management index page designs renders loading icon 1`] = `
<!---->
- <div>
+ <div
+ class="gl-bg-gray-10 gl-border gl-border-t-0 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-5"
+ >
<gl-loading-icon-stub
color="dark"
label="Loading"
diff --git a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
index 26b146f0c8b..9b726b620dd 100644
--- a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
+++ b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
@@ -1,8 +1,16 @@
import { mountExtended } from 'helpers/vue_test_utils_helper';
import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue';
+import PinnedSection from '~/super_sidebar/components/pinned_section.vue';
import { PANELS_WITH_PINS } from '~/super_sidebar/constants';
import { sidebarData } from '../mock_data';
+const menuItems = [
+ { id: 1, title: 'No subitems' },
+ { id: 2, title: 'With subitems', items: [{ id: 21, title: 'Pinned subitem' }] },
+ { id: 3, title: 'Empty subitems array', items: [] },
+ { id: 4, title: 'Also with subitems', items: [{ id: 41, title: 'Subitem' }] },
+];
+
describe('SidebarMenu component', () => {
let wrapper;
@@ -17,14 +25,10 @@ describe('SidebarMenu component', () => {
});
};
- describe('computed', () => {
- const menuItems = [
- { id: 1, title: 'No subitems' },
- { id: 2, title: 'With subitems', items: [{ id: 21, title: 'Pinned subitem' }] },
- { id: 3, title: 'Empty subitems array', items: [] },
- { id: 4, title: 'Also with subitems', items: [{ id: 41, title: 'Subitem' }] },
- ];
+ const findPinnedSection = () => wrapper.findComponent(PinnedSection);
+ const findMainMenuSeparator = () => wrapper.findByTestId('main-menu-separator');
+ describe('computed', () => {
describe('supportsPins', () => {
it('is true for the project sidebar', () => {
createWrapper({ ...sidebarData, panel_type: 'project' });
@@ -148,4 +152,33 @@ describe('SidebarMenu component', () => {
});
});
});
+
+ describe('Menu separators', () => {
+ it('should add the separator above pinned section', () => {
+ createWrapper({
+ ...sidebarData,
+ current_menu_items: menuItems,
+ panel_type: 'project',
+ });
+ expect(findPinnedSection().props('separated')).toBe(true);
+ });
+
+ it('should add the separator above main menu items when there is a pinned section', () => {
+ createWrapper({
+ ...sidebarData,
+ current_menu_items: menuItems,
+ panel_type: PANELS_WITH_PINS[0],
+ });
+ expect(findMainMenuSeparator().exists()).toBe(true);
+ });
+
+ it('should NOT add the separator above main menu items when there is no pinned section', () => {
+ createWrapper({
+ ...sidebarData,
+ current_menu_items: menuItems,
+ panel_type: 'explore',
+ });
+ expect(findMainMenuSeparator().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/helpers/ci/pipeline_editor_helper_spec.rb b/spec/helpers/ci/pipeline_editor_helper_spec.rb
index 1a0f2e10a20..b45882d9888 100644
--- a/spec/helpers/ci/pipeline_editor_helper_spec.rb
+++ b/spec/helpers/ci/pipeline_editor_helper_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::PipelineEditorHelper do
let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
describe 'can_view_pipeline_editor?' do
subject { helper.can_view_pipeline_editor?(project) }
@@ -62,6 +63,10 @@ RSpec.describe Ci::PipelineEditorHelper do
.to receive(:image_path)
.with('illustrations/project-run-CICD-pipelines-sm.svg')
.and_return('illustrations/validate.svg')
+
+ allow(helper)
+ .to receive(:current_user)
+ .and_return(user)
end
subject(:pipeline_editor_data) { helper.js_pipeline_editor_data(project) }
diff --git a/spec/helpers/projects/ml/experiments_helper_spec.rb b/spec/helpers/projects/ml/experiments_helper_spec.rb
index e0fbc8ac488..021d518a329 100644
--- a/spec/helpers/projects/ml/experiments_helper_spec.rb
+++ b/spec/helpers/projects/ml/experiments_helper_spec.rb
@@ -98,41 +98,6 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do
end
end
- describe '#show_candidate_view_model' do
- let(:candidate) { candidate0 }
-
- subject { Gitlab::Json.parse(helper.show_candidate_view_model(candidate))['candidate'] }
-
- it 'generates the correct params' do
- expect(subject['params']).to include(
- hash_including('name' => 'param1', 'value' => 'p1'),
- hash_including('name' => 'param2', 'value' => 'p2')
- )
- end
-
- it 'generates the correct metrics' do
- expect(subject['metrics']).to include(
- hash_including('name' => 'metric1', 'value' => 0.1),
- hash_including('name' => 'metric2', 'value' => 0.2),
- hash_including('name' => 'metric3', 'value' => 0.3)
- )
- end
-
- it 'generates the correct info' do
- expected_info = {
- 'iid' => candidate.iid,
- 'eid' => candidate.eid,
- 'path_to_artifact' => "/#{project.full_path}/-/packages/#{candidate.artifact.id}",
- 'experiment_name' => candidate.experiment.name,
- 'path_to_experiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}",
- 'status' => 'running',
- 'path' => "/#{project.full_path}/-/ml/candidates/#{candidate.iid}"
- }
-
- expect(subject['info']).to include(expected_info)
- end
- end
-
describe '#experiments_as_data' do
let(:experiments) { [experiment] }
diff --git a/spec/lib/sidebars/admin/menus/admin_settings_menu_spec.rb b/spec/lib/sidebars/admin/menus/admin_settings_menu_spec.rb
index be23dd4d25b..4c9f603e99f 100644
--- a/spec/lib/sidebars/admin/menus/admin_settings_menu_spec.rb
+++ b/spec/lib/sidebars/admin/menus/admin_settings_menu_spec.rb
@@ -6,7 +6,8 @@ RSpec.describe Sidebars::Admin::Menus::AdminSettingsMenu, feature_category: :nav
it_behaves_like 'Admin menu',
link: '/admin/application_settings/general',
title: s_('Admin|Settings'),
- icon: 'settings'
+ icon: 'settings',
+ separated: true
it_behaves_like 'Admin menu with sub menus'
end
diff --git a/spec/lib/sidebars/groups/menus/settings_menu_spec.rb b/spec/lib/sidebars/groups/menus/settings_menu_spec.rb
index c5246fe93dd..bc30d7628af 100644
--- a/spec/lib/sidebars/groups/menus/settings_menu_spec.rb
+++ b/spec/lib/sidebars/groups/menus/settings_menu_spec.rb
@@ -25,6 +25,12 @@ RSpec.describe Sidebars::Groups::Menus::SettingsMenu, :with_license do
end
end
+ describe '#separated?' do
+ it 'returns true' do
+ expect(menu.separated?).to be true
+ end
+ end
+
describe 'Menu items' do
subject { menu.renderable_items.find { |e| e.item_id == item_id } }
diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb
index 21aec10ec27..4f77cb3aed4 100644
--- a/spec/lib/sidebars/menu_spec.rb
+++ b/spec/lib/sidebars/menu_spec.rb
@@ -54,6 +54,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
link: "foo2",
is_active: true,
pill_count: nil,
+ separated: false,
items: [
{
id: 'id1',
@@ -87,6 +88,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
link: nil,
is_active: false,
pill_count: 'foo',
+ separated: false,
items: []
})
end
diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
index c7aca0fb97e..4be99892631 100644
--- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
@@ -22,6 +22,12 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu do
end
end
+ describe '#separated?' do
+ it 'returns true' do
+ expect(subject.separated?).to be true
+ end
+ end
+
describe 'Menu items' do
subject { described_class.new(context).renderable_items.find { |e| e.item_id == item_id } }
diff --git a/spec/migrations/20230509131736_add_default_organization_spec.rb b/spec/migrations/20230509131736_add_default_organization_spec.rb
new file mode 100644
index 00000000000..539216c57ee
--- /dev/null
+++ b/spec/migrations/20230509131736_add_default_organization_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddDefaultOrganization, feature_category: :cell do
+ let(:organization) { table(:organizations) }
+
+ it "correctly migrates up and down" do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(organization.where(id: 1, name: 'Default')).to be_empty
+ }
+ migration.after -> {
+ expect(organization.where(id: 1, name: 'Default')).not_to be_empty
+ }
+ end
+ end
+end
diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb
index 9966a7132ce..e1aac88e640 100644
--- a/spec/models/organization_spec.rb
+++ b/spec/models/organization_spec.rb
@@ -2,9 +2,97 @@
require 'spec_helper'
-# rubocop: disable Lint/EmptyBlock
-# rubocop: disable RSpec/EmptyExampleGroup
RSpec.describe Organization, type: :model, feature_category: :cell do
+ let_it_be(:organization) { create(:organization) }
+ let_it_be(:default_organization) { create(:organization, :default) }
+
+ describe 'validations' do
+ subject { create(:organization) }
+
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_uniqueness_of(:name).case_insensitive }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ end
+
+ context 'when using scopes' do
+ describe '.without_default' do
+ it 'excludes default organization' do
+ expect(described_class.without_default).not_to include(default_organization)
+ end
+
+ it 'includes other organizations organization' do
+ expect(described_class.without_default).to include(organization)
+ end
+ end
+ end
+
+ describe '#id' do
+ context 'when organization is default' do
+ it 'has id 1' do
+ expect(default_organization.id).to eq(1)
+ end
+ end
+
+ context 'when organization is not default' do
+ it 'does not have id 1' do
+ expect(organization.id).not_to eq(1)
+ end
+ end
+ end
+
+ describe '#destroy!' do
+ context 'when trying to delete the default organization' do
+ it 'raises an error' do
+ expect do
+ default_organization.destroy!
+ end.to raise_error(ActiveRecord::RecordNotDestroyed, _('Cannot delete the default organization'))
+ end
+ end
+
+ context 'when trying to delete a non-default organization' do
+ let(:to_be_removed) { create(:organization) }
+
+ it 'does not raise error' do
+ expect { to_be_removed.destroy! }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#destroy' do
+ context 'when trying to delete the default organization' do
+ it 'returns false' do
+ expect(default_organization.destroy).to eq(false)
+ end
+ end
+
+ context 'when trying to delete a non-default organization' do
+ let(:to_be_removed) { create(:organization) }
+
+ it 'returns true' do
+ expect(to_be_removed.destroy).to eq(to_be_removed)
+ end
+ end
+ end
+
+ describe '#default?' do
+ context 'when organization is default' do
+ it 'returns true' do
+ expect(default_organization.default?).to eq(true)
+ end
+ end
+
+ context 'when organization is not default' do
+ it 'returns false' do
+ expect(organization.default?).to eq(false)
+ end
+ end
+ end
+
+ describe '#name' do
+ context 'when organization is default' do
+ it 'returns Default' do
+ expect(default_organization.name).to eq('Default')
+ end
+ end
+ end
end
-# rubocop: enable RSpec/EmptyExampleGroup
-# rubocop: enable Lint/EmptyBlock
diff --git a/spec/presenters/ml/candidate_details_presenter_spec.rb b/spec/presenters/ml/candidate_details_presenter_spec.rb
new file mode 100644
index 00000000000..d83ffbc7129
--- /dev/null
+++ b/spec/presenters/ml/candidate_details_presenter_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Ml::CandidateDetailsPresenter, feature_category: :mlops do
+ let_it_be(:project) { create(:project, :private) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
+ let_it_be(:user) { project.creator }
+ let_it_be(:experiment) { create(:ml_experiments, user: user, project: project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
+ let_it_be(:candidate) do
+ create(:ml_candidates, :with_artifact, experiment: experiment, user: user, project: project) # rubocop:disable RSpec/FactoryBot/AvoidCreate
+ end
+
+ let_it_be(:metrics) do
+ [
+ build_stubbed(:ml_candidate_metrics, name: 'metric1', value: 0.1, candidate: candidate),
+ build_stubbed(:ml_candidate_metrics, name: 'metric2', value: 0.2, candidate: candidate),
+ build_stubbed(:ml_candidate_metrics, name: 'metric3', value: 0.3, candidate: candidate)
+ ]
+ end
+
+ let_it_be(:params) do
+ [
+ build_stubbed(:ml_candidate_params, name: 'param1', value: 'p1', candidate: candidate),
+ build_stubbed(:ml_candidate_params, name: 'param2', value: 'p2', candidate: candidate)
+ ]
+ end
+
+ subject { Gitlab::Json.parse(described_class.new(candidate).present)['candidate'] }
+
+ before do
+ allow(candidate).to receive(:latest_metrics).and_return(metrics)
+ allow(candidate).to receive(:params).and_return(params)
+ end
+
+ describe '#execute' do
+ context 'when candidate has metrics, params and artifacts' do
+ it 'generates the correct params' do
+ expect(subject['params']).to include(
+ hash_including('name' => 'param1', 'value' => 'p1'),
+ hash_including('name' => 'param2', 'value' => 'p2')
+ )
+ end
+
+ it 'generates the correct metrics' do
+ expect(subject['metrics']).to include(
+ hash_including('name' => 'metric1', 'value' => 0.1),
+ hash_including('name' => 'metric2', 'value' => 0.2),
+ hash_including('name' => 'metric3', 'value' => 0.3)
+ )
+ end
+
+ it 'generates the correct info' do
+ expected_info = {
+ 'iid' => candidate.iid,
+ 'eid' => candidate.eid,
+ 'path_to_artifact' => "/#{project.full_path}/-/packages/#{candidate.artifact.id}",
+ 'experiment_name' => candidate.experiment.name,
+ 'path_to_experiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}",
+ 'status' => 'running',
+ 'path' => "/#{project.full_path}/-/ml/candidates/#{candidate.iid}"
+ }
+
+ expect(subject['info']).to include(expected_info)
+ end
+ end
+
+ context 'when candidate has job' do
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline, project: project, user: user) }
+ let_it_be(:build) { candidate.ci_build = build_stubbed(:ci_build, pipeline: pipeline, user: user) }
+
+ it 'generates the correct ci' do
+ expected_info = {
+ 'path' => "/#{project.full_path}/-/jobs/#{build.id}",
+ 'name' => 'test',
+ 'user' => {
+ 'path' => "/#{pipeline.user.username}",
+ 'username' => pipeline.user.username
+ }
+ }
+
+ expect(subject.dig('info', 'ci_job')).to include(expected_info)
+ end
+
+ context 'when build user is nil' do
+ it 'does not include build user info' do
+ expected_info = {
+ 'path' => "/#{project.full_path}/-/jobs/#{build.id}",
+ 'name' => 'test'
+ }
+
+ allow(build).to receive(:user).and_return(nil)
+
+ expect(subject.dig('info', 'ci_job')).to eq(expected_info)
+ end
+ end
+
+ context 'and job is from MR' do
+ let_it_be(:mr) { pipeline.merge_request = build_stubbed(:merge_request, source_project: project) }
+
+ it 'generates the correct ci' do
+ expected_info = {
+ 'path' => "/#{project.full_path}/-/merge_requests/#{mr.iid}",
+ 'title' => mr.title
+ }
+
+ expect(subject.dig('info', 'ci_job', 'merge_request')).to include(expected_info)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index a53e1e1002c..ceb567e54c4 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -372,6 +372,7 @@ module TestEnv
def seed_db
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions
+ FactoryBot.create(:organization, :default)
end
private
diff --git a/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb
index a9fd66528bd..f913c6b8a9e 100644
--- a/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'Admin menu' do |link:, title:, icon:|
+RSpec.shared_examples 'Admin menu' do |link:, title:, icon:, separated: false|
let_it_be(:user) { build(:user, :admin) }
before do
@@ -23,6 +23,10 @@ RSpec.shared_examples 'Admin menu' do |link:, title:, icon:|
expect(subject.sprite_icon).to be icon
end
+ it 'renders the separator if needed' do
+ expect(subject.separated?).to be separated
+ end
+
describe '#render?' do
context 'when user is admin' do
it 'renders' do