Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-25 12:08:10 +00:00
parent f71f0f5307
commit ba9892d3c1
74 changed files with 867 additions and 138 deletions

View file

@ -1 +1 @@
47335e9fa13b0185b9d479a9e8bffc535eed6a7c
ac989862106589558866e01fc5d77ad7326c99e4

View file

@ -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 }) => {

View file

@ -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">

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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);
}

View file

@ -0,0 +1,11 @@
query issue($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
id
issue(iid: $iid) {
id
title
createdAt
state
}
}
}

View file

@ -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

View file

@ -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"
>

View file

@ -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);
}

View file

@ -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"

View file

@ -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;
}

View file

@ -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,

View file

@ -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%;
}
}

View file

@ -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,

View file

@ -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,

View file

@ -33,6 +33,14 @@ body {
&.active > button {
color: $white;
}
> a,
> button {
&:active,
&:focus {
@include gl-focus;
}
}
}
}

View file

@ -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

View file

@ -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.'

View 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

View file

@ -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

View 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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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|

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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"

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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. |

View file

@ -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:

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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');
});
});
});

View file

@ -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: {

View file

@ -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', () => {

View 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

View file

@ -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 }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 }

View file

@ -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