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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-25 15:08:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-25 15:08:10 +0300
commitba9892d3c122a4f437b4b38926fb75848deaf097 (patch)
tree428fa4e4d19ff38cc6ccdd2036e10f2333720fc2
parentf71f0f5307d836c4b0a7e501e4af799a536f3454 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js2
-rw-r--r--app/assets/javascripts/issuable/popover/components/issue_popover.vue83
-rw-r--r--app/assets/javascripts/issuable/popover/components/mr_popover.vue12
-rw-r--r--app/assets/javascripts/issuable/popover/index.js30
-rw-r--r--app/assets/javascripts/issuable/popover/queries/issue.query.graphql11
-rw-r--r--app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql4
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue2
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js2
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss17
-rw-r--r--app/assets/stylesheets/framework/header.scss10
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss7
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss18
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss18
-rw-r--r--app/assets/stylesheets/themes/theme_light.scss8
-rw-r--r--app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb21
-rw-r--r--app/graphql/mutations/work_items/update.rb11
-rw-r--r--app/graphql/mutations/work_items/update_task.rb78
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/work_items/updated_task_input_type.rb11
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/issue.rb10
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml26
-rw-r--r--app/workers/container_registry/migration/enqueuer_worker.rb2
-rw-r--r--config/feature_flags/development/group_projects_api_preload_groups.yml8
-rw-r--r--db/docs/ci_freeze_periods.yml4
-rw-r--r--db/docs/ci_platform_metrics.yml2
-rw-r--r--db/docs/ci_resource_groups.yml2
-rw-r--r--db/docs/deploy_keys_projects.yml2
-rw-r--r--db/docs/deploy_tokens.yml2
-rw-r--r--db/docs/deployment_approvals.yml4
-rw-r--r--db/docs/deployment_merge_requests.yml4
-rw-r--r--db/docs/deployments.yml2
-rw-r--r--db/docs/environments.yml2
-rw-r--r--db/docs/evidences.yml2
-rw-r--r--db/docs/feature_gates.yml2
-rw-r--r--db/docs/features.yml2
-rw-r--r--db/docs/group_deploy_keys_groups.yml4
-rw-r--r--db/docs/milestone_releases.yml2
-rw-r--r--db/docs/operations_feature_flag_scopes.yml2
-rw-r--r--db/docs/operations_feature_flags_clients.yml2
-rw-r--r--db/docs/operations_feature_flags_issues.yml2
-rw-r--r--db/docs/operations_scopes.yml2
-rw-r--r--db/docs/operations_strategies.yml2
-rw-r--r--db/docs/operations_strategies_user_lists.yml2
-rw-r--r--db/docs/operations_user_lists.yml2
-rw-r--r--db/docs/project_deploy_tokens.yml4
-rw-r--r--db/docs/protected_environment_approval_rules.yml2
-rw-r--r--db/docs/protected_environment_deploy_access_levels.yml2
-rw-r--r--db/docs/protected_environments.yml2
-rw-r--r--db/docs/release_links.yml2
-rw-r--r--db/docs/releases.yml2
-rw-r--r--db/docs/users_ops_dashboard_projects.yml4
-rw-r--r--doc/api/graphql/reference/index.md33
-rw-r--r--doc/user/project/description_templates.md2
-rw-r--r--doc/user/project/integrations/zentao.md7
-rw-r--r--lib/api/namespaces.rb5
-rw-r--r--lib/api/projects_relation_builder.rb2
-rw-r--r--lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake76
-rw-r--r--spec/frontend/issuable/popover/components/__snapshots__/mr_popover_spec.js.snap (renamed from spec/frontend/issuable/popover/__snapshots__/mr_popover_spec.js.snap)0
-rw-r--r--spec/frontend/issuable/popover/components/issue_popover_spec.js81
-rw-r--r--spec/frontend/issuable/popover/components/mr_popover_spec.js (renamed from spec/frontend/issuable/popover/mr_popover_spec.js)4
-rw-r--r--spec/frontend/issuable/popover/index_spec.js13
-rw-r--r--spec/graphql/mutations/work_items/update_task_spec.rb40
-rw-r--r--spec/migrations/schedule_purging_stale_security_scans_spec.rb2
-rw-r--r--spec/models/issue_spec.rb16
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_task_spec.rb101
-rw-r--r--spec/requests/api/groups_spec.rb19
-rw-r--r--spec/requests/api/namespaces_spec.rb18
-rw-r--r--spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb112
74 files changed, 867 insertions, 138 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 5bca1a62d34..d9aa1be0b1e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-47335e9fa13b0185b9d479a9e8bffc535eed6a7c
+ac989862106589558866e01fc5d77ad7326c99e4
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index c1dff1715b9..5119d5021da 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -24,7 +24,7 @@ $.fn.renderGFM = function renderGFM() {
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.js-user-link').get());
- const issuablePopoverElements = this.find('.gfm-merge_request').get();
+ const issuablePopoverElements = this.find('.gfm-issue, .gfm-merge_request').get();
if (issuablePopoverElements.length) {
import(/* webpackChunkName: 'IssuablePopoverBundle' */ '~/issuable/popover')
.then(({ default: initIssuablePopovers }) => {
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index f9cf3af98bb..2df0f7387fb 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -160,7 +160,7 @@ function renderMermaids($els) {
'Warning: Displaying this diagram might cause performance issues on this page.',
)}</div>
<div class="gl-alert-actions">
- <button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md gl-button">Display</button>
+ <button class="js-lazy-render-mermaid btn gl-alert-action btn-confirm btn-md gl-button">Display</button>
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
index 3b9f6011c6d..543e676e85e 100644
--- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
@@ -138,7 +138,7 @@ function renderMermaids($els) {
<div>
<div class="js-warning-text"></div>
<div class="gl-alert-actions">
- <button type="button" class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md gl-button">Display</button>
+ <button type="button" class="js-lazy-render-mermaid btn gl-alert-action btn-confirm btn-md gl-button">Display</button>
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
diff --git a/app/assets/javascripts/issuable/popover/components/issue_popover.vue b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
new file mode 100644
index 00000000000..0cafaa1e500
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
@@ -0,0 +1,83 @@
+<script>
+import { GlPopover, GlSkeletonLoader } from '@gitlab/ui';
+import StatusBox from '~/issuable/components/status_box.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import query from '../queries/issue.query.graphql';
+
+export default {
+ components: {
+ GlPopover,
+ GlSkeletonLoader,
+ StatusBox,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ iid: {
+ type: String,
+ required: true,
+ },
+ cachedTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ issue: {},
+ };
+ },
+ computed: {
+ formattedTime() {
+ return this.timeFormatted(this.issue.createdAt);
+ },
+ title() {
+ return this.issue?.title || this.cachedTitle;
+ },
+ showDetails() {
+ return Object.keys(this.issue).length > 0;
+ },
+ },
+ apollo: {
+ issue: {
+ query,
+ update: (data) => data.project.issue,
+ variables() {
+ const { projectPath, iid } = this;
+
+ return {
+ projectPath,
+ iid,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <gl-skeleton-loader v-if="$apollo.queries.issue.loading" :height="15">
+ <rect width="250" height="15" rx="4" />
+ </gl-skeleton-loader>
+ <div v-else-if="showDetails" class="gl-display-flex gl-align-items-center">
+ <status-box issuable-type="issue" :initial-state="issue.state" />
+ <span class="gl-text-secondary">
+ {{ __('Opened') }} <time :datetime="issue.createdAt">{{ formattedTime }}</time>
+ </span>
+ </div>
+ <h5 v-if="!$apollo.queries.issue.loading" class="gl-my-3">{{ title }}</h5>
+ <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
+ <div class="gl-text-secondary">
+ {{ `${projectPath}#${iid}` }}
+ </div>
+ <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/issuable/popover/components/mr_popover.vue b/app/assets/javascripts/issuable/popover/components/mr_popover.vue
index f467d7e93b1..8573dd702a6 100644
--- a/app/assets/javascripts/issuable/popover/components/mr_popover.vue
+++ b/app/assets/javascripts/issuable/popover/components/mr_popover.vue
@@ -25,11 +25,11 @@ export default {
type: String,
required: true,
},
- mergeRequestIID: {
+ iid: {
type: String,
required: true,
},
- mergeRequestTitle: {
+ cachedTitle: {
type: String,
required: true,
},
@@ -67,7 +67,7 @@ export default {
}
},
title() {
- return this.mergeRequest?.title || this.mergeRequestTitle;
+ return this.mergeRequest?.title || this.cachedTitle;
},
showDetails() {
return Object.keys(this.mergeRequest).length > 0;
@@ -78,11 +78,11 @@ export default {
query,
update: (data) => data.project.mergeRequest,
variables() {
- const { projectPath, mergeRequestIID } = this;
+ const { projectPath, iid } = this;
return {
projectPath,
- mergeRequestIID,
+ iid,
};
},
},
@@ -108,7 +108,7 @@ export default {
<h5 v-if="!$apollo.queries.mergeRequest.loading" class="my-2">{{ title }}</h5>
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<div class="gl-text-secondary">
- {{ `${projectPath}!${mergeRequestIID}` }}
+ {{ `${projectPath}!${iid}` }}
</div>
<!-- eslint-enable @gitlab/vue-require-i18n-strings -->
</div>
diff --git a/app/assets/javascripts/issuable/popover/index.js b/app/assets/javascripts/issuable/popover/index.js
index 1f0a00bc286..a09aae00aeb 100644
--- a/app/assets/javascripts/issuable/popover/index.js
+++ b/app/assets/javascripts/issuable/popover/index.js
@@ -1,8 +1,14 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import IssuePopover from './components/issue_popover.vue';
import MRPopover from './components/mr_popover.vue';
+const componentsByReferenceType = {
+ issue: IssuePopover,
+ merge_request: MRPopover,
+};
+
let renderedPopover;
let renderFn;
@@ -22,20 +28,24 @@ const handleIssuablePopoverMouseOut = ({ target }) => {
* Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes.
* loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover
*/
-const handleIssuablePopoverMount = ({ apolloProvider, projectPath, title, iid }) => ({
- target,
-}) => {
+const handleIssuablePopoverMount = ({
+ apolloProvider,
+ projectPath,
+ title,
+ iid,
+ referenceType,
+}) => ({ target }) => {
// Add listener to actually remove it again
target.addEventListener('mouseleave', handleIssuablePopoverMouseOut);
renderFn = setTimeout(() => {
- const MRPopoverComponent = Vue.extend(MRPopover);
- renderedPopover = new MRPopoverComponent({
+ const PopoverComponent = Vue.extend(componentsByReferenceType[referenceType]);
+ renderedPopover = new PopoverComponent({
propsData: {
target,
projectPath,
- mergeRequestIID: iid,
- mergeRequestTitle: title,
+ iid,
+ cachedTitle: title,
},
apolloProvider,
});
@@ -54,13 +64,13 @@ export default (elements) => {
const listenerAddedAttr = 'data-popover-listener-added';
elements.forEach((el) => {
- const { projectPath, iid } = el.dataset;
+ const { projectPath, iid, referenceType } = el.dataset;
const title = el.dataset.mrTitle || el.title;
- if (!el.getAttribute(listenerAddedAttr) && projectPath && title && iid) {
+ if (!el.getAttribute(listenerAddedAttr) && projectPath && title && iid && referenceType) {
el.addEventListener(
'mouseenter',
- handleIssuablePopoverMount({ apolloProvider, projectPath, title, iid }),
+ handleIssuablePopoverMount({ apolloProvider, projectPath, title, iid, referenceType }),
);
el.setAttribute(listenerAddedAttr, true);
}
diff --git a/app/assets/javascripts/issuable/popover/queries/issue.query.graphql b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
new file mode 100644
index 00000000000..47a62e2b6ea
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
@@ -0,0 +1,11 @@
+query issue($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ id
+ issue(iid: $iid) {
+ id
+ title
+ createdAt
+ state
+ }
+ }
+}
diff --git a/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql b/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
index b3e5d89d495..7cd344c1d5e 100644
--- a/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
+++ b/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
@@ -1,7 +1,7 @@
-query mergeRequest($projectPath: ID!, $mergeRequestIID: String!) {
+query mergeRequest($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
id
- mergeRequest(iid: $mergeRequestIID) {
+ mergeRequest(iid: $iid) {
id
title
createdAt
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 2d3d2e57433..cccaef0508b 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -294,7 +294,7 @@ export default {
/>
<emoji-picker
v-if="canAwardEmoji"
- toggle-class="note-action-button note-emoji-button btn-icon gl-shadow-none!"
+ toggle-class="note-action-button note-emoji-button btn-icon gl-shadow-none! btn-default-tertiary"
data-testid="note-emoji-button"
@click="setAwardEmoji"
>
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index f6f136f2402..690295e6068 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -6,7 +6,7 @@ const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
if (skippable) {
- const button = `<a class="btn btn-sm btn-warning float-right" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
+ const button = `<br/><a class="btn btn-sm btn-confirm gl-mt-3" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
const flashAlert = document.querySelector('.flash-alert');
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
}
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
index 2ab46a7a655..8145506f32c 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -74,7 +74,7 @@ export default {
<gl-button
data-testid="lock-toggle"
category="secondary"
- variant="warning"
+ variant="confirm"
:disabled="isLoading"
:loading="isLoading"
@click.prevent="submitForm"
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 6417213e207..43e14a63f9d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -215,6 +215,13 @@
text-decoration: none;
}
+ &:active,
+ &:focus,
+ &:focus:active,
+ &.is-focused {
+ @include gl-focus($inset: true);
+ }
+
&.dropdown-menu-user-link {
line-height: 16px;
padding-top: 10px;
@@ -280,7 +287,6 @@
display: block;
text-align: left;
list-style: none;
- padding: 0 1px;
> a,
button,
@@ -848,6 +854,15 @@
color: $red-700;
}
+ .frequent-items-list-item-container .gl-button {
+ &:active,
+ &:focus,
+ &:focus:active,
+ &.is-focused {
+ @include gl-focus($inset: true);
+ }
+ }
+
.frequent-items-list-item-container a {
display: flex;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 7c03a2bb049..940932b67d2 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -54,6 +54,11 @@
padding: 2px 8px;
margin: 4px 2px 4px -12px;
border-radius: $border-radius-default;
+
+ &:active,
+ &:focus {
+ @include gl-focus($focus-ring: $focus-ring-dark);
+ }
}
.canary-badge {
@@ -214,6 +219,11 @@
outline: 0;
color: $white;
}
+
+ &:active,
+ &:focus {
+ @include gl-focus($focus-ring: $focus-ring-dark);
+ }
}
.top-nav-toggle,
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 0c754da97cc..4ee062bcf65 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -610,10 +610,10 @@ $tabs-holder-z-index: 250;
.mr-widget-extension {
border-top: 1px solid var(--border-color, $border-color);
- background-color: var(--gray-50, $gray-50);
+ background-color: var(--gray-10, $gray-10);
&.clickable:hover {
- background-color: var(--gray-100, $gray-100);
+ background-color: var(--gray-50, $gray-50);
cursor: pointer;
}
}
@@ -660,6 +660,7 @@ $tabs-holder-z-index: 250;
.mr-widget-workflow {
margin-top: $gl-padding;
+ overflow: hidden;
position: relative;
&:not(:last-child)::before {
@@ -739,7 +740,7 @@ $tabs-holder-z-index: 250;
.merge-request-overview {
@include media-breakpoint-up(md) {
display: grid;
- grid-template-columns: 1fr 270px;
+ grid-template-columns: calc(95% - 270px) auto;
grid-gap: 5%;
}
}
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 54c6c43366f..7e3538f0fa3 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -671,7 +671,6 @@ body {
display: block;
text-align: left;
list-style: none;
- padding: 0 1px;
}
.dropdown-menu li > a,
.dropdown-menu li button {
@@ -697,6 +696,12 @@ body {
outline: 0;
text-decoration: none;
}
+.dropdown-menu li > a:active,
+.dropdown-menu li button:active {
+ box-shadow: inset 0 0 0 2px #1f75cb, inset 0 0 0 3px #333,
+ inset 0 0 0 1px #333;
+ outline: none;
+}
.dropdown-menu .divider {
height: 1px;
margin: 0.25rem 0;
@@ -781,6 +786,10 @@ input {
margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title a:active {
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 0 0 3px #1068bf;
+ outline: none;
+}
.navbar-gitlab .header-content .title .canary-badge {
margin-left: -8px;
}
@@ -886,6 +895,13 @@ input {
height: 32px;
font-weight: 600;
}
+.navbar-sub-nav > li > a:active,
+.navbar-sub-nav > li > button:active,
+.navbar-nav > li > a:active,
+.navbar-nav > li > button:active {
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 0 0 3px #1068bf;
+ outline: none;
+}
.navbar-sub-nav > li .top-nav-toggle,
.navbar-sub-nav > li > button,
.navbar-nav > li .top-nav-toggle,
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 7af31d0f743..0e53f6ea4de 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -656,7 +656,6 @@ body {
display: block;
text-align: left;
list-style: none;
- padding: 0 1px;
}
.dropdown-menu li > a,
.dropdown-menu li button {
@@ -682,6 +681,12 @@ body {
outline: 0;
text-decoration: none;
}
+.dropdown-menu li > a:active,
+.dropdown-menu li button:active {
+ box-shadow: inset 0 0 0 2px #428fdc, inset 0 0 0 3px #fff,
+ inset 0 0 0 1px #fff;
+ outline: none;
+}
.dropdown-menu .divider {
height: 1px;
margin: 0.25rem 0;
@@ -766,6 +771,10 @@ input {
margin: 4px 2px 4px -12px;
border-radius: 4px;
}
+.navbar-gitlab .header-content .title a:active {
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 0 0 3px #63a6e9;
+ outline: none;
+}
.navbar-gitlab .header-content .title .canary-badge {
margin-left: -8px;
}
@@ -871,6 +880,13 @@ input {
height: 32px;
font-weight: 600;
}
+.navbar-sub-nav > li > a:active,
+.navbar-sub-nav > li > button:active,
+.navbar-nav > li > a:active,
+.navbar-nav > li > button:active {
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 0 0 3px #63a6e9;
+ outline: none;
+}
.navbar-sub-nav > li .top-nav-toggle,
.navbar-sub-nav > li > button,
.navbar-nav > li .top-nav-toggle,
diff --git a/app/assets/stylesheets/themes/theme_light.scss b/app/assets/stylesheets/themes/theme_light.scss
index 66b2b3c3437..cbd14246d91 100644
--- a/app/assets/stylesheets/themes/theme_light.scss
+++ b/app/assets/stylesheets/themes/theme_light.scss
@@ -33,6 +33,14 @@ body {
&.active > button {
color: $white;
}
+
+ > a,
+ > button {
+ &:active,
+ &:focus {
+ @include gl-focus;
+ }
+ }
}
}
diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
new file mode 100644
index 00000000000..6a91a097a17
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ module UpdateArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+ argument :state_event, Types::WorkItems::StateEventEnum,
+ description: 'Close or reopen a work item.',
+ required: false
+ argument :title, GraphQL::Types::String,
+ required: false,
+ description: copy_field_description(Types::WorkItemType, :title)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb
index 20319301482..e8c5bb72340 100644
--- a/app/graphql/mutations/work_items/update.rb
+++ b/app/graphql/mutations/work_items/update.rb
@@ -8,19 +8,10 @@ module Mutations
" Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice."
include Mutations::SpamProtection
+ include Mutations::WorkItems::UpdateArguments
authorize :update_work_item
- argument :id, ::Types::GlobalIDType[::WorkItem],
- required: true,
- description: 'Global ID of the work item.'
- argument :state_event, Types::WorkItems::StateEventEnum,
- description: 'Close or reopen a work item.',
- required: false
- argument :title, GraphQL::Types::String,
- required: false,
- description: copy_field_description(Types::WorkItemType, :title)
-
field :work_item, Types::WorkItemType,
null: true,
description: 'Updated work item.'
diff --git a/app/graphql/mutations/work_items/update_task.rb b/app/graphql/mutations/work_items/update_task.rb
new file mode 100644
index 00000000000..8a94004c3e2
--- /dev/null
+++ b/app/graphql/mutations/work_items/update_task.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class UpdateTask < BaseMutation
+ graphql_name 'WorkItemUpdateTask'
+ description "Updates a work item's task by Global ID." \
+ " Available only when feature flag `work_items` is enabled. The feature is experimental and is" \
+ " subject to change without notice."
+
+ include Mutations::SpamProtection
+
+ authorize :read_work_item
+
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+ argument :task_data, ::Types::WorkItems::UpdatedTaskInputType,
+ required: true,
+ description: 'Arguments necessary to update a task.'
+
+ field :task, Types::WorkItemType,
+ null: true,
+ description: 'Updated task.'
+ field :work_item, Types::WorkItemType,
+ null: true,
+ description: 'Updated work item.'
+
+ def resolve(id:, task_data:)
+ task_data_hash = task_data.to_h
+ work_item = authorized_find!(id: id)
+ task = authorized_find_task!(task_data_hash[:id])
+
+ unless work_item.project.work_items_feature_flag_enabled?
+ return { errors: ['`work_items` feature flag disabled for this project'] }
+ end
+
+ spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
+
+ ::WorkItems::UpdateService.new(
+ project: task.project,
+ current_user: current_user,
+ params: task_data_hash.except(:id),
+ spam_params: spam_params
+ ).execute(task)
+
+ check_spam_action_response!(task)
+
+ response = { errors: errors_on_object(task) }
+
+ if task.valid?
+ work_item.expire_etag_cache
+
+ response.merge(work_item: work_item, task: task)
+ else
+ response
+ end
+ end
+
+ private
+
+ def authorized_find_task!(task_id)
+ task = task_id.find
+
+ if current_user.can?(:update_work_item, task)
+ task
+ else
+ # Fail early if user cannot update task
+ raise_resource_not_available_error!
+ end
+ end
+
+ def find_object(id:)
+ id.find
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 7d8ada82d40..b9cf66edaaf 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -142,6 +142,7 @@ module Types
mount_mutation Mutations::WorkItems::Delete
mount_mutation Mutations::WorkItems::DeleteTask
mount_mutation Mutations::WorkItems::Update
+ mount_mutation Mutations::WorkItems::UpdateTask
mount_mutation Mutations::SavedReplies::Create
mount_mutation Mutations::SavedReplies::Update
mount_mutation Mutations::SavedReplies::Destroy
diff --git a/app/graphql/types/work_items/updated_task_input_type.rb b/app/graphql/types/work_items/updated_task_input_type.rb
new file mode 100644
index 00000000000..9f8afa2ff1b
--- /dev/null
+++ b/app/graphql/types/work_items/updated_task_input_type.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class UpdatedTaskInputType < BaseInputObject
+ graphql_name 'WorkItemUpdatedTaskInput'
+
+ include Mutations::WorkItems::UpdateArguments
+ end
+ end
+end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 22e5436dc5c..5430575ace7 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -70,7 +70,7 @@ class AwardEmoji < ApplicationRecord
def expire_cache
awardable.try(:bump_updated_at)
- awardable.try(:expire_etag_cache)
+ awardable.expire_etag_cache if awardable.is_a?(Note)
awardable.try(:update_upvotes_count) if upvote?
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d4eb77ef6de..ce7bac3fd5f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -613,6 +613,11 @@ class Issue < ApplicationRecord
super || WorkItems::Type.default_by_type(issue_type)
end
+ def expire_etag_cache
+ key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
+ Gitlab::EtagCaching::Store.new.touch(key)
+ end
+
private
override :persist_pg_full_text_search_vector
@@ -643,11 +648,6 @@ class Issue < ApplicationRecord
!confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
end
- def expire_etag_cache
- key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
- Gitlab::EtagCaching::Store.new.touch(key)
- end
-
def could_not_move(exception)
# Symptom of running out of space - schedule rebalancing
Issues::RebalancingWorker.perform_async(nil, *project.self_or_root_group_ids)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 9c3e337c0c2..415a655467d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -210,7 +210,7 @@ class Namespace < ApplicationRecord
end
end
- def clean_path(path)
+ def clean_path(path, limited_to: Namespace.all)
path = path.dup
# Get the email username by removing everything after an `@` sign.
path.gsub!(/@.*\z/, "")
@@ -231,7 +231,7 @@ class Namespace < ApplicationRecord
path = "blank" if path.blank?
uniquify = Uniquify.new
- uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
+ uniquify.string(path) { |s| limited_to.find_by_path_or_name(s) }
end
def clean_name(value)
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 508e63f77d8..9419dacc16f 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -39,25 +39,19 @@
%hr
.form-group
- %h5.gl-mt-0
+ %h5.gl-mt-0.gl-mb-3
= _("Git strategy")
- %p
+ .gl-mb-3
= _("Choose which Git strategy to use when fetching the project.")
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'choose-the-default-git-strategy'), target: '_blank', rel: 'noopener noreferrer'
- .form-check
- = f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
- = f.label :build_allow_git_fetch_false, class: 'form-check-label' do
- %strong git clone
- %br
- %span
- = _("For each job, clone the repository.")
- .form-check
- = f.radio_button :build_allow_git_fetch, 'true', { class: 'form-check-input' }
- = f.label :build_allow_git_fetch_true, class: 'form-check-label' do
- %strong git fetch
- %br
- %span
- = html_escape(_("For each job, re-use the project workspace. If the workspace doesn't exist, use %{code_open}git clone%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
+ = f.gitlab_ui_radio_component :build_allow_git_fetch,
+ false,
+ "git clone",
+ help_text: _("For each job, clone the repository.")
+ = f.gitlab_ui_radio_component :build_allow_git_fetch,
+ true,
+ "git fetch",
+ help_text: html_escape(_("For each job, re-use the project workspace. If the workspace doesn't exist, use %{code_open}git clone%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
.form-group
= f.fields_for :ci_cd_settings_attributes, @project.ci_cd_settings do |form|
diff --git a/app/workers/container_registry/migration/enqueuer_worker.rb b/app/workers/container_registry/migration/enqueuer_worker.rb
index a0babb98e82..0013af20dd7 100644
--- a/app/workers/container_registry/migration/enqueuer_worker.rb
+++ b/app/workers/container_registry/migration/enqueuer_worker.rb
@@ -160,7 +160,7 @@ module ContainerRegistry
def next_aborted_repository
strong_memoize(:next_aborted_repository) do
- ContainerRepository.with_migration_state('import_aborted').take # rubocop:disable CodeReuse/ActiveRecord
+ ContainerRepository.with_migration_state('import_aborted').limit(2)[0] # rubocop:disable CodeReuse/ActiveRecord
end
end
diff --git a/config/feature_flags/development/group_projects_api_preload_groups.yml b/config/feature_flags/development/group_projects_api_preload_groups.yml
deleted file mode 100644
index dea472548ae..00000000000
--- a/config/feature_flags/development/group_projects_api_preload_groups.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: group_projects_api_preload_groups
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81838
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354372
-milestone: '14.9'
-type: development
-group: group::authentication and authorization
-default_enabled: false
diff --git a/db/docs/ci_freeze_periods.yml b/db/docs/ci_freeze_periods.yml
index 877e18acd9d..5c6e25ecc32 100644
--- a/db/docs/ci_freeze_periods.yml
+++ b/db/docs/ci_freeze_periods.yml
@@ -3,7 +3,7 @@ table_name: ci_freeze_periods
classes:
- Ci::FreezePeriod
feature_categories:
-- continuous_integration
-description: TODO
+- continuous_delivery
+description: https://docs.gitlab.com/ee/ci/environments/deployment_safety.html#prevent-deployments-during-deploy-freeze-windows
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29162
milestone: '13.0'
diff --git a/db/docs/ci_platform_metrics.yml b/db/docs/ci_platform_metrics.yml
index 26039b8a7c8..b96f613f3ac 100644
--- a/db/docs/ci_platform_metrics.yml
+++ b/db/docs/ci_platform_metrics.yml
@@ -4,6 +4,6 @@ classes:
- CiPlatformMetric
feature_categories:
- continuous_integration
-description: TODO
+description: Instrumentation for https://docs.gitlab.com/ee/ci/cloud_deployment/ecs/quick_start_guide.html
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40036
milestone: '13.4'
diff --git a/db/docs/ci_resource_groups.yml b/db/docs/ci_resource_groups.yml
index b64929a074c..716dea0b182 100644
--- a/db/docs/ci_resource_groups.yml
+++ b/db/docs/ci_resource_groups.yml
@@ -4,6 +4,6 @@ classes:
- Ci::ResourceGroup
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/resource_groups/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20950
milestone: '12.7'
diff --git a/db/docs/deploy_keys_projects.yml b/db/docs/deploy_keys_projects.yml
index 42d5c520db2..d308af56712 100644
--- a/db/docs/deploy_keys_projects.yml
+++ b/db/docs/deploy_keys_projects.yml
@@ -4,6 +4,6 @@ classes:
- DeployKeysProject
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/deploy_keys/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/a735ce2aa7da72242629a4452c33e7a1900fdd62
milestone: "<6.0"
diff --git a/db/docs/deploy_tokens.yml b/db/docs/deploy_tokens.yml
index b39ccdef9e8..320fc9e2ba8 100644
--- a/db/docs/deploy_tokens.yml
+++ b/db/docs/deploy_tokens.yml
@@ -4,6 +4,6 @@ classes:
- DeployToken
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/deploy_tokens/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/db18993f652425b72c4b854e18a002e0ec44b196
milestone: '10.7'
diff --git a/db/docs/deployment_approvals.yml b/db/docs/deployment_approvals.yml
index 3c04bff052a..1defeb8dbb5 100644
--- a/db/docs/deployment_approvals.yml
+++ b/db/docs/deployment_approvals.yml
@@ -3,7 +3,7 @@ table_name: deployment_approvals
classes:
- Deployments::Approval
feature_categories:
-- advanced_deployments
-description: TODO
+- continuous_delivery
+description: https://docs.gitlab.com/ee/ci/environments/deployment_approvals.html
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74932
milestone: '14.6'
diff --git a/db/docs/deployment_merge_requests.yml b/db/docs/deployment_merge_requests.yml
index 33bce15b5c8..9af247a03d8 100644
--- a/db/docs/deployment_merge_requests.yml
+++ b/db/docs/deployment_merge_requests.yml
@@ -3,7 +3,7 @@ table_name: deployment_merge_requests
classes:
- DeploymentMergeRequest
feature_categories:
-- advanced_deployments
-description: TODO
+- continuous_delivery
+description: https://docs.gitlab.com/ee/ci/environments/index.html#track-newly-included-merge-requests-per-deployment
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18755
milestone: '12.5'
diff --git a/db/docs/deployments.yml b/db/docs/deployments.yml
index c5e402fb15f..960e2c67a1e 100644
--- a/db/docs/deployments.yml
+++ b/db/docs/deployments.yml
@@ -4,6 +4,6 @@ classes:
- Deployment
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/environments/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/907c0e6796b69f9577c147dd489cf55748c749ac
milestone: '8.9'
diff --git a/db/docs/environments.yml b/db/docs/environments.yml
index eb27637f1d5..08165712766 100644
--- a/db/docs/environments.yml
+++ b/db/docs/environments.yml
@@ -4,6 +4,6 @@ classes:
- Environment
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/environments/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/907c0e6796b69f9577c147dd489cf55748c749ac
milestone: '8.9'
diff --git a/db/docs/evidences.yml b/db/docs/evidences.yml
index 6440681feec..ddfb42dd5a1 100644
--- a/db/docs/evidences.yml
+++ b/db/docs/evidences.yml
@@ -4,6 +4,6 @@ classes:
- Releases::Evidence
feature_categories:
- release_evidence
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/releases/#release-evidence
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17217
milestone: '12.4'
diff --git a/db/docs/feature_gates.yml b/db/docs/feature_gates.yml
index 5f405ed0f1a..19d74975c6e 100644
--- a/db/docs/feature_gates.yml
+++ b/db/docs/feature_gates.yml
@@ -5,6 +5,6 @@ classes:
- Flipper::Adapters::ActiveRecord::Gate
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/development/feature_flags/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/671284ba375109becbfa2a288032cdc7301b157b
milestone: '9.3'
diff --git a/db/docs/features.yml b/db/docs/features.yml
index 82598999b9e..f5628a17c19 100644
--- a/db/docs/features.yml
+++ b/db/docs/features.yml
@@ -5,6 +5,6 @@ classes:
- Flipper::Adapters::ActiveRecord::Feature
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/development/feature_flags/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/ee2d3de1a634611a1c660516c955be0d3000904b
milestone: '8.12'
diff --git a/db/docs/group_deploy_keys_groups.yml b/db/docs/group_deploy_keys_groups.yml
index c8c36cc16b4..3db288647f9 100644
--- a/db/docs/group_deploy_keys_groups.yml
+++ b/db/docs/group_deploy_keys_groups.yml
@@ -3,7 +3,7 @@ table_name: group_deploy_keys_groups
classes:
- GroupDeployKeysGroup
feature_categories:
-- advanced_deployments
-description: TODO
+- continuous_delivery
+description: https://docs.gitlab.com/ee/user/project/deploy_keys/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32901
milestone: '13.2'
diff --git a/db/docs/milestone_releases.yml b/db/docs/milestone_releases.yml
index a5c95b086dd..de2b6a9cfbc 100644
--- a/db/docs/milestone_releases.yml
+++ b/db/docs/milestone_releases.yml
@@ -4,6 +4,6 @@ classes:
- MilestoneRelease
feature_categories:
- release_orchestration
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/releases/#associate-milestones-with-a-release
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/a43ab8d6a430014e875deb3bff3fd8d8da256747
milestone: '12.3'
diff --git a/db/docs/operations_feature_flag_scopes.yml b/db/docs/operations_feature_flag_scopes.yml
index 05ff4882394..ac1665fb3a6 100644
--- a/db/docs/operations_feature_flag_scopes.yml
+++ b/db/docs/operations_feature_flag_scopes.yml
@@ -3,6 +3,6 @@ table_name: operations_feature_flag_scopes
classes: []
feature_categories:
- feature_flags
-description: TODO
+description: Deprecated in favor of `operations_scopes`. To be dropped.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9110
milestone: '11.8'
diff --git a/db/docs/operations_feature_flags_clients.yml b/db/docs/operations_feature_flags_clients.yml
index ab5f8e5597b..f8f04cadbb7 100644
--- a/db/docs/operations_feature_flags_clients.yml
+++ b/db/docs/operations_feature_flags_clients.yml
@@ -4,6 +4,6 @@ classes:
- Operations::FeatureFlagsClient
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7433
milestone: '11.4'
diff --git a/db/docs/operations_feature_flags_issues.yml b/db/docs/operations_feature_flags_issues.yml
index 660c8161a08..6b62629a38d 100644
--- a/db/docs/operations_feature_flags_issues.yml
+++ b/db/docs/operations_feature_flags_issues.yml
@@ -4,6 +4,6 @@ classes:
- FeatureFlagIssue
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html#feature-flag-related-issues
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32876
milestone: '13.1'
diff --git a/db/docs/operations_scopes.yml b/db/docs/operations_scopes.yml
index 12b8f5b740b..781b0a459ab 100644
--- a/db/docs/operations_scopes.yml
+++ b/db/docs/operations_scopes.yml
@@ -4,6 +4,6 @@ classes:
- Operations::FeatureFlags::Scope
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html#feature-flag-strategies
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24819
milestone: '12.8'
diff --git a/db/docs/operations_strategies.yml b/db/docs/operations_strategies.yml
index 8eb16d28e46..c21859e2de6 100644
--- a/db/docs/operations_strategies.yml
+++ b/db/docs/operations_strategies.yml
@@ -4,6 +4,6 @@ classes:
- Operations::FeatureFlags::Strategy
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html#feature-flag-strategies
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24819
milestone: '12.8'
diff --git a/db/docs/operations_strategies_user_lists.yml b/db/docs/operations_strategies_user_lists.yml
index d56950b877c..ec8062ab57c 100644
--- a/db/docs/operations_strategies_user_lists.yml
+++ b/db/docs/operations_strategies_user_lists.yml
@@ -4,6 +4,6 @@ classes:
- Operations::FeatureFlags::StrategyUserList
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html#user-list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30243
milestone: '13.0'
diff --git a/db/docs/operations_user_lists.yml b/db/docs/operations_user_lists.yml
index 68af1fae839..af1e091ee45 100644
--- a/db/docs/operations_user_lists.yml
+++ b/db/docs/operations_user_lists.yml
@@ -4,6 +4,6 @@ classes:
- Operations::FeatureFlags::UserList
feature_categories:
- feature_flags
-description: TODO
+description: https://docs.gitlab.com/ee/operations/feature_flags.html#user-list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28822
milestone: '13.0'
diff --git a/db/docs/project_deploy_tokens.yml b/db/docs/project_deploy_tokens.yml
index 6f46af5d2e9..12e565bf4de 100644
--- a/db/docs/project_deploy_tokens.yml
+++ b/db/docs/project_deploy_tokens.yml
@@ -3,7 +3,7 @@ table_name: project_deploy_tokens
classes:
- ProjectDeployToken
feature_categories:
-- advanced_deployments
-description: TODO
+- continuous_delivery
+description: https://docs.gitlab.com/ee/user/project/deploy_tokens/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/8315861c9a50675b4f4f4ca536f0da90f27994f3
milestone: '10.7'
diff --git a/db/docs/protected_environment_approval_rules.yml b/db/docs/protected_environment_approval_rules.yml
index ea7f0e1d05d..fe3d9d7ad08 100644
--- a/db/docs/protected_environment_approval_rules.yml
+++ b/db/docs/protected_environment_approval_rules.yml
@@ -4,6 +4,6 @@ classes:
- ProtectedEnvironments::ApprovalRule
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/environments/deployment_approvals.html#multiple-approval-rules
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82800
milestone: '14.10'
diff --git a/db/docs/protected_environment_deploy_access_levels.yml b/db/docs/protected_environment_deploy_access_levels.yml
index e8b10ba099e..cd3bba9171f 100644
--- a/db/docs/protected_environment_deploy_access_levels.yml
+++ b/db/docs/protected_environment_deploy_access_levels.yml
@@ -4,6 +4,6 @@ classes:
- ProtectedEnvironment::DeployAccessLevel
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/environments/protected_environments.html
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6672
milestone: '11.3'
diff --git a/db/docs/protected_environments.yml b/db/docs/protected_environments.yml
index 92b6cdcc1f7..6a0d18ee4b5 100644
--- a/db/docs/protected_environments.yml
+++ b/db/docs/protected_environments.yml
@@ -4,6 +4,6 @@ classes:
- ProtectedEnvironment
feature_categories:
- continuous_delivery
-description: TODO
+description: https://docs.gitlab.com/ee/ci/environments/protected_environments.html
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6672
milestone: '11.3'
diff --git a/db/docs/release_links.yml b/db/docs/release_links.yml
index b4fc4f4b043..03fa9e2bbbb 100644
--- a/db/docs/release_links.yml
+++ b/db/docs/release_links.yml
@@ -4,6 +4,6 @@ classes:
- Releases::Link
feature_categories:
- release_orchestration
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/releases/#links
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/66755c9ed506af9f51022a678ed26e5d31ee87ac
milestone: '11.7'
diff --git a/db/docs/releases.yml b/db/docs/releases.yml
index 0c496b7355f..da4fbfe830f 100644
--- a/db/docs/releases.yml
+++ b/db/docs/releases.yml
@@ -4,6 +4,6 @@ classes:
- Release
feature_categories:
- release_orchestration
-description: TODO
+description: https://docs.gitlab.com/ee/user/project/releases
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/1c4d1c3bd69a6f9ec43cce4ab59de4ba47f73229
milestone: '8.2'
diff --git a/db/docs/users_ops_dashboard_projects.yml b/db/docs/users_ops_dashboard_projects.yml
index ba3f07609e2..d8854d1db45 100644
--- a/db/docs/users_ops_dashboard_projects.yml
+++ b/db/docs/users_ops_dashboard_projects.yml
@@ -3,7 +3,7 @@ table_name: users_ops_dashboard_projects
classes:
- UsersOpsDashboardProject
feature_categories:
-- release_orchestration
-description: TODO
+- environment_management
+description: https://docs.gitlab.com/ee/user/operations_dashboard/
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7341
milestone: '11.5'
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 54b5f2dd92e..3c8e6732c6e 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5477,6 +5477,29 @@ Input type: `WorkItemUpdateInput`
| <a id="mutationworkitemupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationworkitemupdateworkitem"></a>`workItem` | [`WorkItem`](#workitem) | Updated work item. |
+### `Mutation.workItemUpdateTask`
+
+Updates a work item's task by Global ID. Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice.
+
+Input type: `WorkItemUpdateTaskInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationworkitemupdatetaskclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationworkitemupdatetaskid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
+| <a id="mutationworkitemupdatetasktaskdata"></a>`taskData` | [`WorkItemUpdatedTaskInput!`](#workitemupdatedtaskinput) | Arguments necessary to update a task. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationworkitemupdatetaskclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationworkitemupdatetaskerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationworkitemupdatetasktask"></a>`task` | [`WorkItem`](#workitem) | Updated task. |
+| <a id="mutationworkitemupdatetaskworkitem"></a>`workItem` | [`WorkItem`](#workitem) | Updated work item. |
+
## Connections
Some types in our schema are `Connection` types - they represent a paginated
@@ -21278,3 +21301,13 @@ A time-frame defined as a closed inclusive range of two dates.
| <a id="workitemdeletedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the task referenced in the work item's description. |
| <a id="workitemdeletedtaskinputlinenumberend"></a>`lineNumberEnd` | [`Int!`](#int) | Last line in the Markdown source that defines the list item task. |
| <a id="workitemdeletedtaskinputlinenumberstart"></a>`lineNumberStart` | [`Int!`](#int) | First line in the Markdown source that defines the list item task. |
+
+### `WorkItemUpdatedTaskInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemupdatedtaskinputid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
+| <a id="workitemupdatedtaskinputstateevent"></a>`stateEvent` | [`WorkItemStateEvent`](#workitemstateevent) | Close or reopen a work item. |
+| <a id="workitemupdatedtaskinputtitle"></a>`title` | [`String`](#string) | Title of the work item. |
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 342d843c306..42287ff84ce 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -142,7 +142,7 @@ To set a default description template for merge requests, either:
To set a default description template for issues, either:
-- [In GitLab 14.8 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78302), [create an issue template](#create-an-issue-template) named `Default.md` (case insensitive) [in GitLab 14.8 or higher]
+- [In GitLab 14.8 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78302), [create an issue template](#create-an-issue-template) named `Default.md` (case insensitive)
and save it in `.gitlab/issue_templates/`.
This [doesn't overwrite](#priority-of-default-description-templates) the default template if one has been set in the project settings.
- Users on GitLab Premium and higher: set the default template in project settings:
diff --git a/doc/user/project/integrations/zentao.md b/doc/user/project/integrations/zentao.md
index 32e07c0b6e9..0256c52e4a3 100644
--- a/doc/user/project/integrations/zentao.md
+++ b/doc/user/project/integrations/zentao.md
@@ -10,6 +10,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
[ZenTao](https://www.zentao.net/) is a web-based project management platform.
+The following versions of ZenTao are supported:
+
+- ZenTao 15.4
+- ZenTao Pro 10.2
+- ZenTao Biz 5.2
+- ZenTao Max 2.2
+
## Configure ZenTao
This integration requires a ZenTao API secret key.
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 4ff7096b5d9..a12fbbb9bb6 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -67,9 +67,10 @@ module API
end
get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups, urgency: :low do
namespace_path = params[:namespace]
+ existing_namespaces_within_the_parent = Namespace.without_project_namespaces.by_parent(params[:parent_id])
- exists = Namespace.without_project_namespaces.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists?
- suggestions = exists ? [Namespace.clean_path(namespace_path)] : []
+ exists = existing_namespaces_within_the_parent.filter_by_path(namespace_path).exists?
+ suggestions = exists ? [Namespace.clean_path(namespace_path, limited_to: existing_namespaces_within_the_parent)] : []
present :exists, exists
present :suggests, suggestions
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 35f555e16b5..fb782b49f02 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -45,8 +45,6 @@ module API
# For all projects except those in a user namespace, the `namespace`
# and `group` are identical. Preload the group when it's not a user namespace.
def preload_groups(projects_relation)
- return unless Feature.enabled?(:group_projects_api_preload_groups)
-
group_projects = projects_for_group_preload(projects_relation)
groups = group_projects.map(&:namespace)
diff --git a/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake b/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake
new file mode 100644
index 00000000000..50f3992c635
--- /dev/null
+++ b/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :db do
+ namespace :decomposition do
+ namespace :rollback do
+ SEQUENCE_NAME_MATCHER = /nextval\('([a-z_]+)'::regclass\)/.freeze
+ # These aren't used by anything so we can ignore these https://gitlab.com/gitlab-org/gitlab/-/issues/362984
+ EXCLUDED_SEQUENCES = %w[
+ ci_build_report_results_build_id_seq
+ ci_job_artifact_states_job_artifact_id_seq
+ ci_pipelines_config_pipeline_id_seq
+ ].freeze
+
+ desc 'Bump all the CI tables sequences on the Main Database'
+ task :bump_ci_sequences, [:increase_by] => :environment do |_t, args|
+ increase_by = args.increase_by.to_i
+ if increase_by < 1
+ puts 'Please specify a positive integer `increase_by` value'.color(:red)
+ puts 'Example: rake gitlab:db:decomposition:rollback:bump_ci_sequences[100000]'.color(:green)
+ exit 1
+ end
+
+ sequences_by_gitlab_schema(ApplicationRecord, :gitlab_ci).each do |sequence_name|
+ next if EXCLUDED_SEQUENCES.include?(sequence_name)
+
+ increment_sequence_by(ApplicationRecord.connection, sequence_name, increase_by)
+ end
+ end
+ end
+ end
+ end
+end
+
+# base_model is to choose which connection to use to query the tables
+# gitlab_schema, can be 'gitlab_main', 'gitlab_ci', 'gitlab_shared'
+def sequences_by_gitlab_schema(base_model, gitlab_schema)
+ tables = Gitlab::Database::GitlabSchema.tables_to_schema.select do |_table_name, schema_name|
+ schema_name == gitlab_schema
+ end.keys
+
+ models = tables.map do |table|
+ model = Class.new(base_model)
+ model.table_name = table
+ model
+ end
+
+ sequences = []
+ models.each do |model|
+ model.columns.each do |column|
+ match_result = column.default_function&.match(SEQUENCE_NAME_MATCHER)
+ next unless match_result
+
+ sequences << match_result[1]
+ end
+ end
+
+ sequences
+end
+
+# This method is going to increase the sequence next_value by:
+# - increment_by + 1 if the sequence has the attribute is_called = True (which is the common case)
+# - increment_by if the sequence has the attribute is_called = False (for example, a newly created sequence)
+# It uses ALTER SEQUENCE as a safety mechanism to avoid that no concurrent insertions
+# will cause conflicts on the sequence.
+# This is because ALTER SEQUENCE blocks concurrent nextval, currval, lastval, and setval calls.
+def increment_sequence_by(connection, sequence_name, increment_by)
+ connection.transaction do
+ # The first call is to make sure that the sequence's is_called value is set to `true`
+ # This guarantees that the next call to `nextval` will increase the sequence by `increment_by`
+ connection.select_value("SELECT nextval($1)", nil, [sequence_name])
+ connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY #{increment_by}")
+ connection.select_value("select nextval($1)", nil, [sequence_name])
+ connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY 1")
+ end
+end
diff --git a/spec/frontend/issuable/popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/issuable/popover/components/__snapshots__/mr_popover_spec.js.snap
index a03d8bf5bf4..a03d8bf5bf4 100644
--- a/spec/frontend/issuable/popover/__snapshots__/mr_popover_spec.js.snap
+++ b/spec/frontend/issuable/popover/components/__snapshots__/mr_popover_spec.js.snap
diff --git a/spec/frontend/issuable/popover/components/issue_popover_spec.js b/spec/frontend/issuable/popover/components/issue_popover_spec.js
new file mode 100644
index 00000000000..3e77e750f3a
--- /dev/null
+++ b/spec/frontend/issuable/popover/components/issue_popover_spec.js
@@ -0,0 +1,81 @@
+import { GlSkeletonLoader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import StatusBox from '~/issuable/components/status_box.vue';
+import IssuePopover from '~/issuable/popover/components/issue_popover.vue';
+import issueQuery from '~/issuable/popover/queries/issue.query.graphql';
+
+describe('Issue Popover', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const issueQueryResponse = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: '1',
+ issue: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/1',
+ createdAt: '2020-07-01T04:08:01Z',
+ state: 'opened',
+ title: 'Issue title',
+ },
+ },
+ },
+ };
+
+ const mountComponent = ({
+ queryResponse = jest.fn().mockResolvedValue(issueQueryResponse),
+ } = {}) => {
+ wrapper = shallowMount(IssuePopover, {
+ apolloProvider: createMockApollo([[issueQuery, queryResponse]]),
+ propsData: {
+ target: document.createElement('a'),
+ projectPath: 'foo/bar',
+ iid: '1',
+ cachedTitle: 'Cached title',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows skeleton-loader while apollo is loading', () => {
+ mountComponent();
+
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
+ });
+
+ describe('when loaded', () => {
+ beforeEach(() => {
+ mountComponent();
+ return waitForPromises();
+ });
+
+ it('shows status badge', () => {
+ expect(wrapper.findComponent(StatusBox).props()).toEqual({
+ issuableType: 'issue',
+ initialState: issueQueryResponse.data.project.issue.state,
+ });
+ });
+
+ it('shows opened time', () => {
+ expect(wrapper.text()).toContain('Opened 4 days ago');
+ });
+
+ it('shows title', () => {
+ expect(wrapper.find('h5').text()).toBe(issueQueryResponse.data.project.issue.title);
+ });
+
+ it('shows reference', () => {
+ expect(wrapper.text()).toContain('foo/bar#1');
+ });
+ });
+});
diff --git a/spec/frontend/issuable/popover/mr_popover_spec.js b/spec/frontend/issuable/popover/components/mr_popover_spec.js
index 653666b0395..e66c6e22947 100644
--- a/spec/frontend/issuable/popover/mr_popover_spec.js
+++ b/spec/frontend/issuable/popover/components/mr_popover_spec.js
@@ -11,8 +11,8 @@ describe('MR Popover', () => {
propsData: {
target: document.createElement('a'),
projectPath: 'foo/bar',
- mergeRequestIID: '1',
- mergeRequestTitle: 'MR Title',
+ iid: '1',
+ cachedTitle: 'MR Title',
},
mocks: {
$apollo: {
diff --git a/spec/frontend/issuable/popover/index_spec.js b/spec/frontend/issuable/popover/index_spec.js
index d782b2558cf..b1aa7f0f0b0 100644
--- a/spec/frontend/issuable/popover/index_spec.js
+++ b/spec/frontend/issuable/popover/index_spec.js
@@ -8,34 +8,41 @@ describe('initIssuablePopovers', () => {
let mr1;
let mr2;
let mr3;
+ let issue1;
beforeEach(() => {
setHTMLFixture(`
- <div id="one" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project">
+ <div id="one" class="gfm-merge_request" data-mr-title="title" data-iid="1" data-project-path="group/project" data-reference-type="merge_request">
MR1
</div>
- <div id="two" class="gfm-merge_request" title="title" data-iid="1" data-project-path="group/project">
+ <div id="two" class="gfm-merge_request" title="title" data-iid="1" data-project-path="group/project" data-reference-type="merge_request">
MR2
</div>
<div id="three" class="gfm-merge_request">
MR3
</div>
+ <div id="four" class="gfm-issue" title="title" data-iid="1" data-project-path="group/project" data-reference-type="issue">
+ MR3
+ </div>
`);
mr1 = document.querySelector('#one');
mr2 = document.querySelector('#two');
mr3 = document.querySelector('#three');
+ issue1 = document.querySelector('#four');
mr1.addEventListener = jest.fn();
mr2.addEventListener = jest.fn();
mr3.addEventListener = jest.fn();
+ issue1.addEventListener = jest.fn();
});
it('does not add the same event listener twice', () => {
- initIssuablePopovers([mr1, mr1, mr2]);
+ initIssuablePopovers([mr1, mr1, mr2, issue1]);
expect(mr1.addEventListener).toHaveBeenCalledTimes(1);
expect(mr2.addEventListener).toHaveBeenCalledTimes(1);
+ expect(issue1.addEventListener).toHaveBeenCalledTimes(1);
});
it('does not add listener if it does not have the necessary data attributes', () => {
diff --git a/spec/graphql/mutations/work_items/update_task_spec.rb b/spec/graphql/mutations/work_items/update_task_spec.rb
new file mode 100644
index 00000000000..cb93e97504a
--- /dev/null
+++ b/spec/graphql/mutations/work_items/update_task_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::WorkItems::UpdateTask do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+ let_it_be(:referenced_work_item, refind: true) { create(:work_item, project: project, title: 'REFERENCED') }
+ let_it_be(:parent_work_item) do
+ create(:work_item, project: project, description: "- [ ] #{referenced_work_item.to_reference}+")
+ end
+
+ let(:task_params) { { title: 'UPDATED' } }
+ let(:task_input) { { id: referenced_work_item.to_global_id }.merge(task_params) }
+ let(:input) { { id: parent_work_item.to_global_id, task_data: task_input } }
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ describe '#resolve' do
+ subject(:resolve) do
+ mutation.resolve(**input)
+ end
+
+ before do
+ stub_spam_services
+ end
+
+ context 'when user has sufficient permissions' do
+ let(:current_user) { developer }
+
+ it 'expires etag cache for parent work item' do
+ allow(WorkItem).to receive(:find).and_call_original
+ allow(WorkItem).to receive(:find).with(parent_work_item.id.to_s).and_return(parent_work_item)
+
+ expect(parent_work_item).to receive(:expire_etag_cache)
+
+ resolve
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_purging_stale_security_scans_spec.rb b/spec/migrations/schedule_purging_stale_security_scans_spec.rb
index b58ded6a4f6..b5a38634b58 100644
--- a/spec/migrations/schedule_purging_stale_security_scans_spec.rb
+++ b/spec/migrations/schedule_purging_stale_security_scans_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe SchedulePurgingStaleSecurityScans do
let_it_be(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a', status: 'success') }
let_it_be(:ci_build) { builds.create!(commit_id: pipeline.id, retried: false, type: 'Ci::Build') }
- let!(:security_scan_1) { security_scans.create!(build_id: ci_build.id, scan_type: 1, created_at: 91.days.ago) }
+ let!(:security_scan_1) { security_scans.create!(build_id: ci_build.id, scan_type: 1, created_at: 92.days.ago) }
let!(:security_scan_2) { security_scans.create!(build_id: ci_build.id, scan_type: 2, created_at: 91.days.ago) }
let(:com?) { false }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index c77c0a5504a..d665b92d80f 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1565,4 +1565,20 @@ RSpec.describe Issue do
expect(issue.escalation_status).to eq(escalation_status)
end
end
+
+ describe '#expire_etag_cache' do
+ let_it_be(:issue) { create(:issue) }
+
+ subject(:expire_cache) { issue.expire_etag_cache }
+
+ it 'touches the etag cache store' do
+ key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(issue.project, issue)
+
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |cache_store|
+ expect(cache_store).to receive(:touch).with(key)
+ end
+
+ expire_cache
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb
new file mode 100644
index 00000000000..32468a46ace
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Update a work item task' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+ let_it_be(:unauthorized_work_item) { create(:work_item) }
+ let_it_be(:referenced_work_item, refind: true) { create(:work_item, project: project, title: 'REFERENCED') }
+ let_it_be(:parent_work_item) do
+ create(:work_item, project: project, description: "- [ ] #{referenced_work_item.to_reference}+")
+ end
+
+ let(:task) { referenced_work_item }
+ let(:work_item) { parent_work_item }
+ let(:task_params) { { 'title' => 'UPDATED' } }
+ let(:task_input) { { 'id' => task.to_global_id.to_s }.merge(task_params) }
+ let(:input) { { 'id' => work_item.to_global_id.to_s, 'taskData' => task_input } }
+ let(:mutation) { graphql_mutation(:workItemUpdateTask, input) }
+ let(:mutation_response) { graphql_mutation_response(:work_item_update_task) }
+
+ context 'the user is not allowed to read a work item' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to update a work item' do
+ let(:current_user) { developer }
+
+ it 'updates the work item and invalidates markdown cache on the original work item' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ referenced_work_item.reload
+ end.to change(referenced_work_item, :title).from(referenced_work_item.title).to('UPDATED')
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response).to include(
+ 'workItem' => hash_including(
+ 'title' => work_item.title,
+ 'descriptionHtml' => a_string_including('UPDATED')
+ ),
+ 'task' => hash_including(
+ 'title' => 'UPDATED'
+ )
+ )
+ end
+
+ context 'when providing invalid task params' do
+ let(:task_params) { { 'title' => '' } }
+
+ it 'makes no changes to the DB and returns an error message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ task.reload
+ end.to not_change(task, :title).and(
+ not_change(work_item, :description_html)
+ )
+
+ expect(mutation_response['errors']).to contain_exactly("Title can't be blank")
+ end
+ end
+
+ context 'when user cannot update the task' do
+ let(:task) { unauthorized_work_item }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ it_behaves_like 'has spam protection' do
+ let(:mutation_class) { ::Mutations::WorkItems::UpdateTask }
+ end
+
+ context 'when the work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'does not update the task item and returns and error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ task.reload
+ end.to not_change(task, :title)
+
+ expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
+ end
+ end
+ end
+
+ context 'when user does not have permissions to update a work item' do
+ let(:current_user) { developer }
+ let(:work_item) { unauthorized_work_item }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index f1078c7cdd3..56f08249bdd 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -1186,25 +1186,6 @@ RSpec.describe API::Groups do
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(6)
end
-
- context 'when group_projects_api_preload_groups feature is disabled' do
- before do
- stub_feature_flags(group_projects_api_preload_groups: false)
- end
-
- it 'looks up the root ancestor multiple times' do
- expect(Namespace).to receive(:find_by).with(id: group1.id.to_s).once.and_call_original
- expect(Namespace).to receive(:find_by).with(id: group1.traversal_ids.first).at_least(:twice).and_call_original
- expect(Namespace).not_to receive(:joins).with(start_with('INNER JOIN (SELECT id, traversal_ids[1]'))
-
- get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(6)
- end
- end
end
context 'when include_ancestor_groups is true' do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 01dbf523071..09b87f41b82 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -325,6 +325,24 @@ RSpec.describe API::Namespaces do
expect(response.body).to eq(expected_json)
end
+ it 'ignores paths of groups present in other hierarchies when making suggestions' do
+ (1..2).to_a.each do |suffix|
+ create(:group, name: "mygroup#{suffix}", path: "mygroup#{suffix}", parent: namespace2)
+ end
+
+ create(:group, name: 'mygroup', path: 'mygroup', parent: namespace1)
+
+ get api("/namespaces/mygroup/exists", user), params: { parent_id: namespace1.id }
+
+ # if the paths of groups present in hierachies aren't ignored, the suggestion generated would have
+ # been `mygroup3`, just because groups with path `mygroup1` and `mygroup2` exists somewhere else.
+ # But there is no reason for those groups that exists elsewhere to cause a conflict because
+ # their hierarchies differ. Hence, the correct suggestion to be generated would be `mygroup1`
+ expected_json = { exists: true, suggests: ["mygroup1"] }.to_json
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(expected_json)
+ end
+
it 'ignores top-level namespaces when checking with parent_id' do
get api("/namespaces/#{namespace1.path}/exists", user), params: { parent_id: namespace1.id }
diff --git a/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb b/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb
new file mode 100644
index 00000000000..e0144dcbb1c
--- /dev/null
+++ b/spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:db:decomposition:rollback:bump_ci_sequences', :silence_stdout do
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/db/decomposition/rollback/bump_ci_sequences'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ let(:expected_error_message) do
+ <<-EOS.strip_heredoc
+ Please specify a positive integer `increase_by` value
+ Example: rake gitlab:db:decomposition:rollback:bump_ci_sequences[100000]
+ EOS
+ end
+
+ let(:main_sequence_name) { 'issues_id_seq' }
+ let(:ci_sequence_name) { 'ci_build_needs_id_seq' }
+ let(:ignored_ci_sequence_name) { 'ci_build_report_results_build_id_seq' }
+
+ # This is just to make sure that all of the sequences start with `is_called=True`
+ # which means that the next call to nextval() is going to increment the sequence.
+ # To give predictable test results.
+ before do
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ end
+
+ context 'when passing wrong argument' do
+ it 'will print an error message and exit when passing no argument' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences')
+ end.to raise_error(SystemExit) { |error| expect(error.status).to eq(1) }
+ .and output(expected_error_message).to_stdout
+ end
+
+ it 'will print an error message and exit when passing a non positive integer value' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '-5')
+ end.to raise_error(SystemExit) { |error| expect(error.status).to eq(1) }
+ .and output(expected_error_message).to_stdout
+ end
+ end
+
+ context 'when bumping the ci sequences' do
+ it 'changes ci sequences by the passed argument `increase_by` value on the main database' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(16) # the +1 is because the sequence has is_called = true
+ end
+
+ it 'will still increase the value of sequences that have is_called = False' do
+ # see `is_called`: https://www.postgresql.org/docs/12/functions-sequence.html
+ # choosing a new arbitrary value for the sequence
+ new_value = last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name) + 1000
+ ApplicationRecord.connection.select_value("select setval($1, $2, false)", nil, [ci_sequence_name, new_value])
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(15)
+ end
+
+ it 'resets the INCREMENT value of the sequences back to 1 for the following calls to nextval()' do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '15')
+ value_1 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ value_2 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ expect(value_2 - value_1).to eq(1)
+ end
+
+ it 'does not change the sequences on the gitlab_main tables' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '10')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, main_sequence_name)
+ }.by(0)
+ .and change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(11) # the +1 is because the sequence has is_called = true
+ end
+
+ it 'does not change the sequences on ci ignored sequences' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '20')
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, ignored_ci_sequence_name)
+ }.by(0)
+ end
+ end
+
+ context 'when multiple databases' do
+ before do
+ skip_if_multiple_databases_not_setup
+ end
+
+ it 'does not change ci sequences on the ci database' do
+ expect do
+ run_rake_task('gitlab:db:decomposition:rollback:bump_ci_sequences', '10')
+ end.to change {
+ last_value_of_sequence(Ci::ApplicationRecord.connection, ci_sequence_name)
+ }.by(0)
+ end
+ end
+end
+
+def last_value_of_sequence(connection, sequence_name)
+ connection.select_value("select last_value from #{sequence_name}")
+end