Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f71f0f5307
commit
ba9892d3c1
74 changed files with 867 additions and 138 deletions
|
@ -1 +1 @@
|
|||
47335e9fa13b0185b9d479a9e8bffc535eed6a7c
|
||||
ac989862106589558866e01fc5d77ad7326c99e4
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
query issue($projectPath: ID!, $iid: String!) {
|
||||
project(fullPath: $projectPath) {
|
||||
id
|
||||
issue(iid: $iid) {
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -33,6 +33,14 @@ body {
|
|||
&.active > button {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
> a,
|
||||
> button {
|
||||
&:active,
|
||||
&:focus {
|
||||
@include gl-focus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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.'
|
||||
|
|
78
app/graphql/mutations/work_items/update_task.rb
Normal file
78
app/graphql/mutations/work_items/update_task.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
11
app/graphql/types/work_items/updated_task_input_type.rb
Normal file
11
app/graphql/types/work_items/updated_task_input_type.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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: {
|
|
@ -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', () => {
|
||||
|
|
40
spec/graphql/mutations/work_items/update_task_spec.rb
Normal file
40
spec/graphql/mutations/work_items/update_task_spec.rb
Normal file
|
@ -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
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue