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
path: root/app
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 /app
parentf71f0f5307d836c4b0a7e501e4af799a536f3454 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-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
27 files changed, 331 insertions, 65 deletions
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