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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-15 18:14:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-15 18:14:09 +0300
commit3f9e76c30fd80f01bbe2e69569851fa5e3ea71d6 (patch)
treeb6fcb33e71119081b99ea4b820e4c7fb87c52b4d
parentd221a274982118d92d424ce382646a7ef2f5887a (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/rspec/expect_in_hook.yml1
-rw-r--r--.rubocop_todo/rspec/feature_category.yml9
-rw-r--r--.rubocop_todo/rspec/file_path.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue2
-rw-r--r--app/assets/javascripts/editor/schema/ci.json24
-rw-r--r--app/assets/javascripts/emoji/index.js33
-rw-r--r--app/assets/javascripts/organizations/users/components/app.vue13
-rw-r--r--app/assets/javascripts/organizations/users/components/users_view.vue30
-rw-r--r--app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql6
-rw-r--r--app/assets/javascripts/organizations/users/index.js5
-rw-r--r--app/assets/javascripts/search/index.js1
-rw-r--r--app/assets/javascripts/search/sidebar/components/app.vue43
-rw-r--r--app/assets/javascripts/search/sidebar/components/archived_filter/index.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/blobs_filters.vue16
-rw-r--r--app/assets/javascripts/search/sidebar/components/confidentiality_filter/index.vue5
-rw-r--r--app/assets/javascripts/search/sidebar/components/filters_template.vue6
-rw-r--r--app/assets/javascripts/search/sidebar/components/issues_filters.vue13
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/index.vue15
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter/index.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/merge_requests_filters.vue11
-rw-r--r--app/assets/javascripts/search/sidebar/components/radio_filter.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue85
-rw-r--r--app/assets/javascripts/search/sidebar/components/small_screen_drawer_navigation.vue61
-rw-r--r--app/assets/javascripts/search/sidebar/components/status_filter/index.vue2
-rw-r--r--app/assets/javascripts/search/sidebar/constants/index.js2
-rw-r--r--app/assets/javascripts/search/store/state.js3
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue11
-rw-r--r--app/assets/stylesheets/page_bundles/search.scss20
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss10
-rw-r--r--app/helpers/organizations/organization_helper.rb12
-rw-r--r--app/helpers/sidebars_helper.rb2
-rw-r--r--app/validators/json_schema_validator.rb15
-rw-r--r--app/views/organizations/organizations/users.html.haml2
-rw-r--r--app/views/search/show.html.haml3
-rw-r--r--config/feature_flags/development/ci_catalog_create_metadata.yml2
-rw-r--r--config/feature_flags/development/code_tasks.yml8
-rw-r--r--db/click_house/migrate/20230724064832_create_contribution_analytics_events.rb2
-rw-r--r--doc/administration/backup_restore/backup_gitlab.md8
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/ci/yaml/index.md63
-rw-r--r--doc/tutorials/website_project_with_analytics/index.md2
-rw-r--r--doc/update/index.md2
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/okrs.md12
-rw-r--r--doc/user/project/members/img/project_members_filter_direct_v14_4.pngbin24945 -> 0 bytes
-rw-r--r--doc/user/project/members/img/project_members_filter_inherited_v14_4.pngbin33118 -> 0 bytes
-rw-r--r--doc/user/project/members/img/project_members_search_v14_4.pngbin23277 -> 0 bytes
-rw-r--r--doc/user/project/members/img/project_members_sort_v14_4.pngbin44923 -> 0 bytes
-rw-r--r--doc/user/project/members/index.md46
-rw-r--r--lib/api/entities/ci/job_request/image.rb1
-rw-r--r--lib/api/entities/ci/job_request/service.rb1
-rw-r--r--lib/gitlab/ci/build/image.rb4
-rw-r--r--lib/gitlab/ci/config/entry/image.rb15
-rw-r--r--lib/gitlab/ci/config/entry/imageable.rb34
-rw-r--r--lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json19
-rw-r--r--lib/gitlab/ci/config/entry/service.rb11
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/page/search/results.rb5
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml28
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml14
-rw-r--r--spec/frontend/emoji/index_spec.js27
-rw-r--r--spec/frontend/organizations/users/components/app_spec.js28
-rw-r--r--spec/frontend/organizations/users/components/users_view_spec.js44
-rw-r--r--spec/frontend/organizations/users/mock_data.js26
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js55
-rw-r--r--spec/frontend/search/sidebar/components/blobs_filters_spec.js34
-rw-r--r--spec/frontend/search/sidebar/components/confidentiality_filter_spec.js20
-rw-r--r--spec/frontend/search/sidebar/components/filters_template_spec.js5
-rw-r--r--spec/frontend/search/sidebar/components/issues_filters_spec.js50
-rw-r--r--spec/frontend/search/sidebar/components/merge_requests_filters_spec.js34
-rw-r--r--spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js145
-rw-r--r--spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js68
-rw-r--r--spec/frontend/search/sidebar/components/status_filter_spec.js20
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js22
-rw-r--r--spec/frontend/super_sidebar/mock_data.js2
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb2
-rw-r--r--spec/helpers/organizations/organization_helper_spec.rb9
-rw-r--r--spec/helpers/sidebars_helper_spec.rb10
-rw-r--r--spec/helpers/storage_helper_spec.rb2
-rw-r--r--spec/lib/api/entities/ci/job_request/image_spec.rb9
-rw-r--r--spec/lib/api/entities/ci/job_request/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb40
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb57
-rw-r--r--spec/requests/api/ci/runner/jobs_request_yamls_spec.rb64
-rw-r--r--spec/requests/api/ci/runner/yamls/README.md15
-rw-r--r--spec/requests/api/ci/runner/yamls/image-basic.yml19
-rw-r--r--spec/requests/api/ci/runner/yamls/image-executor_opts-platform.yml25
-rw-r--r--spec/requests/api/ci/runner/yamls/service-basic.yml23
-rw-r--r--spec/requests/api/ci/runner/yamls/service-executor_opts-platform.yml27
-rw-r--r--spec/support/rspec_order_todo.yml1
-rw-r--r--spec/validators/json_schema_validator_spec.rb27
95 files changed, 917 insertions, 848 deletions
diff --git a/.rubocop_todo/rspec/expect_in_hook.yml b/.rubocop_todo/rspec/expect_in_hook.yml
index aa150006d08..dbf7abf6905 100644
--- a/.rubocop_todo/rspec/expect_in_hook.yml
+++ b/.rubocop_todo/rspec/expect_in_hook.yml
@@ -73,7 +73,6 @@ RSpec/ExpectInHook:
- 'ee/spec/services/members/await_service_spec.rb'
- 'ee/spec/services/merge_requests/mergeability/check_approved_service_spec.rb'
- 'ee/spec/services/merge_requests/mergeability/check_blocked_by_other_mrs_service_spec.rb'
- - 'ee/spec/services/merge_requests/mergeability/check_denied_policies_service_spec.rb'
- 'ee/spec/services/projects/create_from_template_service_spec.rb'
- 'ee/spec/services/projects/mark_for_deletion_service_spec.rb'
- 'ee/spec/services/projects/update_mirror_service_spec.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index 57bd3343860..eb740c099c3 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -1,7 +1,6 @@
---
RSpec/FeatureCategory:
Exclude:
- - 'ee/spec/components/namespaces/storage/subgroup_pre_enforcement_alert_component_spec.rb'
- 'ee/spec/controllers/admin/application_settings_controller_spec.rb'
- 'ee/spec/controllers/admin/clusters_controller_spec.rb'
- 'ee/spec/controllers/autocomplete_controller_spec.rb'
@@ -652,7 +651,6 @@ RSpec/FeatureCategory:
- 'ee/spec/lib/ee/gitlab/issuable_metadata_spec.rb'
- 'ee/spec/lib/ee/gitlab/metrics/samplers/database_sampler_spec.rb'
- 'ee/spec/lib/ee/gitlab/middleware/read_only_spec.rb'
- - 'ee/spec/lib/ee/gitlab/namespace_storage_size_error_message_spec.rb'
- 'ee/spec/lib/ee/gitlab/omniauth_initializer_spec.rb'
- 'ee/spec/lib/ee/gitlab/pages/deployment_update_spec.rb'
- 'ee/spec/lib/ee/gitlab/rack_attack/request_spec.rb'
@@ -950,7 +948,6 @@ RSpec/FeatureCategory:
- 'ee/spec/mailers/ee/emails/projects_spec.rb'
- 'ee/spec/mailers/emails/epics_spec.rb'
- 'ee/spec/mailers/emails/group_memberships_spec.rb'
- - 'ee/spec/mailers/emails/namespace_storage_usage_mailer_spec.rb'
- 'ee/spec/mailers/emails/requirements_spec.rb'
- 'ee/spec/mailers/emails/user_cap_spec.rb'
- 'ee/spec/mailers/license_mailer_spec.rb'
@@ -1058,7 +1055,6 @@ RSpec/FeatureCategory:
- 'ee/spec/models/ee/members_preloader_spec.rb'
- 'ee/spec/models/ee/merge_request/metrics_spec.rb'
- 'ee/spec/models/ee/merge_request_diff_spec.rb'
- - 'ee/spec/models/ee/namespace/root_storage_statistics_spec.rb'
- 'ee/spec/models/ee/namespace_ci_cd_setting_spec.rb'
- 'ee/spec/models/ee/namespace_spec.rb'
- 'ee/spec/models/ee/namespace_statistics_spec.rb'
@@ -1131,8 +1127,6 @@ RSpec/FeatureCategory:
- 'ee/spec/models/milestone_spec.rb'
- 'ee/spec/models/namespace_limit_spec.rb'
- 'ee/spec/models/namespace_setting_spec.rb'
- - 'ee/spec/models/namespaces/storage/root_excess_size_spec.rb'
- - 'ee/spec/models/namespaces/storage/root_size_spec.rb'
- 'ee/spec/models/packages/package_file_spec.rb'
- 'ee/spec/models/path_lock_spec.rb'
- 'ee/spec/models/plan_spec.rb'
@@ -2355,7 +2349,6 @@ RSpec/FeatureCategory:
- 'spec/graphql/types/release_type_spec.rb'
- 'spec/graphql/types/repository_type_spec.rb'
- 'spec/graphql/types/resolvable_interface_spec.rb'
- - 'spec/graphql/types/root_storage_statistics_type_spec.rb'
- 'spec/graphql/types/saved_reply_type_spec.rb'
- 'spec/graphql/types/security/report_types_enum_spec.rb'
- 'spec/graphql/types/snippet_type_spec.rb'
@@ -2502,7 +2495,6 @@ RSpec/FeatureCategory:
- 'spec/helpers/ssh_keys_helper_spec.rb'
- 'spec/helpers/startupjs_helper_spec.rb'
- 'spec/helpers/stat_anchors_helper_spec.rb'
- - 'spec/helpers/storage_helper_spec.rb'
- 'spec/helpers/subscribable_banner_helper_spec.rb'
- 'spec/helpers/tab_helper_spec.rb'
- 'spec/helpers/terms_helper_spec.rb'
@@ -4683,7 +4675,6 @@ RSpec/FeatureCategory:
- 'spec/models/namespace/aggregation_schedule_spec.rb'
- 'spec/models/namespace/detail_spec.rb'
- 'spec/models/namespace/package_setting_spec.rb'
- - 'spec/models/namespace/root_storage_statistics_spec.rb'
- 'spec/models/namespace_ci_cd_setting_spec.rb'
- 'spec/models/namespace_statistics_spec.rb'
- 'spec/models/namespaces/project_namespace_spec.rb'
diff --git a/.rubocop_todo/rspec/file_path.yml b/.rubocop_todo/rspec/file_path.yml
index 06832184c04..cf457973e0e 100644
--- a/.rubocop_todo/rspec/file_path.yml
+++ b/.rubocop_todo/rspec/file_path.yml
@@ -31,6 +31,7 @@ RSpec/FilePath:
- 'spec/requests/api/ci/runner/jobs_artifacts_spec.rb'
- 'spec/requests/api/ci/runner/jobs_put_spec.rb'
- 'spec/requests/api/ci/runner/jobs_request_post_spec.rb'
+ - 'spec/requests/api/ci/runner/jobs_request_yamls_spec.rb'
- 'spec/requests/api/ci/runner/jobs_trace_spec.rb'
- 'spec/requests/api/ci/runner/runners_delete_spec.rb'
- 'spec/requests/api/ci/runner/runners_post_spec.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e53a8e9c1eb..c93d718b5a0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-fc7f05c95d10184fa76b2e613779bce4e46dd190
+d6591534c5cabaaf8b4ebce6d4923b32310ef576
diff --git a/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue b/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue
index f6a375ab94c..ab2fe63ae05 100644
--- a/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue
+++ b/app/assets/javascripts/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue
@@ -92,7 +92,7 @@ export default {
'is-upstream': isUpstream,
'is-downstream': isDownstream,
}"
- class="linked-pipeline-mini-list gl-display-inline gl-vertical-align-middle"
+ class="linked-pipeline-mini-list gl-display-inline-flex gl-gap-2 gl-vertical-align-middle"
>
<ci-icon
v-for="pipeline in linkedPipelinesTrimmed"
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index 308a68544bc..2319ecccd5b 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -509,6 +509,18 @@
"description": "Command or script that should be executed as the container's entrypoint. It will be translated to Docker's --entrypoint option while creating the container. The syntax is similar to Dockerfile's ENTRYPOINT directive, where each shell token is a separate string in the array.",
"minItems": 1
},
+ "docker": {
+ "type": "object",
+ "description": "Options to pass to Runners Docker Executor",
+ "additionalProperties": false,
+ "properties": {
+ "platform": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Image architecture to pull."
+ }
+ }
+ },
"pull_policy": {
"markdownDescription": "Specifies how to pull the image in Runner. It can be one of `always`, `never` or `if-not-present`. The default value is `always`. [Learn more](https://docs.gitlab.com/ee/ci/yaml/#imagepull_policy).",
"default": "always",
@@ -579,6 +591,18 @@
"type": "string"
}
},
+ "docker": {
+ "type": "object",
+ "description": "Options to pass to Runners Docker Executor",
+ "additionalProperties": false,
+ "properties": {
+ "platform": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Image architecture to pull."
+ }
+ }
+ },
"pull_policy": {
"markdownDescription": "Specifies how to pull the image in Runner. It can be one of `always`, `never` or `if-not-present`. The default value is `always`. [Learn more](https://docs.gitlab.com/ee/ci/yaml/#servicepull_policy).",
"default": "always",
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index f98369c2fde..c4279e9d8e7 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -77,6 +77,8 @@ async function loadEmojiWithNames() {
}
export async function loadCustomEmojiWithNames() {
+ const emojiData = { emojis: {}, names: [] };
+
if (document.body?.dataset?.groupFullPath && window.gon?.features?.customEmoji) {
const client = createApolloClient();
const { data } = await client.query({
@@ -86,26 +88,21 @@ export async function loadCustomEmojiWithNames() {
},
});
- return data?.group?.customEmoji?.nodes?.reduce(
- (acc, e) => {
- // Map the custom emoji into the format of the normal emojis
- acc.emojis[e.name] = {
- c: 'custom',
- d: e.name,
- e: undefined,
- name: e.name,
- src: e.url,
- u: 'custom',
- };
- acc.names.push(e.name);
-
- return acc;
- },
- { emojis: {}, names: [] },
- );
+ data?.group?.customEmoji?.nodes?.forEach((e) => {
+ // Map the custom emoji into the format of the normal emojis
+ emojiData.emojis[e.name] = {
+ c: 'custom',
+ d: e.name,
+ e: undefined,
+ name: e.name,
+ src: e.url,
+ u: 'custom',
+ };
+ emojiData.names.push(e.name);
+ });
}
- return { emojis: {}, names: [] };
+ return emojiData;
}
async function prepareEmojiMap() {
diff --git a/app/assets/javascripts/organizations/users/components/app.vue b/app/assets/javascripts/organizations/users/components/app.vue
index ae22bedd69a..1d5ea895ff0 100644
--- a/app/assets/javascripts/organizations/users/components/app.vue
+++ b/app/assets/javascripts/organizations/users/components/app.vue
@@ -2,9 +2,13 @@
import { __, s__ } from '~/locale';
import { createAlert } from '~/alert';
import organizationUsersQuery from '../graphql/organization_users.query.graphql';
+import UsersView from './users_view.vue';
export default {
name: 'OrganizationsUsersApp',
+ components: {
+ UsersView,
+ },
i18n: {
users: __('Users'),
loadingPlaceholder: __('Loading'),
@@ -25,7 +29,9 @@ export default {
return { id: this.organizationGid };
},
update(data) {
- return data.organization.organizationUsers.nodes;
+ return data.organization.organizationUsers.nodes.map(({ badges, user }) => {
+ return { ...user, badges, email: user.publicEmail };
+ });
},
error(error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
@@ -43,9 +49,6 @@ export default {
<template>
<section>
<h1 class="gl-my-4 gl-font-size-h-display">{{ $options.i18n.users }}</h1>
- <template v-if="loading">
- {{ $options.i18n.loadingPlaceholder }}
- </template>
- <div data-testid="organization-users">{{ users }}</div>
+ <users-view :users="users" :loading="loading" />
</section>
</template>
diff --git a/app/assets/javascripts/organizations/users/components/users_view.vue b/app/assets/javascripts/organizations/users/components/users_view.vue
new file mode 100644
index 00000000000..fac353bdaf6
--- /dev/null
+++ b/app/assets/javascripts/organizations/users/components/users_view.vue
@@ -0,0 +1,30 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
+
+export default {
+ name: 'UsersView',
+ components: {
+ GlLoadingIcon,
+ UsersTable,
+ },
+ inject: ['paths'],
+ props: {
+ users: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-loading-icon v-if="loading" class="gl-mt-5" size="md" />
+ <users-table v-else :users="users" :admin-user-path="paths.adminUser" />
+</template>
diff --git a/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql b/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql
index a0b2a639401..d98ebf9cd26 100644
--- a/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql
+++ b/app/assets/javascripts/organizations/users/graphql/organization_users.query.graphql
@@ -10,6 +10,12 @@ query getOrganizationUsers($id: OrganizationsOrganizationID!) {
id
user {
id
+ username
+ avatarUrl
+ name
+ publicEmail
+ createdAt
+ lastActivityOn
}
}
}
diff --git a/app/assets/javascripts/organizations/users/index.js b/app/assets/javascripts/organizations/users/index.js
index 76656243075..794ae9e70a6 100644
--- a/app/assets/javascripts/organizations/users/index.js
+++ b/app/assets/javascripts/organizations/users/index.js
@@ -13,7 +13,9 @@ export const initOrganizationsUsers = () => {
defaultClient: createDefaultClient(),
});
- const { organizationGid } = convertObjectPropsToCamelCase(el.dataset);
+ const { organizationGid, paths } = convertObjectPropsToCamelCase(JSON.parse(el.dataset.appData), {
+ deep: true,
+ });
return new Vue({
el,
@@ -21,6 +23,7 @@ export const initOrganizationsUsers = () => {
apolloProvider,
provide: {
organizationGid,
+ paths,
},
render(createElement) {
return createElement(OrganizationsUsersApp);
diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js
index f83130213f2..dfb2c519c28 100644
--- a/app/assets/javascripts/search/index.js
+++ b/app/assets/javascripts/search/index.js
@@ -15,7 +15,6 @@ export const initSearchApp = () => {
const store = createStore({
query,
navigation,
- useSidebarNavigation: gon.use_new_navigation,
searchType,
});
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue
index 86a5f5107f8..e0c49412d56 100644
--- a/app/assets/javascripts/search/sidebar/components/app.vue
+++ b/app/assets/javascripts/search/sidebar/components/app.vue
@@ -2,9 +2,7 @@
// eslint-disable-next-line no-restricted-imports
import { mapState, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue';
import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue';
-import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue';
import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue';
import { toggleSuperSidebarCollapsed } from '~/super_sidebar/super_sidebar_collapsed_state_manager';
import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
@@ -37,18 +35,15 @@ export default {
ProjectsFilters,
NotesFilters,
WikiBlobsFilters,
- ScopeLegacyNavigation,
ScopeSidebarNavigation,
SidebarPortal,
DomElementListener,
- SmallScreenDrawerNavigation,
CommitsFilters,
MilestonesFilters,
},
mixins: [glFeatureFlagsMixin()],
computed: {
- // useSidebarNavigation refers to whether the new left sidebar navigation is enabled
- ...mapState(['useSidebarNavigation', 'searchType']),
+ ...mapState(['searchType']),
...mapGetters(['currentScope']),
showIssuesFilters() {
return this.currentScope === SCOPE_ISSUES;
@@ -77,12 +72,6 @@ export default {
this.glFeatures?.searchProjectWikisHideArchivedProjects
);
},
- showScopeNavigation() {
- // showScopeNavigation refers to whether the scope navigation should be shown
- // while the legacy navigation is being used and there are no search results
- // the scope navigation has to be hidden
- return Boolean(this.currentScope);
- },
},
methods: {
toggleFiltersFromSidebar() {
@@ -93,7 +82,7 @@ export default {
</script>
<template>
- <section v-if="useSidebarNavigation">
+ <section>
<dom-element-listener selector="#js-open-mobile-filters" @click="toggleFiltersFromSidebar" />
<sidebar-portal>
<scope-sidebar-navigation />
@@ -107,32 +96,4 @@ export default {
<wiki-blobs-filters v-if="showWikiBlobsFilters" />
</sidebar-portal>
</section>
-
- <section
- v-else-if="showScopeNavigation"
- class="gl-display-flex gl-flex-direction-column gl-lg-mr-0 gl-md-mr-5 gl-lg-mb-6 gl-lg-mt-5"
- >
- <div class="search-sidebar gl-display-none gl-lg-display-block">
- <scope-legacy-navigation />
- <issues-filters v-if="showIssuesFilters" />
- <merge-requests-filters v-if="showMergeRequestFilters" />
- <blobs-filters v-if="showBlobFilters" />
- <projects-filters v-if="showProjectsFilters" />
- <notes-filters v-if="showNotesFilters" />
- <commits-filters v-if="showCommitsFilters" />
- <milestones-filters v-if="showMilestonesFilters" />
- <wiki-blobs-filters v-if="showWikiBlobsFilters" />
- </div>
- <small-screen-drawer-navigation class="gl-lg-display-none">
- <scope-legacy-navigation />
- <issues-filters v-if="showIssuesFilters" />
- <merge-requests-filters v-if="showMergeRequestFilters" />
- <blobs-filters v-if="showBlobFilters" />
- <projects-filters v-if="showProjectsFilters" />
- <notes-filters v-if="showNotesFilters" />
- <commits-filters v-if="showCommitsFilters" />
- <milestones-filters v-if="showMilestonesFilters" />
- <wiki-blobs-filters v-if="showWikiBlobsFilters" />
- </small-screen-drawer-navigation>
- </section>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue b/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue
index b0e84beabc4..914ff99075b 100644
--- a/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue
@@ -21,7 +21,7 @@ export default {
tooltip: s__('GlobalSearch|Include search results from archived projects'),
},
computed: {
- ...mapState(['urlQuery', 'useSidebarNavigation']),
+ ...mapState(['urlQuery']),
selectedFilter: {
get() {
return [parseBoolean(this.urlQuery?.include_archived)];
@@ -48,7 +48,7 @@ export default {
<template>
<gl-form-checkbox-group v-model="selectedFilter">
- <h5 class="gl-mt-0 gl-mb-5" :class="{ 'gl-font-sm': useSidebarNavigation }">
+ <h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
{{ $options.archivedFilterData.headerLabel }}
</h5>
<gl-form-checkbox
diff --git a/app/assets/javascripts/search/sidebar/components/blobs_filters.vue b/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
index 0ed2c24efba..e282bacae31 100644
--- a/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
+++ b/app/assets/javascripts/search/sidebar/components/blobs_filters.vue
@@ -1,8 +1,4 @@
<script>
-// eslint-disable-next-line no-restricted-imports
-import { mapGetters, mapState } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { HR_DEFAULT_CLASSES } from '../constants';
import LanguageFilter from './language_filter/index.vue';
import ArchivedFilter from './archived_filter/index.vue';
import FiltersTemplate from './filters_template.vue';
@@ -14,24 +10,12 @@ export default {
FiltersTemplate,
ArchivedFilter,
},
- mixins: [glFeatureFlagsMixin()],
- computed: {
- ...mapGetters(['currentScope']),
- ...mapState(['useSidebarNavigation', 'searchType']),
- showDivider() {
- return !this.useSidebarNavigation;
- },
- hrClasses() {
- return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
- },
- },
};
</script>
<template>
<filters-template>
<language-filter class="gl-mb-5" />
- <hr v-if="showDivider" :class="hrClasses" />
<archived-filter class="gl-mb-5" />
</filters-template>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/confidentiality_filter/index.vue b/app/assets/javascripts/search/sidebar/components/confidentiality_filter/index.vue
index 176614be6da..4e91158fa36 100644
--- a/app/assets/javascripts/search/sidebar/components/confidentiality_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/confidentiality_filter/index.vue
@@ -1,6 +1,4 @@
<script>
-// eslint-disable-next-line no-restricted-imports
-import { mapState } from 'vuex';
import RadioFilter from '../radio_filter.vue';
import { confidentialFilterData } from './data';
@@ -9,9 +7,6 @@ export default {
components: {
RadioFilter,
},
- computed: {
- ...mapState(['useSidebarNavigation']),
- },
confidentialFilterData,
};
</script>
diff --git a/app/assets/javascripts/search/sidebar/components/filters_template.vue b/app/assets/javascripts/search/sidebar/components/filters_template.vue
index 0f68bf92048..a3aa392d7fc 100644
--- a/app/assets/javascripts/search/sidebar/components/filters_template.vue
+++ b/app/assets/javascripts/search/sidebar/components/filters_template.vue
@@ -5,7 +5,6 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import Tracking from '~/tracking';
import {
- HR_DEFAULT_CLASSES,
TRACKING_ACTION_CLICK,
TRACKING_LABEL_APPLY,
TRACKING_LABEL_RESET,
@@ -19,7 +18,7 @@ export default {
GlForm,
},
computed: {
- ...mapState(['sidebarDirty', 'useSidebarNavigation']),
+ ...mapState(['sidebarDirty']),
...mapGetters(['currentScope']),
},
methods: {
@@ -37,15 +36,12 @@ export default {
this.resetQuery();
},
},
- HR_DEFAULT_CLASSES,
};
</script>
<template>
<gl-form class="issue-filters gl-px-5 gl-pt-0" @submit.prevent="applyQueryWithTracking">
- <hr v-if="!useSidebarNavigation" :class="$options.HR_DEFAULT_CLASSES" />
<slot></slot>
- <hr v-if="!useSidebarNavigation" :class="$options.HR_DEFAULT_CLASSES" />
<div class="gl-display-flex gl-align-items-center gl-mt-4">
<gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
{{ __('Apply') }}
diff --git a/app/assets/javascripts/search/sidebar/components/issues_filters.vue b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
index a77fb34cdba..7136959ee0b 100644
--- a/app/assets/javascripts/search/sidebar/components/issues_filters.vue
+++ b/app/assets/javascripts/search/sidebar/components/issues_filters.vue
@@ -2,7 +2,7 @@
// eslint-disable-next-line no-restricted-imports
import { mapGetters, mapState } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { HR_DEFAULT_CLASSES, SEARCH_TYPE_ADVANCED } from '../constants';
+import { SEARCH_TYPE_ADVANCED } from '../constants';
import { confidentialFilterData } from './confidentiality_filter/data';
import { statusFilterData } from './status_filter/data';
import ConfidentialityFilter from './confidentiality_filter/index.vue';
@@ -26,7 +26,7 @@ export default {
mixins: [glFeatureFlagsMixin()],
computed: {
...mapGetters(['currentScope']),
- ...mapState(['useSidebarNavigation', 'searchType']),
+ ...mapState(['searchType']),
showConfidentialityFilter() {
return Object.values(confidentialFilterData.scopes).includes(this.currentScope);
},
@@ -43,12 +43,6 @@ export default {
showArchivedFilter() {
return archivedFilterData.scopes.includes(this.currentScope);
},
- showDivider() {
- return !this.useSidebarNavigation;
- },
- hrClasses() {
- return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
- },
},
};
</script>
@@ -56,11 +50,8 @@ export default {
<template>
<filters-template>
<status-filter v-if="showStatusFilter" class="gl-mb-5" />
- <hr v-if="showConfidentialityFilter && showDivider" :class="hrClasses" />
<confidentiality-filter v-if="showConfidentialityFilter" class="gl-mb-5" />
- <hr v-if="showLabelFilter && showDivider" :class="hrClasses" />
<label-filter v-if="showLabelFilter" class="gl-mb-5" />
- <hr v-if="showArchivedFilter && showDivider" :class="hrClasses" />
<archived-filter v-if="showArchivedFilter" class="gl-mb-5" />
</filters-template>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/index.vue b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue
index 97583730958..a53f519161b 100644
--- a/app/assets/javascripts/search/sidebar/components/label_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue
@@ -55,7 +55,7 @@ export default {
},
i18n: I18N,
computed: {
- ...mapState(['useSidebarNavigation', 'searchLabelString', 'query', 'urlQuery', 'aggregations']),
+ ...mapState(['searchLabelString', 'query', 'urlQuery', 'aggregations']),
...mapGetters([
'filteredLabels',
'filteredUnselectedLabels',
@@ -179,11 +179,7 @@ export default {
<template>
<div class="gl-pb-0 gl-md-pt-0 label-filter gl-relative">
- <h5
- class="gl-my-0"
- data-testid="label-filter-title"
- :class="{ 'gl-font-sm': useSidebarNavigation }"
- >
+ <h5 class="gl-my-0 gl-font-sm" data-testid="label-filter-title">
{{ $options.labelFilterData.header }}
</h5>
<div class="gl-my-5">
@@ -246,12 +242,7 @@ export default {
v-if="isFocused"
v-outside="closeDropdown"
data-testid="header-search-dropdown-menu"
- class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0 gl-mt-3 gl-z-index-2"
- :class="{
- 'gl-max-w-none!': useSidebarNavigation,
- 'gl-min-w-full!': useSidebarNavigation,
- 'gl-w-full!': useSidebarNavigation,
- }"
+ class="header-search-dropdown-menu gl-overflow-y-auto gl-absolute gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0 gl-mt-3 gl-z-index-2 gl-w-full! gl-min-w-full! gl-max-w-none!"
>
<div class="header-search-dropdown-content gl-py-2">
<dropdown-keyboard-navigation
diff --git a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
index 784207cc702..4a9975641c6 100644
--- a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue
@@ -27,7 +27,7 @@ export default {
loadError: s__('GlobalSearch|Aggregations load error.'),
},
computed: {
- ...mapState(['aggregations', 'useSidebarNavigation']),
+ ...mapState(['aggregations']),
...mapGetters(['languageAggregationBuckets']),
hasBuckets() {
return this.languageAggregationBuckets.length > 0;
@@ -75,7 +75,7 @@ export default {
<template>
<div v-if="hasBuckets" class="language-filter-checkbox">
- <h5 class="gl-mt-0 gl-mb-5" :class="{ 'gl-font-sm': useSidebarNavigation }">
+ <h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
{{ $options.languageFilterData.header }}
</h5>
<div
diff --git a/app/assets/javascripts/search/sidebar/components/merge_requests_filters.vue b/app/assets/javascripts/search/sidebar/components/merge_requests_filters.vue
index f86906ebd26..18074db7603 100644
--- a/app/assets/javascripts/search/sidebar/components/merge_requests_filters.vue
+++ b/app/assets/javascripts/search/sidebar/components/merge_requests_filters.vue
@@ -1,7 +1,6 @@
<script>
// eslint-disable-next-line no-restricted-imports
-import { mapGetters, mapState } from 'vuex';
-import { HR_DEFAULT_CLASSES } from '../constants';
+import { mapGetters } from 'vuex';
import { statusFilterData } from './status_filter/data';
import StatusFilter from './status_filter/index.vue';
import FiltersTemplate from './filters_template.vue';
@@ -17,19 +16,12 @@ export default {
},
computed: {
...mapGetters(['currentScope']),
- ...mapState(['useSidebarNavigation', 'searchType']),
showArchivedFilter() {
return archivedFilterData.scopes.includes(this.currentScope);
},
showStatusFilter() {
return Object.values(statusFilterData.scopes).includes(this.currentScope);
},
- showDivider() {
- return !this.useSidebarNavigation;
- },
- hrClasses() {
- return [...HR_DEFAULT_CLASSES, 'gl-display-none', 'gl-md-display-block'];
- },
},
};
</script>
@@ -37,7 +29,6 @@ export default {
<template>
<filters-template>
<status-filter v-if="showStatusFilter" class="gl-mb-5" />
- <hr v-if="showArchivedFilter && showDivider" :class="hrClasses" />
<archived-filter v-if="showArchivedFilter" class="gl-mb-5" />
</filters-template>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/radio_filter.vue b/app/assets/javascripts/search/sidebar/components/radio_filter.vue
index a1eb5ccecd8..d67844b93a7 100644
--- a/app/assets/javascripts/search/sidebar/components/radio_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/radio_filter.vue
@@ -17,7 +17,7 @@ export default {
},
},
computed: {
- ...mapState(['query', 'useSidebarNavigation']),
+ ...mapState(['query']),
...mapGetters(['currentScope']),
ANY() {
return this.filterData.filters.ANY;
@@ -57,7 +57,7 @@ export default {
<template>
<div>
- <h5 class="gl-mt-0 gl-mb-5" :class="{ 'gl-font-sm': useSidebarNavigation }">
+ <h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
{{ filterData.header }}
</h5>
<gl-form-radio-group v-model="selectedFilter">
diff --git a/app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue
deleted file mode 100644
index a4c1119736f..00000000000
--- a/app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script>
-import { GlNav, GlNavItem, GlIcon } from '@gitlab/ui';
-// eslint-disable-next-line no-restricted-imports
-import { mapActions, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import Tracking from '~/tracking';
-import { formatSearchResultCount, addCountOverLimit } from '~/search/store/utils';
-import { NAV_LINK_DEFAULT_CLASSES, NAV_LINK_COUNT_DEFAULT_CLASSES } from '../constants';
-import { slugifyWithUnderscore } from '../../../lib/utils/text_utility';
-
-export default {
- name: 'ScopeLegacyNavigation',
- i18n: {
- countOverLimitLabel: s__('GlobalSearch|Result count is over limit.'),
- },
- components: {
- GlNav,
- GlNavItem,
- GlIcon,
- },
- mixins: [Tracking.mixin()],
- computed: {
- ...mapState(['navigation', 'urlQuery']),
- },
- created() {
- if (this.urlQuery?.search) {
- this.fetchSidebarCount();
- }
- },
- methods: {
- ...mapActions(['fetchSidebarCount']),
- showFormatedCount(countString) {
- return formatSearchResultCount(countString);
- },
- isCountOverLimit(countString) {
- return Boolean(addCountOverLimit(countString));
- },
- handleClick(scope) {
- this.track('click_menu_item', { label: `vertical_navigation_${scope}` });
- },
- linkClasses(isHighlighted) {
- return [...this.$options.NAV_LINK_DEFAULT_CLASSES, { 'gl-font-weight-bold': isHighlighted }];
- },
- countClasses(isHighlighted) {
- return [
- ...this.$options.NAV_LINK_COUNT_DEFAULT_CLASSES,
- isHighlighted ? 'gl-text-gray-900' : 'gl-text-gray-500',
- ];
- },
- qaSelectorValue(item) {
- return `${slugifyWithUnderscore(item.label)}_tab`;
- },
- },
- NAV_LINK_DEFAULT_CLASSES,
- NAV_LINK_COUNT_DEFAULT_CLASSES,
-};
-</script>
-
-<template>
- <nav data-testid="search-filter" class="gl-border-none">
- <gl-nav vertical pills>
- <gl-nav-item
- v-for="(item, scope) in navigation"
- :key="scope"
- :link-classes="linkClasses(item.active)"
- class="gl-mb-1"
- :href="item.link"
- :active="item.active"
- :data-qa-selector="qaSelectorValue(item)"
- :data-testid="qaSelectorValue(item)"
- @click="handleClick(scope)"
- ><span data-testid="label">{{ item.label }}</span
- ><span v-if="item.count" data-testid="count" :class="countClasses(item.active)">
- {{ showFormatedCount(item.count)
- }}<gl-icon
- v-if="isCountOverLimit(item.count)"
- name="plus"
- :aria-label="$options.i18n.countOverLimitLabel"
- :size="8"
- />
- </span>
- </gl-nav-item>
- </gl-nav>
- </nav>
-</template>
diff --git a/app/assets/javascripts/search/sidebar/components/small_screen_drawer_navigation.vue b/app/assets/javascripts/search/sidebar/components/small_screen_drawer_navigation.vue
deleted file mode 100644
index e966b8d877e..00000000000
--- a/app/assets/javascripts/search/sidebar/components/small_screen_drawer_navigation.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script>
-import { GlDrawer } from '@gitlab/ui';
-import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
-import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
-import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
-import { s__ } from '~/locale';
-
-export default {
- name: 'SmallScreenDrawerNavigation',
- components: {
- GlDrawer,
- DomElementListener,
- },
- i18n: {
- smallScreenFiltersDrawerHeader: s__('GlobalSearch|Filters'),
- },
- data() {
- return {
- openSmallScreenFilters: false,
- };
- },
- computed: {
- getDrawerHeaderHeight() {
- if (!this.openSmallScreenFilters) return '0';
- return getContentWrapperHeight();
- },
- },
- methods: {
- closeSmallScreenFilters() {
- this.openSmallScreenFilters = false;
- },
- toggleSmallScreenFilters() {
- this.openSmallScreenFilters = !this.openSmallScreenFilters;
- },
- },
- DRAWER_Z_INDEX,
-};
-</script>
-<template>
- <dom-element-listener selector="#js-open-mobile-filters" @click="toggleSmallScreenFilters">
- <gl-drawer
- :header-height="getDrawerHeaderHeight"
- :z-index="$options.DRAWER_Z_INDEX"
- variant="sidebar"
- class="small-screen-drawer-navigation"
- :open="openSmallScreenFilters"
- @close="closeSmallScreenFilters"
- >
- <template #title>
- <h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">
- {{ $options.i18n.smallScreenFiltersDrawerHeader }}
- </h2>
- </template>
- <template #default>
- <div>
- <slot></slot>
- </div>
- </template>
- </gl-drawer>
- </dom-element-listener>
-</template>
diff --git a/app/assets/javascripts/search/sidebar/components/status_filter/index.vue b/app/assets/javascripts/search/sidebar/components/status_filter/index.vue
index a5f717dcf06..cbc1a26f1ae 100644
--- a/app/assets/javascripts/search/sidebar/components/status_filter/index.vue
+++ b/app/assets/javascripts/search/sidebar/components/status_filter/index.vue
@@ -1,5 +1,4 @@
<script>
-import { HR_DEFAULT_CLASSES } from '../../constants';
import RadioFilter from '../radio_filter.vue';
import { statusFilterData } from './data';
@@ -9,7 +8,6 @@ export default {
RadioFilter,
},
statusFilterData,
- HR_DEFAULT_CLASSES,
};
</script>
diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js
index 1559155a941..95906c840d7 100644
--- a/app/assets/javascripts/search/sidebar/constants/index.js
+++ b/app/assets/javascripts/search/sidebar/constants/index.js
@@ -18,8 +18,6 @@ export const NAV_LINK_DEFAULT_CLASSES = [
'gl-justify-content-space-between',
];
export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
-export const HR_DEFAULT_CLASSES = ['hr-x', 'gl-border-gray-100'];
-export const ONLY_SHOW_MD = ['gl-display-none', 'gl-md-display-block'];
export const TRACKING_ACTION_CLICK = 'search:filters:click';
export const TRACKING_LABEL_APPLY = 'Apply Filters';
diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js
index b4cd2af65ba..9c38a230343 100644
--- a/app/assets/javascripts/search/store/state.js
+++ b/app/assets/javascripts/search/store/state.js
@@ -1,7 +1,7 @@
import { cloneDeep } from 'lodash';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
-const createState = ({ query, navigation, useSidebarNavigation, searchType }) => ({
+const createState = ({ query, navigation, searchType }) => ({
urlQuery: cloneDeep(query),
query,
groups: [],
@@ -14,7 +14,6 @@ const createState = ({ query, navigation, useSidebarNavigation, searchType }) =>
},
sidebarDirty: false,
navigation,
- useSidebarNavigation,
aggregations: {
error: false,
fetching: false,
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index b8618e09761..8f95dd1dcec 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -39,6 +39,7 @@ export default {
i18n: {
skipToMainContent: __('Skip to main content'),
primaryNavigation: s__('Navigation|Primary navigation'),
+ adminArea: s__('Navigation|Admin Area'),
},
inject: ['showTrialStatusWidget'],
props: {
@@ -220,6 +221,16 @@ export default {
</div>
<div class="gl-p-3">
<help-center ref="helpCenter" :sidebar-data="sidebarData" />
+ <gl-button
+ v-if="sidebarData.is_admin"
+ class="gl-fixed gl-right-0 gl-mr-3 gl-mt-2"
+ data-testid="sidebar-admin-link"
+ :href="sidebarData.admin_url"
+ icon="admin"
+ size="small"
+ >
+ {{ $options.i18n.adminArea }}
+ </gl-button>
</div>
</div>
</nav>
diff --git a/app/assets/stylesheets/page_bundles/search.scss b/app/assets/stylesheets/page_bundles/search.scss
index a3a62b44e98..b145d046fa4 100644
--- a/app/assets/stylesheets/page_bundles/search.scss
+++ b/app/assets/stylesheets/page_bundles/search.scss
@@ -31,26 +31,6 @@ $language-filter-max-height: 20rem;
}
}
-.search-sidebar {
- @include media-breakpoint-down(lg) {
- max-width: 100%;
- }
-
- @include media-breakpoint-down(xl) {
- min-width: $search-sidebar-min-width;
- max-width: $search-sidebar-min-width;
- }
-
- @include media-breakpoint-up(xl) {
- min-width: $search-sidebar-max-width;
- max-width: $search-sidebar-max-width;
- }
-
- .language-filter-max-height {
- max-height: $language-filter-max-height;
- }
-}
-
.issue-filters {
.label-filter {
list-style: none;
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index db20034419a..9d4f6cabcd9 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -235,16 +235,6 @@
}
}
- .search-sidebar {
- .nav-link {
- &.active,
- &:hover {
- background-color: rgba($gray-50, 0.8);
- color: $gray-900;
- }
- }
- }
-
// Sidebar
.nav-sidebar li.active > a {
color: $gray-900;
diff --git a/app/helpers/organizations/organization_helper.rb b/app/helpers/organizations/organization_helper.rb
index 61eb9b5c35f..533d5409ab7 100644
--- a/app/helpers/organizations/organization_helper.rb
+++ b/app/helpers/organizations/organization_helper.rb
@@ -44,8 +44,9 @@ module Organizations
def organization_user_app_data(organization)
{
- organization_gid: organization.to_global_id
- }
+ organization_gid: organization.to_global_id,
+ paths: organizations_users_paths
+ }.to_json
end
def home_organization_setting_app_data
@@ -65,5 +66,12 @@ module Organizations
new_project_path: new_project_path
}
end
+
+ # See UsersHelper#admin_users_paths for inspiration to this method
+ def organizations_users_paths
+ {
+ admin_user: admin_user_path(:id)
+ }
+ end
end
end
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index f983812ad22..1670e628ffe 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -72,8 +72,10 @@ module SidebarsHelper
def super_sidebar_logged_in_context(user, group:, project:, panel:, panel_type:) # rubocop:disable Metrics/AbcSize
super_sidebar_logged_out_context(panel: panel, panel_type: panel_type).merge({
is_logged_in: true,
+ is_admin: user.can_admin_all_resources?,
name: user.name,
username: user.username,
+ admin_url: admin_root_url,
avatar_url: user.avatar_url,
has_link_to_profile: current_user_menu?(:profile),
link_to_profile: user_path(user),
diff --git a/app/validators/json_schema_validator.rb b/app/validators/json_schema_validator.rb
index 2ef011df73e..b6b21f13546 100644
--- a/app/validators/json_schema_validator.rb
+++ b/app/validators/json_schema_validator.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
#
# JsonSchemaValidator
#
@@ -24,11 +25,19 @@ class JsonSchemaValidator < ActiveModel::EachValidator
end
def validate_each(record, attribute, value)
- value = value.to_h.stringify_keys if options[:hash_conversion] == true
+ value = value.to_h.deep_stringify_keys if options[:hash_conversion] == true
value = Gitlab::Json.parse(value.to_s) if options[:parse_json] == true && !value.nil?
- unless valid_schema?(value)
- record.errors.add(attribute, _("must be a valid json schema"))
+ if options[:detail_errors]
+ validator.validate(value).each do |error|
+ message = format(
+ _("the '%{data_pointer}' must be a valid '%{type}'"),
+ data_pointer: error['data_pointer'], type: error['type']
+ )
+ record.errors.add(attribute, message)
+ end
+ else
+ record.errors.add(attribute, _("must be a valid json schema")) unless valid_schema?(value)
end
end
diff --git a/app/views/organizations/organizations/users.html.haml b/app/views/organizations/organizations/users.html.haml
index 5fb9d786e0b..0ed15d8e4d0 100644
--- a/app/views/organizations/organizations/users.html.haml
+++ b/app/views/organizations/organizations/users.html.haml
@@ -1,4 +1,4 @@
- page_title _('Users')
-#js-organizations-users{ data: organization_user_app_data(@organization) }
+#js-organizations-users{ data: { app_data: organization_user_app_data(@organization) } }
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 4fda5379876..55d7a4ed041 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -8,7 +8,6 @@
= hidden_field_tag :project_id, params[:project_id]
- group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name)
- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace)
-- search_bar_classes = !show_super_sidebar? ? 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4' : ''
- if @search_results && !(@search_results.respond_to?(:failed?) && @search_results.failed?)
- if @search_service_presenter.without_count?
@@ -22,6 +21,6 @@
#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "default-branch-name": @project&.default_branch } }
.results.gl-lg-display-flex.gl-mt-0
- #js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json, search_type: search_service.search_type } }
+ #js-search-sidebar{ data: { navigation_json: search_navigation_json, search_type: search_service.search_type } }
- if @search_term
= render 'search/results'
diff --git a/config/feature_flags/development/ci_catalog_create_metadata.yml b/config/feature_flags/development/ci_catalog_create_metadata.yml
index a73f499554d..205e7e0c414 100644
--- a/config/feature_flags/development/ci_catalog_create_metadata.yml
+++ b/config/feature_flags/development/ci_catalog_create_metadata.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430120
milestone: '16.6'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/code_tasks.yml b/config/feature_flags/development/code_tasks.yml
deleted file mode 100644
index fec0e8326f3..00000000000
--- a/config/feature_flags/development/code_tasks.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: code_tasks
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135717
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430962
-milestone: '16.6'
-type: development
-group: group::code creation
-default_enabled: false
diff --git a/db/click_house/migrate/20230724064832_create_contribution_analytics_events.rb b/db/click_house/migrate/20230724064832_create_contribution_analytics_events.rb
index 2606ae3adc9..992d266fda7 100644
--- a/db/click_house/migrate/20230724064832_create_contribution_analytics_events.rb
+++ b/db/click_house/migrate/20230724064832_create_contribution_analytics_events.rb
@@ -13,7 +13,7 @@ class CreateContributionAnalyticsEvents < ClickHouse::Migration
created_at Date DEFAULT toDate(now()),
updated_at DateTime64(6, 'UTC') DEFAULT now()
)
- ENGINE = MergeTree
+ ENGINE = ReplacingMergeTree
ORDER BY (path, created_at, author_id, id)
PARTITION BY toYear(created_at);
SQL
diff --git a/doc/administration/backup_restore/backup_gitlab.md b/doc/administration/backup_restore/backup_gitlab.md
index 12d63232068..9cb01910816 100644
--- a/doc/administration/backup_restore/backup_gitlab.md
+++ b/doc/administration/backup_restore/backup_gitlab.md
@@ -362,13 +362,13 @@ Caveats:
- The resultant filenames will still end in `.gz`.
- The default decompression command, used during restore, is `gzip -cd`. Therefore if you override the compression command to use a format that cannot be decompressed by `gzip -cd`, you must override the decompression command during restore.
-### Default compression: Gzip with fastest method
+##### Default compression: Gzip with fastest method
```shell
gitlab-backup create
```
-### Gzip with slowest method
+##### Gzip with slowest method
```shell
gitlab-backup create COMPRESS_CMD="gzip -c --best"
@@ -380,7 +380,7 @@ If `gzip` was used for backup, then restore does not require any options:
gitlab-backup restore
```
-### No compression
+##### No compression
If your backup destination has built-in automatic compression, then you may wish to skip compression.
@@ -396,7 +396,7 @@ And on restore:
gitlab-backup restore DECOMPRESS_CMD=tee
```
-### Replace Gzip
+##### Replace Gzip
This is an example of how to use a compression tool which you installed manually:
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ea7d59669a6..6edf78aeec2 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -29682,7 +29682,6 @@ Representation of mergeability check identifier.
| <a id="mergeabilitycheckidentifierneed_rebase"></a>`NEED_REBASE` | Mergeability check identifier is need_rebase. |
| <a id="mergeabilitycheckidentifiernot_approved"></a>`NOT_APPROVED` | Mergeability check identifier is not_approved. |
| <a id="mergeabilitycheckidentifiernot_open"></a>`NOT_OPEN` | Mergeability check identifier is not_open. |
-| <a id="mergeabilitycheckidentifierpolicies_denied"></a>`POLICIES_DENIED` | Mergeability check identifier is policies_denied. |
| <a id="mergeabilitycheckidentifierstatus_checks_must_pass"></a>`STATUS_CHECKS_MUST_PASS` | Mergeability check identifier is status_checks_must_pass. |
### `MergeabilityCheckStatus`
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index f89bf54b979..a5515160b76 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -2404,6 +2404,37 @@ image:
- [Override the entrypoint of an image](../docker/using_docker_images.md#override-the-entrypoint-of-an-image).
+#### `image:docker`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+
+Use `image:docker` to pass options to the Docker executor of a GitLab Runner.
+
+**Keyword type**: Job keyword. You can use it only as part of a job or in the
+[`default` section](#default).
+
+**Possible inputs**:
+
+A hash of options for the Docker executor, which can include:
+
+- `platform`: Selects the architecture of the image to pull. When not specified,
+ the default is the same platform as the host runner.
+
+**Example of `image:docker`**:
+
+```yaml
+arm-sql-job:
+ script: echo "Run sql tests"
+ image:
+ name: super/sql:experimental
+ docker:
+ platform: arm64/v8
+```
+
+**Additional details**:
+
+- `image:docker:platform` maps to the [`docker pull --platform` option](https://docs.docker.com/engine/reference/commandline/pull/#options).
+
#### `image:pull_policy`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21619) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`. Disabled by default.
@@ -4220,6 +4251,38 @@ In this example, GitLab launches two containers for the job:
- [Run your CI/CD jobs in Docker containers](../docker/using_docker_images.md).
- [Use Docker to build Docker images](../docker/using_docker_build.md).
+#### `services:docker`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+
+Use `services:docker` to pass options to the Docker executor of a GitLab Runner.
+
+**Keyword type**: Job keyword. You can use it only as part of a job or in the
+[`default` section](#default).
+
+**Possible inputs**:
+
+A hash of options for the Docker executor, which can include:
+
+- `platform`: Selects the architecture of the image to pull. When not specified,
+ the default is the same platform as the host runner.
+
+**Example of `service:docker`**:
+
+```yaml
+arm-sql-job:
+ script: echo "Run sql tests in service container"
+ image: ruby:2.6
+ services:
+ - name: super/sql:experimental
+ docker:
+ platform: arm64/v8
+```
+
+**Additional details**:
+
+- `services:docker:platform` maps to the [`docker pull --platform` option](https://docs.docker.com/engine/reference/commandline/pull/#options).
+
#### `service:pull_policy`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21619) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`. Disabled by default.
diff --git a/doc/tutorials/website_project_with_analytics/index.md b/doc/tutorials/website_project_with_analytics/index.md
index 90e85d0f88c..156ebad11d3 100644
--- a/doc/tutorials/website_project_with_analytics/index.md
+++ b/doc/tutorials/website_project_with_analytics/index.md
@@ -70,7 +70,7 @@ To invite a user to the `My website` project:
1. Select **Invite**.
The invited user should now be a member of the project.
-You can [view, filter, and search for members](../../user/project/members/index.md#filter-and-sort-members) of your project.
+You can [view, filter, and search for members](../../user/project/members/index.md#filter-and-sort-project-members) of your project.
## Create project labels
diff --git a/doc/update/index.md b/doc/update/index.md
index 169805ed79e..284b538f086 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -195,7 +195,7 @@ When upgrading:
- GitLab 16: [`16.0.x`](versions/gitlab_16_changes.md#1600) (only
instances with [lots of users](versions/gitlab_16_changes.md#long-running-user-type-data-change) or
[large pipeline variables history](versions/gitlab_16_changes.md#1610)) >
- [`16.1`](versions/gitlab_16_changes.md#1610)(instances with NPM packages in their package registry) >
+ [`16.1`](versions/gitlab_16_changes.md#1610) (instances with NPM packages in their package registry) >
[`16.2.x`](versions/gitlab_16_changes.md#1620) (only instances with [large pipeline variables history](versions/gitlab_16_changes.md#1630)) >
[`16.3`](versions/gitlab_16_changes.md#1630) > [latest `16.Y.Z`](https://gitlab.com/gitlab-org/gitlab/-/releases).
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 683ba6ad19b..40fa32a93db 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -961,7 +961,7 @@ this information is removed from the resulting merged file.
## Versioning and release process
-Check the [Release Process documentation](https://gitlab.com/gitlab-org/security-products/release/blob/master/docs/release_process.md).
+Check the [Release Process documentation](../../../development/sec/analyzer_development_guide.md#versioning-and-release-process).
## Contributing to the vulnerability database
diff --git a/doc/user/okrs.md b/doc/user/okrs.md
index ca5882da22a..3dadc406de2 100644
--- a/doc/user/okrs.md
+++ b/doc/user/okrs.md
@@ -54,10 +54,6 @@ To learn how to create better OKRs and how we use them at GitLab, see the
## Create an objective
-Prerequisites:
-
-- You must have at least the Guest role for the project.
-
To create an objective:
1. On the left sidebar, select **Search or go to** and find your project.
@@ -529,11 +525,11 @@ Prerequisite:
To link an item to an objective or key result:
1. In the **Linked items** section of an objective or key result,
- select the **Add** button.
+ select **Add**.
1. Select the relationship between the two items. Either:
- - **relates to**
- - **blocks**
- - **is blocked by**
+ - **Relates to**
+ - **Blocks**
+ - **Is blocked by**
1. Enter the search text of the item.
1. When you have added all the items to be linked, select **Add** below the search box.
diff --git a/doc/user/project/members/img/project_members_filter_direct_v14_4.png b/doc/user/project/members/img/project_members_filter_direct_v14_4.png
deleted file mode 100644
index 79cee06bc30..00000000000
--- a/doc/user/project/members/img/project_members_filter_direct_v14_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/img/project_members_filter_inherited_v14_4.png b/doc/user/project/members/img/project_members_filter_inherited_v14_4.png
deleted file mode 100644
index ce2a0ebf088..00000000000
--- a/doc/user/project/members/img/project_members_filter_inherited_v14_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/img/project_members_search_v14_4.png b/doc/user/project/members/img/project_members_search_v14_4.png
deleted file mode 100644
index 8c52c5788d4..00000000000
--- a/doc/user/project/members/img/project_members_search_v14_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/img/project_members_sort_v14_4.png b/doc/user/project/members/img/project_members_sort_v14_4.png
deleted file mode 100644
index 20834b9307e..00000000000
--- a/doc/user/project/members/img/project_members_sort_v14_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 6df33a4fb06..a008bfdebae 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -189,7 +189,7 @@ To add a group to a project:
From that date onward, the group can no longer access the project.
1. Select **Invite**.
-The members of the group are not displayed on the **Members** tab.
+The members of the invited group are not displayed on the **Members** tab.
Private groups are masked from unauthorized users.
The **Members** tab shows:
@@ -210,7 +210,7 @@ If the importing member's role in the target project is:
- Maintainer, then members with the Owner role in the source project are imported with the Maintainer role.
- Owner, then members with the Owner role in the source project are imported with the Owner role.
-To import users:
+To import a project's members:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Manage > Members**.
@@ -218,7 +218,8 @@ To import users:
1. Select the project. You can view only the projects for which you're a maintainer.
1. Select **Import project members**.
-After the success message displays, refresh the page to view the new members.
+If the import is successful, a success message is displayed.
+To view the imported members, refresh the page.
## Remove a member from a project
@@ -264,7 +265,7 @@ To avoid this problem, GitLab administrators can:
- Remove the malicious user account.
- Change the password for the malicious user account.
-## Filter and sort members
+## Filter and sort project members
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6.
> - [Improved](https://gitlab.com/groups/gitlab-org/-/epics/4901) in GitLab 13.9.
@@ -279,8 +280,6 @@ You can filter and sort members in a project.
1. In the **Filter members** box, select `Membership` `=` `Inherited`.
1. Press <kbd>Enter</kbd>.
-![Project members filter inherited](img/project_members_filter_inherited_v14_4.png)
-
### Display direct members
1. On the left sidebar, select **Search or go to** and find your project.
@@ -288,19 +287,31 @@ You can filter and sort members in a project.
1. In the **Filter members** box, select `Membership` `=` `Direct`.
1. Press <kbd>Enter</kbd>.
-![Project members filter direct](img/project_members_filter_direct_v14_4.png)
+### Search for members in a project
-### Search
+To search for a project member:
-You can search for members by name, username, or email.
+1. On the left sidebar, select **Search or go to** and find your project.
+1. Select **Manage > Members**.
+1. In the search box, enter the member's name, username, or email.
+1. Press <kbd>Enter</kbd>.
-![Project members search](img/project_members_search_v14_4.png)
+### Sort members in a project
-### Sort
+You can sort members in ascending or descending order by:
-You can sort members by **Account**, **Access granted**, **Max role**, or **Last sign-in** in ascending or descending order.
+- **Account** name
+- **Access granted** date
+- **Max role** the members have in the group
+- **User created** date
+- **Last activity** date
+- **Last sign-in** date
-![Project members sort](img/project_members_sort_v14_4.png)
+To sort members:
+
+1. On the left sidebar, select **Search or go to** and find your project.
+1. Select **Manage > Members**.
+1. At the top of the member list, from the dropdown list, select the item you want to sort by.
## Request access to a project
@@ -319,8 +330,13 @@ Project maintainers cannot approve Owner role access requests.
If a project does not have any direct owners or maintainers, the notification is sent to the
most recently active owners of the project's group.
-If you change your mind before your request is approved, select
-**Withdraw Access Request**.
+### Withdraw an access request to a project
+
+You can withdraw an access request to a project before the request is approved.
+To withdraw the access request:
+
+1. On the left sidebar, select **Search or go to** and find the project you requested access to.
+1. Next to the project name, select **Withdraw Access Request**.
## Prevent users from requesting access to a project
diff --git a/lib/api/entities/ci/job_request/image.rb b/lib/api/entities/ci/job_request/image.rb
index 92d68269265..08b0075a657 100644
--- a/lib/api/entities/ci/job_request/image.rb
+++ b/lib/api/entities/ci/job_request/image.rb
@@ -8,6 +8,7 @@ module API
expose :name, :entrypoint
expose :ports, using: Entities::Ci::JobRequest::Port
+ expose :executor_opts
expose :pull_policy
end
end
diff --git a/lib/api/entities/ci/job_request/service.rb b/lib/api/entities/ci/job_request/service.rb
index 128591058fe..ae726a6b888 100644
--- a/lib/api/entities/ci/job_request/service.rb
+++ b/lib/api/entities/ci/job_request/service.rb
@@ -8,6 +8,7 @@ module API
expose :name, :entrypoint
expose :ports, using: Entities::Ci::JobRequest::Port
+ expose :executor_opts
expose :pull_policy
expose :alias, :command
expose :variables
diff --git a/lib/gitlab/ci/build/image.rb b/lib/gitlab/ci/build/image.rb
index 84f8eae8deb..660d7701a8f 100644
--- a/lib/gitlab/ci/build/image.rb
+++ b/lib/gitlab/ci/build/image.rb
@@ -4,7 +4,7 @@ module Gitlab
module Ci
module Build
class Image
- attr_reader :alias, :command, :entrypoint, :name, :ports, :variables, :pull_policy
+ attr_reader :alias, :command, :entrypoint, :name, :ports, :variables, :executor_opts, :pull_policy
class << self
def from_image(job)
@@ -28,6 +28,7 @@ module Gitlab
when String
@name = image
@ports = []
+ @executor_opts = {}
when Hash
@alias = image[:alias]
@command = image[:command]
@@ -35,6 +36,7 @@ module Gitlab
@name = image[:name]
@ports = build_ports(image).select(&:valid?)
@variables = build_variables(image)
+ @executor_opts = image.fetch(:executor_opts, {})
@pull_policy = image[:pull_policy]
end
end
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 84e31ca1fc6..58ab488d833 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -13,21 +13,6 @@ module Gitlab
validations do
validates :config, allowed_keys: IMAGEABLE_ALLOWED_KEYS
end
-
- def value
- if string?
- { name: @config }
- elsif hash?
- {
- name: @config[:name],
- entrypoint: @config[:entrypoint],
- ports: (ports_value if ports_defined?),
- pull_policy: pull_policy_value
- }.compact
- else
- {}
- end
- end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/imageable.rb b/lib/gitlab/ci/config/entry/imageable.rb
index 1aecfee9ab9..53b810b3037 100644
--- a/lib/gitlab/ci/config/entry/imageable.rb
+++ b/lib/gitlab/ci/config/entry/imageable.rb
@@ -12,7 +12,9 @@ module Gitlab
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Configurable
- IMAGEABLE_ALLOWED_KEYS = %i[name entrypoint ports pull_policy].freeze
+ EXECUTOR_OPTS_KEYS = %i[docker].freeze
+
+ IMAGEABLE_ALLOWED_KEYS = EXECUTOR_OPTS_KEYS + %i[name entrypoint ports pull_policy].freeze
included do
include ::Gitlab::Config::Entry::Validatable
@@ -23,9 +25,15 @@ module Gitlab
validates :name, type: String, presence: true
validates :entrypoint, array_of_strings: true, allow_nil: true
+ validates :executor_opts, json_schema: {
+ base_directory: "lib/gitlab/ci/config/entry/schemas/imageable",
+ detail_errors: true,
+ filename: "executor_opts",
+ hash_conversion: true
+ }, allow_nil: true
end
- attributes :ports, :pull_policy
+ attributes :docker, :ports, :pull_policy
entry :ports, Entry::Ports,
description: 'Ports used to expose the image/service'
@@ -49,6 +57,28 @@ module Gitlab
def skip_config_hash_validation?
true
end
+
+ def executor_opts
+ return unless config.is_a?(Hash)
+
+ config.slice(*EXECUTOR_OPTS_KEYS).compact.presence
+ end
+
+ def value
+ if string?
+ { name: config }
+ elsif hash?
+ {
+ name: config[:name],
+ entrypoint: config[:entrypoint],
+ executor_opts: executor_opts,
+ ports: (ports_value if ports_defined?),
+ pull_policy: pull_policy_value
+ }.compact
+ else
+ {}
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json b/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json
new file mode 100644
index 00000000000..a31374650e6
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "Describe `image:` and `service:` options like `docker:`",
+ "type": "object",
+ "properties": {
+ "docker": {
+ "type": "object",
+ "properties": {
+ "platform": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index 4b3a9990df4..1482ec3dd2b 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -34,14 +34,13 @@ module Gitlab
end
def value
- if string?
- { name: @config }
- elsif hash?
- @config.merge(
- pull_policy: pull_policy_value
+ if hash?
+ super.merge(
+ command: @config[:command],
+ alias: @config[:alias]
).compact
else
- {}
+ super
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1ed622246ed..ab7cca65467 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -58328,6 +58328,9 @@ msgstr ""
msgid "test case"
msgstr ""
+msgid "the '%{data_pointer}' must be a valid '%{type}'"
+msgstr ""
+
msgid "the correct format."
msgstr ""
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
index 1f30cb3b756..5d856875e0b 100644
--- a/qa/qa/page/search/results.rb
+++ b/qa/qa/page/search/results.rb
@@ -4,11 +4,6 @@ module QA
module Page
module Search
class Results < QA::Page::Base
- view 'app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue' do
- element :code_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern
- element :projects_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/views/search/results/_blob_data.html.haml' do
element :result_item_content
element :file_title_content
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
index 6761a603a0a..6307ff31cf9 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
@@ -36,3 +36,31 @@ empty_pull_policy:
services:
- name: postgres:11.6
pull_policy: []
+
+invalid_image_platform:
+ script: echo "Specifying platform."
+ image:
+ name: alpine:latest
+ docker:
+ platform: ["arm64"] # The expected value is a string, not an array
+
+invalid_image_executor_opts:
+ script: echo "Specifying platform."
+ image:
+ name: alpine:latest
+ docker:
+ unknown_key: test
+
+invalid_service_executor_opts:
+ script: echo "Specifying platform."
+ services:
+ - name: mysql:5.7
+ docker:
+ unknown_key: test
+
+invalid_service_platform:
+ script: echo "Specifying platform."
+ services:
+ - name: mysql:5.7
+ docker:
+ platform: ["arm64"] # The expected value is a string, not an array
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
index 8a0f59d1dfd..aebf928613f 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
@@ -29,3 +29,17 @@ pull_policy_array:
services:
- name: postgres:11.6
pull_policy: [always, if-not-present]
+
+image_platform_string:
+ script: echo "Specifying platform."
+ services:
+ - name: mysql:5.7
+ docker:
+ platform: arm64
+
+services_platform_string:
+ script: echo "Specifying platform."
+ image:
+ name: alpine:latest
+ docker:
+ platform: arm64
diff --git a/spec/frontend/emoji/index_spec.js b/spec/frontend/emoji/index_spec.js
index 7d6a45fbf30..577b7bc726e 100644
--- a/spec/frontend/emoji/index_spec.js
+++ b/spec/frontend/emoji/index_spec.js
@@ -925,7 +925,7 @@ describe('emoji', () => {
window.gon = {};
});
- it('returns empty object', async () => {
+ it('returns empty emoji data', async () => {
const result = await loadCustomEmojiWithNames();
expect(result).toEqual({ emojis: {}, names: [] });
@@ -937,7 +937,28 @@ describe('emoji', () => {
delete document.body.dataset.groupFullPath;
});
- it('returns empty object', async () => {
+ it('returns empty emoji data', async () => {
+ const result = await loadCustomEmojiWithNames();
+
+ expect(result).toEqual({ emojis: {}, names: [] });
+ });
+ });
+
+ describe('when GraphQL request returns null data', () => {
+ beforeEach(() => {
+ mockClient = createMockClient([
+ [
+ customEmojiQuery,
+ jest.fn().mockResolvedValue({
+ data: {
+ group: null,
+ },
+ }),
+ ],
+ ]);
+ });
+
+ it('returns empty emoji data', async () => {
const result = await loadCustomEmojiWithNames();
expect(result).toEqual({ emojis: {}, names: [] });
@@ -945,7 +966,7 @@ describe('emoji', () => {
});
describe('when in a group with flag enabled', () => {
- it('returns empty object', async () => {
+ it('returns emoji data', async () => {
const result = await loadCustomEmojiWithNames();
expect(result).toEqual({
diff --git a/spec/frontend/organizations/users/components/app_spec.js b/spec/frontend/organizations/users/components/app_spec.js
index b30fd984099..e7ed712c309 100644
--- a/spec/frontend/organizations/users/components/app_spec.js
+++ b/spec/frontend/organizations/users/components/app_spec.js
@@ -6,7 +6,8 @@ import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import organizationUsersQuery from '~/organizations/users/graphql/organization_users.query.graphql';
import OrganizationsUsersApp from '~/organizations/users/components/app.vue';
-import { MOCK_ORGANIZATION_GID, MOCK_USERS } from '../mock_data';
+import OrganizationsUsersView from '~/organizations/users/components/users_view.vue';
+import { MOCK_ORGANIZATION_GID, MOCK_USERS, MOCK_USERS_FORMATTED } from '../mock_data';
jest.mock('~/alert');
@@ -40,31 +41,26 @@ describe('OrganizationsUsersApp', () => {
mockApollo = null;
});
- const findOrganizationUsersLoading = () => wrapper.findByText('Loading');
- const findOrganizationUsers = () => wrapper.findByTestId('organization-users');
+ const findOrganizationUsersView = () => wrapper.findComponent(OrganizationsUsersView);
describe.each`
- description | mockResolver | loading | userData | error
- ${'when API call is loading'} | ${loadingResolver} | ${true} | ${[]} | ${false}
- ${'when API returns successful with results'} | ${successfulResolver(MOCK_USERS)} | ${false} | ${MOCK_USERS} | ${false}
- ${'when API returns successful without results'} | ${successfulResolver([])} | ${false} | ${[]} | ${false}
- ${'when API returns error'} | ${errorResolver} | ${false} | ${[]} | ${true}
+ description | mockResolver | loading | userData | error
+ ${'when API call is loading'} | ${loadingResolver} | ${true} | ${[]} | ${false}
+ ${'when API returns successful with results'} | ${successfulResolver(MOCK_USERS)} | ${false} | ${MOCK_USERS_FORMATTED} | ${false}
+ ${'when API returns successful without results'} | ${successfulResolver([])} | ${false} | ${[]} | ${false}
+ ${'when API returns error'} | ${errorResolver} | ${false} | ${[]} | ${true}
`('$description', ({ mockResolver, loading, userData, error }) => {
beforeEach(async () => {
createComponent(mockResolver);
await waitForPromises();
});
- it(`does ${
- loading ? '' : 'not '
- }render the organization users view with loading placeholder`, () => {
- expect(findOrganizationUsersLoading().exists()).toBe(loading);
+ it(`renders OrganizationUsersView with loading prop set to ${loading}`, () => {
+ expect(findOrganizationUsersView().props('loading')).toBe(loading);
});
- it(`renders the organization users view with ${
- userData.length ? 'correct' : 'empty'
- } users array raw data`, () => {
- expect(JSON.parse(findOrganizationUsers().text())).toStrictEqual(userData);
+ it('renders OrganizationUsersView with correct users prop', () => {
+ expect(findOrganizationUsersView().props('users')).toStrictEqual(userData);
});
it(`does ${error ? '' : 'not '}render an error message`, () => {
diff --git a/spec/frontend/organizations/users/components/users_view_spec.js b/spec/frontend/organizations/users/components/users_view_spec.js
new file mode 100644
index 00000000000..5f47e18edd8
--- /dev/null
+++ b/spec/frontend/organizations/users/components/users_view_spec.js
@@ -0,0 +1,44 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import UsersView from '~/organizations/users/components/users_view.vue';
+import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
+import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
+
+describe('UsersView', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(UsersView, {
+ propsData: {
+ loading: false,
+ users: MOCK_USERS_FORMATTED,
+ ...props,
+ },
+ provide: {
+ paths: MOCK_PATHS,
+ },
+ });
+ };
+
+ const findGlLoading = () => wrapper.findComponent(GlLoadingIcon);
+ const findUsersTable = () => wrapper.findComponent(UsersTable);
+
+ describe.each`
+ description | loading | usersData
+ ${'when loading'} | ${true} | ${[]}
+ ${'when not loading and has users'} | ${false} | ${MOCK_USERS_FORMATTED}
+ ${'when not loading and has no users'} | ${false} | ${[]}
+ `('$description', ({ loading, usersData }) => {
+ beforeEach(() => {
+ createComponent({ loading, users: usersData });
+ });
+
+ it(`does ${loading ? '' : 'not '}render loading icon`, () => {
+ expect(findGlLoading().exists()).toBe(loading);
+ });
+
+ it(`does ${!loading ? '' : 'not '}render users table`, () => {
+ expect(findUsersTable().exists()).toBe(!loading);
+ });
+ });
+});
diff --git a/spec/frontend/organizations/users/mock_data.js b/spec/frontend/organizations/users/mock_data.js
index 4f159c70c2c..b6ca00bed79 100644
--- a/spec/frontend/organizations/users/mock_data.js
+++ b/spec/frontend/organizations/users/mock_data.js
@@ -1,15 +1,31 @@
+const createUser = (id) => {
+ return {
+ id: `gid://gitlab/User/${id}`,
+ username: `test_user_${id}`,
+ avatarUrl: `/path/test_user_${id}`,
+ name: `Test User ${id}`,
+ publicEmail: `test_user_${id}@gitlab.com`,
+ createdAt: Date.now(),
+ lastActivityOn: Date.now(),
+ };
+};
+
export const MOCK_ORGANIZATION_GID = 'gid://gitlab/Organizations::Organization/1';
+export const MOCK_PATHS = {
+ adminUser: '/admin/users/:id',
+};
+
export const MOCK_USERS = [
{
badges: [],
id: 'gid://gitlab/Organizations::OrganizationUser/3',
- user: { id: 'gid://gitlab/User/3' },
+ user: createUser(3),
},
{
badges: [],
id: 'gid://gitlab/Organizations::OrganizationUser/2',
- user: { id: 'gid://gitlab/User/2' },
+ user: createUser(2),
},
{
badges: [
@@ -17,6 +33,10 @@ export const MOCK_USERS = [
{ text: "It's you!", variant: 'muted' },
],
id: 'gid://gitlab/Organizations::OrganizationUser/1',
- user: { id: 'gid://gitlab/User/1' },
+ user: createUser(1),
},
];
+
+export const MOCK_USERS_FORMATTED = MOCK_USERS.map(({ badges, user }) => {
+ return { ...user, badges, email: user.publicEmail };
+});
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index c2d88493d71..9413927ac26 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -18,8 +18,6 @@ import NotesFilters from '~/search/sidebar/components/notes_filters.vue';
import CommitsFilters from '~/search/sidebar/components/commits_filters.vue';
import MilestonesFilters from '~/search/sidebar/components/milestones_filters.vue';
import WikiBlobsFilters from '~/search/sidebar/components/wiki_blobs_filters.vue';
-import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue';
-import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue';
import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue';
import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
@@ -62,8 +60,6 @@ describe('GlobalSearchSidebar', () => {
const findCommitsFilters = () => wrapper.findComponent(CommitsFilters);
const findMilestonesFilters = () => wrapper.findComponent(MilestonesFilters);
const findWikiBlobsFilters = () => wrapper.findComponent(WikiBlobsFilters);
- const findScopeLegacyNavigation = () => wrapper.findComponent(ScopeLegacyNavigation);
- const findSmallScreenDrawerNavigation = () => wrapper.findComponent(SmallScreenDrawerNavigation);
const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation);
const findDomElementListener = () => wrapper.findComponent(DomElementListener);
@@ -129,46 +125,27 @@ describe('GlobalSearchSidebar', () => {
});
});
- describe.each`
- currentScope | sidebarNavShown | legacyNavShown
- ${'issues'} | ${false} | ${true}
- ${'test'} | ${false} | ${true}
- ${'issues'} | ${true} | ${false}
- ${'test'} | ${true} | ${false}
- `(
- 'renders navigation for scope $currentScope',
- ({ currentScope, sidebarNavShown, legacyNavShown }) => {
- beforeEach(() => {
- getterSpies.currentScope = jest.fn(() => currentScope);
- createComponent({ useSidebarNavigation: sidebarNavShown });
- });
-
- it(`renders navigation correctly with legacyNavShown ${legacyNavShown}`, () => {
- expect(findScopeLegacyNavigation().exists()).toBe(legacyNavShown);
- expect(findSmallScreenDrawerNavigation().exists()).toBe(legacyNavShown);
- });
-
- it(`renders navigation correctly with sidebarNavShown ${sidebarNavShown}`, () => {
- expect(findScopeSidebarNavigation().exists()).toBe(sidebarNavShown);
- });
- },
- );
- });
+ describe.each(['issues', 'test'])('for scope %p', (currentScope) => {
+ beforeEach(() => {
+ getterSpies.currentScope = jest.fn(() => currentScope);
+ createComponent();
+ });
- describe('when useSidebarNavigation=true', () => {
- beforeEach(() => {
- createComponent({ useSidebarNavigation: true });
+ it(`renders navigation correctly`, () => {
+ expect(findScopeSidebarNavigation().exists()).toBe(true);
+ });
});
+ });
- it('toggles super sidebar when button is clicked', () => {
- const elListener = findDomElementListener();
+ it('toggles super sidebar when button is clicked', () => {
+ createComponent();
+ const elListener = findDomElementListener();
- expect(toggleSuperSidebarCollapsed).not.toHaveBeenCalled();
+ expect(toggleSuperSidebarCollapsed).not.toHaveBeenCalled();
- elListener.vm.$emit('click');
+ elListener.vm.$emit('click');
- expect(toggleSuperSidebarCollapsed).toHaveBeenCalledTimes(1);
- expect(elListener.props('selector')).toBe('#js-open-mobile-filters');
- });
+ expect(toggleSuperSidebarCollapsed).toHaveBeenCalledTimes(1);
+ expect(elListener.props('selector')).toBe('#js-open-mobile-filters');
});
});
diff --git a/spec/frontend/search/sidebar/components/blobs_filters_spec.js b/spec/frontend/search/sidebar/components/blobs_filters_spec.js
index 245ddb8f8bb..3f1feae8527 100644
--- a/spec/frontend/search/sidebar/components/blobs_filters_spec.js
+++ b/spec/frontend/search/sidebar/components/blobs_filters_spec.js
@@ -17,13 +17,11 @@ describe('GlobalSearch BlobsFilters', () => {
currentScope: () => 'blobs',
};
- const createComponent = ({ initialState = {} } = {}) => {
+ const createComponent = () => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
- useSidebarNavigation: false,
searchType: SEARCH_TYPE_ADVANCED,
- ...initialState,
},
getters: defaultGetters,
});
@@ -35,10 +33,9 @@ describe('GlobalSearch BlobsFilters', () => {
const findLanguageFilter = () => wrapper.findComponent(LanguageFilter);
const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter);
- const findDividers = () => wrapper.findAll('hr');
beforeEach(() => {
- createComponent({});
+ createComponent();
});
it('renders LanguageFilter', () => {
@@ -48,31 +45,4 @@ describe('GlobalSearch BlobsFilters', () => {
it('renders ArchivedFilter', () => {
expect(findArchivedFilter().exists()).toBe(true);
});
-
- it('renders divider correctly', () => {
- expect(findDividers()).toHaveLength(1);
- });
-
- describe('Renders correctly in new nav', () => {
- beforeEach(() => {
- createComponent({
- initialState: {
- searchType: SEARCH_TYPE_ADVANCED,
- useSidebarNavigation: true,
- },
- });
- });
-
- it('renders correctly LanguageFilter', () => {
- expect(findLanguageFilter().exists()).toBe(true);
- });
-
- it('renders correctly ArchivedFilter', () => {
- expect(findArchivedFilter().exists()).toBe(true);
- });
-
- it("doesn't render dividers", () => {
- expect(findDividers()).toHaveLength(0);
- });
- });
});
diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
index 6444ec10466..fedbd407b0b 100644
--- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
@@ -22,23 +22,11 @@ describe('ConfidentialityFilter', () => {
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
- describe('old sidebar', () => {
- beforeEach(() => {
- createComponent({ useSidebarNavigation: false });
- });
-
- it('renders the component', () => {
- expect(findRadioFilter().exists()).toBe(true);
- });
+ beforeEach(() => {
+ createComponent();
});
- describe('new sidebar', () => {
- beforeEach(() => {
- createComponent({ useSidebarNavigation: true });
- });
-
- it('renders the component', () => {
- expect(findRadioFilter().exists()).toBe(true);
- });
+ it('renders the component', () => {
+ expect(findRadioFilter().exists()).toBe(true);
});
});
diff --git a/spec/frontend/search/sidebar/components/filters_template_spec.js b/spec/frontend/search/sidebar/components/filters_template_spec.js
index f1a807c5ceb..18144e25ac3 100644
--- a/spec/frontend/search/sidebar/components/filters_template_spec.js
+++ b/spec/frontend/search/sidebar/components/filters_template_spec.js
@@ -52,7 +52,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
};
const findForm = () => wrapper.findComponent(GlForm);
- const findDividers = () => wrapper.findAll('hr');
const findApplyButton = () => wrapper.findComponent(GlButton);
const findResetButton = () => wrapper.findComponent(GlLink);
const findSlotContent = () => wrapper.findByText('Filters Content');
@@ -66,10 +65,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
expect(findForm().exists()).toBe(true);
});
- it('renders dividers', () => {
- expect(findDividers()).toHaveLength(2);
- });
-
it('renders slot content', () => {
expect(findSlotContent().exists()).toBe(true);
});
diff --git a/spec/frontend/search/sidebar/components/issues_filters_spec.js b/spec/frontend/search/sidebar/components/issues_filters_spec.js
index 860c5c147a6..acdfa41b5f8 100644
--- a/spec/frontend/search/sidebar/components/issues_filters_spec.js
+++ b/spec/frontend/search/sidebar/components/issues_filters_spec.js
@@ -23,7 +23,6 @@ describe('GlobalSearch IssuesFilters', () => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
- useSidebarNavigation: false,
searchType: SEARCH_TYPE_ADVANCED,
...initialState,
},
@@ -44,7 +43,6 @@ describe('GlobalSearch IssuesFilters', () => {
const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter);
const findLabelFilter = () => wrapper.findComponent(LabelFilter);
const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter);
- const findDividers = () => wrapper.findAll('hr');
describe.each`
description | searchIssueLabelAggregation
@@ -72,15 +70,6 @@ describe('GlobalSearch IssuesFilters', () => {
it(`renders correctly LabelFilter when searchIssueLabelAggregation is ${searchIssueLabelAggregation}`, () => {
expect(findLabelFilter().exists()).toBe(searchIssueLabelAggregation);
});
-
- it('renders divider correctly', () => {
- // two dividers can't be disabled
- let dividersCount = 2;
- if (searchIssueLabelAggregation) {
- dividersCount += 1;
- }
- expect(findDividers()).toHaveLength(dividersCount);
- });
});
describe('Renders correctly with basic search', () => {
@@ -102,41 +91,6 @@ describe('GlobalSearch IssuesFilters', () => {
it("doesn't render ArchivedFilter", () => {
expect(findArchivedFilter().exists()).toBe(true);
});
-
- it('renders 1 divider', () => {
- expect(findDividers()).toHaveLength(2);
- });
- });
-
- describe('Renders correctly in new nav', () => {
- beforeEach(() => {
- createComponent({
- initialState: {
- searchType: SEARCH_TYPE_ADVANCED,
- useSidebarNavigation: true,
- },
- searchIssueLabelAggregation: true,
- });
- });
- it('renders StatusFilter', () => {
- expect(findStatusFilter().exists()).toBe(true);
- });
-
- it('renders ConfidentialityFilter', () => {
- expect(findConfidentialityFilter().exists()).toBe(true);
- });
-
- it('renders LabelFilter', () => {
- expect(findLabelFilter().exists()).toBe(true);
- });
-
- it('renders ArchivedFilter', () => {
- expect(findArchivedFilter().exists()).toBe(true);
- });
-
- it("doesn't render dividers", () => {
- expect(findDividers()).toHaveLength(0);
- });
});
describe('Renders correctly with wrong scope', () => {
@@ -159,9 +113,5 @@ describe('GlobalSearch IssuesFilters', () => {
it("doesn't render ArchivedFilter", () => {
expect(findArchivedFilter().exists()).toBe(false);
});
-
- it("doesn't render dividers", () => {
- expect(findDividers()).toHaveLength(0);
- });
});
});
diff --git a/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js
index b02228a418f..8cd3cb45a20 100644
--- a/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js
+++ b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js
@@ -21,7 +21,6 @@ describe('GlobalSearch MergeRequestsFilters', () => {
const store = new Vuex.Store({
state: {
urlQuery: MOCK_QUERY,
- useSidebarNavigation: false,
searchType: SEARCH_TYPE_ADVANCED,
...initialState,
},
@@ -35,7 +34,6 @@ describe('GlobalSearch MergeRequestsFilters', () => {
const findStatusFilter = () => wrapper.findComponent(StatusFilter);
const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter);
- const findDividers = () => wrapper.findAll('hr');
describe('Renders correctly with Archived Filter', () => {
beforeEach(() => {
@@ -46,8 +44,8 @@ describe('GlobalSearch MergeRequestsFilters', () => {
expect(findStatusFilter().exists()).toBe(true);
});
- it('renders divider correctly', () => {
- expect(findDividers()).toHaveLength(1);
+ it('renders ArchivedFilter', () => {
+ expect(findArchivedFilter().exists()).toBe(true);
});
});
@@ -60,33 +58,9 @@ describe('GlobalSearch MergeRequestsFilters', () => {
expect(findStatusFilter().exists()).toBe(true);
});
- it('renders render ArchivedFilter', () => {
- expect(findArchivedFilter().exists()).toBe(true);
- });
-
- it('renders 1 divider', () => {
- expect(findDividers()).toHaveLength(1);
- });
- });
-
- describe('Renders correctly in new nav', () => {
- beforeEach(() => {
- createComponent({
- searchType: SEARCH_TYPE_ADVANCED,
- useSidebarNavigation: true,
- });
- });
- it('renders StatusFilter', () => {
- expect(findStatusFilter().exists()).toBe(true);
- });
-
it('renders ArchivedFilter', () => {
expect(findArchivedFilter().exists()).toBe(true);
});
-
- it("doesn't render divider", () => {
- expect(findDividers()).toHaveLength(0);
- });
});
describe('Renders correctly with wrong scope', () => {
@@ -101,9 +75,5 @@ describe('GlobalSearch MergeRequestsFilters', () => {
it("doesn't render ArchivedFilter", () => {
expect(findArchivedFilter().exists()).toBe(false);
});
-
- it("doesn't render dividers", () => {
- expect(findDividers()).toHaveLength(0);
- });
});
});
diff --git a/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js
deleted file mode 100644
index 63d8b34fcf0..00000000000
--- a/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import { GlNav, GlNavItem, GlIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-// eslint-disable-next-line no-restricted-imports
-import Vuex from 'vuex';
-import { MOCK_QUERY, MOCK_NAVIGATION } from 'jest/search/mock_data';
-import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue';
-
-Vue.use(Vuex);
-
-const MOCK_NAVIGATION_ENTRIES = Object.entries(MOCK_NAVIGATION);
-
-describe('ScopeLegacyNavigation', () => {
- let wrapper;
-
- const actionSpies = {
- fetchSidebarCount: jest.fn(),
- };
-
- const getterSpies = {
- currentScope: jest.fn(() => 'issues'),
- };
-
- const createComponent = (initialState) => {
- const store = new Vuex.Store({
- state: {
- urlQuery: MOCK_QUERY,
- navigation: MOCK_NAVIGATION,
- ...initialState,
- },
- actions: actionSpies,
- getters: getterSpies,
- });
-
- wrapper = shallowMount(ScopeLegacyNavigation, {
- store,
- });
- };
-
- const findNavElement = () => wrapper.find('nav');
- const findGlNav = () => wrapper.findComponent(GlNav);
- const findGlNavItems = () => wrapper.findAllComponents(GlNavItem);
- const findGlNavItemActive = () => wrapper.find('[active=true]');
- const findGlNavItemActiveLabel = () => findGlNavItemActive().find('[data-testid="label"]');
- const findGlNavItemActiveCount = () => findGlNavItemActive().find('[data-testid="count"]');
-
- describe('scope navigation', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders section', () => {
- expect(findNavElement().exists()).toBe(true);
- });
-
- it('renders nav component', () => {
- expect(findGlNav().exists()).toBe(true);
- });
-
- it('renders all nav item components', () => {
- expect(findGlNavItems()).toHaveLength(MOCK_NAVIGATION_ENTRIES.length);
- });
-
- it('has all proper links', () => {
- const linkAtPosition = 3;
- const { link } = MOCK_NAVIGATION_ENTRIES[linkAtPosition][1];
-
- expect(findGlNavItems().at(linkAtPosition).attributes('href')).toBe(link);
- });
- });
-
- describe('scope navigation sets proper state with url scope set', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('has correct active item', () => {
- expect(findGlNavItemActive().exists()).toBe(true);
- expect(findGlNavItemActiveLabel().text()).toBe('Issues');
- });
-
- it('has correct active item count', () => {
- expect(findGlNavItemActiveCount().text()).toBe('2.4K');
- });
-
- it('does not have plus sign after count text', () => {
- expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(false);
- });
-
- it('has count is highlighted correctly', () => {
- expect(findGlNavItemActiveCount().classes('gl-text-gray-900')).toBe(true);
- });
- });
-
- describe('scope navigation sets proper state with NO url scope set', () => {
- beforeEach(() => {
- getterSpies.currentScope = jest.fn(() => 'projects');
- createComponent({
- urlQuery: {},
- navigation: {
- ...MOCK_NAVIGATION,
- projects: {
- ...MOCK_NAVIGATION.projects,
- active: true,
- },
- issues: {
- ...MOCK_NAVIGATION.issues,
- active: false,
- },
- },
- });
- });
-
- it('has correct active item', () => {
- expect(findGlNavItemActive().exists()).toBe(true);
- expect(findGlNavItemActiveLabel().text()).toBe('Projects');
- });
-
- it('has correct active item count', () => {
- expect(findGlNavItemActiveCount().text()).toBe('10K');
- });
-
- it('has correct active item count and over limit sign', () => {
- expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true);
- });
- });
-
- describe.each`
- searchTherm | hasBeenCalled
- ${null} | ${0}
- ${'test'} | ${1}
- `('fetchSidebarCount', ({ searchTherm, hasBeenCalled }) => {
- beforeEach(() => {
- createComponent({
- urlQuery: {
- search: searchTherm,
- },
- });
- });
-
- it('is only called when search term is set', () => {
- expect(actionSpies.fetchSidebarCount).toHaveBeenCalledTimes(hasBeenCalled);
- });
- });
-});
diff --git a/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js b/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js
deleted file mode 100644
index 5ab4afba7f0..00000000000
--- a/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { nextTick } from 'vue';
-import { GlDrawer } from '@gitlab/ui';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
-import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
-import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue';
-
-describe('ScopeLegacyNavigation', () => {
- let wrapper;
- let closeSpy;
- let toggleSpy;
-
- const createComponent = () => {
- wrapper = shallowMountExtended(SmallScreenDrawerNavigation, {
- slots: {
- default: '<div data-testid="default-slot-content">test</div>',
- },
- });
- };
-
- const findGlDrawer = () => wrapper.findComponent(GlDrawer);
- const findTitle = () => wrapper.findComponent('h2');
- const findSlot = () => wrapper.findByTestId('default-slot-content');
- const findDomElementListener = () => wrapper.findComponent(DomElementListener);
-
- describe('small screen navigation', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders drawer', () => {
- expect(findGlDrawer().exists()).toBe(true);
- expect(findGlDrawer().attributes('zindex')).toBe(DRAWER_Z_INDEX.toString());
- expect(findGlDrawer().attributes('headerheight')).toBe('0');
- });
-
- it('renders title', () => {
- expect(findTitle().exists()).toBe(true);
- });
-
- it('renders slots', () => {
- expect(findSlot().exists()).toBe(true);
- });
- });
-
- describe('actions', () => {
- beforeEach(() => {
- closeSpy = jest.spyOn(SmallScreenDrawerNavigation.methods, 'closeSmallScreenFilters');
- toggleSpy = jest.spyOn(SmallScreenDrawerNavigation.methods, 'toggleSmallScreenFilters');
- createComponent();
- });
-
- it('calls onClose', () => {
- findGlDrawer().vm.$emit('close');
- expect(closeSpy).toHaveBeenCalled();
- });
-
- it('calls toggleSmallScreenFilters', async () => {
- expect(findGlDrawer().props('open')).toBe(false);
-
- findDomElementListener().vm.$emit('click');
- await nextTick();
-
- expect(toggleSpy).toHaveBeenCalled();
- expect(findGlDrawer().props('open')).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js
index c230341c172..719932a79ef 100644
--- a/spec/frontend/search/sidebar/components/status_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/status_filter_spec.js
@@ -22,23 +22,9 @@ describe('StatusFilter', () => {
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
- describe('old sidebar', () => {
- beforeEach(() => {
- createComponent({ useSidebarNavigation: false });
- });
-
- it('renders the component', () => {
- expect(findRadioFilter().exists()).toBe(true);
- });
- });
+ it('renders the component', () => {
+ createComponent();
- describe('new sidebar', () => {
- beforeEach(() => {
- createComponent({ useSidebarNavigation: true });
- });
-
- it('renders the component', () => {
- expect(findRadioFilter().exists()).toBe(true);
- });
+ expect(findRadioFilter().exists()).toBe(true);
});
});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index fd34e7893a9..31fa7cd3ec4 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -58,6 +58,7 @@ describe('SuperSidebar component', () => {
const findTrialStatusWidget = () => wrapper.findByTestId(trialStatusWidgetStubTestId);
const findTrialStatusPopover = () => wrapper.findByTestId(trialStatusPopoverStubTestId);
const findSidebarMenu = () => wrapper.findComponent(SidebarMenu);
+ const findAdminLink = () => wrapper.findByTestId('sidebar-admin-link');
let trackingSpy = null;
const createWrapper = ({
@@ -337,4 +338,25 @@ describe('SuperSidebar component', () => {
expect(document.addEventListener).toHaveBeenCalledWith('keydown', wrapper.vm.focusTrap);
});
});
+
+ describe('link to Admin area', () => {
+ describe('when user is admin', () => {
+ it('renders', () => {
+ createWrapper({
+ sidebarData: {
+ ...mockSidebarData,
+ is_admin: true,
+ },
+ });
+ expect(findAdminLink().attributes('href')).toBe(mockSidebarData.admin_url);
+ });
+ });
+
+ describe('when user is not admin', () => {
+ it('renders', () => {
+ createWrapper();
+ expect(findAdminLink().exists()).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js
index ef0c3686a4e..f5e7a7507ea 100644
--- a/spec/frontend/super_sidebar/mock_data.js
+++ b/spec/frontend/super_sidebar/mock_data.js
@@ -79,6 +79,8 @@ export const contextSwitcherLinks = [
export const sidebarData = {
is_logged_in: true,
+ is_admin: false,
+ admin_url: '/admin',
current_menu_items: [],
current_context: {},
current_context_header: 'Your work',
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
index 00f4092baf4..8ac3b32948f 100644
--- a/spec/graphql/types/root_storage_statistics_type_spec.rb
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe GitlabSchema.types['RootStorageStatistics'] do
+RSpec.describe GitlabSchema.types['RootStorageStatistics'], feature_category: :consumables_cost_management do
specify { expect(described_class.graphql_name).to eq('RootStorageStatistics') }
it 'has the expected fields' do
diff --git a/spec/helpers/organizations/organization_helper_spec.rb b/spec/helpers/organizations/organization_helper_spec.rb
index 9d55d2a84f8..86a7a88797c 100644
--- a/spec/helpers/organizations/organization_helper_spec.rb
+++ b/spec/helpers/organizations/organization_helper_spec.rb
@@ -119,10 +119,13 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
end
describe '#organization_user_app_data' do
- it 'returns expected data object' do
- expect(helper.organization_user_app_data(organization)).to eq(
+ it 'returns expected json' do
+ expect(Gitlab::Json.parse(helper.organization_user_app_data(organization))).to eq(
{
- organization_gid: organization.to_global_id
+ 'organization_gid' => organization.to_global_id.to_s,
+ 'paths' => {
+ 'admin_user' => admin_user_path(:id)
+ }
}
)
end
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index c9131ca518f..421b1c178aa 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -131,8 +131,10 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do
expect(subject).to include({
is_logged_in: true,
+ is_admin: false,
name: user.name,
username: user.username,
+ admin_url: admin_root_url,
avatar_url: user.avatar_url,
has_link_to_profile: helper.current_user_menu?(:profile),
link_to_profile: user_path(user),
@@ -174,6 +176,14 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
})
end
+ context 'when user is admin' do
+ before do
+ allow(user).to receive(:can_admin_all_resources?).and_return(true)
+ end
+
+ it { is_expected.to include({ is_admin: true }) }
+ end
+
describe "shortcut links" do
describe "as the anonymous user" do
let_it_be(:user) { nil }
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index b2da9fa8801..e840dddbedd 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe StorageHelper do
+RSpec.describe StorageHelper, feature_category: :consumables_cost_management do
describe "#storage_counter" do
it "formats bytes to one decimal place" do
expect(helper.storage_counter(1.23.megabytes)).to eq("1.2 MiB")
diff --git a/spec/lib/api/entities/ci/job_request/image_spec.rb b/spec/lib/api/entities/ci/job_request/image_spec.rb
index 14d4a074fce..666ec31d3d9 100644
--- a/spec/lib/api/entities/ci/job_request/image_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/image_spec.rb
@@ -4,7 +4,10 @@ require 'spec_helper'
RSpec.describe API::Entities::Ci::JobRequest::Image do
let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }] }
- let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports, pull_policy: ['if-not-present']) }
+ let(:image) do
+ double(name: 'image_name', entrypoint: ['foo'], executor_opts: {}, ports: ports, pull_policy: ['if-not-present'])
+ end
+
let(:entity) { described_class.new(image) }
subject { entity.as_json }
@@ -29,6 +32,10 @@ RSpec.describe API::Entities::Ci::JobRequest::Image do
end
end
+ it 'returns the executor_opts options' do
+ expect(subject[:executor_opts]).to eq({})
+ end
+
it 'returns the pull policy' do
expect(subject[:pull_policy]).to eq(['if-not-present'])
end
diff --git a/spec/lib/api/entities/ci/job_request/service_spec.rb b/spec/lib/api/entities/ci/job_request/service_spec.rb
index 11350f7c41b..c2331799314 100644
--- a/spec/lib/api/entities/ci/job_request/service_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/service_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe API::Entities::Ci::JobRequest::Service do
::Gitlab::Ci::Build::Image,
name: 'image_name',
entrypoint: ['foo'],
+ executor_opts: {},
ports: ports,
pull_policy: ['if-not-present'],
alias: 'alias',
@@ -25,6 +26,7 @@ RSpec.describe API::Entities::Ci::JobRequest::Service do
expect(result).to eq(
name: 'image_name',
entrypoint: ['foo'],
+ executor_opts: {},
ports: ports,
pull_policy: ['if-not-present'],
alias: 'alias',
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 4895077a731..f8c0d69be2e 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
context 'when image is defined in job' do
let(:image_name) { 'image:1.0' }
- let(:job) { create(:ci_build, options: { image: image_name } ) }
+ let(:job) { create(:ci_build, options: { image: image_name }) }
context 'when image is defined as string' do
it 'fabricates an object of the proper class' do
@@ -29,12 +29,14 @@ RSpec.describe Gitlab::Ci::Build::Image do
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
let(:pull_policy) { %w[always if-not-present] }
+ let(:executor_opts) { { docker: { platform: 'arm64' } } }
let(:job) do
create(:ci_build, options: { image: { name: image_name,
entrypoint: entrypoint,
ports: [80],
- pull_policy: pull_policy } } )
+ executor_opts: executor_opts,
+ pull_policy: pull_policy } })
end
it 'fabricates an object of the proper class' do
@@ -44,6 +46,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
it 'populates fabricated object with the proper attributes' do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
+ expect(subject.executor_opts).to eq(executor_opts)
expect(subject.pull_policy).to eq(pull_policy)
end
@@ -98,11 +101,12 @@ RSpec.describe Gitlab::Ci::Build::Image do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
+ let(:executor_opts) { { docker: { platform: 'amd64' } } }
let(:pull_policy) { %w[always if-not-present] }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
alias: service_alias, command: service_command, ports: [80],
- pull_policy: pull_policy }] })
+ executor_opts: executor_opts, pull_policy: pull_policy }] })
end
it 'fabricates an non-empty array of objects' do
@@ -116,6 +120,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
+ expect(subject.first.executor_opts).to eq(executor_opts)
expect(subject.first.pull_policy).to eq(pull_policy)
port = subject.first.ports.first
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 17c45ec4c2c..50f3e209ad0 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -42,6 +42,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
end
+ describe '#executor_opts' do
+ it "returns nil" do
+ expect(entry.executor_opts).to be_nil
+ end
+ end
+
describe '#ports' do
it "returns image's ports" do
expect(entry.ports).to be_nil
@@ -88,6 +94,52 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
end
+ context 'when configuration specifies docker' do
+ let(:config) { { name: 'image:1.0', docker: {} } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: {}
+ }
+ )
+ end
+ end
+
+ context "when docker specifies an option" do
+ let(:config) { { name: 'image:1.0', docker: { platform: 'amd64' } } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: { platform: 'amd64' }
+ }
+ )
+ end
+ end
+ end
+
+ context "when docker specifies an invalid option" do
+ let(:config) { { name: 'image:1.0', docker: { platform: 1 } } }
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run], ports: ports } }
@@ -146,7 +198,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors.first)
- .to match /config should be a hash or a string/
+ .to match(/config should be a hash or a string/)
end
end
@@ -163,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors.first)
- .to match /config contains unknown keys: non_existing/
+ .to match(/config contains unknown keys: non_existing/)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 1f935bebed5..9f9c15145ed 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -47,6 +47,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
expect(entry.ports).to be_nil
end
end
+
+ describe '#executor_opts' do
+ it "returns service's executor_opts configuration" do
+ expect(entry.executor_opts).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
@@ -141,6 +147,27 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
end
end
+ context 'when configuration has docker options' do
+ let(:config) { { name: 'postgresql:9.5', docker: { platform: 'amd64' } } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5',
+ executor_opts: {
+ docker: { platform: 'amd64' }
+ }
+ )
+ end
+ end
+ end
+
context 'when configuration has pull_policy' do
let(:config) { { name: 'postgresql:9.5', pull_policy: 'if-not-present' } }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index f01c1c7d053..e44e01b2ffa 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1313,6 +1313,46 @@ module Gitlab
})
end
end
+
+ context 'when image and service have docker options' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: exit 0
+ image:
+ name: ruby:2.7
+ docker:
+ platform: linux/amd64
+ services:
+ - name: postgres:11.9
+ docker:
+ platform: linux/amd64
+ YAML
+ end
+
+ it { is_expected.to be_valid }
+
+ it "returns with image" do
+ expect(processor.builds).to contain_exactly({
+ stage: "test",
+ stage_idx: 2,
+ name: "test",
+ only: { refs: %w[branches tags] },
+ options: {
+ script: ["exit 0"],
+ image: { name: "ruby:2.7",
+ executor_opts: { docker: { platform: 'linux/amd64' } } },
+ services: [{ name: "postgres:11.9",
+ executor_opts: { docker: { platform: 'linux/amd64' } } }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ job_variables: [],
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ })
+ end
+ end
end
describe 'Variables' do
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index 2a870a25ea6..3d6d86335eb 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -272,16 +272,19 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect(json_response['job_info']).to include(expected_job_info)
expect(json_response['git_info']).to eq(expected_git_info)
expect(json_response['image']).to eq(
- { 'name' => 'image:1.0', 'entrypoint' => '/bin/sh', 'ports' => [], 'pull_policy' => nil }
+ { 'name' => 'image:1.0', 'entrypoint' => '/bin/sh', 'ports' => [], 'executor_opts' => {},
+ 'pull_policy' => nil }
)
expect(json_response['services']).to eq(
[
{ 'name' => 'postgres', 'entrypoint' => nil, 'alias' => nil, 'command' => nil, 'ports' => [],
- 'variables' => nil, 'pull_policy' => nil },
- { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh', 'alias' => 'docker', 'command' => 'sleep 30',
- 'ports' => [], 'variables' => [], 'pull_policy' => nil },
+ 'variables' => nil, 'executor_opts' => {}, 'pull_policy' => nil },
+ { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh', 'alias' => 'docker',
+ 'command' => 'sleep 30', 'ports' => [], 'variables' => [], 'executor_opts' => {},
+ 'pull_policy' => nil },
{ 'name' => 'mysql:latest', 'entrypoint' => nil, 'alias' => nil, 'command' => nil, 'ports' => [],
- 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }], 'pull_policy' => nil }
+ 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }], 'executor_opts' => {},
+ 'pull_policy' => nil }
])
expect(json_response['steps']).to eq(expected_steps)
expect(json_response['hooks']).to eq(expected_hooks)
@@ -920,6 +923,41 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
end
+ context 'when image has docker options' do
+ let(:job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) }
+
+ let(:options) do
+ {
+ image: {
+ name: 'ruby',
+ executor_opts: {
+ docker: {
+ platform: 'amd64'
+ }
+ }
+ }
+ }
+ end
+
+ it 'returns the image with docker options' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to include(
+ 'id' => job.id,
+ 'image' => { 'name' => 'ruby',
+ 'executor_opts' => {
+ 'docker' => {
+ 'platform' => 'amd64'
+ }
+ },
+ 'pull_policy' => nil,
+ 'entrypoint' => nil,
+ 'ports' => [] }
+ )
+ end
+ end
+
context 'when image has pull_policy' do
let(:job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) }
@@ -938,7 +976,11 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to include(
'id' => job.id,
- 'image' => { 'name' => 'ruby', 'pull_policy' => ['if-not-present'], 'entrypoint' => nil, 'ports' => [] }
+ 'image' => { 'name' => 'ruby',
+ 'executor_opts' => {},
+ 'pull_policy' => ['if-not-present'],
+ 'entrypoint' => nil,
+ 'ports' => [] }
)
end
end
@@ -962,7 +1004,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect(json_response).to include(
'id' => job.id,
'services' => [{ 'alias' => nil, 'command' => nil, 'entrypoint' => nil, 'name' => 'postgres:11.9',
- 'ports' => [], 'pull_policy' => ['if-not-present'], 'variables' => [] }]
+ 'ports' => [], 'executor_opts' => {}, 'pull_policy' => ['if-not-present'],
+ 'variables' => [] }]
)
end
end
diff --git a/spec/requests/api/ci/runner/jobs_request_yamls_spec.rb b/spec/requests/api/ci/runner/jobs_request_yamls_spec.rb
new file mode 100644
index 00000000000..f399c3e310e
--- /dev/null
+++ b/spec/requests/api/ci/runner/jobs_request_yamls_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :continuous_integration do
+ include StubGitlabCalls
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, shared_runners_enabled: false) }
+ let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }
+
+ before_all do
+ project.add_maintainer(user)
+ end
+
+ Dir[Rails.root.join("spec/requests/api/ci/runner/yamls/*.yml")].each do |yml_file|
+ context "for #{File.basename(yml_file)}" do
+ let(:yaml_content) { YAML.load_file(yml_file) }
+ let(:gitlab_ci_yml) { yaml_content.fetch("gitlab_ci") }
+ let(:request_response) { yaml_content.fetch("request_response") }
+
+ it 'runs a job' do
+ stub_ci_pipeline_yaml_file(YAML.dump(gitlab_ci_yml))
+
+ pipeline_response = create_pipeline!
+ expect(pipeline_response).to be_success, pipeline_response.message
+ expect(pipeline_response.payload).to be_created_successfully
+ expect(pipeline_response.payload.builds).to be_one
+
+ build = pipeline_response.payload.builds.first
+
+ process_pipeline!(pipeline_response.payload)
+ expect(build.reload).to be_pending
+
+ request_job(runner.token)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response.headers['Content-Type']).to eq('application/json')
+ expect(json_response).to include('id' => build.id, 'token' => build.token)
+ expect(json_response).to include(request_response)
+ end
+ end
+ end
+
+ def create_pipeline!
+ params = { ref: 'master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: 'some commit' }] }
+
+ Ci::CreatePipelineService.new(project, user, params).execute(:push)
+ end
+
+ def process_pipeline!(pipeline)
+ PipelineProcessWorker.new.perform(pipeline.id)
+ end
+
+ def request_job(token, **params)
+ new_params = params.merge(token: token)
+ post api('/jobs/request'), params: new_params.to_json,
+ headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }
+ end
+end
diff --git a/spec/requests/api/ci/runner/yamls/README.md b/spec/requests/api/ci/runner/yamls/README.md
new file mode 100644
index 00000000000..db8ef51ff9c
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/README.md
@@ -0,0 +1,15 @@
+# .gitlab-ci.yml end-to-end tests
+
+The purpose of this folder is to provide a single job `.gitlab-ci.yml`
+that will be validated against end-to-end response that is send to runner.
+
+This allows to easily test end-to-end all CI job transformation that
+and impact on how such job is rendered to be executed by the GitLab Runner.
+
+```yaml
+gitlab_ci:
+ # .gitlab-ci.yml to stub
+
+request_response:
+ # exact payload that is checked as returned by `/api/v4/jobs/request`
+```
diff --git a/spec/requests/api/ci/runner/yamls/image-basic.yml b/spec/requests/api/ci/runner/yamls/image-basic.yml
new file mode 100644
index 00000000000..0c01dbc6e8b
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/image-basic.yml
@@ -0,0 +1,19 @@
+gitlab_ci:
+ rspec:
+ image: alpine:latest
+ script: echo Hello World
+
+request_response:
+ image:
+ name: alpine:latest
+ entrypoint: null
+ executor_opts: {}
+ ports: []
+ pull_policy: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services: []
diff --git a/spec/requests/api/ci/runner/yamls/image-executor_opts-platform.yml b/spec/requests/api/ci/runner/yamls/image-executor_opts-platform.yml
new file mode 100644
index 00000000000..62e301f2e9a
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/image-executor_opts-platform.yml
@@ -0,0 +1,25 @@
+gitlab_ci:
+ rspec:
+ image:
+ name: alpine:latest
+ docker:
+ platform: amd64
+ script: echo Hello World
+
+request_response:
+ image:
+ name: alpine:latest
+ entrypoint: null
+ executor_opts:
+ docker:
+ platform: amd64
+ ports: []
+ pull_policy: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services: []
+
diff --git a/spec/requests/api/ci/runner/yamls/service-basic.yml b/spec/requests/api/ci/runner/yamls/service-basic.yml
new file mode 100644
index 00000000000..5438837c496
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/service-basic.yml
@@ -0,0 +1,23 @@
+gitlab_ci:
+ rspec:
+ services:
+ - docker:dind
+ script: echo Hello World
+
+request_response:
+ image: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services:
+ - name: docker:dind
+ alias: null
+ command: null
+ entrypoint: null
+ executor_opts: {}
+ ports: []
+ pull_policy: null
+ variables: []
diff --git a/spec/requests/api/ci/runner/yamls/service-executor_opts-platform.yml b/spec/requests/api/ci/runner/yamls/service-executor_opts-platform.yml
new file mode 100644
index 00000000000..6483d749c45
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/service-executor_opts-platform.yml
@@ -0,0 +1,27 @@
+gitlab_ci:
+ rspec:
+ services:
+ - name: docker:dind
+ docker:
+ platform: amd64
+ script: echo Hello World
+
+request_response:
+ image: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services:
+ - name: docker:dind
+ alias: null
+ command: null
+ entrypoint: null
+ executor_opts:
+ docker:
+ platform: amd64
+ ports: []
+ pull_policy: null
+ variables: []
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index da23f81e86e..fc6823352cd 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -2709,7 +2709,6 @@
- './ee/spec/services/merge_requests/build_service_spec.rb'
- './ee/spec/services/merge_requests/mergeability/check_approved_service_spec.rb'
- './ee/spec/services/merge_requests/mergeability/check_blocked_by_other_mrs_service_spec.rb'
-- './ee/spec/services/merge_requests/mergeability/check_denied_policies_service_spec.rb'
- './ee/spec/services/merge_requests/merge_service_spec.rb'
- './ee/spec/services/merge_requests/merge_to_ref_service_spec.rb'
- './ee/spec/services/merge_requests/push_options_handler_service_spec.rb'
diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb
index 01caf4ab0bd..a12e7fbd160 100644
--- a/spec/validators/json_schema_validator_spec.rb
+++ b/spec/validators/json_schema_validator_spec.rb
@@ -58,5 +58,32 @@ RSpec.describe JsonSchemaValidator do
end
end
end
+
+ context "for executor_opts schema" do
+ context 'when detail_errors is true' do
+ let(:validator) { described_class.new(attributes: [:data], detail_errors: true, filename: "build_report_result_data") }
+
+ context 'when data is valid' do
+ it 'returns no errors' do
+ subject
+
+ expect(build_report_result.errors).to be_empty
+ end
+ end
+
+ context 'when data is invalid' do
+ it 'returns json schema is invalid' do
+ build_report_result.data = { invalid: 'data' }
+
+ subject
+
+ expect(build_report_result.errors.size).to eq(1)
+ expect(build_report_result.errors.full_messages).to match_array(
+ ["Data the '/invalid' must be a valid 'schema'"]
+ )
+ end
+ end
+ end
+ end
end
end