summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-23 21:10:28 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-23 21:10:28 +0000
commit4e7abe540dbd1d170bfb2b3594e645cbfb48cac3 (patch)
treef3de940e069b4d927acfdf54247c9900113a4c79
parentf6b95a66bc12adeb4fac7277d1eb345d9e7819fd (diff)
downloadgitlab-ce-4e7abe540dbd1d170bfb2b3594e645cbfb48cac3.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml23
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml76
-rw-r--r--.gitlab/ci/rails/shared.gitlab-ci.yml10
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml9
-rw-r--r--CHANGELOG.md7
-rw-r--r--app/assets/javascripts/ide/components/ide.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job/manual_variables_form.vue16
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js23
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js4
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue3
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_portal.vue30
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_portal_target.vue17
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue3
-rw-r--r--app/assets/javascripts/super_sidebar/constants.js11
-rw-r--r--app/assets/stylesheets/framework/super_sidebar.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss15
-rw-r--r--app/assets/stylesheets/pages/issues.scss44
-rw-r--r--app/controllers/concerns/sorting_preference.rb4
-rw-r--r--app/controllers/jira_connect/public_keys_controller.rb2
-rw-r--r--app/helpers/jira_connect_helper.rb2
-rw-r--r--app/helpers/nav_helper.rb13
-rw-r--r--app/helpers/sorting_helper.rb6
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml6
-rw-r--r--app/views/projects/settings/integrations/_form.html.haml3
-rw-r--r--config/gitlab.yml.example1
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--db/docs/ci_cost_settings.yml10
-rw-r--r--db/migrate/20230216171309_create_ci_runner_cost_settings.rb17
-rw-r--r--db/migrate/20230221110256_create_initial_partition_for_ci_runner_machine_builds.rb49
-rw-r--r--db/schema_migrations/202302161713091
-rw-r--r--db/schema_migrations/202302211102561
-rw-r--r--db/structure.sql29
-rw-r--r--doc/ci/jobs/ci_job_token.md4
-rw-r--r--doc/development/application_slis/rails_request.md17
-rw-r--r--doc/development/navigation_sidebar.md38
-rw-r--r--doc/development/pipelines/index.md19
-rw-r--r--doc/development/pipelines/internals.md2
-rw-r--r--doc/user/packages/yarn_repository/index.md351
-rw-r--r--doc/user/shortcuts.md15
-rw-r--r--lib/gitlab/database/gitlab_schema.rb5
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/controllers/concerns/sorting_preference_spec.rb41
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb38
-rw-r--r--spec/features/commit_spec.rb13
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb39
-rw-r--r--spec/frontend/issuable/components/issue_milestone_spec.js159
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js50
-rw-r--r--spec/frontend/profile/account/components/update_username_spec.js29
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js4
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/mock_data.js2
-rw-r--r--spec/frontend/sidebar/components/participants/participants_spec.js197
-rw-r--r--spec/frontend/super_sidebar/components/sidebar_portal_spec.js68
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js6
-rw-r--r--spec/helpers/jira_connect_helper_spec.rb19
-rw-r--r--spec/helpers/sorting_helper_spec.rb54
-rw-r--r--spec/lib/gitlab/database/gitlab_schema_spec.rb20
-rw-r--r--spec/lib/gitlab/database/partitioning/partition_manager_spec.rb17
-rw-r--r--spec/lib/gitlab/database/postgres_foreign_key_spec.rb4
-rw-r--r--spec/requests/api/avatar_spec.rb1
-rw-r--r--spec/requests/jira_connect/public_keys_controller_spec.rb21
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/matchers/request_urgency_matcher.rb29
63 files changed, 1032 insertions, 682 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index ba623ef4cbe..0ee810340ff 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -229,14 +229,6 @@
- *node-modules-cache # We don't push this cache as it's already rebuilt by `update-assets-compile-*-cache`
- *storybook-node-modules-cache-push
-.use-pg11:
- services:
- - name: postgres:11.6
- command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- - name: redis:5.0-alpine
- variables:
- POSTGRES_HOST_AUTH_METHOD: trust
- PG_VERSION: "11"
.use-pg12:
services:
@@ -256,21 +248,6 @@
POSTGRES_HOST_AUTH_METHOD: trust
PG_VERSION: "13"
-.use-pg11-es7-ee:
- services:
- - name: postgres:11.6
- command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- - name: redis:5.0-alpine
- - name: elasticsearch:7.17.6
- command: ["elasticsearch", "-E", "discovery.type=single-node", "-E", "xpack.security.enabled=false"]
- - name: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:zoekt-ci-image-1.0
- alias: zoekt-ci-image
- variables:
- POSTGRES_HOST_AUTH_METHOD: trust
- PG_VERSION: "11"
- ZOEKT_INDEX_BASE_URL: http://zoekt-ci-image:6060
- ZOEKT_SEARCH_BASE_URL: http://zoekt-ci-image:6070
-
.use-pg12-es7-ee:
services:
- name: postgres:12
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 23f38fddb80..671325101e2 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -191,16 +191,6 @@ rspec system pg12 praefect:
- .praefect-with-db
- .rails:rules:praefect-with-db
-# Dedicated job to test DB library code against PG11.
-# Note that these are already tested against PG12 in the `rspec unit pg12` / `rspec-ee unit pg12` jobs.
-rspec db-library-code pg11:
- extends:
- - .rspec-base-pg11
- - .rails:rules:ee-and-foss-db-library-code
- script:
- - !reference [.base-script, script]
- - rspec_db_library_code
-
rspec fast_spec_helper:
extends:
- .rspec-base-pg12
@@ -616,39 +606,6 @@ rspec-ee system pg12 single-db:
##########################################
# EE/FOSS: default branch nightly scheduled jobs #
-# PG11
-rspec migration pg11:
- extends:
- - .rspec-base-pg11
- - .rspec-base-migration
- - .rails:rules:rspec-on-pg11
- - .rspec-migration-parallel
-
-rspec background_migration pg11:
- extends:
- - .rspec-base-pg11
- - .rspec-base-migration
- - .rails:rules:rspec-on-pg11
- - .rspec-background-migration-parallel
-
-rspec unit pg11:
- extends:
- - .rspec-base-pg11
- - .rails:rules:rspec-on-pg11
- - .rspec-unit-parallel
-
-rspec integration pg11:
- extends:
- - .rspec-base-pg11
- - .rails:rules:rspec-on-pg11
- - .rspec-integration-parallel
-
-rspec system pg11:
- extends:
- - .rspec-base-pg11
- - .rails:rules:rspec-on-pg11
- - .rspec-system-parallel
-
# PG13
rspec migration pg13:
extends:
@@ -687,39 +644,6 @@ rspec system pg13:
#####################################
# EE: default branch nightly scheduled jobs #
-# PG11
-rspec-ee migration pg11:
- extends:
- - .rspec-ee-base-pg11
- - .rspec-base-migration
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- - .rspec-ee-migration-parallel
-
-rspec-ee background_migration pg11:
- extends:
- - .rspec-ee-base-pg11
- - .rspec-base-migration
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- - .rspec-ee-background-migration-parallel
-
-rspec-ee unit pg11:
- extends:
- - .rspec-ee-base-pg11
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- - .rspec-ee-unit-parallel
-
-rspec-ee integration pg11:
- extends:
- - .rspec-ee-base-pg11
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- - .rspec-ee-integration-parallel
-
-rspec-ee system pg11:
- extends:
- - .rspec-ee-base-pg11
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
- - .rspec-ee-system-parallel
-
# PG12
rspec-ee unit pg12 opensearch1:
extends:
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index 4943f7c2e28..ba0781a845f 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -92,11 +92,6 @@ include:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag ~quarantine --tag ~zoekt"
-.rspec-base-pg11:
- extends:
- - .rspec-base
- - .use-pg11
-
.rspec-base-pg12:
extends:
- .rspec-base
@@ -119,11 +114,6 @@ include:
- .rspec-base
- .use-pg13
-.rspec-ee-base-pg11:
- extends:
- - .rspec-base
- - .use-pg11-es7-ee
-
.rspec-ee-base-pg12:
extends:
- .rspec-base
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index d1e29084a5a..ee85ca82673 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -85,9 +85,6 @@
.if-merge-request-labels-run-review-app: &if-merge-request-labels-run-review-app
if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-review-app/'
-.if-merge-request-labels-run-on-pg11: &if-merge-request-labels-run-on-pg11
- if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-on-pg11/'
-
.if-merge-request-labels-skip-undercoverage: &if-merge-request-labels-skip-undercoverage
if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-undercoverage/'
@@ -1605,7 +1602,6 @@
- <<: *if-default-refs
changes: *db-library-patterns
- <<: *if-merge-request-labels-run-all-rspec
- - <<: *if-merge-request-labels-run-on-pg11
.rails:rules:ee-mr-and-default-branch-only:
rules:
@@ -1695,11 +1691,6 @@
- <<: *if-merge-request
changes: *backend-patterns
-.rails:rules:rspec-on-pg11:
- rules:
- - <<: *if-merge-request-labels-run-on-pg11
- - !reference [".rails:rules:default-branch-schedule-nightly--code-backstage-default-rules", rules]
-
.rails:rules:default-branch-schedule-nightly--code-backstage-default-rules:
rules:
- <<: *if-default-branch-schedule-nightly
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bba2aeb77cc..b82e5de350f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 15.9.1 (2023-02-23)
+
+### Fixed (2 changes)
+
+- [Fix Broadcast messages not showing in admin console](gitlab-org/gitlab@f50dfdfe43231b4bb52378eaaa515ee76c918d03) ([merge request](gitlab-org/gitlab!112831))
+- [Fix dependency check in license approval policies](gitlab-org/gitlab@ff5a77036fdb74c4b410fbb954428dbf8736ffd8) ([merge request](gitlab-org/gitlab!112831)) **GitLab Enterprise Edition**
+
## 15.9.0 (2023-02-21)
### Added (223 changes)
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 838debf1ceb..6bbad88715f 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -82,7 +82,7 @@ export default {
eventHub.$on('skip-beforeunload', this.handleSkipBeforeUnload);
if (this.themeName)
- document.querySelector('.navbar-gitlab').classList.add(`theme-${this.themeName}`);
+ document.querySelector('.navbar-gitlab')?.classList.add(`theme-${this.themeName}`);
},
destroyed() {
eventHub.$off('skip-beforeunload', this.handleSkipBeforeUnload);
diff --git a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
index df37cd1f585..a92153c0509 100644
--- a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue
@@ -71,14 +71,14 @@ export default {
required: true,
},
},
- clearBtnSharedClasses: ['gl-flex-grow-0 gl-flex-basis-0'],
+ clearBtnSharedClasses: ['gl-flex-grow-0 gl-flex-basis-0 gl-m-0! gl-ml-3!'],
inputTypes: {
key: 'key',
value: 'value',
},
i18n: {
cancel: s__('CiVariables|Cancel'),
- clearInputs: s__('CiVariables|Clear inputs'),
+ removeInputs: s__('CiVariables|Remove inputs'),
formHelpText: s__(
'CiVariables|Specify variable values to be used in this run. The variables specified in the configuration file and %{linkStart}CI/CD settings%{linkEnd} are used by default.',
),
@@ -209,7 +209,7 @@ export default {
<div
v-for="(variable, index) in variables"
:key="variable.id"
- class="gl-display-flex gl-align-items-center gl-mb-4"
+ class="gl-display-flex gl-align-items-center gl-mb-5"
data-testid="ci-variable-row"
>
<gl-form-input-group class="gl-mr-4 gl-flex-grow-1">
@@ -244,12 +244,11 @@ export default {
<gl-button
v-if="canRemove(index)"
v-gl-tooltip
- :aria-label="$options.i18n.clearInputs"
- :title="$options.i18n.clearInputs"
+ :aria-label="$options.i18n.removeInputs"
+ :title="$options.i18n.removeInputs"
:class="$options.clearBtnSharedClasses"
category="tertiary"
- variant="danger"
- icon="clear"
+ icon="remove"
data-testid="delete-variable-btn"
@click="deleteVariable(variable.id)"
/>
@@ -260,8 +259,7 @@ export default {
:class="$options.clearBtnSharedClasses"
data-testid="delete-variable-btn-placeholder"
category="tertiary"
- variant="danger"
- icon="clear"
+ icon="remove"
/>
</div>
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index ebc118c0d87..83ae7142cf2 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
import { insertText } from '~/lib/utils/common_utils';
+import { ENTER_KEY } from '~/lib/utils/keys';
import axios from '~/lib/utils/axios_utils';
const LINK_TAG_PATTERN = '[{text}](url)';
@@ -520,7 +521,7 @@ function continueOlText(listLineMatch, nextLineMatch) {
function handleContinueList(e, textArea) {
if (!gon.markdown_automatic_lists) return;
- if (!(e.key === 'Enter')) return;
+ if (!(e.key === ENTER_KEY)) return;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (textArea.selectionStart !== textArea.selectionEnd) return;
@@ -577,6 +578,25 @@ function handleContinueList(e, textArea) {
}
}
+/**
+ * Adds a Markdown hard break when `Shift+Enter` is pressed
+ *
+ * @param {Object} e - the event
+ * @param {Object} textArea - the targeted text area
+ */
+function handleHardBreak(e, textArea) {
+ if (!(e.key === ENTER_KEY)) return;
+ if (!e.shiftKey) return;
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
+
+ // prevent unintended line breaks inserted using Japanese IME on MacOS
+ if (compositioningNoteText) return;
+
+ e.preventDefault();
+
+ insertText(textArea, '\\\n');
+}
+
export function keypressNoteText(e) {
const textArea = this;
@@ -584,6 +604,7 @@ export function keypressNoteText(e) {
handleContinueList(e, textArea);
handleSurroundSelectedText(e, textArea);
+ handleHardBreak(e, textArea);
}
export function compositionStartNoteText() {
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 667fd89af55..3a293956089 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -20,7 +20,9 @@ import { initReportAbuse } from '~/projects/report_abuse';
const hasPerfBar = document.querySelector('.with-performance-bar');
const performanceHeight = hasPerfBar ? 35 : 0;
-initDiffStatsDropdown(document.querySelector('.navbar-gitlab').offsetHeight + performanceHeight);
+initDiffStatsDropdown(
+ (document.querySelector('.navbar-gitlab')?.offsetHeight ?? 0) + performanceHeight,
+);
new ZenMode();
new ShortcutsNavigation();
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index fa96eee5f92..35e65698cd2 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -70,7 +70,7 @@ export default {
return this.approvalDetails.length;
},
detailsPath() {
- return `${this.branchRulesPath}?branch=${this.name}`;
+ return `${this.branchRulesPath}?branch=${encodeURIComponent(this.name)}`;
},
statusChecksText() {
return sprintf(this.$options.i18n.statusChecks, {
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 2f25c2fd4b0..b8f659ac56f 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -99,7 +99,7 @@ export default {
>
<gl-icon name="users" />
<gl-loading-icon v-if="loading" size="sm" />
- <span v-else data-testid="collapsed-count" class="gl-pt-2 gl-px-3 gl-font-sm">
+ <span v-else class="gl-pt-2 gl-px-3 gl-font-sm">
{{ participantCount }}
</span>
</div>
@@ -133,7 +133,6 @@ export default {
<gl-button
variant="link"
button-text-classes="gl-text-secondary"
- data-testid="more-participants"
@click="toggleMoreParticipants"
>{{ toggleLabel }}</gl-button
>
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_portal.vue b/app/assets/javascripts/super_sidebar/components/sidebar_portal.vue
new file mode 100644
index 00000000000..2a805c86a3b
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_portal.vue
@@ -0,0 +1,30 @@
+<script>
+import { MountingPortal } from 'portal-vue';
+import { SIDEBAR_PORTAL_ID, portalState } from '../constants';
+
+/**
+ * Use this component to render content into the sidebar.
+ *
+ * Arbitrary content is allowed, but nav items should be added using a Ruby
+ * Sidebars::Panel subclass instead.
+ *
+ * Only one instance of this component on a given page is supported. This is to
+ * avoid ordering issues and cluttering the sidebar.
+ */
+export default {
+ components: {
+ MountingPortal,
+ },
+ data() {
+ // This is shared state, by design. Do not mutate this state here.
+ return portalState;
+ },
+ mountSelector: `#${SIDEBAR_PORTAL_ID}`,
+};
+</script>
+
+<template>
+ <mounting-portal v-if="ready" :mount-to="$options.mountSelector" append>
+ <slot></slot>
+ </mounting-portal>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_portal_target.vue b/app/assets/javascripts/super_sidebar/components/sidebar_portal_target.vue
new file mode 100644
index 00000000000..1154a4357e0
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_portal_target.vue
@@ -0,0 +1,17 @@
+<script>
+import { SIDEBAR_PORTAL_ID, portalState } from '../constants';
+
+export default {
+ mounted() {
+ portalState.ready = true;
+ },
+ beforeDestroy() {
+ portalState.ready = false;
+ },
+ mountId: SIDEBAR_PORTAL_ID,
+};
+</script>
+
+<template>
+ <div v-once :id="$options.mountId"></div>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index b7a70825b62..2da71c029fd 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -1,6 +1,7 @@
<script>
import { GlCollapse } from '@gitlab/ui';
import UserBar from './user_bar.vue';
+import SidebarPortalTarget from './sidebar_portal_target.vue';
import ContextSwitcherToggle from './context_switcher_toggle.vue';
import ContextSwitcher from './context_switcher.vue';
import HelpCenter from './help_center.vue';
@@ -14,6 +15,7 @@ export default {
ContextSwitcher,
HelpCenter,
SidebarMenu,
+ SidebarPortalTarget,
},
props: {
sidebarData: {
@@ -53,6 +55,7 @@ export default {
</gl-collapse>
<gl-collapse :visible="!contextSwitcherOpened">
<sidebar-menu :items="menuItems" />
+ <sidebar-portal-target />
</gl-collapse>
</div>
<div class="gl-p-3">
diff --git a/app/assets/javascripts/super_sidebar/constants.js b/app/assets/javascripts/super_sidebar/constants.js
new file mode 100644
index 00000000000..a1046062cec
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/constants.js
@@ -0,0 +1,11 @@
+// Note: all constants defined here are considered internal implementation
+// details for the sidebar. They should not be imported by anything outside of
+// the super_sidebar directory.
+
+import Vue from 'vue';
+
+export const SIDEBAR_PORTAL_ID = 'sidebar-portal-mount';
+
+export const portalState = Vue.observable({
+ ready: false,
+});
diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss
index cb3293dd1c3..c15bc8d9895 100644
--- a/app/assets/stylesheets/framework/super_sidebar.scss
+++ b/app/assets/stylesheets/framework/super_sidebar.scss
@@ -133,6 +133,7 @@
.page-with-super-sidebar {
padding-left: 0;
+ transition: padding-left $gl-transition-duration-medium;
@include media-breakpoint-up(xl) {
padding-left: $contextual-sidebar-width;
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index c8a84988859..a53601445ec 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1082,6 +1082,9 @@ $tabs-holder-z-index: 250;
.merge-request-sticky-header {
z-index: 204;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
+}
+
+.page-with-contextual-sidebar .merge-request-sticky-header {
--width: calc(100% - #{$contextual-sidebar-width});
@include media-breakpoint-down(lg) {
@@ -1093,6 +1096,18 @@ $tabs-holder-z-index: 250;
--width: calc(100% - #{$contextual-sidebar-collapsed-width});
}
+.page-with-super-sidebar .merge-request-sticky-header {
+ @include media-breakpoint-up(xl) {
+ --width: calc(100% - #{$super-sidebar-width});
+ }
+}
+
+.page-with-super-sidebar-collapsed .merge-request-sticky-header {
+ @include media-breakpoint-up(xl) {
+ --width: 100%;
+ }
+}
+
.merge-request-notification-toggle {
.gl-toggle {
@include gl-ml-auto;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 75c81b74ba7..d7f95e88290 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -272,20 +272,30 @@ ul.related-merge-requests > li gl-emoji {
@include media-breakpoint-up(md) {
// collapsed left sidebar + collapsed right sidebar
- .issue-sticky-header {
+ .page-with-contextual-sidebar .issue-sticky-header {
left: $contextual-sidebar-collapsed-width;
--width: calc(100% - #{$contextual-sidebar-collapsed-width} - #{$gutter-collapsed-width});
}
// collapsed left sidebar + expanded right sidebar
- .right-sidebar-expanded .issue-sticky-header {
+ .page-with-contextual-sidebar.right-sidebar-expanded .issue-sticky-header {
--width: calc(100% - #{$contextual-sidebar-collapsed-width} - #{$gutter-width});
}
+
+ // collapsed super sidebar + collapsed right sidebar
+ .page-with-super-sidebar .issue-sticky-header {
+ --width: calc(100% - #{$gutter-collapsed-width});
+ }
+
+ // collapsed super sidebar + expanded right sidebar
+ .page-with-super-sidebar.right-sidebar-expanded .issue-sticky-header {
+ --width: calc(100% - #{$gutter-width});
+ }
}
@include media-breakpoint-up(xl) {
// expanded left sidebar + collapsed right sidebar
- .issue-sticky-header {
+ .page-with-contextual-sidebar .issue-sticky-header {
left: $contextual-sidebar-width;
--width: calc(100% - #{$contextual-sidebar-width} - #{$gutter-collapsed-width});
}
@@ -297,14 +307,38 @@ ul.related-merge-requests > li gl-emoji {
}
// expanded left sidebar + expanded right sidebar
- .right-sidebar-expanded .issue-sticky-header {
+ .page-with-contextual-sidebar.right-sidebar-expanded .issue-sticky-header {
--width: calc(100% - #{$contextual-sidebar-width} - #{$gutter-width});
}
// collapsed left sidebar + expanded right sidebar
- .right-sidebar-expanded.page-with-icon-sidebar .issue-sticky-header {
+ .page-with-contextual-sidebar.right-sidebar-expanded.page-with-icon-sidebar .issue-sticky-header {
--width: calc(100% - #{$contextual-sidebar-collapsed-width} - #{$gutter-width});
}
+
+ // expanded super sidebar + collapsed right sidebar
+ .page-with-super-sidebar .issue-sticky-header {
+ left: $super-sidebar-width;
+ --width: calc(100% - #{$super-sidebar-width} - #{$gutter-collapsed-width});
+ }
+
+ // collapsed super sidebar + collapsed right sidebar
+ .page-with-super-sidebar-collapsed .issue-sticky-header {
+ left: 0;
+ --width: calc(100% - #{$gutter-collapsed-width});
+ }
+
+ // expanded super sidebar + expanded right sidebar
+ .page-with-super-sidebar.right-sidebar-expanded .issue-sticky-header {
+ left: $super-sidebar-width;
+ --width: calc(100% - #{$super-sidebar-width} - #{$gutter-width});
+ }
+
+ // collapsed super sidebar + expanded right sidebar
+ .page-with-super-sidebar-collapsed.right-sidebar-expanded .issue-sticky-header {
+ left: 0;
+ --width: calc(100% - #{$gutter-width});
+ }
}
.issuable-header-slide-enter-active,
diff --git a/app/controllers/concerns/sorting_preference.rb b/app/controllers/concerns/sorting_preference.rb
index 300c1d6d779..3dc1780d6fe 100644
--- a/app/controllers/concerns/sorting_preference.rb
+++ b/app/controllers/concerns/sorting_preference.rb
@@ -90,6 +90,10 @@ module SortingPreference
return false unless sort_order
return can_sort_by_issue_weight?(action_name == 'issues') if sort_order.include?('weight')
+ if sort_order.include?('merged_at')
+ return can_sort_by_merged_date?(controller_name == 'merge_requests' || action_name == 'merge_requests')
+ end
+
true
end
end
diff --git a/app/controllers/jira_connect/public_keys_controller.rb b/app/controllers/jira_connect/public_keys_controller.rb
index 4505ab16926..8cb932c087f 100644
--- a/app/controllers/jira_connect/public_keys_controller.rb
+++ b/app/controllers/jira_connect/public_keys_controller.rb
@@ -22,8 +22,6 @@ module JiraConnect
end
def public_key_storage_enabled?
- return true if Gitlab.config.jira_connect.enable_public_keys_storage
-
Gitlab::CurrentSettings.jira_connect_public_key_storage_enabled?
end
end
diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb
index 50e3c3cc5fe..28b30ae051c 100644
--- a/app/helpers/jira_connect_helper.rb
+++ b/app/helpers/jira_connect_helper.rb
@@ -12,7 +12,7 @@ module JiraConnectHelper
users_path: current_user ? nil : jira_connect_users_path, # users_path is used to determine if user is signed in
gitlab_user_path: current_user ? user_path(current_user) : nil,
oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data(installation).to_json : nil,
- public_key_storage_enabled: Gitlab.config.jira_connect.enable_public_keys_storage || Gitlab::CurrentSettings.jira_connect_public_key_storage_enabled?
+ public_key_storage_enabled: Gitlab::CurrentSettings.jira_connect_public_key_storage_enabled?
}
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 0f328410abc..ad1aa3ad734 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -11,10 +11,15 @@ module NavHelper
def page_with_sidebar_class
class_name = page_gutter_class
- class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar
- class_name << 'page-with-super-sidebar' if show_super_sidebar? && @left_sidebar
- class_name << 'page-with-super-sidebar-collapsed' if show_super_sidebar? && collapsed_super_sidebar? && @left_sidebar
- class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar && !show_super_sidebar?
+
+ if show_super_sidebar?
+ class_name << 'page-with-super-sidebar' if defined?(@left_sidebar) && @left_sidebar
+ class_name << 'page-with-super-sidebar-collapsed' if collapsed_super_sidebar? && @left_sidebar
+ else
+ class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar
+ class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
+ end
+
class_name -= ['right-sidebar-expanded'] if defined?(@right_sidebar) && !@right_sidebar
class_name
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 4a9596a1347..9038d972f65 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -227,7 +227,7 @@ module SortingHelper
options.concat([due_date_option]) if viewing_issues
options.concat([popularity_option, label_priority_option])
- options.concat([merged_option]) if viewing_merge_requests
+ options.concat([merged_option]) if can_sort_by_merged_date?(viewing_merge_requests)
options.concat([relative_position_option]) if viewing_issues
options.concat([title_option])
@@ -237,6 +237,10 @@ module SortingHelper
false
end
+ def can_sort_by_merged_date?(viewing_merge_requests)
+ viewing_merge_requests && %w[all merged].include?(params[:state])
+ end
+
def due_date_option
{ value: sort_value_due_date, text: sort_title_due_date, href: page_filter_path(sort: sort_value_due_date) }
end
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 5a3e94afc63..87e8bd69994 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -58,15 +58,15 @@
= c.body do
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
- .form-group
- = label_tag :pin_code, _('Enter verification code'), class: "label-bold"
- = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' }
- if current_password_required?
.form-group
= label_tag :current_password, _('Current password'), class: 'label-bold'
= password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { qa_selector: 'current_password_field' }
%p.form-text.text-muted
= _('Your current password is required to register a two-factor authenticator app.')
+ .form-group
+ = label_tag :pin_code, _('Enter verification code'), class: "label-bold"
+ = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' }
.gl-mt-3
= submit_tag _('Register with two-factor app'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'register_2fa_app_button' }
diff --git a/app/views/projects/settings/integrations/_form.html.haml b/app/views/projects/settings/integrations/_form.html.haml
index 9d74f99bb19..97d90976f18 100644
--- a/app/views/projects/settings/integrations/_form.html.haml
+++ b/app/views/projects/settings/integrations/_form.html.haml
@@ -11,6 +11,9 @@
= c.body do
= s_('ExternalIssueIntegration|Only one issue tracker integration can be active at a time. Please disable the active tracker first and try again.')
+- if integration.to_param === 'slack'
+ = render 'shared/integrations/slack_notifications_deprecation_alert'
+
%h2.gl-mb-4
= integration.title
- if integration.operating?
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 5a9811c0e91..c683250bde3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -495,7 +495,6 @@ production: &base
## To switch to a Jira connect development environment
jira_connect:
# atlassian_js_url: 'http://localhost:9292/atlassian.js'
- # enable_public_keys_storage: true
# enforce_jira_base_url_https: false
# additional_iframe_ancestors: ['localhost:*']
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b05fa6c1d8d..bcd051e8ace 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -449,8 +449,6 @@ Settings.mattermost['host'] = nil unless Settings.mattermost.enabled
Settings['jira_connect'] ||= Settingslogic.new({})
Settings.jira_connect['atlassian_js_url'] ||= 'https://connect-cdn.atl-paas.net/all.js'
-Settings.jira_connect['enable_public_keys_storage'] ||= false
-Settings.jira_connect['enable_public_keys_storage'] = true if Gitlab.com?
Settings.jira_connect['enforce_jira_base_url_https'] = true if Settings.jira_connect['enforce_jira_base_url_https'].nil?
Settings.jira_connect['additional_iframe_ancestors'] ||= []
@@ -830,7 +828,7 @@ Gitlab.ee do
Settings.cron_jobs['abandoned_trial_emails']['cron'] ||= "0 1 * * *"
Settings.cron_jobs['abandoned_trial_emails']['job_class'] = 'Emails::AbandonedTrialEmailsCronWorker'
Settings.cron_jobs['package_metadata_sync_worker'] ||= Settingslogic.new({})
- Settings.cron_jobs['package_metadata_sync_worker']['cron'] ||= "0 1 * * *"
+ Settings.cron_jobs['package_metadata_sync_worker']['cron'] ||= "0 * * * *"
Settings.cron_jobs['package_metadata_sync_worker']['job_class'] = 'PackageMetadata::SyncWorker'
Gitlab.com do
Settings.cron_jobs['free_user_cap_backfill_notification_jobs_worker'] ||= Settingslogic.new({})
diff --git a/db/docs/ci_cost_settings.yml b/db/docs/ci_cost_settings.yml
new file mode 100644
index 00000000000..3c5fc00cee0
--- /dev/null
+++ b/db/docs/ci_cost_settings.yml
@@ -0,0 +1,10 @@
+---
+table_name: ci_cost_settings
+classes:
+- Ci::Minutes::CostSetting
+feature_categories:
+- continuous_integration
+description: A set of cost factors per runner which are applied to ci job duration based on project type.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111977
+milestone: '15.10'
+gitlab_schema: gitlab_ci
diff --git a/db/migrate/20230216171309_create_ci_runner_cost_settings.rb b/db/migrate/20230216171309_create_ci_runner_cost_settings.rb
new file mode 100644
index 00000000000..5bc624c635a
--- /dev/null
+++ b/db/migrate/20230216171309_create_ci_runner_cost_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateCiRunnerCostSettings < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ def change
+ create_table :ci_cost_settings, id: false do |t|
+ t.timestamps_with_timezone null: false
+ t.references :runner, null: false, primary_key: true, index: false,
+ foreign_key: { to_table: :ci_runners, on_delete: :cascade },
+ type: :bigint, default: nil
+ t.float :standard_factor, null: false, default: 1.00
+ t.float :os_contribution_factor, null: false, default: 0.008
+ t.float :os_plan_factor, null: false, default: 0.5
+ end
+ end
+end
diff --git a/db/migrate/20230221110256_create_initial_partition_for_ci_runner_machine_builds.rb b/db/migrate/20230221110256_create_initial_partition_for_ci_runner_machine_builds.rb
new file mode 100644
index 00000000000..9aa7049dde1
--- /dev/null
+++ b/db/migrate/20230221110256_create_initial_partition_for_ci_runner_machine_builds.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class CreateInitialPartitionForCiRunnerMachineBuilds < Gitlab::Database::Migration[2.1]
+ PARTITION_NAME = 'gitlab_partitions_dynamic.ci_runner_machine_builds_100'
+ TABLE_NAME = 'p_ci_runner_machine_builds'
+ FIRST_PARTITION = 100
+ BUILDS_TABLE = 'ci_builds'
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries(**lock_args) do
+ connection.execute(<<~SQL)
+ LOCK TABLE #{BUILDS_TABLE} IN SHARE UPDATE EXCLUSIVE MODE;
+ LOCK TABLE ONLY #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE;
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE IF NOT EXISTS #{PARTITION_NAME}
+ PARTITION OF #{TABLE_NAME}
+ FOR VALUES IN (#{FIRST_PARTITION});
+ SQL
+ end
+ end
+
+ def down
+ # no-op
+ #
+ # The migration should not remove the partition table since it might
+ # have been created by 20230215074223_add_ci_runner_machine_builds_partitioned_table.rb.
+ # In that case, the rollback would result in a different state.
+ end
+
+ private
+
+ def lock_args
+ {
+ raise_on_exhaustion: true,
+ timing_configuration: lock_timing_configuration
+ }
+ end
+
+ def lock_timing_configuration
+ iterations = Gitlab::Database::WithLockRetries::DEFAULT_TIMING_CONFIGURATION
+ aggressive_iterations = Array.new(5) { [10.seconds, 1.minute] }
+
+ iterations + aggressive_iterations
+ end
+end
diff --git a/db/schema_migrations/20230216171309 b/db/schema_migrations/20230216171309
new file mode 100644
index 00000000000..1e25ca0d3b1
--- /dev/null
+++ b/db/schema_migrations/20230216171309
@@ -0,0 +1 @@
+ee00d6aba8a310c236dd16749228a42589657d060bbf1785c4358bf886fd59cc \ No newline at end of file
diff --git a/db/schema_migrations/20230221110256 b/db/schema_migrations/20230221110256
new file mode 100644
index 00000000000..2448c317588
--- /dev/null
+++ b/db/schema_migrations/20230221110256
@@ -0,0 +1 @@
+661fdc00029ab9bae8b4da6a8d92f172db89087aecc13f3ad65b2b3e8ad501d3 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 335ce18eb3b..f593891995a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -543,6 +543,13 @@ CREATE TABLE batched_background_migration_job_transition_logs (
)
PARTITION BY RANGE (created_at);
+CREATE TABLE p_ci_runner_machine_builds (
+ partition_id bigint NOT NULL,
+ build_id bigint NOT NULL,
+ runner_machine_id bigint NOT NULL
+)
+PARTITION BY LIST (partition_id);
+
CREATE TABLE incident_management_pending_alert_escalations (
id bigint NOT NULL,
rule_id bigint NOT NULL,
@@ -13060,6 +13067,15 @@ CREATE SEQUENCE ci_builds_runner_session_id_seq
ALTER SEQUENCE ci_builds_runner_session_id_seq OWNED BY ci_builds_runner_session.id;
+CREATE TABLE ci_cost_settings (
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ runner_id bigint NOT NULL,
+ standard_factor double precision DEFAULT 1.0 NOT NULL,
+ os_contribution_factor double precision DEFAULT 0.008 NOT NULL,
+ os_plan_factor double precision DEFAULT 0.5 NOT NULL
+);
+
CREATE TABLE ci_daily_build_group_report_results (
id bigint NOT NULL,
date date NOT NULL,
@@ -19018,13 +19034,6 @@ CREATE SEQUENCE operations_user_lists_id_seq
ALTER SEQUENCE operations_user_lists_id_seq OWNED BY operations_user_lists.id;
-CREATE TABLE p_ci_runner_machine_builds (
- partition_id bigint NOT NULL,
- build_id bigint NOT NULL,
- runner_machine_id bigint NOT NULL
-)
-PARTITION BY LIST (partition_id);
-
CREATE TABLE packages_build_infos (
id bigint NOT NULL,
package_id integer NOT NULL,
@@ -26219,6 +26228,9 @@ ALTER TABLE ONLY ci_builds
ALTER TABLE ONLY ci_builds_runner_session
ADD CONSTRAINT ci_builds_runner_session_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY ci_cost_settings
+ ADD CONSTRAINT ci_cost_settings_pkey PRIMARY KEY (runner_id);
+
ALTER TABLE ONLY ci_daily_build_group_report_results
ADD CONSTRAINT ci_daily_build_group_report_results_pkey PRIMARY KEY (id);
@@ -35618,6 +35630,9 @@ ALTER TABLE ONLY geo_hashed_storage_migrated_events
ALTER TABLE ONLY plan_limits
ADD CONSTRAINT fk_rails_69f8b6184f FOREIGN KEY (plan_id) REFERENCES plans(id) ON DELETE CASCADE;
+ALTER TABLE ONLY ci_cost_settings
+ ADD CONSTRAINT fk_rails_6a70651f75 FOREIGN KEY (runner_id) REFERENCES ci_runners(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY operations_feature_flags_issues
ADD CONSTRAINT fk_rails_6a8856ca4f FOREIGN KEY (feature_flag_id) REFERENCES operations_feature_flags(id) ON DELETE CASCADE;
diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md
index d9cfbdf124e..95b8a49f408 100644
--- a/doc/ci/jobs/ci_job_token.md
+++ b/doc/ci/jobs/ci_job_token.md
@@ -113,6 +113,8 @@ To disable the inbound job token scope allowlist:
1. Toggle **Allow access to this project with a CI_JOB_TOKEN** to disabled.
Enabled by default in new projects.
+You can also disable the allowlist [with the API](../../api/graphql/reference/index.md#mutationprojectcicdsettingsupdate).
+
### Add a project to the inbound job token scope allowlist
You can add projects to the inbound allowlist for a project. Projects added to the allowlist
@@ -133,6 +135,8 @@ To add a project:
1. Under **Allow CI job tokens from the following projects to access this project**,
add projects to the allowlist.
+You can also add a target project to the allowlist [with the API](../../api/graphql/reference/index.md#mutationcijobtokenscopeaddproject).
+
### Limit your project's job token access
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328553) in GitLab 14.1. [Deployed behind the `:ci_scoped_job_token` feature flag](../../user/feature_flags.md), disabled by default.
diff --git a/doc/development/application_slis/rails_request.md b/doc/development/application_slis/rails_request.md
index fa22b5f6aca..b3ee326aa87 100644
--- a/doc/development/application_slis/rails_request.md
+++ b/doc/development/application_slis/rails_request.md
@@ -207,6 +207,14 @@ class Boards::ListsController < ApplicationController
end
```
+A custom RSpec matcher is available to check endpoint's request urgency in the controller specs:
+
+```ruby
+specify do
+ expect(get(:index, params: request_params)).to have_request_urgency(:medium)
+end
+```
+
### Grape endpoints
To specify the urgency for an entire API class:
@@ -240,6 +248,15 @@ get 'client/features', urgency: :low do
end
```
+A custom RSpec matcher is also compatible with grape endpoints' specs:
+
+```ruby
+
+specify do
+ expect(get(api('/avatar'), params: { email: 'public@example.com' })).to have_request_urgency(:medium)
+end
+```
+
WARNING:
We can't specify the urgency at the namespace level. The directive is ignored when doing so.
diff --git a/doc/development/navigation_sidebar.md b/doc/development/navigation_sidebar.md
new file mode 100644
index 00000000000..495f30a796c
--- /dev/null
+++ b/doc/development/navigation_sidebar.md
@@ -0,0 +1,38 @@
+---
+stage: Manage
+group: Foundations
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Navigation sidebar
+
+Follow these guidelines when contributing additions or changes to the
+[redesigned](https://gitlab.com/groups/gitlab-org/-/epics/9044) navigation
+sidebar.
+
+These guidelines reflect the current state of the navigation sidebar. However,
+the sidebar is a work in progress, and so is this documentation.
+
+## Enable the new navigation sidebar
+
+To enable the new navigation sidebar:
+
+- Enable the `super_sidebar_nav` feature flag.
+- Select your avatar, then turn on the **New navigation** toggle.
+
+## Adding page-specific Vue content
+
+Pages can render arbitrary content into the sidebar using the `SidebarPortal`
+component. Content passed to its default slot is rendered below that
+page's navigation items in the sidebar.
+
+NOTE:
+Only one instance of this component on a given page is supported. This is to
+avoid ordering issues and cluttering the sidebar.
+
+NOTE:
+Arbitrary content is allowed, but nav items should be implemented by
+subclassing `::Sidebars::Panel`.
+
+NOTE:
+Do not use the `SidebarPortalTarget` component. It is internal to the sidebar.
diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md
index 240d98a855f..e27b332d746 100644
--- a/doc/development/pipelines/index.md
+++ b/doc/development/pipelines/index.md
@@ -484,19 +484,17 @@ This should let us:
Our test suite runs against PG12 as GitLab.com runs on PG12 and
[Omnibus defaults to PG12 for new installs and upgrades](../../administration/package_information/postgresql_versions.md).
-We do run our test suite against PG11 and PG13 on nightly scheduled pipelines.
-
-We also run our test suite against PG11 upon specific database library changes in MRs and `main` pipelines (with the `rspec db-library-code pg11` job).
+We do run our test suite against PG13 on nightly scheduled pipelines.
#### Current versions testing
-| Where? | PostgreSQL version | Ruby version |
-|------------------------------------------------------------------------------------------------|-------------------------------------------------|--------------|
-| Merge requests | 12 (default version), 11 for DB library changes | 3.0 (default version) |
-| `master` branch commits | 12 (default version), 11 for DB library changes | 3.0 (default version) |
-| `maintenance` scheduled pipelines for the `master` branch (every even-numbered hour) | 12 (default version), 11 for DB library changes | 3.0 (default version) |
-| `maintenance` scheduled pipelines for the `ruby2` branch (every odd-numbered hour), see below. | 12 (default version), 11 for DB library changes | 2.7 |
-| `nightly` scheduled pipelines for the `master` branch | 12 (default version), 11, 13 | 3.0 (default version) |
+| Where? | PostgreSQL version | Ruby version |
+|------------------------------------------------------------------------------------------------|--------------------------|-----------------------|
+| Merge requests | 12 (default version) | 3.0 (default version) |
+| `master` branch commits | 12 (default version) | 3.0 (default version) |
+| `maintenance` scheduled pipelines for the `master` branch (every even-numbered hour) | 12 (default version) | 3.0 (default version) |
+| `maintenance` scheduled pipelines for the `ruby2` branch (every odd-numbered hour), see below. | 12 (default version) | 2.7 |
+| `nightly` scheduled pipelines for the `master` branch | 12 (default version), 13 | 3.0 (default version) |
There are 2 pipeline schedules used for testing Ruby 2.7. One is triggering a
pipeline in `ruby2-sync` branch, which updates the `ruby2` branch with latest
@@ -518,7 +516,6 @@ We follow the [PostgreSQL versions shipped with Omnibus GitLab](../../administra
| PostgreSQL version | 14.1 (July 2021) | 14.2 (August 2021) | 14.3 (September 2021) | 14.4 (October 2021) | 14.5 (November 2021) | 14.6 (December 2021) |
| -------------------| ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
| PG12 | MRs/`2-hour`/`nightly` | MRs/`2-hour`/`nightly` | MRs/`2-hour`/`nightly` | MRs/`2-hour`/`nightly` | MRs/`2-hour`/`nightly` | MRs/`2-hour`/`nightly` |
-| PG11 | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` |
| PG13 | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` | `nightly` |
### Redis versions testing
diff --git a/doc/development/pipelines/internals.md b/doc/development/pipelines/internals.md
index 9ff4e5a35ec..bd96f2f2872 100644
--- a/doc/development/pipelines/internals.md
+++ b/doc/development/pipelines/internals.md
@@ -136,8 +136,6 @@ that are scoped to a single [configuration keyword](../../ci/yaml/index.md#job-k
| `.qa-cache` | Allows a job to use a default `cache` definition suitable for QA tasks. |
| `.yarn-cache` | Allows a job to use a default `cache` definition suitable for frontend jobs that do a `yarn install`. |
| `.assets-compile-cache` | Allows a job to use a default `cache` definition suitable for frontend jobs that compile assets. |
-| `.use-pg11` | Allows a job to run the `postgres` 11 and `redis` services (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific versions of the services). |
-| `.use-pg11-ee` | Same as `.use-pg11` but also use an `elasticsearch` service (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific version of the service). |
| `.use-pg12` | Allows a job to use the `postgres` 12 and `redis` services (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific versions of the services). |
| `.use-pg12-ee` | Same as `.use-pg12` but also use an `elasticsearch` service (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific version of the service). |
| `.use-pg13` | Allows a job to use the `postgres` 13 and `redis` services (see [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/global.gitlab-ci.yml) for the specific versions of the services). |
diff --git a/doc/user/packages/yarn_repository/index.md b/doc/user/packages/yarn_repository/index.md
index 7e2f45019cd..e756a912928 100644
--- a/doc/user/packages/yarn_repository/index.md
+++ b/doc/user/packages/yarn_repository/index.md
@@ -6,220 +6,312 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Publish packages with Yarn
-Publish npm packages in your project's Package Registry using Yarn. Then install the
-packages whenever you need to use them as a dependency.
+You can publish packages with [Yarn 1 (Classic)](https://classic.yarnpkg.com) and [Yarn 2+](https://yarnpkg.com).
-Learn how to build a [yarn](../workflows/build_packages.md#yarn) package.
+To find the Yarn version used in the deployment container, run `yarn --version` in the `script` block of the CI
+script job block that is responsible for calling `yarn publish`**`. The Yarn version is shown in the pipeline output.
-You can get started with Yarn 2 by following the [Yarn documentation](https://yarnpkg.com/getting-started/install/).
+Learn how to build a [Yarn](../workflows/build_packages.md#yarn) package.
+
+You can use the Yarn documentation to get started with
+[Yarn Classic](https://classic.yarnpkg.com/en/docs/getting-started) and
+[Yarn 2+](https://yarnpkg.com/getting-started/).
## Publish to GitLab Package Registry
+You can use Yarn to publish to the GitLab Package Registry.
+
### Authentication to the Package Registry
You need a token to publish a package. Different tokens are available depending on what you're trying to
achieve. For more information, review the [guidance on tokens](../../../user/packages/package_registry/index.md#authenticate-with-the-registry).
-- If your organization uses two-factor authentication (2FA), you must use a personal access token with the scope set to `api`.
-- If you publish a package via CI/CD pipelines, you must use a CI job token.
+- If your organization uses two-factor authentication (2FA), you must use a
+ personal access token with the scope set to `api`.
+- If you publish a package via CI/CD pipelines, you can use a CI job token in
+ private runners or you can register a variable for shared runners.
-Create a token and save it to use later in the process.
+### Publish configuration
-### Naming convention
+To publish, set the following configuration in `.yarnrc.yml`. This file should be
+located in the root directory of your package project source where `package.json` is found.
-Depending on how you install the package, you may need to adhere to the naming convention.
+```yaml
+npmScopes:
+ <my-org>:
+ npmPublishRegistry: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
+ npmAlwaysAuth: true
+ npmAuthToken: '<your_token>'
+```
-You can use one of two API endpoints to install packages:
+In this configuration:
-- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace.
-- **Project-level**: Use when you have a few npm packages, and they are not in the same GitLab group.
+- Replace `<my-org>` with your organization scope, exclude the `@` symbol.
+- Replace `<your_domain>` with your domain name.
+- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page.
+- Replace `<your_token>` with a deployment token, group access token, project access token, or personal access token.
-If you plan to install a package through the [project level](#install-from-the-project-level), you do not have to
-adhere to the naming convention.
+Scoped registry does not work in Yarn Classic in `package.json` file, based on
+this [issue](https://github.com/yarnpkg/yarn/pull/7829).
+Therefore, under `publishConfig` there should be `registry` and not `@scope:registry` for Yarn Classic.
+You can publish using your command line or a CI/CD pipeline to the GitLab Package Registry.
-If you plan to install a package through the [instance level](#install-from-the-instance-level), then you must name
-your package with a [scope](https://docs.npmjs.com/misc/scope/). Scoped packages begin with a `@` and have the
-`@owner/package-name` format. You can set up the scope for your package in the `.yarnrc.yml` file and by using the
-`publishConfig` option in the `package.json`.
+### Publishing via the command line - Manual Publish
-- The value used for the `@scope` is the root of the project that hosts the packages and not the root
- of the project with the package's source code. The scope should be lowercase.
-- The package name can be anything you want
+```shell
+# Yarn 1 (Classic)
+yarn publish
-| Project URL | Package Registry in | Scope | Full package name |
-| ------------------------------------------------------- | ------------------- | --------- | ---------------------- |
-| `https://gitlab.com/my-org/engineering-group/analytics` | Analytics | `@my-org` | `@my-org/package-name` |
+# Yarn 2+
+yarn npm publish
+```
-### Configuring `.yarnrc.yml` to publish from the project level
+Your package should now publish to the Package Registry.
-To publish with the project-level npm endpoint, set the following configuration in
-`.yarnrc.yml`:
+### Publishing via a CI/CD pipeline - Automated Publish
-```yaml
-npmScopes:
- foo:
- npmRegistryServer: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
- npmPublishRegistry: 'https://<your_domain>/api/v4/projects/<your_project_id>/packages/npm/'
+You can use pipeline variables when you use this method.
-npmRegistries:
- //gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:
- npmAlwaysAuth: true
- npmAuthToken: '<your_token>'
-```
+You can use **Shared Runners** *(Default)* or **Private Runners** (Advanced).
-In this configuration:
+#### Shared runners
-- Replace `<your_domain>` with your domain name.
-- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page.
-- Replace `<your_token>` with a deploy token, group access token, project access token, or personal access token.
+Third party images such as `node:latest` or `node:current` do not have direct access
+to the `CI_JOB_TOKEN` when operating in a shared runner. You must configure an
+authentication token or use a private runner.
+
+To create a authentication token:
-### Configuring `.yarnrc.yml` to publish from the instance level
+1. On the top bar, select **Main menu**, and:
+ - For a project, select **Projects** and find your project.
+ - For a group, select **Groups** and find your group.
+1. On the left sidebar, select **Settings > Repository > Deploy Tokens**.
+1. Create a deployment token with `read_package_registry` and `write_package_registry` scopes and copy the generated token.
+1. On the left sidebar, select **Settings > CI/CD > Variables**.
+1. Select `Add variable` and use the following settings:
-For the instance-level npm endpoint, use this Yarn 2 configuration in `.yarnrc.yml`:
+| Field | Value |
+|--------------------|------------------------------|
+| key | `NPM_AUTH_TOKEN` |
+| value | `<DEPLOY-TOKEN-FROM-STEP-3>` |
+| type | Variable |
+| Protected variable | `CHECKED` |
+| Mask variable | `CHECKED` |
+| Expand variable | `CHECKED` |
+
+To use any **Protected variable**:
+
+ 1. Go to the repository that contains the Yarn package source code.
+ 1. On the left sidebar, select **Settings > Repository**.
+ - If you are building from branches with tags, select **Protected Tags** and add `v*` (wildcard) for semantic versioning.
+ - If you are building from branches without tags, select **Protected Branches**.
+
+Then add the `NPM_AUTH_TOKEN` created above, to the `.yarnrc.yml` configuration
+in your package project root directory where `package.json` is found:
```yaml
npmScopes:
- <scope>:
- npmRegistryServer: 'https://<your_domain>/api/v4/packages/npm/'
-
-npmRegistries:
- //gitlab.example.com/api/v4/packages/npm/:
+ esp-code:
+ npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
npmAlwaysAuth: true
- npmAuthToken: '<your_token>'
+ npmAuthToken: "${NPM_AUTH_TOKEN}"
```
-In this configuration:
+#### Private runners
-- Replace `<your_domain>` with your domain name.
-- Your scope is `<scope>`, without `@`.
-- Replace `<your_token>` with a deploy token, group access token, project access token, or personal access token.
+Add the `CI_JOB_TOKEN` to the `.yarnrc.yml` configuration in your package project
+root directory where `package.json` is found:
-### Publishing a package via the command line
+```yaml
+npmScopes:
+ esp-code:
+ npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
+ npmAlwaysAuth: true
+ npmAuthToken: "${CI_JOB_TOKEN}"
+```
-Publish a package:
+To publish the package using CI/CD pipeline, In the GitLab project that houses
+your `yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. For example to trigger
+only on any tag push:
-```shell
-npm publish
-```
+```yaml
+# Yarn 1
+image: node:lts
-Your package should now publish to the Package Registry.
+stages:
+ - deploy
-### Publishing via a CI/CD pipeline
+rules:
+- if: $CI_COMMIT_TAG
-In the GitLab project that houses your `yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. For example:
+deploy:
+ stage: deploy
+ script:
+ - yarn publish
+```
```yaml
-image: node:latest
+# Yarn 2+
+image: node:lts
stages:
- deploy
+rules:
+ - if: $CI_COMMIT_TAG
+
deploy:
stage: deploy
+ before_script:
+ - corepack enable
+ - yarn set version stable
script:
- - npm publish
+ - yarn npm publish
```
Your package should now publish to the Package Registry when the pipeline runs.
## Install a package
-If multiple packages have the same name and version, the most recently-published package is retrieved when you install a package.
+NOTE:
+If multiple packages have the same name and version, the most recently-published
+package is retrieved when you install a package.
-You can install a package from a GitLab project or instance:
+You can use one of two API endpoints to install packages:
-- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace.
-- **Project-level**: Use when you have a few npm packages, and they are not in the same GitLab group.
+- **Instance-level**: Best used when working with many packages in an organization scope.
-### Install from the instance level
+- If you plan to install a package through the [instance level](#install-from-the-instance-level),
+ then you must name your package with a [scope](https://docs.npmjs.com/misc/scope/).
+ Scoped packages begin with a `@` and have the `@owner/package-name` format. You can set up
+ the scope for your package in the `.yarnrc.yml` file and by using the `publishConfig`
+ option in the `package.json`.
-WARNING:
-You must use packages published with the scoped [naming convention](#naming-convention) when you install a package from the instance level.
+- The value used for the `@scope` is the organization root (top-level project) `...com/my-org`
+ *(@my-org)* that hosts the packages, not the root of the project with the package's source code.
+- The scope is always lowercase.
+- The package name can be anything you want `@my-org/any-name`.
-1. Authenticate to the Package Registry
+- **Project-level**: For when you have a one-off package.
- If you install a package from a private project, you must authenticate to the Package Registry. Skip this step if the project is not private.
+If you plan to install a package through the [project level](#install-from-the-project-level),
+you do not have to adhere to the naming convention.
- ```shell
- npm config set -- //your_domain_name/api/v4/packages/npm/:_authToken=your_token
- ```
+| Project URL | Package Registry | Organization Scope | Full package name |
+|-------------------------------------------------------------------|----------------------|--------------------|-----------------------------|
+| `https://gitlab.com/<my-org>/<group-name>/<package-name-example>` | Package Name Example | `@my-org` | `@my-org/package-name` |
+| `https://gitlab.com/<example-org>/<group-name>/<project-name>` | Project Name | `@example-org` | `@example-org/project-name` |
- - Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
- - Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
+You can install from the instance level or from the project level.
-1. Set the registry
+The configurations for `.yarnrc.yml` can be added per package consuming project
+root where `package.json` is located, or you can use a global
+configuration located in your system user home directory.
- ```shell
- npm config set @scope:registry https://your_domain_name.com/api/v4/packages/npm/
- ```
+### Install from the instance level
- - Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from.
- - Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
- - Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
+Use these steps for global configuration in the `.yarnrc.yml` file:
-1. Install the package
+1. [Configure organization scope](#configure-organization-scope).
+1. [Set the registry](#set-the-registry).
- ```shell
- yarn add @scope/my-package
- ```
+#### Configure organization scope
+
+```yaml
+npmScopes:
+ <my-org>:
+ npmRegistryServer: "https://<your_domain_name>/api/v4/packages/npm"
+```
+
+- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
+- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
+
+#### Set the registry
+
+Skip this step if your package is public not private.
+
+```yaml
+ npmRegistries:
+ //<your_domain_name>/api/v4/packages/npm:
+ npmAlwaysAuth: true
+ npmAuthToken: "<your_token>"
+```
+
+- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
+- Replace `<your_token>` with a deployment token (recommended), group access token, project access token, or personal access token.
### Install from the project level
-1. Authenticate to the Package Registry
+Use these steps for each project in the `.yarnrc.yml` file:
+
+1. [Configure project scope](#configure-project-scope).
+1. [Set the registry](#set-the-registry-project-level).
- If you install a package from a private project, you must authenticate to the Package Registry. Skip this step if the project is not private.
+#### Configure project scope
- ```shell
- npm config set -- //your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken=your_token
- ```
+ ```yaml
+ npmScopes:
+ <my-org>:
+ npmRegistryServer: "https://<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm"
+```
- - Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
- - Replace `your_project_id` is your project ID, found on the project's home page.
- - Replace `your_token` with a deploy token, group access token, project access token, or personal access token.
+- Replace `<my-org>` with the root level group of the project you're installing to the package from excluding the `@` symbol.
+- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
+- Replace `<your_project_id>` with your project ID, found on the project's home page.
-1. Set the registry
+#### Set the registry (project level)
- ```shell
- npm config set @scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/
- ```
+Skip this step if your package is public not private.
- - Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from.
- - Replace `your_domain_name` with your domain name, for example, `gitlab.com`.
- - Replace `your_project_id` is your project ID, found on the project's home page.
+```yaml
+npmRegistries:
+ //<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm:
+ npmAlwaysAuth: true
+ npmAuthToken: "<your_token>"
+```
-1. Install the package
+- Replace `<your_domain_name>` with your domain name, for example, `gitlab.com`.
+- Replace `<your_token>` with a deployment token (recommended), group access token, project access token, or personal access token.
+- Replace `<your_project_id>` with your project ID, found on the project's home page.
- ```shell
- yarn add @scope/my-package
- ```
+### Install the package
-## Helpful hints
+For Yarn 2+, use `yarn add` either in the command line or in the CI/CD pipelines to install your packages:
-For full helpful hints information, refer to the [npm documentation](../npm_registry/index.md#helpful-hints).
+```shell
+yarn add @scope/my-package
+```
-### Supported CLI commands
+#### For Yarn Classic
-The GitLab npm repository supports the following commands for the npm CLI (`npm`) and yarn CLI
-(`yarn`):
+The Yarn Classic setup, requires both `.npmrc` and `.yarnrc` files as
+[mentioned in issue](https://github.com/yarnpkg/yarn/issues/4451#issuecomment-753670295):
-- `npm install`: Install npm packages.
-- `npm publish`: Publish an npm package to the registry.
-- `npm dist-tag add`: Add a dist-tag to an npm package.
-- `npm dist-tag ls`: List dist-tags for a package.
-- `npm dist-tag rm`: Delete a dist-tag.
-- `npm ci`: Install npm packages directly from your `package-lock.json` file.
-- `npm view`: Show package metadata.
-- `yarn add`: Install an npm package.
-- `yarn update`: Update your dependencies.
+- Place credentials in the `.npmrc` file.
+- Place the scoped registry in the `.yarnrc` file.
-## Troubleshooting
+```shell
+# .npmrc
+//<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm/:_authToken="<your_token>"
+
+# .yarnrc
+"@scope:registry" "https://<your_domain_name>/api/v4/projects/<your_project_id>/packages/npm/"
+```
+
+Then you can use `yarn add` to install your packages.
+
+## Related topics
-For full troubleshooting information, refer to the [npm documentation](../npm_registry/index.md#troubleshooting).
+- For full helpful hints information, see the
+ [npm documentation](../npm_registry/index.md#helpful-hints).
+- For Yarn 1 to Yarn 2+ migration information see the
+ [Yarn Migration Guide](https://yarnpkg.com/getting-started/migration).
+
+## Troubleshooting
### Error running Yarn with the Package Registry for the npm registry
-If you are using [Yarn](https://classic.yarnpkg.com/en/) with the npm registry, you may get
-an error message like:
+If you are using [Yarn](https://classic.yarnpkg.com/en/) with the npm registry, you may get an error message like:
```shell
yarn install v1.15.2
@@ -233,14 +325,7 @@ info If you think this is a bug, please open a bug report with the information p
info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation about this command
```
-In this case, try adding this to your `.npmrc` file (and replace `<your_token>`
-with your personal access token or deploy token):
-
-```plaintext
-//gitlab.example.com/api/v4/projects/:_authToken=<your_token>
-```
-
-You can also use `yarn config` instead of `npm config` when setting your auth-token dynamically:
+In this case, the following commands creates a file called `.yarnrc` in the current directory. Make sure to be in either your user home directory for global configuration or your project root for per-project configuration:
```shell
yarn config set '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>"
diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md
index 9c2925ec647..dde4abc7a3c 100644
--- a/doc/user/shortcuts.md
+++ b/doc/user/shortcuts.md
@@ -39,9 +39,9 @@ These shortcuts are available in most areas of GitLab:
| <kbd>Shift</kbd> + <kbd>m</kbd> | Go to your [Merge requests](project/merge_requests/index.md) page. |
| <kbd>Shift</kbd> + <kbd>r</kbd> | Go to your Review requests page. |
| <kbd>Shift</kbd> + <kbd>t</kbd> | Go to your To-Do List page. |
-| <kbd>p</kbd>, then <kbd>b</kbd> | Show or hide the Performance Bar. |
+| <kbd>p</kbd>, then <kbd>b</kbd> | Show or hide the Performance Bar. |
| <kbd>Escape</kbd> | Hide tooltips or popovers. |
-| <kbd>g</kbd>, then <kbd>x</kbd> | Toggle between [GitLab](https://gitlab.com/) and [GitLab Next](https://next.gitlab.com/) (GitLab SaaS only). |
+| <kbd>g</kbd>, then <kbd>x</kbd> | Toggle between [GitLab](https://gitlab.com/) and [GitLab Next](https://next.gitlab.com/) (GitLab SaaS only). |
| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). |
Additionally, the following shortcuts are available when editing text in text
@@ -55,9 +55,10 @@ descriptions):
| <kbd>Command</kbd> + <kbd>b</kbd> | <kbd>Control</kbd> + <kbd>b</kbd> | Bold the selected text (surround it with `**`). |
| <kbd>Command</kbd> + <kbd>i</kbd> | <kbd>Control</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>x</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>x</kbd> | Strike through the selected text (surround it with `~~`). |
-| <kbd>Command</kbd> + <kbd>k</kbd> | <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
-| <kbd>Command</kbd> + <kbd>&#93;</kbd> | <kbd>Control</kbd> + <kbd>&#93;</kbd> | Indent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
-| <kbd>Command</kbd> + <kbd>&#91;</kbd> | <kbd>Control</kbd> + <kbd>&#91;</kbd> | Outdent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
+| <kbd>Command</kbd> + <kbd>k</kbd> | <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
+| <kbd>Command</kbd> + <kbd>&#93;</kbd> | <kbd>Control</kbd> + <kbd>&#93;</kbd> | Indent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
+| <kbd>Command</kbd> + <kbd>&#91;</kbd> | <kbd>Control</kbd> + <kbd>&#91;</kbd> | Outdent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
+| <kbd>Shift</kbd> + <kbd>Enter</kbd> | <kbd>Shift</kbd> + <kbd>Enter</kbd> | Add a [line break](markdown.md#newlines). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21916) in GitLab 15.10. |
The shortcuts for editing in text fields are always enabled, even if other
keyboard shortcuts are disabled.
@@ -112,7 +113,7 @@ These shortcuts are available when viewing [merge requests](project/merge_reques
| macOS shortcut | Windows shortcut | Description |
|-----------------------------------|---------------------|-------------|
| <kbd>]</kbd> or <kbd>j</kbd> | | Move to next file. |
-| <kbd>&#91;</kbd> or <kbd>k</kbd> | | Move to previous file. |
+| <kbd>&#91;</kbd> or <kbd>k</kbd> | | Move to previous file. |
| <kbd>Command</kbd> + <kbd>p</kbd> | <kbd>Control</kbd> + <kbd>p</kbd> | Search for, and then jump to a file for review. |
| <kbd>n</kbd> | | Move to next unresolved discussion. |
| <kbd>p</kbd> | | Move to previous unresolved discussion. |
@@ -277,7 +278,7 @@ These shortcuts are available when editing a file with the
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>h</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>h</kbd> | Highlight |
| <kbd>Command</kbd> + <kbd>,</kbd> | <kbd>Control</kbd> + <kbd>,</kbd> | Subscript |
| <kbd>Command</kbd> + <kbd>.</kbd> | <kbd>Control</kbd> + <kbd>.</kbd> | Superscript |
-| <kbd>Tab</kbd> | <kbd>Tab</kbd> | Indent list |
+| <kbd>Tab</kbd> | <kbd>Tab</kbd> | Indent list |
| <kbd>Shift</kbd> + <kbd>Tab</kbd> | <kbd>Shift</kbd> + <kbd>Tab</kbd> | Outdent list |
#### Text selection
diff --git a/lib/gitlab/database/gitlab_schema.rb b/lib/gitlab/database/gitlab_schema.rb
index dbb55c0afd1..926a4aeedf1 100644
--- a/lib/gitlab/database/gitlab_schema.rb
+++ b/lib/gitlab/database/gitlab_schema.rb
@@ -45,6 +45,11 @@ module Gitlab
return gitlab_schema
end
+ # Partitions that belong to the CI domain
+ if table_name.start_with?('ci_') && gitlab_schema = views_and_tables_to_schema["p_#{table_name}"]
+ return gitlab_schema
+ end
+
# All tables from `information_schema.` are marked as `internal`
return :gitlab_internal if schema_name == 'information_schema'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8659f21df42..aa7bd1c5af2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8869,9 +8869,6 @@ msgstr ""
msgid "CiVariables|Cannot use Masked Variable with current value"
msgstr ""
-msgid "CiVariables|Clear inputs"
-msgstr ""
-
msgid "CiVariables|Environments"
msgstr ""
@@ -8899,6 +8896,9 @@ msgstr ""
msgid "CiVariables|Protected"
msgstr ""
+msgid "CiVariables|Remove inputs"
+msgstr ""
+
msgid "CiVariables|Remove variable"
msgstr ""
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb
index 82a920215ca..6880d83142d 100644
--- a/spec/controllers/concerns/sorting_preference_spec.rb
+++ b/spec/controllers/concerns/sorting_preference_spec.rb
@@ -26,11 +26,14 @@ RSpec.describe SortingPreference do
describe '#set_sort_order' do
let(:group) { build(:group) }
+ let(:controller_name) { 'issues' }
+ let(:action_name) { 'issues' }
let(:issue_weights_available) { true }
before do
allow(controller).to receive(:default_sort_order).and_return('updated_desc')
- allow(controller).to receive(:action_name).and_return('issues')
+ allow(controller).to receive(:controller_name).and_return(controller_name)
+ allow(controller).to receive(:action_name).and_return(action_name)
allow(controller).to receive(:can_sort_by_issue_weight?).and_return(issue_weights_available)
user.user_preference.update!(issues_sort: sorting_field)
end
@@ -62,6 +65,42 @@ RSpec.describe SortingPreference do
end
end
end
+
+ context 'when user preference contains merged date sorting' do
+ let(:sorting_field) { 'merged_at_desc' }
+ let(:can_sort_by_merged_date?) { false }
+
+ before do
+ allow(controller)
+ .to receive(:can_sort_by_merged_date?)
+ .with(can_sort_by_merged_date?)
+ .and_return(can_sort_by_merged_date?)
+ end
+
+ it 'sets default sort order' do
+ is_expected.to eq('updated_desc')
+ end
+
+ shared_examples 'user can sort by merged date' do
+ it 'sets sort order from user_preference' do
+ is_expected.to eq('merged_at_desc')
+ end
+ end
+
+ context 'when controller_name is merge_requests' do
+ let(:controller_name) { 'merge_requests' }
+ let(:can_sort_by_merged_date?) { true }
+
+ it_behaves_like 'user can sort by merged date'
+ end
+
+ context 'when action_name is merge_requests' do
+ let(:action_name) { 'merge_requests' }
+ let(:can_sort_by_merged_date?) { true }
+
+ it_behaves_like 'user can sort by merged date'
+ end
+ end
end
describe '#set_sort_order_from_user_preference' do
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 1f913e10f43..96006974c13 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -37,6 +37,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
project.add_developer(user)
end
+ specify { expect(get(:index, params: request_params)).to have_request_urgency(:medium) }
+
it 'passes last_fetched_at from headers to NotesFinder and MergeIntoNotesService' do
last_fetched_at = Time.zone.at(3.hours.ago.to_i) # remove nanoseconds
@@ -244,6 +246,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
sign_in(user)
end
+ specify { expect(create!).to have_request_urgency(:low) }
+
describe 'making the creation request' do
before do
create!
@@ -732,19 +736,21 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
end
describe 'PUT update' do
- context "should update the note with a valid issue" do
- let(:request_params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- id: note,
- format: :json,
- note: {
- note: "New comment"
- }
+ let(:request_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :json,
+ note: {
+ note: "New comment"
}
- end
+ }
+ end
+
+ specify { expect(put(:update, params: request_params)).to have_request_urgency(:low) }
+ context "should update the note with a valid issue" do
before do
sign_in(note.author)
project.add_developer(note.author)
@@ -790,6 +796,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
}
end
+ specify { expect(delete(:destroy, params: request_params)).to have_request_urgency(:low) }
+
context 'user is the author of a note' do
before do
sign_in(note.author)
@@ -831,6 +839,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
let(:emoji_name) { 'thumbsup' }
+ it { is_expected.to have_request_urgency(:low) }
+
it "toggles the award emoji" do
expect do
subject
@@ -866,6 +876,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
sign_in user
end
+ specify { expect(post(:resolve, params: request_params)).to have_request_urgency(:low) }
+
context "when the user is not authorized to resolve the note" do
it "returns status 404" do
post :resolve, params: request_params
@@ -929,6 +941,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
note.resolve!(user)
end
+ specify { expect(delete(:unresolve, params: request_params)).to have_request_urgency(:low) }
+
context "when the user is not authorized to resolve the note" do
it "returns status 404" do
delete :unresolve, params: request_params
@@ -998,6 +1012,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: :
expect(json_response.count).to eq(1)
expect(json_response.first).to include({ "line_text" => "Test" })
end
+
+ specify { expect(get(:outdated_line_change, params: request_params)).to have_request_urgency(:low) }
end
# Convert a time to an integer number of microseconds
diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb
index a9672569a4a..a3208ca6d37 100644
--- a/spec/features/commit_spec.rb
+++ b/spec/features/commit_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Commit', feature_category: :source_code_management do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- describe "single commit view" do
+ shared_examples "single commit view" do
let(:commit) do
project.repository.commits(nil, limit: 100).find do |commit|
commit.diffs.size > 1
@@ -69,4 +69,15 @@ RSpec.describe 'Commit', feature_category: :source_code_management do
end
end
end
+
+ it_behaves_like "single commit view"
+
+ context "when super sidebar is enabled" do
+ before do
+ user.update!(use_new_navigation: true)
+ stub_feature_flags(super_sidebar_nav: true)
+ end
+
+ it_behaves_like "single commit view"
+ end
end
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 3171ae89fe6..371c40b40a5 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
milestone: create(:milestone, project: project, due_date: '2013-12-11'),
created_at: 1.minute.ago,
updated_at: 1.minute.ago)
- @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.seconds.ago)
+ @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 20.seconds.ago)
@markdown = create(:merge_request,
title: 'markdown',
@@ -33,7 +33,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
reviewers: [user, user2, user3, user4],
milestone: create(:milestone, project: project, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
- updated_at: 2.minutes.ago)
+ updated_at: 2.minutes.ago,
+ state: 'merged')
@markdown.metrics.update!(merged_at: 10.minutes.ago, latest_closed_at: 10.seconds.ago)
@merge_test = create(:merge_request,
@@ -49,7 +50,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
source_project: project,
source_branch: 'feautre',
created_at: 2.minutes.ago,
- updated_at: 1.minute.ago)
+ updated_at: 1.minute.ago,
+ state: 'merged')
@feature.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.minutes.ago)
end
@@ -79,10 +81,9 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
expect(page).to have_current_path(project_merge_requests_path(project), ignore_query: true)
expect(page).to have_content 'merge-test'
- expect(page).to have_content 'feature'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
- expect(count_merge_requests).to eq(2)
+ expect(count_merge_requests).to eq(1)
end
it 'filters on a specific assignee' do
@@ -90,8 +91,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
expect(page).not_to have_content 'merge-test'
expect(page).to have_content 'fix'
- expect(page).to have_content 'markdown'
- expect(count_merge_requests).to eq(2)
+ expect(count_merge_requests).to eq(1)
end
it 'sorts by newest' do
@@ -99,35 +99,35 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
expect(first_merge_request).to include('fix')
expect(last_merge_request).to include('merge-test')
- expect(count_merge_requests).to eq(4)
+ expect(count_merge_requests).to eq(2)
end
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
expect(first_merge_request).to include('merge-test')
- expect(count_merge_requests).to eq(4)
+ expect(count_merge_requests).to eq(2)
end
it 'sorts by milestone due date' do
visit_merge_requests(project, sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
- expect(count_merge_requests).to eq(4)
+ expect(count_merge_requests).to eq(2)
end
- it 'sorts by merged at' do
+ it 'ignores sorting by merged at' do
visit_merge_requests(project, sort: sort_value_merged_date)
- expect(first_merge_request).to include('markdown')
- expect(count_merge_requests).to eq(4)
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(2)
end
it 'sorts by closed at' do
visit_merge_requests(project, sort: sort_value_closed_date)
- expect(first_merge_request).to include('feature')
- expect(count_merge_requests).to eq(4)
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(2)
end
it 'filters on one label and sorts by milestone due date' do
@@ -141,6 +141,15 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: :
expect(count_merge_requests).to eq(1)
end
+ context 'when viewing merged merge requests' do
+ it 'sorts by merged at' do
+ visit_merge_requests(project, state: 'merged', sort: sort_value_merged_date)
+
+ expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(2)
+ end
+ end
+
context 'while filtering on two labels' do
let(:label) { create(:label, project: project) }
let(:label2) { create(:label, project: project) }
diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js
index eac53c5f761..232d6177862 100644
--- a/spec/frontend/issuable/components/issue_milestone_spec.js
+++ b/spec/frontend/issuable/components/issue_milestone_spec.js
@@ -1,160 +1,61 @@
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-
import { mockMilestone } from 'jest/boards/mock_data';
import IssueMilestone from '~/issuable/components/issue_milestone.vue';
-const createComponent = (milestone = mockMilestone) => {
- const Component = Vue.extend(IssueMilestone);
-
- return shallowMount(Component, {
- propsData: {
- milestone,
- },
- });
-};
-
-describe('IssueMilestoneComponent', () => {
+describe('IssueMilestone component', () => {
let wrapper;
- let vm;
- beforeEach(async () => {
- wrapper = createComponent();
+ const findTooltip = () => wrapper.findComponent(GlTooltip);
- ({ vm } = wrapper);
+ const createComponent = (milestone = mockMilestone) =>
+ shallowMount(IssueMilestone, { propsData: { milestone } });
- await nextTick();
+ beforeEach(() => {
+ wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
+ it('renders milestone icon', () => {
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock');
});
- describe('computed', () => {
- describe('isMilestoneStarted', () => {
- it('should return `false` when milestoneStart prop is not defined', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.isMilestoneStarted).toBe(false);
- });
-
- it('should return `true` when milestone start date is past current date', async () => {
- await wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '1990-07-22' },
- });
- await nextTick();
+ it('renders milestone title', () => {
+ expect(wrapper.find('.milestone-title').text()).toBe(mockMilestone.title);
+ });
- expect(wrapper.vm.isMilestoneStarted).toBe(true);
- });
+ describe('tooltip', () => {
+ it('renders `Milestone`', () => {
+ expect(findTooltip().text()).toContain('Milestone');
});
- describe('isMilestonePastDue', () => {
- it('should return `false` when milestoneDue prop is not defined', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.isMilestonePastDue).toBe(false);
- });
-
- it('should return `true` when milestone due is past current date', () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '1990-07-22' },
- });
-
- expect(wrapper.vm.isMilestonePastDue).toBe(true);
- });
+ it('renders milestone title', () => {
+ expect(findTooltip().text()).toContain(mockMilestone.title);
});
- describe('milestoneDatesAbsolute', () => {
- it('returns string containing absolute milestone due date', () => {
- expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
- });
+ describe('humanized dates', () => {
+ it('renders `Expired` when there is a due date in the past', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '2019-12-31', start_date: '' });
- it('returns string containing absolute milestone start date when due date is not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
+ expect(findTooltip().text()).toContain('Expired 6 months ago(December 31, 2019)');
});
- it('returns empty string when both milestone start and due dates are not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '', due_date: '' },
- });
- await nextTick();
+ it('renders `remaining` when there is a due date in the future', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '2020-12-31', start_date: '' });
- expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
+ expect(findTooltip().text()).toContain('5 months remaining(December 31, 2020)');
});
- });
- describe('milestoneDatesHuman', () => {
- it('returns string containing milestone due date when date is yet to be due', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` },
- });
- await nextTick();
+ it('renders `Started` when there is a start date in the past', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2019-12-31' });
- expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
+ expect(findTooltip().text()).toContain('Started 6 months ago(December 31, 2019)');
});
- it('returns string containing milestone start date when date has already started and due date is not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' },
- });
- await nextTick();
+ it('renders `Starts` when there is a start date in the future', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2020-12-31' });
- expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
+ expect(findTooltip().text()).toContain('Starts in 5 months(December 31, 2020)');
});
-
- it('returns string containing milestone start date when date is yet to start and due date is not present', async () => {
- wrapper.setProps({
- milestone: {
- ...mockMilestone,
- start_date: `${new Date().getFullYear() + 10}-01-01`,
- due_date: '',
- },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
- });
-
- it('returns empty string when milestone start and due dates are not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '', due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesHuman).toBe('');
- });
- });
- });
-
- describe('template', () => {
- it('renders component root element with class `issue-milestone-details`', () => {
- expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
- });
-
- it('renders milestone icon', () => {
- expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock');
- });
-
- it('renders milestone title', () => {
- expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
- });
-
- it('renders milestone tooltip', () => {
- expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
- mockMilestone.title,
- );
});
});
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index 2180ea7e6c2..7ca9715430d 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -10,6 +10,7 @@ import {
} from '~/lib/utils/text_markdown';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import '~/lib/utils/jquery_at_who';
+import { ENTER_KEY } from '~/lib/utils/keys';
import axios from '~/lib/utils/axios_utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
@@ -208,7 +209,7 @@ describe('init markdown', () => {
let enterEvent;
beforeEach(() => {
- enterEvent = new KeyboardEvent('keydown', { key: 'Enter', cancelable: true });
+ enterEvent = new KeyboardEvent('keydown', { key: ENTER_KEY, cancelable: true });
textArea.addEventListener('keydown', keypressNoteText);
textArea.addEventListener('compositionstart', compositionStartNoteText);
textArea.addEventListener('compositionend', compositionEndNoteText);
@@ -492,6 +493,53 @@ describe('init markdown', () => {
});
});
+ describe('adding a hard break using Shift+Enter', () => {
+ let enterEvent;
+
+ beforeEach(() => {
+ enterEvent = new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true });
+ textArea.addEventListener('keydown', keypressNoteText);
+ textArea.addEventListener('compositionstart', compositionStartNoteText);
+ textArea.addEventListener('compositionend', compositionEndNoteText);
+ });
+
+ it.each`
+ selectionStart | selectionEnd | expected | expectedSelectionStart
+ ${0} | ${0} | ${'\\\n0123456789'} | ${2}
+ ${3} | ${3} | ${'012\\\n3456789'} | ${5}
+ ${3} | ${6} | ${'012\\\n6789'} | ${5}
+ `(
+ 'adds a hard break',
+ ({ selectionStart, selectionEnd, expected, expectedSelectionStart }) => {
+ const text = '0123456789';
+ textArea.value = text;
+ textArea.setSelectionRange(selectionStart, selectionEnd);
+
+ textArea.dispatchEvent(enterEvent);
+
+ expect(textArea.value).toEqual(expected);
+ expect(textArea.selectionStart).toEqual(expectedSelectionStart);
+ expect(textArea.selectionEnd).toEqual(expectedSelectionStart);
+ },
+ );
+
+ it.each`
+ keyEvent
+ ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: false })}
+ ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, metaKey: true })}
+ ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, altKey: true })}
+ ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, ctrlKey: true })}
+ `('does not add when shift is pressed with other keys', ({ keyEvent }) => {
+ const text = '0123456789';
+ textArea.value = text;
+ textArea.setSelectionRange(0, 0);
+
+ textArea.dispatchEvent(keyEvent);
+
+ expect(textArea.value).toEqual(text);
+ });
+ });
+
describe('with selection', () => {
let text = 'initial selected value';
let selected = 'selected';
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js
index 028c30cb291..b19db73459d 100644
--- a/spec/frontend/profile/account/components/update_username_spec.js
+++ b/spec/frontend/profile/account/components/update_username_spec.js
@@ -1,6 +1,7 @@
import { GlModal } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'helpers/test_constants';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createAlert } from '~/flash';
@@ -43,6 +44,7 @@ describe('UpdateUsername component', () => {
afterEach(() => {
wrapper.destroy();
axiosMock.restore();
+ Vue.config.errorHandler = null;
});
const findElements = () => {
@@ -58,6 +60,13 @@ describe('UpdateUsername component', () => {
};
};
+ const clickModalWithErrorResponse = () => {
+ Vue.config.errorHandler = jest.fn(); // silence thrown error
+ const { modal } = findElements();
+ modal.vm.$emit('primary');
+ return waitForPromises();
+ };
+
it('has a disabled button if the username was not changed', async () => {
const { openModalBtn } = findElements();
@@ -98,14 +107,15 @@ describe('UpdateUsername component', () => {
axiosMock.onPut(actionUrl).replyOnce(() => [HTTP_STATUS_OK, { message: 'Username changed' }]);
jest.spyOn(axios, 'put');
- await wrapper.vm.onConfirm();
- await nextTick();
+ const { modal } = findElements();
+ modal.vm.$emit('primary');
+ await waitForPromises();
expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
});
it('sets the username after a successful update', async () => {
- const { input, openModalBtn } = findElements();
+ const { input, openModalBtn, modal } = findElements();
axiosMock.onPut(actionUrl).replyOnce(() => {
expect(input.attributes('disabled')).toBe('disabled');
@@ -115,8 +125,8 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_OK, { message: 'Username changed' }];
});
- await wrapper.vm.onConfirm();
- await nextTick();
+ modal.vm.$emit('primary');
+ await waitForPromises();
expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(true);
@@ -134,7 +144,8 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
+
expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(false);
expect(openModalBtn.props('loading')).toBe(false);
@@ -145,7 +156,7 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
expect(createAlert).toHaveBeenCalledWith({
message: 'Invalid username',
@@ -157,7 +168,7 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while updating your username, please try again.',
diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
index 8d0fd390e35..8bea84f4429 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
@@ -71,8 +71,10 @@ describe('Branch rule', () => {
});
it('renders a detail button with the correct href', () => {
+ const encodedBranchName = encodeURIComponent(branchRulePropsMock.name);
+
expect(findDetailsButton().attributes('href')).toBe(
- `${branchRuleProvideMock.branchRulesPath}?branch=${branchRulePropsMock.name}`,
+ `${branchRuleProvideMock.branchRulesPath}?branch=${encodedBranchName}`,
);
});
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
index de7f6c8b88d..d169397241d 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
@@ -74,7 +74,7 @@ export const branchRuleProvideMock = {
};
export const branchRulePropsMock = {
- name: 'main',
+ name: 'branch-with-$speci@l-#-chars',
isDefault: true,
matchingBranchesCount: 1,
branchProtection: {
diff --git a/spec/frontend/sidebar/components/participants/participants_spec.js b/spec/frontend/sidebar/components/participants/participants_spec.js
index f7a626a189c..72d83ebeca4 100644
--- a/spec/frontend/sidebar/components/participants/participants_spec.js
+++ b/spec/frontend/sidebar/components/participants/participants_spec.js
@@ -1,203 +1,114 @@
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import Participants from '~/sidebar/components/participants/participants.vue';
-const PARTICIPANT = {
- id: 1,
- state: 'active',
- username: 'marcene',
- name: 'Allie Will',
- web_url: 'foo.com',
- avatar_url: 'gravatar.com/avatar/xxx',
-};
-
-const PARTICIPANT_LIST = [PARTICIPANT, { ...PARTICIPANT, id: 2 }, { ...PARTICIPANT, id: 3 }];
-
-describe('Participants', () => {
+describe('Participants component', () => {
let wrapper;
- const getMoreParticipantsButton = () => wrapper.find('[data-testid="more-participants"]');
- const getCollapsedParticipantsCount = () => wrapper.find('[data-testid="collapsed-count"]');
+ const participant = {
+ id: 1,
+ state: 'active',
+ username: 'marcene',
+ name: 'Allie Will',
+ web_url: 'foo.com',
+ avatar_url: 'gravatar.com/avatar/xxx',
+ };
- const mountComponent = (propsData) =>
- shallowMount(Participants, {
- propsData,
- });
+ const participants = [participant, { ...participant, id: 2 }, { ...participant, id: 3 }];
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findMoreParticipantsButton = () => wrapper.findComponent(GlButton);
+ const findCollapsedIcon = () => wrapper.find('.sidebar-collapsed-icon');
+ const findParticipantsAuthor = () => wrapper.findAll('.participants-author');
+
+ const mountComponent = (propsData) => shallowMount(Participants, { propsData });
describe('collapsed sidebar state', () => {
it('shows loading spinner when loading', () => {
- wrapper = mountComponent({
- loading: true,
- });
+ wrapper = mountComponent({ loading: true });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('does not show loading spinner not loading', () => {
- wrapper = mountComponent({
- loading: false,
- });
+ it('does not show loading spinner when not loading', () => {
+ wrapper = mountComponent({ loading: false });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('shows participant count when given', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- });
+ wrapper = mountComponent({ participants });
- expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`);
+ expect(findCollapsedIcon().text()).toBe(participants.length.toString());
});
it('shows full participant count when there are hidden participants', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 1,
- });
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 1 });
- expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`);
+ expect(findCollapsedIcon().text()).toBe(participants.length.toString());
});
});
describe('expanded sidebar state', () => {
it('shows loading spinner when loading', () => {
- wrapper = mountComponent({
- loading: true,
- });
+ wrapper = mountComponent({ loading: true });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => {
+ it('when only showing visible participants, shows an avatar only for each participant under the limit', () => {
const numberOfLessParticipants = 2;
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants,
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isShowingMoreParticipants: false,
- });
-
- await nextTick();
- expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
+ wrapper = mountComponent({ participants, numberOfLessParticipants });
+
+ expect(findParticipantsAuthor()).toHaveLength(numberOfLessParticipants);
});
it('when only showing all participants, each has an avatar', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isShowingMoreParticipants: true,
- });
-
- await nextTick();
- expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
+
+ await findMoreParticipantsButton().vm.$emit('click');
+
+ expect(findParticipantsAuthor()).toHaveLength(participants.length);
});
it('does not have more participants link when they can all be shown', () => {
const numberOfLessParticipants = 100;
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants,
- });
-
- expect(PARTICIPANT_LIST.length).toBeLessThan(numberOfLessParticipants);
- expect(getMoreParticipantsButton().exists()).toBe(false);
- });
+ wrapper = mountComponent({ participants, numberOfLessParticipants });
- it('when too many participants, has more participants link to show more', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isShowingMoreParticipants: false,
- });
-
- await nextTick();
- expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
+ expect(participants.length).toBeLessThan(numberOfLessParticipants);
+ expect(findMoreParticipantsButton().exists()).toBe(false);
});
- it('when too many participants and already showing them, has more participants link to show less', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isShowingMoreParticipants: true,
- });
-
- await nextTick();
- expect(getMoreParticipantsButton().text()).toBe('- show less');
- });
+ it('when too many participants, has more participants link to show more', () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- it('clicking more participants link emits event', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
+ expect(findMoreParticipantsButton().text()).toBe('+ 1 more');
+ });
- expect(wrapper.vm.isShowingMoreParticipants).toBe(false);
+ it('when too many participants and already showing them, has more participants link to show less', async () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- getMoreParticipantsButton().vm.$emit('click');
+ await findMoreParticipantsButton().vm.$emit('click');
- expect(wrapper.vm.isShowingMoreParticipants).toBe(true);
+ expect(findMoreParticipantsButton().text()).toBe('- show less');
});
- it('clicking on participants icon emits `toggleSidebar` event', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
-
- const spy = jest.spyOn(wrapper.vm, '$emit');
+ it('clicking on participants icon emits `toggleSidebar` event', () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- wrapper.find('.sidebar-collapsed-icon').trigger('click');
+ findCollapsedIcon().trigger('click');
- await nextTick();
- expect(spy).toHaveBeenCalledWith('toggleSidebar');
- spy.mockRestore();
+ expect(wrapper.emitted('toggleSidebar')).toEqual([[]]);
});
});
describe('when not showing participants label', () => {
beforeEach(() => {
- wrapper = mountComponent({
- participants: PARTICIPANT_LIST,
- showParticipantLabel: false,
- });
+ wrapper = mountComponent({ participants, showParticipantLabel: false });
});
it('does not show sidebar collapsed icon', () => {
- expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(false);
+ expect(findCollapsedIcon().exists()).toBe(false);
});
it('does not show participants label title', () => {
diff --git a/spec/frontend/super_sidebar/components/sidebar_portal_spec.js b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js
new file mode 100644
index 00000000000..3ef1cb7e692
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js
@@ -0,0 +1,68 @@
+import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue';
+import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
+
+describe('SidebarPortal', () => {
+ let targetWrapper;
+
+ const Target = {
+ components: { SidebarPortalTarget },
+ props: ['show'],
+ template: '<sidebar-portal-target v-if="show" />',
+ };
+
+ const Source = {
+ components: { SidebarPortal },
+ template: '<sidebar-portal><br data-testid="test"></sidebar-portal>',
+ };
+
+ const mountSource = () => {
+ mount(Source);
+ };
+
+ const mountTarget = ({ show = true } = {}) => {
+ targetWrapper = mount(Target, {
+ propsData: { show },
+ attachTo: document.body,
+ });
+ };
+
+ const findTestContent = () => targetWrapper.find('[data-testid="test"]');
+
+ it('renders content into the target', async () => {
+ mountTarget();
+ await nextTick();
+
+ mountSource();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(true);
+ });
+
+ it('waits for target to be available before rendering', async () => {
+ mountSource();
+ await nextTick();
+
+ mountTarget();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(true);
+ });
+
+ it('supports conditional rendering of target', async () => {
+ mountTarget({ show: false });
+ await nextTick();
+
+ mountSource();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(false);
+
+ await targetWrapper.setProps({ show: true });
+ expect(findTestContent().exists()).toBe(true);
+
+ await targetWrapper.setProps({ show: false });
+ expect(findTestContent().exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index 45fc30c08f0..57c84bc87a6 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -2,6 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue';
import HelpCenter from '~/super_sidebar/components/help_center.vue';
import UserBar from '~/super_sidebar/components/user_bar.vue';
+import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
import { sidebarData } from '../mock_data';
describe('SuperSidebar component', () => {
@@ -9,6 +10,7 @@ describe('SuperSidebar component', () => {
const findUserBar = () => wrapper.findComponent(UserBar);
const findHelpCenter = () => wrapper.findComponent(HelpCenter);
+ const findSidebarPortalTarget = () => wrapper.findComponent(SidebarPortalTarget);
const createWrapper = (props = {}) => {
wrapper = shallowMountExtended(SuperSidebar, {
@@ -31,5 +33,9 @@ describe('SuperSidebar component', () => {
it('renders HelpCenter with sidebarData', () => {
expect(findHelpCenter().props('sidebarData')).toBe(sidebarData);
});
+
+ it('renders SidebarPortalTarget', () => {
+ expect(findSidebarPortalTarget().exists()).toBe(true);
+ });
});
});
diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb
index 31aeff85c70..4f56bb7467f 100644
--- a/spec/helpers/jira_connect_helper_spec.rb
+++ b/spec/helpers/jira_connect_helper_spec.rb
@@ -9,8 +9,7 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do
let(:user) { create(:user) }
let(:client_id) { '123' }
- let(:enable_public_keys_storage_config) { false }
- let(:enable_public_keys_storage_setting) { false }
+ let(:enable_public_keys_storage) { false }
before do
stub_application_setting(jira_connect_application_key: client_id)
@@ -22,9 +21,7 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do
before do
allow(view).to receive(:current_user).and_return(nil)
allow(Gitlab.config.gitlab).to receive(:url).and_return('http://test.host')
- allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage)
- .and_return(enable_public_keys_storage_config)
- stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage_setting)
+ stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage)
end
it 'includes Jira Connect app attributes' do
@@ -108,16 +105,8 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do
expect(subject[:public_key_storage_enabled]).to eq(false)
end
- context 'when public_key_storage is enabled via config' do
- let(:enable_public_keys_storage_config) { true }
-
- it 'assignes public_key_storage_enabled to true' do
- expect(subject[:public_key_storage_enabled]).to eq(true)
- end
- end
-
- context 'when public_key_storage is enabled via setting' do
- let(:enable_public_keys_storage_setting) { true }
+ context 'when public_key_storage is enabled' do
+ let(:enable_public_keys_storage) { true }
it 'assignes public_key_storage_enabled to true' do
expect(subject[:public_key_storage_enabled]).to eq(true)
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index d561b08efac..d625b46e286 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -10,6 +10,60 @@ RSpec.describe SortingHelper do
allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: option }))
end
+ describe '#issuable_sort_options' do
+ let(:viewing_issues) { false }
+ let(:viewing_merge_requests) { false }
+ let(:params) { {} }
+
+ subject(:options) { helper.issuable_sort_options(viewing_issues, viewing_merge_requests) }
+
+ before do
+ allow(helper).to receive(:params).and_return(params)
+ end
+
+ shared_examples 'with merged date option' do
+ it 'adds merged date option' do
+ expect(options).to include(
+ a_hash_including(
+ value: 'merged_at',
+ text: 'Merged date'
+ )
+ )
+ end
+ end
+
+ shared_examples 'without merged date option' do
+ it 'does not set merged date option' do
+ expect(options).not_to include(
+ a_hash_including(
+ value: 'merged_at',
+ text: 'Merged date'
+ )
+ )
+ end
+ end
+
+ it_behaves_like 'without merged date option'
+
+ context 'when viewing_merge_requests is true' do
+ let(:viewing_merge_requests) { true }
+
+ it_behaves_like 'without merged date option'
+
+ context 'when state param is all' do
+ let(:params) { { state: 'all' } }
+
+ it_behaves_like 'with merged date option'
+ end
+
+ context 'when state param is merged' do
+ let(:params) { { state: 'merged' } }
+
+ it_behaves_like 'with merged date option'
+ end
+ end
+ end
+
describe '#admin_users_sort_options' do
it 'returns correct link attributes in array' do
options = admin_users_sort_options(filter: 'filter', search_query: 'search')
diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb
index 28a087d5401..b187b29c270 100644
--- a/spec/lib/gitlab/database/gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb
@@ -16,19 +16,21 @@ RSpec.shared_examples 'validate schema data' do |tables_and_views|
end
end
-RSpec.describe Gitlab::Database::GitlabSchema do
+RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do
shared_examples 'maps table name to table schema' do
using RSpec::Parameterized::TableSyntax
where(:name, :classification) do
- 'ci_builds' | :gitlab_ci
- 'my_schema.ci_builds' | :gitlab_ci
- 'information_schema.columns' | :gitlab_internal
- 'audit_events_part_5fc467ac26' | :gitlab_main
- '_test_gitlab_main_table' | :gitlab_main
- '_test_gitlab_ci_table' | :gitlab_ci
- '_test_my_table' | :gitlab_shared
- 'pg_attribute' | :gitlab_internal
+ 'ci_builds' | :gitlab_ci
+ 'my_schema.ci_builds' | :gitlab_ci
+ 'my_schema.ci_runner_machine_builds_100' | :gitlab_ci
+ 'my_schema._test_gitlab_main_table' | :gitlab_main
+ 'information_schema.columns' | :gitlab_internal
+ 'audit_events_part_5fc467ac26' | :gitlab_main
+ '_test_gitlab_main_table' | :gitlab_main
+ '_test_gitlab_ci_table' | :gitlab_ci
+ '_test_my_table' | :gitlab_shared
+ 'pg_attribute' | :gitlab_internal
end
with_them do
diff --git a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
index c7e4eaf8f46..ac54c307108 100644
--- a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
@@ -238,23 +238,20 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
expect(pending_drop.drop_after).to eq(Time.current + described_class::RETAIN_DETACHED_PARTITIONS_FOR)
end
- # Postgres 11 does not support foreign keys to partitioned tables
- if ApplicationRecord.database.version.to_f >= 12
- context 'when the model is the target of a foreign key' do
- before do
- connection.execute(<<~SQL)
+ context 'when the model is the target of a foreign key' do
+ before do
+ connection.execute(<<~SQL)
create unique index idx_for_fk ON #{partitioned_table_name}(created_at);
create table _test_gitlab_main_referencing_table (
id bigserial primary key not null,
referencing_created_at timestamptz references #{partitioned_table_name}(created_at)
);
- SQL
- end
+ SQL
+ end
- it 'does not detach partitions with a referenced foreign key' do
- expect { subject }.not_to change { find_partitions(my_model.table_name).size }
- end
+ it 'does not detach partitions with a referenced foreign key' do
+ expect { subject }.not_to change { find_partitions(my_model.table_name).size }
end
end
end
diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
index 75f94bf2654..c128c56c708 100644
--- a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
+++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
@@ -203,10 +203,8 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ
end
end
- context 'when supporting foreign keys to inherited tables in postgres 12' do
+ context 'when supporting foreign keys to inherited tables' do
before do
- skip('not supported before postgres 12') if ApplicationRecord.database.version.to_f < 12
-
ApplicationRecord.connection.execute(<<~SQL)
create table #{schema_table_name('parent')} (
id bigserial primary key not null
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index fcef5b6ca78..0a77b6e228e 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe API::Avatar, feature_category: :user_profile do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}")
+ is_expected.to have_request_urgency(:medium)
end
end
diff --git a/spec/requests/jira_connect/public_keys_controller_spec.rb b/spec/requests/jira_connect/public_keys_controller_spec.rb
index 7f0262eaf65..62a81d43e65 100644
--- a/spec/requests/jira_connect/public_keys_controller_spec.rb
+++ b/spec/requests/jira_connect/public_keys_controller_spec.rb
@@ -5,11 +5,10 @@ require 'spec_helper'
RSpec.describe JiraConnect::PublicKeysController, feature_category: :integrations do
describe 'GET /-/jira_connect/public_keys/:uuid' do
let(:uuid) { non_existing_record_id }
- let(:public_key_storage_enabled_config) { true }
+ let(:public_key_storage_enabled) { true }
before do
- allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage)
- .and_return(public_key_storage_enabled_config)
+ stub_application_setting(jira_connect_public_key_storage_enabled: public_key_storage_enabled)
end
it 'renders 404' do
@@ -30,26 +29,14 @@ RSpec.describe JiraConnect::PublicKeysController, feature_category: :integration
expect(response.body).to eq(public_key.key)
end
- context 'when public key storage config disabled' do
- let(:public_key_storage_enabled_config) { false }
+ context 'when public key storage setting disabled' do
+ let(:public_key_storage_enabled) { false }
it 'renders 404' do
get jira_connect_public_key_path(id: uuid)
expect(response).to have_gitlab_http_status(:not_found)
end
-
- context 'when public key storage setting is enabled' do
- before do
- stub_application_setting(jira_connect_public_key_storage_enabled: true)
- end
-
- it 'renders 404' do
- get jira_connect_public_key_path(id: uuid)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3f0900886eb..f76ba3528e9 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -178,6 +178,8 @@ RSpec.configure do |config|
config.include RenderedHelpers
config.include RSpec::Benchmark::Matchers, type: :benchmark
config.include DetailedErrorHelpers
+ config.include RequestUrgencyMatcher, type: :controller
+ config.include RequestUrgencyMatcher, type: :request
config.include_context 'when rendered has no HTML escapes', type: :view
diff --git a/spec/support/matchers/request_urgency_matcher.rb b/spec/support/matchers/request_urgency_matcher.rb
new file mode 100644
index 00000000000..d3c5093719e
--- /dev/null
+++ b/spec/support/matchers/request_urgency_matcher.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+module RequestUrgencyMatcher
+ RSpec::Matchers.define :have_request_urgency do |request_urgency|
+ match do |_actual|
+ if controller_instance = request.env["action_controller.instance"]
+ controller_instance.urgency.name == request_urgency
+ elsif endpoint = request.env['api.endpoint']
+ urgency = endpoint.options[:for].try(:urgency_for_app, endpoint)
+ urgency.name == request_urgency
+ else
+ raise 'neither a controller nor a request spec'
+ end
+ end
+
+ failure_message do |_actual|
+ if controller_instance = request.env["action_controller.instance"]
+ "request urgency #{controller_instance.urgency.name} is set, \
+ but expected to be #{request_urgency}".squish
+ elsif endpoint = request.env['api.endpoint']
+ urgency = endpoint.options[:for].try(:urgency_for_app, endpoint)
+ "request urgency #{urgency.name} is set, \
+ but expected to be #{request_urgency}".squish
+ end
+ end
+ end
+end