diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-25 15:08:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-25 15:08:10 +0300 |
commit | ba9892d3c122a4f437b4b38926fb75848deaf097 (patch) | |
tree | 428fa4e4d19ff38cc6ccdd2036e10f2333720fc2 | |
parent | f71f0f5307d836c4b0a7e501e4af799a536f3454 (diff) |
Add latest changes from gitlab-org/gitlab@master
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 |