Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-21 15:09:11 +00:00
parent 7f521d2781
commit 97a128c1d1
115 changed files with 1188 additions and 544 deletions

View File

@ -16,7 +16,7 @@ review-cleanup:
- install_gitlab_gem
- setup_gcloud
script:
- scripts/review_apps/automated_cleanup.rb --dry-run="${DRY_RUN}" || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL} - <https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#review-cleanup-job-failed|📗 RUNBOOK 📕>" warning "GitLab Bot" && exit 1);
- scripts/review_apps/automated_cleanup.rb --dry-run="${DRY_RUN:-false}" || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL} - <https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#review-cleanup-job-failed|📗 RUNBOOK 📕>" warning "GitLab Bot" && exit 1);
review-stop:
extends:

View File

@ -21,7 +21,7 @@ export function getProjects(query, options, callback = () => {}) {
defaults.membership = true;
}
if (gon.features.fullPathProjectSearch && query?.includes('/')) {
if (query?.includes('/')) {
defaults.search_namespaces = true;
}

View File

@ -275,7 +275,7 @@ export default {
<gl-loading-icon v-if="item.isLoading" size="lg" class="gl-mt-5" />
<span
v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3 gl-text-secondary"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3 gl-font-sm gl-text-secondary"
:class="{ 'gl-font-base': isEpicBoard }"
>
<work-item-type-icon

View File

@ -95,9 +95,12 @@ export default {
class="board-card-info-icon gl-mr-2"
name="calendar"
/>
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
<time
:class="{ 'text-danger': isPastDue }"
datetime="date"
class="gl-font-sm board-card-info-text"
>{{ body }}</time
>
</span>
<gl-tooltip :target="() => $refs.issueDueDate" :placement="tooltipPlacement">
<span class="bold">{{ __('Due date') }}</span>

View File

@ -38,7 +38,7 @@ export default {
<span>
<span ref="issueTimeEstimate" class="board-card-info gl-mr-3 gl-text-secondary gl-cursor-help">
<gl-icon name="hourglass" class="board-card-info-icon gl-mr-2" />
<time class="board-card-info-text">{{ timeEstimate }}</time>
<time class="gl-font-sm board-card-info-text">{{ timeEstimate }}</time>
</span>
<gl-tooltip
:target="() => $refs.issueTimeEstimate"

View File

@ -154,7 +154,7 @@ export default {
<div
ref="scrollRoot"
:class="{ 'tree-list-blobs': !renderTreeList || search }"
class="gl-flex-grow-1"
class="gl-flex-grow-1 mr-tree-list"
>
<recycle-scroller
v-if="flatFilteredTreeList.length"
@ -172,7 +172,7 @@ export default {
:hide-file-stats="hideFileStats"
:current-diff-file-id="currentDiffFileId"
:style="{ '--level': item.level }"
:class="{ 'tree-list-parent': item.tree.length }"
:class="{ 'tree-list-parent': item.level > 0 }"
class="gl-relative"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="(path) => scrollToFile({ path })"

View File

@ -111,13 +111,14 @@ export default {
<div class="item-title gl-display-flex gl-gap-3 gl-min-w-0">
<gl-icon
v-if="hasState"
:id="`iconElementXL-${itemId}`"
ref="iconElementXL"
:class="iconClasses"
:name="iconName"
:title="stateTitle"
:aria-label="state"
/>
<gl-tooltip :target="() => $refs.iconElementXL">
<gl-tooltip :target="`iconElementXL-${itemId}`">
<span v-safe-html="stateTitle"></span>
</gl-tooltip>
<gl-icon
@ -221,7 +222,7 @@ export default {
category="tertiary"
size="small"
:disabled="removeDisabled"
class="js-issue-item-remove-button"
class="js-issue-item-remove-button gl-mr-2"
data-qa-selector="remove_related_issue_button"
:title="__('Remove')"
:aria-label="__('Remove')"

View File

@ -71,6 +71,11 @@ export default {
header: s__('PerformanceBar|Elasticsearch calls'),
keys: ['request', 'body'],
},
{
metric: 'zkt',
header: s__('PerformanceBar|Zoekt calls'),
keys: ['request', 'body'],
},
{
metric: 'external-http',
title: 'external',
@ -127,6 +132,16 @@ export default {
memoryReportPath() {
return mergeUrlParams({ performance_bar: 'memory' }, window.location.href);
},
showZoekt() {
return document.body.dataset.page === 'search:show';
},
},
created() {
if (!this.showZoekt) {
this.$options.detailedMetrics = this.$options.detailedMetrics.filter(
(item) => item.metric !== 'zkt',
);
}
},
mounted() {
this.currentRequest = this.requestId;

View File

@ -1,6 +1,6 @@
<script>
import * as Sentry from '@sentry/browser';
import { GlSearchBoxByType } from '@gitlab/ui';
import { GlSearchBoxByType, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import searchUserProjectsAndGroups from '../graphql/queries/search_user_groups_and_projects.query.graphql';
@ -14,11 +14,12 @@ export default {
contextNavigation: s__('Navigation|Context navigation'),
switchTo: s__('Navigation|Switch to...'),
searchPlaceholder: s__('Navigation|Search for projects or groups'),
searchingLabel: s__('Navigation|Retrieving search results'),
searchError: s__('Navigation|There was an error fetching search results.'),
},
apollo: {
groupsAndProjects: {
query: searchUserProjectsAndGroups,
debounce: DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
manual: true,
variables() {
return {
@ -27,6 +28,7 @@ export default {
};
},
result(response) {
this.hasError = false;
try {
const {
data: {
@ -40,11 +42,11 @@ export default {
this.projects = formatContextSwitcherItems(projects);
this.groups = formatContextSwitcherItems(groups);
} catch (e) {
Sentry.captureException(e);
this.handleError(e);
}
},
error(e) {
Sentry.captureException(e);
this.handleError(e);
},
skip() {
return !this.searchString;
@ -53,6 +55,8 @@ export default {
},
components: {
GlSearchBoxByType,
GlLoadingIcon,
GlAlert,
NavItem,
ProjectsList,
GroupsList,
@ -85,18 +89,36 @@ export default {
searchString: '',
projects: [],
groups: [],
hasError: false,
};
},
computed: {
isSearch() {
return Boolean(this.searchString);
},
isSearching() {
return this.$apollo.queries.groupsAndProjects.loading;
},
},
created() {
if (this.currentContext.namespace) {
trackContextAccess(this.username, this.currentContext);
}
},
methods: {
/**
* This needs to be exposed publicly so that we can auto-focus the search input when the parent
* GlCollapse is shown.
*/
focusInput() {
this.$refs['search-box'].focusInput();
},
handleError(e) {
Sentry.captureException(e);
this.hasError = true;
},
},
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
};
</script>
@ -104,13 +126,24 @@ export default {
<div>
<div class="gl-p-1 gl-border-b gl-border-gray-50 gl-bg-white">
<gl-search-box-by-type
ref="search-box"
v-model="searchString"
class="context-switcher-search-box"
:placeholder="$options.i18n.searchPlaceholder"
:debounce="$options.DEFAULT_DEBOUNCE_AND_THROTTLE_MS"
borderless
/>
</div>
<nav :aria-label="$options.i18n.contextNavigation">
<gl-loading-icon
v-if="isSearching"
class="gl-mt-5"
size="md"
:label="$options.i18n.searchingLabel"
/>
<gl-alert v-else-if="hasError" variant="danger" :dismissible="false" class="gl-m-2">
{{ $options.i18n.searchError }}
</gl-alert>
<nav v-else :aria-label="$options.i18n.contextNavigation">
<ul class="gl-p-0 gl-list-style-none">
<li v-if="!isSearch">
<div aria-hidden="true" class="gl-font-weight-bold gl-px-3 gl-py-3">

View File

@ -44,7 +44,7 @@ export default {
v-if="context.icon"
class="gl-avatar avatar-container gl-bg-t-gray-a-08 icon-avatar rect-avatar s24 gl-mr-3 gl-ml-4"
>
<gl-icon :name="context.icon" :size="16" />
<gl-icon class="gl-text-gray-700" :name="context.icon" :size="16" />
</span>
<gl-avatar
v-else
@ -55,11 +55,11 @@ export default {
:src="context.avatar"
class="gl-mr-3 gl-ml-4"
/>
<div class="gl-overflow-auto">
<div class="gl-overflow-auto gl-text-gray-900">
<gl-truncate :text="context.title" />
</div>
<span class="gl-flex-grow-1 gl-text-right gl-mr-4">
<gl-icon :name="collapseIcon" />
<gl-icon class="gl-text-gray-400" :name="collapseIcon" />
</span>
</button>
</template>

View File

@ -31,7 +31,7 @@ export default {
},
data() {
return {
contextSwitcherOpened: false,
contextSwitcherOpen: false,
isCollapased: isCollapsed(),
};
},
@ -44,6 +44,9 @@ export default {
collapseSidebar() {
toggleSuperSidebarCollapsed(true, false);
},
onContextSwitcherShown() {
this.$refs['context-switcher'].focusInput();
},
},
};
</script>
@ -72,10 +75,15 @@ export default {
<div class="gl-flex-grow-1 gl-overflow-auto">
<context-switcher-toggle
:context="sidebarData.current_context_header"
:expanded="contextSwitcherOpened"
:expanded="contextSwitcherOpen"
/>
<gl-collapse id="context-switcher" v-model="contextSwitcherOpened">
<gl-collapse
id="context-switcher"
v-model="contextSwitcherOpen"
@shown="onContextSwitcherShown"
>
<context-switcher
ref="context-switcher"
:persistent-links="sidebarData.context_switcher_links"
:username="sidebarData.username"
:projects-path="sidebarData.projects_path"
@ -83,7 +91,7 @@ export default {
:current-context="sidebarData.current_context"
/>
</gl-collapse>
<gl-collapse :visible="!contextSwitcherOpened">
<gl-collapse :visible="!contextSwitcherOpen">
<sidebar-menu :items="menuItems" />
<sidebar-portal-target />
</gl-collapse>

View File

@ -484,18 +484,24 @@ span.idiff {
@include gl-display-none;
}
.tree-list-scroll:not(.tree-list-blobs) {
.mr-tree-list:not(.tree-list-blobs) {
.tree-list-parent::before {
@include gl-content-empty;
@include gl-absolute;
@include gl-z-index-1;
@include gl-pointer-events-none;
top: 28px;
left: calc(14px + (var(--level) * 16px));
width: 1px;
height: calc(100% - 24px);
background-color: var(--gray-100, $gray-100);
top: -4px;
left: 0;
width: 100%;
bottom: -4px;
// The virtual scroller has a flat HTML structure so instead of the ::before
// element stretching over multiple rows we instead create a repeating background image
// for the line
background: repeating-linear-gradient(to right, var(--gray-100, $gray-100), var(--gray-100, $gray-100) 1px, transparent 1px, transparent 14px);
background-size: calc(var(--level) * 14px);
background-repeat: no-repeat;
background-position: 14px;
}
}

View File

@ -107,6 +107,9 @@
&[aria-expanded='true'] {
background-color: $t-gray-a-08;
}
&:focus {
@include gl-focus($inset: true); }
}
.btn-with-notification {

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Mutations
module FindsNamespace
private
def find_object(full_path)
Routable.find_by_full_path(full_path)
end
end
end

View File

@ -6,13 +6,15 @@ module Mutations
graphql_name 'WorkItemCreate'
include Mutations::SpamProtection
include FindsProject
include FindsNamespace
include Mutations::WorkItems::Widgetable
description "Creates a work item."
authorize :create_work_item
MUTUALLY_EXCLUSIVE_ARGUMENTS_ERROR = 'Please provide either projectPath or namespacePath argument, but not both.'
argument :confidential, GraphQL::Types::Boolean,
required: false,
description: 'Sets the work item confidentiality.'
@ -25,9 +27,16 @@ module Mutations
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
required: false,
description: 'Input for milestone widget.'
argument :namespace_path, GraphQL::Types::ID,
required: false,
description: 'Full path of the namespace(project or group) the work item is created in.'
argument :project_path, GraphQL::Types::ID,
required: true,
description: 'Full path of the project the work item is associated with.'
required: false,
description: 'Full path of the project the work item is associated with.',
deprecated: {
reason: 'Please use namespace_path instead. That will cover for both projects and groups',
milestone: '15.10'
}
argument :title, GraphQL::Types::String,
required: true,
description: copy_field_description(Types::WorkItemType, :title)
@ -39,8 +48,17 @@ module Mutations
null: true,
description: 'Created work item.'
def resolve(project_path:, **attributes)
project = authorized_find!(project_path)
def ready?(**args)
if args.slice(:project_path, :namespace_path)&.length != 1
raise Gitlab::Graphql::Errors::ArgumentError, MUTUALLY_EXCLUSIVE_ARGUMENTS_ERROR
end
super
end
def resolve(project_path: nil, namespace_path: nil, **attributes)
container_path = project_path || namespace_path
container = authorized_find!(container_path)
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
@ -48,7 +66,7 @@ module Mutations
widget_params = extract_widget_params!(type, params)
create_result = ::WorkItems::CreateService.new(
container: project,
container: container,
current_user: current_user,
params: params,
spam_params: spam_params,

View File

@ -31,7 +31,7 @@ module Resolvers
def preloads
{
work_item_type: :work_item_type,
web_url: { project: { namespace: :route } },
web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
widgets: { work_item_type: :enabled_widget_definitions }
}
end

View File

@ -11,6 +11,7 @@ module Types
value 'local', description: 'Local include.', value: :local
value 'file', description: 'Project file include.', value: :file
value 'template', description: 'Template include.', value: :template
value 'component', description: 'Component include.', value: :component
end
end
end

View File

@ -27,7 +27,10 @@ module Types
GraphQL::Types::Int,
null: false,
description: 'Lock version of the work item. Incremented each time the work item is updated.'
field :project, Types::ProjectType, null: false,
field :namespace, Types::NamespaceType, null: true,
description: 'Namespace the work item belongs to.',
alpha: { milestone: '15.10' }
field :project, Types::ProjectType, null: true,
description: 'Project the work item belongs to.',
alpha: { milestone: '15.3' }
field :state, WorkItemStateEnum, null: false,

View File

@ -594,6 +594,10 @@ class Issue < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
def update_project_counter_caches
# TODO: Fix counter cache for issues in group
# TODO: see https://gitlab.com/gitlab-org/gitlab/-/work_items/393125?iid_path=true
return unless project
Projects::OpenIssuesCountService.new(project).refresh_cache
end
# rubocop: enable CodeReuse/ServiceClass
@ -688,6 +692,10 @@ class Issue < ApplicationRecord
end
def expire_etag_cache
# TODO: Fix this for the case when issues is created at group level
# TODO: https://gitlab.com/gitlab-org/gitlab/-/work_items/395814?iid_path=true
return unless project
key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
Gitlab::EtagCaching::Store.new.touch(key)
end
@ -702,6 +710,10 @@ class Issue < ApplicationRecord
::Gitlab::GlobalId.as_global_id(id, model_name: WorkItem.name)
end
def resource_parent
project || namespace
end
private
def due_date_after_start_date
@ -729,6 +741,10 @@ class Issue < ApplicationRecord
override :persist_pg_full_text_search_vector
def persist_pg_full_text_search_vector(search_vector)
# TODO: Fix search vector for issues at group level
# TODO: https://gitlab.com/gitlab-org/gitlab/-/work_items/393126?iid_path=true
return unless project
Issues::SearchData.upsert({ project_id: project_id, issue_id: id, search_vector: search_vector }, unique_by: %i(project_id issue_id))
end
@ -745,12 +761,14 @@ class Issue < ApplicationRecord
end
def record_create_action
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author, project: project)
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(
author: author, namespace: namespace.reset
)
end
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
project.public? && project.feature_available?(:issues, nil) &&
resource_parent.public? && resource_parent.feature_available?(:issues, nil) &&
!confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
end

View File

@ -11,6 +11,8 @@ module Namespaces
alias_attribute :namespace_id, :parent_id
has_one :project, foreign_key: :project_namespace_id, inverse_of: :project_namespace
delegate :execute_hooks, :execute_integrations, to: :project, allow_nil: true
def self.sti_name
'Project'
end

View File

@ -500,7 +500,7 @@ class Project < ApplicationRecord
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_member, :add_members, to: :team
delegate :add_member, :add_members, :member?, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
@ -3143,6 +3143,12 @@ class Project < ApplicationRecord
false
end
def crm_enabled?
return false unless group
group.crm_enabled?
end
private
def pages_unique_domain_enabled?

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true
class IssuablePolicy < BasePolicy
delegate { @subject.project }
delegate { subject_container }
condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) }
condition(:is_project_member) { @user && subject_container.member?(@user) }
condition(:can_read_issuable) { can?(:"read_#{@subject.to_ability_name}") }
desc "User is the assignee or author"
@ -57,6 +57,10 @@ class IssuablePolicy < BasePolicy
enable :read_issuable
enable :read_issuable_participables
end
def subject_container
@subject.project || @subject.try(:namespace)
end
end
IssuablePolicy.prepend_mod_with('IssuablePolicy')

View File

@ -14,8 +14,8 @@ class IssuePolicy < IssuablePolicy
desc "Project belongs to a group, crm is enabled and user can read contacts in the root group"
condition(:can_read_crm_contacts, scope: :subject) do
subject.project.group&.crm_enabled? &&
(@user&.can?(:read_crm_contact, @subject.project.root_ancestor) || @user&.support_bot?)
subject_container&.crm_enabled? &&
(@user&.can?(:read_crm_contact, subject_container.root_ancestor) || @user&.support_bot?)
end
desc "Issue is confidential"
@ -43,6 +43,7 @@ class IssuePolicy < IssuablePolicy
rule { confidential & ~can_read_confidential }.policy do
prevent(*create_read_update_admin_destroy(:issue))
prevent(*create_read_update_admin_destroy(:work_item))
prevent :read_issue_iid
end

View File

@ -17,5 +17,16 @@ module Namespaces
rule { can?(:reporter_access) }.policy do
enable :read_timelog_category
end
rule { can?(:guest_access) }.policy do
enable :create_work_item
enable :read_work_item
enable :read_issue
enable :read_namespace
end
rule { can?(:create_work_item) }.enable :create_task
end
end
Namespaces::GroupProjectNamespaceSharedPolicy.prepend_mod

View File

@ -16,7 +16,11 @@ module Issues
# rubocop: disable CodeReuse/ActiveRecord
def merge_request_to_resolve_discussions_of
strong_memoize(:merge_request_to_resolve_discussions_of) do
MergeRequestsFinder.new(current_user, project_id: project.id)
# sometimes this will be a Group, when work item is created at group level.
# Not sure if we will need to handle resolving an MR with an issue at group level?
next unless container.is_a?(Project)
MergeRequestsFinder.new(current_user, project_id: container.id)
.find_by(iid: merge_request_to_resolve_discussions_of_iid)
end
end

View File

@ -103,8 +103,8 @@ module Issues
def execute_hooks(issue, action = 'open', old_associations: {})
issue_data = Gitlab::Lazy.new { hook_data(issue, action, old_associations: old_associations) }
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_integrations(issue_data, hooks_scope)
issue.namespace.execute_hooks(issue_data, hooks_scope)
issue.namespace.execute_integrations(issue_data, hooks_scope)
execute_incident_hooks(issue, issue_data) if issue.incident?
end
@ -114,7 +114,7 @@ module Issues
def execute_incident_hooks(issue, issue_data)
issue_data[:object_kind] = 'incident'
issue_data[:event_type] = 'incident'
issue.project.execute_integrations(issue_data, :incident_hooks)
issue.namespace.execute_integrations(issue_data, :incident_hooks)
end
def update_project_counter_caches?(issue)

View File

@ -7,7 +7,16 @@ module Issues
def execute
filter_resolve_discussion_params
@issue = model_klass.new(issue_params.merge(project: project)).tap do |issue|
container_param = case container
when Project
{ project: project }
when Namespaces::ProjectNamespace
{ project: container.project }
else
{ namespace: container }
end
@issue = model_klass.new(issue_params.merge(container_param)).tap do |issue|
ensure_milestone_available(issue)
end
end
@ -91,9 +100,10 @@ module Issues
params[:work_item_type] = WorkItems::Type.find_by(id: params[:work_item_type_id]) if params[:work_item_type_id].present? # rubocop: disable CodeReuse/ActiveRecord
public_issue_params << :milestone_id if can?(current_user, :admin_issue, project)
public_issue_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type])
public_issue_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type)
public_issue_params << :milestone_id if can?(current_user, :admin_issue, container)
public_issue_params << :issue_type if create_issue_type_allowed?(container, params[:issue_type])
base_type = params[:work_item_type]&.base_type
public_issue_params << :work_item_type if create_issue_type_allowed?(container, base_type)
params.slice(*public_issue_params)
end

View File

@ -22,7 +22,7 @@ module Issues
end
def execute(skip_system_notes: false)
return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, @project)
return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, container)
@issue = @build_service.execute
# issue_type is set in BuildService, so we can delete it from params, in later phase
@ -60,7 +60,8 @@ module Issues
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
Issues::PlacementWorker.perform_async(nil, issue.project_id)
Onboarding::IssueCreatedWorker.perform_async(issue.project.namespace_id)
# issue.namespace_id can point to either a project through project namespace or a group.
Onboarding::IssueCreatedWorker.perform_async(issue.namespace_id)
end
end

View File

@ -20,12 +20,13 @@ module WorkItems
return
end
project = work_item.project
resource_group = work_item.project&.group || work_item.namespace
project_ids = [work_item.project&.id].compact
milestone = MilestonesFinder.new({
project_ids: [project.id],
group_ids: project.group&.self_and_ancestors&.select(:id),
ids: [params[:milestone_id]]
}).execute.first
project_ids: project_ids,
group_ids: resource_group&.self_and_ancestors&.select(:id),
ids: [params[:milestone_id]]
}).execute.first
if milestone
work_item.milestone = milestone

View File

@ -30,7 +30,8 @@
help_text: s_('GroupSettings|Group members are not notified if the group is mentioned.')
= render 'groups/settings/resource_access_token_creation', f: f, group: @group
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
- unless Feature.enabled?(:always_perform_delayed_deletion)
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
= render 'groups/settings/ip_restriction_registration_features_cta', f: f
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group

View File

@ -1,7 +1,7 @@
---
name: data_transfer_monitoring
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110211
rollout_issue_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/391682
milestone: '15.9'
type: development
group: group::source code

View File

@ -1,8 +1,8 @@
---
name: full_path_project_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108906
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388473
milestone: '15.9'
name: free_user_cap_clear_over_limit_notification_flags
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111312
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/398150
milestone: '15.11'
type: development
group: group::threat insights
default_enabled: true
group: group::acquisition
default_enabled: false

View File

@ -838,6 +838,9 @@ Gitlab.ee do
Settings.cron_jobs['free_user_cap_backfill_notification_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['free_user_cap_backfill_notification_jobs_worker']['cron'] ||= '*/5 * * * *'
Settings.cron_jobs['free_user_cap_backfill_notification_jobs_worker']['job_class'] = 'Namespaces::FreeUserCap::BackfillNotificationJobsWorker'
Settings.cron_jobs['free_user_cap_backfill_clear_notified_flag'] ||= Settingslogic.new({})
Settings.cron_jobs['free_user_cap_backfill_clear_notified_flag']['cron'] ||= '*/5 * * * *'
Settings.cron_jobs['free_user_cap_backfill_clear_notified_flag']['job_class'] ||= 'Namespaces::FreeUserCap::BackfillNotificationClearingJobsWorker'
Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects'] ||= Settingslogic.new({})
Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects']['cron'] ||= "30 5 * * 0"
Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects']['job_class'] = 'Projects::DisableLegacyOpenSourceLicenseForInactiveProjectsWorker'

View File

@ -15,6 +15,7 @@ Peek.into Peek::Views::ActiveRecord
Peek.into Peek::Views::Gitaly
Peek.into Peek::Views::RedisDetailed
Peek.into Peek::Views::Elasticsearch
Peek.into Peek::Views::Zoekt
Peek.into Peek::Views::Rugged
Peek.into Peek::Views::ExternalHttp
Peek.into Peek::Views::BulletDetailed if defined?(Bullet)

View File

@ -341,6 +341,8 @@
- 1
- - migrate_external_diffs
- 1
- - namespaces_free_user_cap_notification_clearing
- 1
- - namespaces_free_user_cap_over_limit_notification
- 1
- - namespaces_process_sync_events

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class PrepareCiBuildsConstraintsForListPartitioning < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_check_constraint(:ci_builds, 'partition_id = 100', 'partitioning_constraint', validate: false)
prepare_async_check_constraint_validation(:ci_builds, name: 'partitioning_constraint')
end
def down
unprepare_async_check_constraint_validation(:ci_builds, name: 'partitioning_constraint')
remove_check_constraint(:ci_builds, 'partitioning_constraint')
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class SyncSecurityPolicyRuleSchedulesThatMayHaveBeenDeletedByABug < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
class OrchestrationPolicyRuleSchedule < MigrationRecord
self.table_name = 'security_orchestration_policy_rule_schedules'
end
def up
return unless Gitlab.ee?
return unless sync_scan_policies_worker
OrchestrationPolicyRuleSchedule
.select(:security_orchestration_policy_configuration_id)
.distinct
.where(policy_index: 1..)
.pluck(:security_orchestration_policy_configuration_id)
.map { |policy_configuration_id| [policy_configuration_id] }
.then { |args_list| sync_scan_policies_worker.bulk_perform_async(args_list) }
end
def down
# no-op
end
private
def sync_scan_policies_worker
unless defined?(@sync_scan_policies_worker)
@sync_scan_policies_worker = 'Security::SyncScanPoliciesWorker'.safe_constantize
end
@sync_scan_policies_worker
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
class SwapCiRunnerMachineBuildsPrimaryKey < Gitlab::Database::Migration[2.1]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
TABLE_NAME = :p_ci_runner_machine_builds
def up
reorder_primary_key_columns([:build_id, :partition_id])
end
def down
reorder_primary_key_columns([:partition_id, :build_id])
end
private
def reorder_primary_key_columns(columns)
with_lock_retries(raise_on_exhaustion: true) do
partitions = Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME).to_a
partitions.each { |partition| drop_table partition.identifier }
execute <<~SQL
ALTER TABLE #{TABLE_NAME}
DROP CONSTRAINT p_ci_runner_machine_builds_pkey CASCADE;
ALTER TABLE #{TABLE_NAME}
ADD PRIMARY KEY (#{columns.join(', ')});
SQL
partitions.each do |partition|
connection.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS #{partition.identifier}
PARTITION OF #{partition.parent_identifier} #{partition.condition};
SQL
end
end
end
end

View File

@ -0,0 +1 @@
48fadefd7b9ba4383a2e69715e21f5f19d10cc524174a463299b49ae736e6d2f

View File

@ -0,0 +1 @@
e6bd8bddb33ee72a183da7381bc599b98db03fdf3de6bc20860fe3b119d5a6a2

View File

@ -0,0 +1 @@
65380616067b8dc968e5d200092ebbb58e6c8bc8967ac9ed84282c450aefe016

View File

@ -27226,7 +27226,7 @@ ALTER TABLE ONLY operations_user_lists
ADD CONSTRAINT operations_user_lists_pkey PRIMARY KEY (id);
ALTER TABLE ONLY p_ci_runner_machine_builds
ADD CONSTRAINT p_ci_runner_machine_builds_pkey PRIMARY KEY (partition_id, build_id);
ADD CONSTRAINT p_ci_runner_machine_builds_pkey PRIMARY KEY (build_id, partition_id);
ALTER TABLE ONLY packages_build_infos
ADD CONSTRAINT packages_build_infos_pkey PRIMARY KEY (id);
@ -27342,6 +27342,9 @@ ALTER TABLE ONLY pages_domain_acme_orders
ALTER TABLE ONLY pages_domains
ADD CONSTRAINT pages_domains_pkey PRIMARY KEY (id);
ALTER TABLE ci_builds
ADD CONSTRAINT partitioning_constraint CHECK ((partition_id = 100)) NOT VALID;
ALTER TABLE ONLY path_locks
ADD CONSTRAINT path_locks_pkey PRIMARY KEY (id);

View File

@ -6467,7 +6467,8 @@ Input type: `WorkItemCreateInput`
| <a id="mutationworkitemcreatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyCreateInput`](#workitemwidgethierarchycreateinput) | Input for hierarchy widget. |
| <a id="mutationworkitemcreateiterationwidget"></a>`iterationWidget` | [`WorkItemWidgetIterationInput`](#workitemwidgetiterationinput) | Iteration widget of the work item. |
| <a id="mutationworkitemcreatemilestonewidget"></a>`milestoneWidget` | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | Input for milestone widget. |
| <a id="mutationworkitemcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project the work item is associated with. |
| <a id="mutationworkitemcreatenamespacepath"></a>`namespacePath` | [`ID`](#id) | Full path of the namespace(project or group) the work item is created in. |
| <a id="mutationworkitemcreateprojectpath"></a>`projectPath` **{warning-solid}** | [`ID`](#id) | **Deprecated:** Please use namespace_path instead. That will cover for both projects and groups. Deprecated in 15.10. |
| <a id="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
@ -22205,7 +22206,8 @@ Represents vulnerability letter grades with associated projects.
| <a id="workitemid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
| <a id="workitemiid"></a>`iid` | [`ID!`](#id) | Internal ID of the work item. |
| <a id="workitemlockversion"></a>`lockVersion` | [`Int!`](#int) | Lock version of the work item. Incremented each time the work item is updated. |
| <a id="workitemproject"></a>`project` **{warning-solid}** | [`Project!`](#project) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Project the work item belongs to. |
| <a id="workitemnamespace"></a>`namespace` **{warning-solid}** | [`Namespace`](#namespace) | **Introduced** in 15.10. This feature is in Alpha. It can be changed or removed at any time. Namespace the work item belongs to. |
| <a id="workitemproject"></a>`project` **{warning-solid}** | [`Project`](#project) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Project the work item belongs to. |
| <a id="workitemstate"></a>`state` | [`WorkItemState!`](#workitemstate) | State of the work item. |
| <a id="workitemtitle"></a>`title` | [`String!`](#string) | Title of the work item. |
| <a id="workitemtitlehtml"></a>`titleHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `title`. |
@ -22676,6 +22678,7 @@ Include type.
| Value | Description |
| ----- | ----------- |
| <a id="ciconfigincludetypecomponent"></a>`component` | Component include. |
| <a id="ciconfigincludetypefile"></a>`file` | Project file include. |
| <a id="ciconfigincludetypelocal"></a>`local` | Local include. |
| <a id="ciconfigincludetyperemote"></a>`remote` | Remote include. |
@ -23415,6 +23418,7 @@ Issuable resource link type enum.
| Value | Description |
| ----- | ----------- |
| <a id="issuableresourcelinktypegeneral"></a>`general` | General link type. |
| <a id="issuableresourcelinktypepagerduty"></a>`pagerduty` | Pagerduty link type. |
| <a id="issuableresourcelinktypeslack"></a>`slack` | Slack link type. |
| <a id="issuableresourcelinktypezoom"></a>`zoom` | Zoom link type. |

View File

@ -631,13 +631,13 @@ Supported attributes:
| `assignees` | array | Assignees of the merge request. |
| `author` | object | User who created this merge request. |
| `blocking_discussions_resolved` | boolean | Indicates if all discussions are resolved only if all are required before merge request can be merged. |
| `changes_count` | string | Number of changes made on the merge request. |
| `changes_count` | string | Number of changes made on the merge request. Empty when the merge request is created, and populates asynchronously. See [Empty API Fields for new merge requests](#empty-api-fields-for-new-merge-requests).|
| `closed_at` | datetime | Timestamp of when the merge request was closed. |
| `closed_by` | object | User who closed this merge request. |
| `created_at` | datetime | Timestamp of when the merge request was created. |
| `description` | string | Description of the merge request. Contains Markdown rendered as HTML for caching. |
| `detailed_merge_status` | string | Detailed merge status of the merge request. Read [merge status](#merge-status) for a list of potential values. |
| `diff_refs` | object | References of the base SHA, the head SHA, and the start SHA for this merge request. Corresponds to the latest diff version of the merge request. Empty when the merge request is created, and populates asynchronously. See [Empty `diff_refs` for new merge requests](#empty-diff_refs-for-new-merge-requests). |
| `diff_refs` | object | References of the base SHA, the head SHA, and the start SHA for this merge request. Corresponds to the latest diff version of the merge request. Empty when the merge request is created, and populates asynchronously. See [Empty API fields for new merge requests](#empty-api-fields-for-new-merge-requests). |
| `discussion_locked` | boolean | Indicates if comments on the merge request are locked to members only. |
| `downvotes` | integer | Number of downvotes for the merge request. |
| `draft` | boolean | Indicates if the merge request is a draft. |
@ -2919,11 +2919,11 @@ To track which state was set, who did it, and when it happened, check out
## Troubleshooting
### Empty `diff_refs` for new merge requests
### Empty API fields for new merge requests
When a merge request is created, the `diff_refs` field is initially empty. This field
is populated asynchronously after the merge request is created. For more
information, see the issue
[`diff_refs` empty after merge request is created](https://gitlab.com/gitlab-org/gitlab/-/issues/386562),
When a merge request is created, the `diff_refs` and `changes_count` fields are
initially empty. These fields are populated asynchronously after the
merge request is created. For more information, see the issue
[Some merge request API fields (`diff_refs`, `changes_count`) empty after MR is created](https://gitlab.com/gitlab-org/gitlab/-/issues/386562),
and the [related discussion](https://forum.gitlab.com/t/diff-refs-empty-after-mr-is-created/78975)
in the GitLab forums.

View File

@ -100,28 +100,3 @@ In the graphs, there is a single line per service. In the previous example image
Sidekiq is not included in this dashboard. We're tracking this in
[epic 700](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/700).
### SLI detail
![Rails requests SLI detail](img/error_budget_detail_sli_detail.png)
The SLI details row shows a breakdown of a specific SLI based on the
labels present on the source metrics.
For example, in the previous image, the `rails_requests` SLI has an `endpoint_id` label.
We can show how much a certain endpoint was requested (RPS), and how much it contributed to the error
budget spend.
For Apdex we show the **Apdex Attribution** panel. The more prominent
color is the one that contributed most to the spend. To see the
top spending endpoint over the entire range, sort by the average.
For error ratio we show an error rate. To see which label contributed most to the spend, sort by the
average.
We don't have endpoint information available for Rails errors. This work is being planned in
[epic 663](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/663).
The number of series to be loaded in the SLI details graphs is very
high when compared to the other aggregations. Because of this, it's not possible to
load more than a few days' worth of data.

View File

@ -54,6 +54,7 @@ By default both administrators and anyone with the **Owner** role can delete a p
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) from default delayed project deletion in GitLab 15.1.
> - [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1.
> - [Disabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95495) in GitLab 15.3.
> - [Removed option to delete immediately](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) in GitLab 15.11 [with a flag](../../../administration/feature_flags.md) named `always_perform_delayed_deletion`. Disabled by default.
Instance-level protection against accidental deletion of groups and projects.
@ -82,6 +83,7 @@ To configure delayed project deletion:
1. On the left sidebar, select **Settings > General**.
1. Expand the **Visibility and access controls** section.
1. Scroll to:
- (GitLab 15.11 and later with `always_perform_delayed_deletion` feature flag enabled) **Deletion protection** and set the retention period to a value between `1` and `90`.
- (GitLab 15.1 and later) **Deletion protection** and select keep deleted groups and projects, and select a retention period.
- (GitLab 15.0 and earlier) **Default delayed project protection** and select **Enable delayed project deletion by
default for newly-created groups.** Then set a retention period in **Default deletion delay**.
@ -98,6 +100,10 @@ In GitLab 15.1, and later this setting is enforced on groups when disabled and i
Groups remain restorable if the retention period is `1` or more days.
In GitLab 15.1 and later, delayed group deletion can be enabled by setting **Deletion projection** to **Keep deleted**.
In GitLab 15.11 and later with the `always_perform_delayed_deletion` feature flag enabled:
- The **Keep deleted** option is removed.
- Delayed group deletion is the default.
### Override defaults and delete immediately

View File

@ -371,6 +371,7 @@ To transfer a group:
> - [Instance setting to enable by default added](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2.
> - [Instance setting is inherited and enforced when disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) in GitLab 15.1.
> - [User interface changed](https://gitlab.com/gitlab-org/gitlab/-/issues/352961) in GitLab 15.1.
> - [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) in GitLab 15.11 [with a flag](../../administration/feature_flags.md) named `always_perform_delayed_deletion`. Disabled by default.
[Delayed project deletion](../project/settings/index.md#delayed-project-deletion) is locked and disabled unless the instance-level settings for
[deletion protection](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) are enabled for either groups only or groups and projects.
@ -398,8 +399,10 @@ To enable delayed deletion of projects in a group:
- (GitLab 15.0 and earlier), **Enforce for all subgroups**.
1. Select **Save changes**.
NOTE:
In GitLab 13.11 and above the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md) inheritance can be overridden, unless enforced by an ancestor.
In GitLab 13.11 and later, the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md), inheritance can be overridden unless enforced by an ancestor.
In GitLab 15.11 with the `always_perform_delayed_deletion` feature flag enabled, this setting is removed
and all projects are deleted after the [retention period defined by the admin](../admin_area/settings/visibility_and_access_controls.md#retention-period).
## Disable email notifications

View File

@ -7,11 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Value stream analytics for groups **(PREMIUM)**
Value stream analytics measures the time it takes to go from an idea to production. It also helps businesses:
- Visualize their end-to-end DevSecOps workstreams.
- Identify and solve inefficiencies.
- Optimize their workstreams to deliver more value, faster.
Value stream analytics measures the time it takes to go from an idea to production.
A **value stream** is the entire work process that delivers value to customers. For example,
the [DevOps lifecycle](https://about.gitlab.com/stages-devops-lifecycle/) is a value stream that starts
@ -25,6 +21,12 @@ Use value stream analytics to identify:
- Long-running issues or merge requests.
- Factors that cause your software development lifecycle to slow down.
Value stream analytics helps businesses:
- Visualize their end-to-end DevSecOps workstreams.
- Identify and solve inefficiencies.
- Optimize their workstreams to deliver more value, faster.
Value stream analytics is also available for [projects](../../analytics/value_stream_analytics.md).
## How value stream analytics works

View File

@ -58,7 +58,8 @@ To do this:
1. On the top bar, select **Main menu > Projects** and find your project.
1. If you know the merge request that contains the commit:
1. On the left sidebar, select **Merge requests** and identify your merge request.
1. Select **Commits**, then select the title of the commit you want to revert. GitLab displays the contents of the commit.
1. Select **Commits**, then select the title of the commit you want to revert. This displays the commit in the **Changes** tab of your merge request.
1. Select the commit hash you want to revert. GitLab displays the contents of the commit.
1. If you don't know the merge request the commit originated from:
1. On the left sidebar, select **Repository > Commits**.
1. Select the title of the commit to display full information about the commit.

View File

@ -49,113 +49,114 @@ The following quick actions are applicable to descriptions, discussions, and
threads. Some quick actions might not be available to all subscription tiers.
<!-- Keep this table sorted alphabetically -->
<!-- Don't prettify this table; it will expand out the last field into illegibility -->
| Command | Issue | Merge request | Epic | Action |
|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `/add_contacts [contact:email1@example.com] [contact:email2@example.com]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more active [CRM contacts](../crm/index.md) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413) in GitLab 14.6). |
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. |
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users. |
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself. |
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users as reviewers. |
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. |
| `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award. |
| Command | Issue | Merge request | Epic | Action |
|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------|
| `/add_contacts [contact:email1@example.com] [contact:email2@example.com]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more active [CRM contacts](../crm/index.md) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413) in GitLab 14.6).
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request.
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users.
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself.
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users as reviewers.
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer.
| `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award.
| `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line created a specific type of to-do item notification. |
| `/child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7330) in GitLab 12.0). |
| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear [health status](issues/managing_issues.md#health-status) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7). |
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. |
| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument. |
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. |
| `/child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic.
| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear [health status](issues/managing_issues.md#health-status) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7).
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight.
| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument.
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close.
| `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Mark issue or epic as confidential. Support for epics [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213741) in GitLab 15.6. |
| `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. |
| `/copy_metadata <#issue>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another issue in the project. |
| `/create_merge_request <branch name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Create a new merge request starting from the current issue. |
| `/done` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mark to-do item as done. |
| `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project.
| `/copy_metadata <#issue>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another issue in the project.
| `/create_merge_request <branch name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Create a new merge request starting from the current issue.
| `/done` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mark to-do item as done.
| `/draft` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set the [draft status](merge_requests/drafts.md). Use for toggling the draft status ([deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92654) in GitLab 15.4.) |
| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. See [Chronic](https://gitlab.com/gitlab-org/ruby/gems/gitlab-chronic#examples) for more examples. |
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue. Mark as a duplicate of, and related to, issue `<#issue>`. |
| `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. |
| `/estimate <time>` or `/estimate_time <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. For more information, see [Time tracking](time_tracking.md). Alias `/estimate_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. |
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7). |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. |
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1). |
| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
| `/lock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Lock the discussions. |
| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. See [Chronic](https://gitlab.com/gitlab-org/ruby/gems/gitlab-chronic#examples) for more examples.
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue. Mark as a duplicate of, and related to, issue `<#issue>`.
| `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic.
| `/estimate <time>` or `/estimate_time <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. For more information, see [Time tracking](time_tracking.md). Alias `/estimate_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6.
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7).
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates.
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1).
| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported.
| `/lock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Lock the discussions.
| `/link` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a link and description to [linked resources](../../operations/incident_management/linked_resources.md) in an incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/374964) in GitLab 15.5). |
| `/merge` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Merge changes. Depending on the project setting, this may be [when the pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md), or adding to a [Merge Train](../../ci/pipelines/merge_trains.md). |
| `/milestone %milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set milestone. |
| `/move <path/to/project>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Move this issue to another project. Be careful when moving an issue to a project with different access rules. Before moving the issue, make sure it does not contain sensitive data. |
| `/parent_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10556) in GitLab 12.1). |
| `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic. |
| `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/296787) in GitLab 14.5). In [GitLab 15.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/376760), you can also use the quick action when creating a new issue. |
| `/page <policy name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Start escalations for the incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79977) in GitLab 14.9). |
| `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.0) |
| `/ready` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set the [ready status](merge_requests/drafts.md#mark-merge-requests-as-ready) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90361) in GitLab 15.1). |
| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Replace current assignees with those specified. |
| `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. |
| `/merge` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Merge changes. Depending on the project setting, this may be [when the pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md), or adding to a [Merge Train](../../ci/pipelines/merge_trains.md).
| `/milestone %milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set milestone.
| `/move <path/to/project>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Move this issue to another project. Be careful when moving an issue to a project with different access rules. Before moving the issue, make sure it does not contain sensitive data.
| `/parent_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic.
| `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic.
| `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/296787) in GitLab 14.5). In [GitLab 15.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/376760), you can also use the quick action when creating a new issue.
| `/page <policy name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Start escalations for the incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79977) in GitLab 14.9).
| `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.0)
| `/ready` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set the [ready status](merge_requests/drafts.md#mark-merge-requests-as-ready) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90361) in GitLab 15.1).
| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Replace current assignees with those specified.
| `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified.
| `/rebase` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Rebase source branch. This schedules a background task that attempts to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` is ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. If there are merge conflicts, GitLab displays a message that a rebase cannot be scheduled. Rebase failures are displayed with the merge request status. |
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. |
| `/relate #issue1 #issue2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark issues as related. |
| `/remove_child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7330) in GitLab 12.0). |
| `/remove_contacts [contact:email1@example.com] [contact:email2@example.com]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove one or more [CRM contacts](../crm/index.md) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413) in GitLab 14.6). |
| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove due date. |
| `/remove_epic` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove from epic. |
| `/remove_estimate` or `/remove_time_estimate` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time estimate. Alias `/remove_time_estimate` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. |
| `/remove_iteration` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove iteration ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1). |
| `/remove_milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove milestone. |
| `/remove_parent_epic` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove parent epic from epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10556) in GitLab 12.1). |
| `/remove_time_spent` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time spent. |
| `/remove_zoom` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove Zoom meeting from this issue. |
| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen. |
| `/severity <severity>` | **{check-circle}** Yes | **{check-circle}** No | **{check-circle}** No | Set the severity. Issue type must be `Incident`. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334045) in GitLab 14.2. |
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. |
| `/spend <time> [<date>]` or `/spend_time <time> [<date>]` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add or subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend 1mo 2w 3d 4h 5m 2018-08-26` or `/spend -1h 30m`. For more information, see [Time tracking](time_tracking.md). Alias `/spend_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. |
| `/submit_review` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Submit a pending review ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8041) in GitLab 12.7). |
| `/subscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Subscribe to notifications. |
| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. |
| `/target_branch <local branch name>` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch. |
| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. |
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified.
| `/relate #issue1 #issue2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark issues as related.
| `/remove_child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic.
| `/remove_contacts [contact:email1@example.com] [contact:email2@example.com]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove one or more [CRM contacts](../crm/index.md) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413) in GitLab 14.6).
| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove due date.
| `/remove_epic` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove from epic.
| `/remove_estimate` or `/remove_time_estimate` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time estimate. Alias `/remove_time_estimate` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6.
| `/remove_iteration` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove iteration ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1).
| `/remove_milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove milestone.
| `/remove_parent_epic` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove parent epic from epic.
| `/remove_time_spent` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time spent.
| `/remove_zoom` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove Zoom meeting from this issue.
| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen.
| `/severity <severity>` | **{check-circle}** Yes | **{check-circle}** No | **{check-circle}** No | Set the severity. Issue type must be `Incident`. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334045) in GitLab 14.2.
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`.
| `/spend <time> [<date>]` or `/spend_time <time> [<date>]` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add or subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend 1mo 2w 3d 4h 5m 2018-08-26` or `/spend -1h 30m`. For more information, see [Time tracking](time_tracking.md). Alias `/spend_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6.
| `/submit_review` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Submit a pending review.
| `/subscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Subscribe to notifications.
| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`.
| `/target_branch <local branch name>` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch.
| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title.
| `/timeline <timeline comment> \| <date(YYYY-MM-DD)> <time(HH:MM)>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a timeline event to this incident. For example, `/timeline DB load spiked \| 2022-09-07 09:30`. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368721) in GitLab 15.4). |
| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. |
| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) in GitLab 14.3 |
| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees. |
| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees. |
| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item.
| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) in GitLab 14.3.
| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees.
| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees.
| `/unassign_reviewer @user1 @user2` or `/remove_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific reviewers.
| `/unassign_reviewer me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove yourself as a reviewer. |
| `/unassign_reviewer` or `/remove_reviewer` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all reviewers. |
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels. |
| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels. |
| `/unlock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Unlock the discussions. |
| `/unsubscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Unsubscribe from notifications. |
| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, `2`, and so on. |
| `/zoom <Zoom URL>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a Zoom meeting to this issue or incident. In [GitLab 15.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/230853) users on GitLab Premium can add a short description when [adding a Zoom link to an incident](../../operations/incident_management/linked_resources.md#link-zoom-meetings-from-an-incident).|
| `/unassign_reviewer me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove yourself as a reviewer.
| `/unassign_reviewer` or `/remove_reviewer` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all reviewers.
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels.
| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels.
| `/unlock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Unlock the discussions.
| `/unsubscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Unsubscribe from notifications.
| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, `2`, and so on.
| `/zoom <Zoom URL>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a Zoom meeting to this issue or incident. In [GitLab 15.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/230853) users on GitLab Premium can add a short description when [adding a Zoom link to an incident](../../operations/incident_management/linked_resources.md#link-zoom-meetings-from-an-incident).
## Work items
The following quick actions can be applied through the description field when editing work items.
| Command | Task | Objective | Key Result | Action |
|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. |
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. |
| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen. |
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. |
| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. |
| `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line creates a specific type of to-do item notification. |
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign one or more users. |
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign yourself. |
| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove specific assignees. |
| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove all assignees. |
| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Replace current assignees with those specified. |
| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. |
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels. |
| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels. |
| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. |
| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Remove due date. |
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk`. |
| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Clear [health status](issues/managing_issues.md#health-status). |
| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, and `2`. |
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. |
| Command | Task | Objective | Key Result | Action
|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------|
| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title.
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close.
| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen.
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`.
| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`.
| `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line creates a specific type of to-do item notification.
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign one or more users.
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign yourself.
| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove specific assignees.
| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove all assignees.
| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Replace current assignees with those specified.
| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported.
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified.
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels.
| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels.
| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`.
| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Remove due date.
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk`.
| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Clear [health status](issues/managing_issues.md#health-status).
| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, and `2`.
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight.
## Commit messages

View File

@ -85,7 +85,8 @@ Prerequisites:
- Review the list of [items that are exported](#items-that-are-exported). Not all items are exported.
- You must have at least the Maintainer role for the project.
- Users must [set a public email](../../profile/index.md#set-your-public-email) in the source GitLab instance that matches one of their verified emails in the target GitLab instance for the user mapping to work correctly.
- For the user mapping to work correctly, users must [set a public email](../../profile/index.md#set-your-public-email)
in the source GitLab instance that matches their verified primary email in the target GitLab instance.
To export a project and its data, follow these steps:

View File

@ -95,12 +95,10 @@ To filter code search results by one or more languages:
## Search for projects by full path
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108906) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `full_path_project_search`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available.
To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `full_path_project_search`.
On GitLab.com, this feature is not available.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108906) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `full_path_project_search`. Disabled by default.
> - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/388473) on GitLab.com in GitLab 15.9.
> - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111808) on self-managed GitLab 15.10.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114932) in GitLab 15.11. Feature flag `full_path_project_search` removed.
You can search for a project by entering its full path (including the namespace it belongs to) in the search box.
As you type the project path, [autocomplete suggestions](#autocomplete-suggestions) are displayed.

View File

@ -9,14 +9,14 @@ module Gitlab
class Parser
# keys represent the trailing digit in color changing command (30-37, 40-47, 90-97. 100-107)
COLOR = {
0 => 'black', # not that this is gray in the intense color table
0 => 'black', # Note: This is gray in the intense color table.
1 => 'red',
2 => 'green',
3 => 'yellow',
4 => 'blue',
5 => 'magenta',
6 => 'cyan',
7 => 'white' # not that this is gray in the dark (aka default) color table
7 => 'white' # Note: This is gray in the dark (aka default) color table.
}.freeze
STYLE_SWITCHES = {

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaValidation
class Inconsistency
def initialize(validator_class, structure_sql_object, database_object)
@validator_class = validator_class
@structure_sql_object = structure_sql_object
@database_object = database_object
end
def error_message
format(validator_class::ERROR_MESSAGE, object_name)
end
def type
validator_class.name.demodulize.underscore
end
def object_name
structure_sql_object&.name || database_object&.name
end
def diff
Diffy::Diff.new(structure_sql_statement, database_statement)
end
def inspect
<<~MSG
#{'-' * 54}
#{error_message}
Diff:
#{diff.to_s(:color)}
#{'-' * 54}
MSG
end
private
attr_reader :validator_class, :structure_sql_object, :database_object
def structure_sql_statement
return unless structure_sql_object
"#{structure_sql_object.statement}\n"
end
def database_statement
return unless database_object
"#{database_object.statement}\n"
end
end
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaValidation
class Index
def initialize(parsed_stmt)
@parsed_stmt = parsed_stmt
end
def name
parsed_stmt.idxname
end
def statement
@statement ||= PgQuery.deparse_stmt(parsed_stmt)
end
private
attr_reader :parsed_stmt
end
end
end
end

View File

@ -5,7 +5,7 @@ module Gitlab
module SchemaValidation
module Validators
class BaseValidator
Inconsistency = Struct.new(:type, :object_name, :statement)
ERROR_MESSAGE = 'A schema inconsistency has been found'
def initialize(structure_sql, database)
@structure_sql = structure_sql
@ -31,10 +31,8 @@ module Gitlab
attr_reader :structure_sql, :database
def build_inconsistency(validator_class, schema_object)
inconsistency_type = validator_class.name.demodulize.underscore
Inconsistency.new(inconsistency_type, schema_object.name, schema_object.statement)
def build_inconsistency(validator_class, structure_sql_object, database_object)
Inconsistency.new(validator_class, structure_sql_object, database_object)
end
end
end

View File

@ -5,6 +5,8 @@ module Gitlab
module SchemaValidation
module Validators
class DifferentDefinitionIndexes < BaseValidator
ERROR_MESSAGE = "The %s index has a different statement between structure.sql and database"
def execute
structure_sql.indexes.filter_map do |structure_sql_index|
database_index = database.fetch_index_by_name(structure_sql_index.name)
@ -12,7 +14,7 @@ module Gitlab
next if database_index.nil?
next if database_index.statement == structure_sql_index.statement
build_inconsistency(self.class, structure_sql_index)
build_inconsistency(self.class, structure_sql_index, database_index)
end
end
end

View File

@ -5,6 +5,8 @@ module Gitlab
module SchemaValidation
module Validators
class DifferentDefinitionTriggers < BaseValidator
ERROR_MESSAGE = "The %s trigger has a different statement between structure.sql and database"
def execute
structure_sql.triggers.filter_map do |structure_sql_trigger|
database_trigger = database.fetch_trigger_by_name(structure_sql_trigger.name)
@ -12,7 +14,7 @@ module Gitlab
next if database_trigger.nil?
next if database_trigger.statement == structure_sql_trigger.statement
build_inconsistency(self.class, structure_sql_trigger)
build_inconsistency(self.class, structure_sql_trigger, nil)
end
end
end

View File

@ -5,11 +5,13 @@ module Gitlab
module SchemaValidation
module Validators
class ExtraIndexes < BaseValidator
def execute
database.indexes.filter_map do |index|
next if structure_sql.index_exists?(index.name)
ERROR_MESSAGE = "The index %s is present in the database, but not in the structure.sql file"
build_inconsistency(self.class, index)
def execute
database.indexes.filter_map do |database_index|
next if structure_sql.index_exists?(database_index.name)
build_inconsistency(self.class, nil, database_index)
end
end
end

View File

@ -5,11 +5,13 @@ module Gitlab
module SchemaValidation
module Validators
class ExtraTriggers < BaseValidator
def execute
database.triggers.filter_map do |trigger|
next if structure_sql.trigger_exists?(trigger.name)
ERROR_MESSAGE = "The trigger %s is present in the database, but not in the structure.sql file"
build_inconsistency(self.class, trigger)
def execute
database.triggers.filter_map do |database_trigger|
next if structure_sql.trigger_exists?(database_trigger.name)
build_inconsistency(self.class, nil, database_trigger)
end
end
end

View File

@ -5,11 +5,13 @@ module Gitlab
module SchemaValidation
module Validators
class MissingIndexes < BaseValidator
def execute
structure_sql.indexes.filter_map do |index|
next if database.index_exists?(index.name)
ERROR_MESSAGE = "The index %s is missing from the database"
build_inconsistency(self.class, index)
def execute
structure_sql.indexes.filter_map do |structure_sql_index|
next if database.index_exists?(structure_sql_index.name)
build_inconsistency(self.class, structure_sql_index, nil)
end
end
end

View File

@ -5,11 +5,13 @@ module Gitlab
module SchemaValidation
module Validators
class MissingTriggers < BaseValidator
def execute
structure_sql.triggers.filter_map do |index|
next if database.trigger_exists?(index.name)
ERROR_MESSAGE = "The trigger %s is missing from the database"
build_inconsistency(self.class, index)
def execute
structure_sql.triggers.filter_map do |structure_sql_trigger|
next if database.trigger_exists?(structure_sql_trigger.name)
build_inconsistency(self.class, structure_sql_trigger, nil)
end
end
end

View File

@ -65,7 +65,6 @@ module Gitlab
push_frontend_feature_flag(:security_auto_fix)
push_frontend_feature_flag(:source_editor_toolbar)
push_frontend_feature_flag(:vscode_web_ide, current_user)
push_frontend_feature_flag(:full_path_project_search, current_user)
end
# Exposes the state of a feature flag to the frontend code.

View File

@ -37,8 +37,8 @@ module Gitlab
ISSUE_DESIGN_COMMENT_REMOVED = 'g_project_management_issue_design_comments_removed'
class << self
def track_issue_created_action(author:, project:)
track_snowplow_action(ISSUE_CREATED, author, project)
def track_issue_created_action(author:, namespace:)
track_snowplow_action(ISSUE_CREATED, author, namespace)
track_unique_action(ISSUE_CREATED, author)
end
@ -179,7 +179,16 @@ module Gitlab
private
def track_snowplow_action(event_name, author, project)
def track_snowplow_action(event_name, author, container)
namespace, project = case container
when Project
[container.namespace, container]
when Namespaces::ProjectNamespace
[container.parent, container.project]
else
[container, nil]
end
return unless author
Gitlab::Tracking.event(
@ -188,7 +197,7 @@ module Gitlab
label: ISSUE_LABEL,
property: event_name,
project: project,
namespace: project.namespace,
namespace: namespace,
user: author,
context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context]
)

47
lib/peek/views/zoekt.rb Normal file
View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Peek
module Views
class Zoekt < DetailedView
DEFAULT_THRESHOLDS = {
calls: 3,
duration: 500,
individual_call: 500
}.freeze
THRESHOLDS = {
production: {
calls: 5,
duration: 1000,
individual_call: 1000
}
}.freeze
def key
'zkt'
end
def self.thresholds
@thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
end
private
def duration
::Gitlab::Instrumentation::Zoekt.query_time * 1000
end
def calls
::Gitlab::Instrumentation::Zoekt.get_request_count
end
def call_details
::Gitlab::Instrumentation::Zoekt.detail_store
end
def format_call_details(call)
super.merge(request: "#{call[:method]} #{call[:path]}?#{(call[:params] || {}).to_query}")
end
end
end
end

View File

@ -444,6 +444,23 @@ namespace :gitlab do
end
end
namespace :schema_checker do
desc 'Checks schema inconsistencies'
task run: :environment do
database_model = Gitlab::Database.database_base_models[Gitlab::Database::MAIN_DATABASE_NAME]
database = Gitlab::Database::SchemaValidation::Database.new(database_model.connection)
stucture_sql_path = Rails.root.join('db/structure.sql')
structure_sql = Gitlab::Database::SchemaValidation::StructureSql.new(stucture_sql_path)
inconsistencies = Gitlab::Database::SchemaValidation::Runner.new(structure_sql, database).execute
inconsistencies.each do |inconsistency|
puts inconsistency.inspect
end
end
end
namespace :dictionary do
DB_DOCS_PATH = File.join(Rails.root, 'db', 'docs')
EE_DICTIONARY_PATH = File.join(Rails.root, 'ee', 'db', 'docs')

View File

@ -28435,12 +28435,18 @@ msgstr ""
msgid "Navigation|Projects you visit often will appear here."
msgstr ""
msgid "Navigation|Retrieving search results"
msgstr ""
msgid "Navigation|Search for projects or groups"
msgstr ""
msgid "Navigation|Switch to..."
msgstr ""
msgid "Navigation|There was an error fetching search results."
msgstr ""
msgid "Navigation|View all groups"
msgstr ""
@ -31321,6 +31327,9 @@ msgstr ""
msgid "PerformanceBar|Trace"
msgstr ""
msgid "PerformanceBar|Zoekt calls"
msgstr ""
msgid "PerformanceBar|cpu"
msgstr ""

View File

@ -11,7 +11,8 @@ RSpec.describe 'Database schema', feature_category: :database do
IGNORED_INDEXES_ON_FKS = {
slack_integrations_scopes: %w[slack_api_scope_id],
p_ci_builds_metadata: %w[partition_id] # composable FK, the columns are reversed in the index definition
p_ci_builds_metadata: %w[partition_id], # composable FK, the columns are reversed in the index definition
p_ci_runner_machine_builds: %w[partition_id] # composable FK, the columns are reversed in the index definition
}.with_indifferent_access.freeze
TABLE_PARTITIONS = %w[ci_builds_metadata].freeze

View File

@ -9,14 +9,11 @@ describe('~/api/projects_api.js', () => {
let mock;
const projectId = 1;
const setfullPathProjectSearch = (value) => {
window.gon.features.fullPathProjectSearch = value;
};
beforeEach(() => {
mock = new MockAdapter(axios);
window.gon = { api_version: 'v7', features: { fullPathProjectSearch: true } };
window.gon = { api_version: 'v7' };
});
afterEach(() => {
@ -68,19 +65,6 @@ describe('~/api/projects_api.js', () => {
expect(data.data).toEqual(expectedProjects);
});
});
it('does not search namespaces if fullPathProjectSearch is disabled', () => {
setfullPathProjectSearch(false);
const expectedParams = { params: { per_page: 20, search: 'group/project1', simple: true } };
const query = 'group/project1';
mock.onGet(expectedUrl).reply(HTTP_STATUS_OK, { data: expectedProjects });
return projectsApi.getProjects(query, options).then(({ data }) => {
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedParams);
expect(data.data).toEqual(expectedProjects);
});
});
});
describe('importProjectMembers', () => {

View File

@ -37,7 +37,7 @@ describe('AdminRegisterRunnerApp', () => {
};
describe('When showing runner details', () => {
beforeEach(async () => {
beforeEach(() => {
createComponent();
});
@ -64,7 +64,7 @@ describe('AdminRegisterRunnerApp', () => {
});
describe('When another platform has been selected', () => {
beforeEach(async () => {
beforeEach(() => {
setWindowLocation(`?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`);
createComponent();

View File

@ -81,7 +81,7 @@ describe('AdminRunnerShowApp', () => {
await createComponent({ mountFn: mountExtended });
});
it('expect GraphQL ID to be requested', async () => {
it('expect GraphQL ID to be requested', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
@ -89,7 +89,7 @@ describe('AdminRunnerShowApp', () => {
expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId}`);
});
it('displays the runner edit and pause buttons', async () => {
it('displays the runner edit and pause buttons', () => {
expect(findRunnerEditButton().attributes('href')).toBe(mockRunner.editAdminUrl);
expect(findRunnerPauseButton().exists()).toBe(true);
expect(findRunnerDeleteButton().exists()).toBe(true);
@ -99,7 +99,7 @@ describe('AdminRunnerShowApp', () => {
expect(findRunnerDetailsTabs().props('runner')).toEqual(mockRunner);
});
it('shows basic runner details', async () => {
it('shows basic runner details', () => {
const expected = `Description My Runner
Last contact Never contacted
Version 1.0.0

View File

@ -273,7 +273,7 @@ describe('AdminRunnersApp', () => {
await createComponent({ mountFn: mountExtended });
});
it('Links to the runner page', async () => {
it('Links to the runner page', () => {
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').findComponent(GlLink);
expect(runnerLink.text()).toBe(`#${id} (${shortSha})`);
@ -292,7 +292,7 @@ describe('AdminRunnersApp', () => {
expect(badgeHref.hash).toBe(`#${JOBS_ROUTE_PATH}`);
});
it('When runner is paused or unpaused, some data is refetched', async () => {
it('When runner is paused or unpaused, some data is refetched', () => {
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
@ -301,7 +301,7 @@ describe('AdminRunnersApp', () => {
expect(showToast).toHaveBeenCalledTimes(0);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
it('When runner is deleted, data is refetched and a toast message is shown', () => {
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(showToast).toHaveBeenCalledTimes(1);
@ -410,7 +410,7 @@ describe('AdminRunnersApp', () => {
await createComponent({ mountFn: mountExtended });
});
it('count data is refetched', async () => {
it('count data is refetched', () => {
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerList().vm.$emit('deleted', { message: 'Runners deleted' });
@ -418,7 +418,7 @@ describe('AdminRunnersApp', () => {
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES * 2);
});
it('toast is shown', async () => {
it('toast is shown', () => {
expect(showToast).toHaveBeenCalledTimes(0);
findRunnerList().vm.$emit('deleted', { message: 'Runners deleted' });
@ -480,11 +480,11 @@ describe('AdminRunnersApp', () => {
await createComponent();
});
it('error is shown to the user', async () => {
it('error is shown to the user', () => {
expect(createAlert).toHaveBeenCalledTimes(1);
});
it('error is reported to sentry', async () => {
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
component: 'AdminRunnersApp',

View File

@ -84,7 +84,7 @@ describe('RegistrationInstructions', () => {
window.gon.gitlab_url = TEST_HOST;
});
it('loads runner with id', async () => {
it('loads runner with id', () => {
createComponent();
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunner.id });

View File

@ -82,7 +82,7 @@ describe('RunnerCreateForm', () => {
expect(findSubmitBtn().props('loading')).toBe(true);
});
it('saves runner', async () => {
it('saves runner', () => {
expect(runnerCreateHandler).toHaveBeenCalledWith({
input: {
...defaultRunnerModel,
@ -100,7 +100,7 @@ describe('RunnerCreateForm', () => {
await waitForPromises();
});
it('emits "saved" result', async () => {
it('emits "saved" result', () => {
expect(wrapper.emitted('saved')[0]).toEqual([mockCreatedRunner]);
});
@ -119,7 +119,7 @@ describe('RunnerCreateForm', () => {
await waitForPromises();
});
it('emits "error" result', async () => {
it('emits "error" result', () => {
expect(wrapper.emitted('error')[0]).toEqual([error]);
});
@ -154,7 +154,7 @@ describe('RunnerCreateForm', () => {
await waitForPromises();
});
it('emits "error" results', async () => {
it('emits "error" results', () => {
expect(wrapper.emitted('error')[0]).toEqual([new Error(`${errorMsg1} ${errorMsg2}`)]);
});

View File

@ -124,15 +124,15 @@ describe('RunnerDeleteButton', () => {
});
describe('Immediately after the delete button is clicked', () => {
beforeEach(async () => {
beforeEach(() => {
findModal().vm.$emit('primary');
});
it('The button has a loading state', async () => {
it('The button has a loading state', () => {
expect(findBtn().props('loading')).toBe(true);
});
it('The stale tooltip is removed', async () => {
it('The stale tooltip is removed', () => {
expect(getTooltip()).toBe('');
});
});
@ -255,15 +255,15 @@ describe('RunnerDeleteButton', () => {
});
describe('Immediately after the button is clicked', () => {
beforeEach(async () => {
beforeEach(() => {
findModal().vm.$emit('primary');
});
it('The button has a loading state', async () => {
it('The button has a loading state', () => {
expect(findBtn().props('loading')).toBe(true);
});
it('The stale tooltip is removed', async () => {
it('The stale tooltip is removed', () => {
expect(getTooltip()).toBe('');
});
});

View File

@ -164,7 +164,7 @@ describe('RunnerList', () => {
});
});
it('Emits a deleted event', async () => {
it('Emits a deleted event', () => {
const event = { message: 'Deleted!' };
findRunnerBulkDelete().vm.$emit('deleted', event);

View File

@ -134,7 +134,7 @@ describe('RunnerPauseButton', () => {
await clickAndWait();
});
it(`The mutation to that sets active to ${newActiveValue} is called`, async () => {
it(`The mutation to that sets active to ${newActiveValue} is called`, () => {
expect(runnerToggleActiveHandler).toHaveBeenCalledTimes(1);
expect(runnerToggleActiveHandler).toHaveBeenCalledWith({
input: {

View File

@ -84,7 +84,7 @@ describe('RunnerPlatformsRadioGroup', () => {
text | href
${'Docker'} | ${DOCKER_HELP_URL}
${'Kubernetes'} | ${KUBERNETES_HELP_URL}
`('provides link to "$text" docs', async ({ text, href }) => {
`('provides link to "$text" docs', ({ text, href }) => {
const radio = findFormRadioByText(text);
expect(radio.findComponent(GlLink).attributes()).toEqual({

View File

@ -41,7 +41,7 @@ describe('RunnerPlatformsRadio', () => {
expect(findFormRadio().attributes('value')).toBe(mockValue);
});
it('emits when item is clicked', async () => {
it('emits when item is clicked', () => {
findDiv().trigger('click');
expect(wrapper.emitted('input')).toEqual([[mockValue]]);
@ -94,7 +94,7 @@ describe('RunnerPlatformsRadio', () => {
expect(wrapper.classes('gl-cursor-pointer')).toBe(false);
});
it('does not emit when item is clicked', async () => {
it('does not emit when item is clicked', () => {
findDiv().trigger('click');
expect(wrapper.emitted('input')).toBe(undefined);

View File

@ -89,7 +89,7 @@ describe('RunnerProjects', () => {
await waitForPromises();
});
it('Shows a heading', async () => {
it('Shows a heading', () => {
const expected = sprintf(I18N_ASSIGNED_PROJECTS, { projectCount: mockProjects.length });
expect(findHeading().text()).toBe(expected);

View File

@ -67,7 +67,7 @@ describe('RunnerCount', () => {
expect(wrapper.html()).toBe(`<strong>${runnersCountData.data.runners.count}</strong>`);
});
it('does not fetch from the group query', async () => {
it('does not fetch from the group query', () => {
expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
});
@ -89,7 +89,7 @@ describe('RunnerCount', () => {
await createComponent({ props: { scope: INSTANCE_TYPE, skip: true } });
});
it('does not fetch data', async () => {
it('does not fetch data', () => {
expect(mockRunnersCountHandler).not.toHaveBeenCalled();
expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
@ -106,7 +106,7 @@ describe('RunnerCount', () => {
await createComponent({ props: { scope: INSTANCE_TYPE } });
});
it('data is not shown and error is reported', async () => {
it('data is not shown and error is reported', () => {
expect(wrapper.html()).toBe('<strong></strong>');
expect(captureException).toHaveBeenCalledWith({
@ -121,7 +121,7 @@ describe('RunnerCount', () => {
await createComponent({ props: { scope: GROUP_TYPE } });
});
it('fetches data from the group query', async () => {
it('fetches data from the group query', () => {
expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(1);
expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({});
@ -141,7 +141,7 @@ describe('RunnerCount', () => {
wrapper.vm.refetch();
});
it('data is not shown and error is reported', async () => {
it('data is not shown and error is reported', () => {
expect(mockRunnersCountHandler).toHaveBeenCalledTimes(2);
});
});

View File

@ -83,7 +83,7 @@ describe('GroupRunnerShowApp', () => {
await createComponent({ mountFn: mountExtended });
});
it('expect GraphQL ID to be requested', async () => {
it('expect GraphQL ID to be requested', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
@ -91,7 +91,7 @@ describe('GroupRunnerShowApp', () => {
expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId}`);
});
it('displays the runner edit and pause buttons', async () => {
it('displays the runner edit and pause buttons', () => {
expect(findRunnerEditButton().attributes('href')).toBe(mockEditGroupRunnerPath);
expect(findRunnerPauseButton().exists()).toBe(true);
expect(findRunnerDeleteButton().exists()).toBe(true);

View File

@ -287,7 +287,7 @@ describe('GroupRunnersApp', () => {
});
});
it('When runner is paused or unpaused, some data is refetched', async () => {
it('When runner is paused or unpaused, some data is refetched', () => {
expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
@ -299,7 +299,7 @@ describe('GroupRunnersApp', () => {
expect(showToast).toHaveBeenCalledTimes(0);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
it('When runner is deleted, data is refetched and a toast message is shown', () => {
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(showToast).toHaveBeenCalledTimes(1);
@ -416,7 +416,7 @@ describe('GroupRunnersApp', () => {
expect(createAlert).not.toHaveBeenCalled();
});
it('shows an empty state', async () => {
it('shows an empty state', () => {
expect(findRunnerListEmptyState().exists()).toBe(true);
});
});
@ -427,11 +427,11 @@ describe('GroupRunnersApp', () => {
await createComponent();
});
it('error is shown to the user', async () => {
it('error is shown to the user', () => {
expect(createAlert).toHaveBeenCalledTimes(1);
});
it('error is reported to sentry', async () => {
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
component: 'GroupRunnersApp',

View File

@ -6,7 +6,7 @@ jest.mock('@sentry/browser');
describe('~/ci/runner/sentry_utils', () => {
let mockSetTag;
beforeEach(async () => {
beforeEach(() => {
mockSetTag = jest.fn();
Sentry.withScope.mockImplementation((fn) => {

View File

@ -98,12 +98,6 @@ describe('ErrorTrackingList', () => {
});
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
describe('loading', () => {
beforeEach(() => {
store.state.list.loading = true;
@ -452,32 +446,34 @@ describe('ErrorTrackingList', () => {
describe('When pagination is required', () => {
describe('and previous cursor is not available', () => {
beforeEach(async () => {
beforeEach(() => {
store.state.list.loading = false;
delete store.state.list.pagination.previous;
mountComponent();
});
it('disables Prev button in the pagination', async () => {
it('disables Prev button in the pagination', () => {
expect(findPagination().props('prevPage')).toBe(null);
expect(findPagination().props('nextPage')).not.toBe(null);
});
});
describe('and next cursor is not available', () => {
beforeEach(async () => {
beforeEach(() => {
store.state.list.loading = false;
delete store.state.list.pagination.next;
mountComponent();
});
it('disables Next button in the pagination', async () => {
it('disables Next button in the pagination', () => {
expect(findPagination().props('prevPage')).not.toBe(null);
expect(findPagination().props('nextPage')).toBe(null);
});
});
describe('and the user is not on the first page', () => {
describe('and the previous button is clicked', () => {
beforeEach(async () => {
const currentPage = 2;
beforeEach(() => {
store.state.list.loading = false;
mountComponent({
stubs: {
@ -485,15 +481,12 @@ describe('ErrorTrackingList', () => {
GlPagination: false,
},
});
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ pageValue: 2 });
await nextTick();
findPagination().vm.$emit('input', currentPage);
});
it('fetches the previous page of results', () => {
expect(wrapper.find('.prev-page-item').attributes('aria-disabled')).toBe(undefined);
wrapper.vm.goToPrevPage();
findPagination().vm.$emit('input', currentPage - 1);
expect(actions.fetchPaginatedResults).toHaveBeenCalled();
expect(actions.fetchPaginatedResults).toHaveBeenLastCalledWith(
expect.anything(),

View File

@ -33,7 +33,6 @@ describe('Frequent Items App Component', () => {
const createComponent = (props = {}) => {
const session = currentSession[TEST_NAMESPACE];
gon.api_version = session.apiVersion;
gon.features = { fullPathProjectSearch: true };
wrapper = mountExtended(App, {
store,

View File

@ -25,7 +25,6 @@ describe('Frequent Items Dropdown Store Actions', () => {
mockedState.namespace = mockNamespace;
mockedState.storageKey = mockStorageKey;
gon.features = { fullPathProjectSearch: true };
});
afterEach(() => {

View File

@ -61,7 +61,6 @@ describe('ImportHistoryApp', () => {
beforeEach(() => {
gon.api_version = 'v4';
gon.features = { fullPathProjectSearch: true };
mock = new MockAdapter(axios);
});

View File

@ -1,6 +1,6 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlSearchBoxByType } from '@gitlab/ui';
import { GlSearchBoxByType, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@ -23,6 +23,7 @@ jest.mock('~/super_sidebar/utils', () => ({
.formatContextSwitcherItems,
trackContextAccess: jest.fn(),
}));
const focusInputMock = jest.fn();
const persistentLinks = [{ title: 'Explore', link: '/explore', icon: 'compass' }];
const username = 'root';
@ -39,6 +40,8 @@ describe('ContextSwitcher component', () => {
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findProjectsList = () => wrapper.findComponent(ProjectsList);
const findGroupsList = () => wrapper.findComponent(GroupsList);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
const triggerSearchQuery = async () => {
findSearchBox().vm.$emit('input', 'foo');
@ -72,6 +75,7 @@ describe('ContextSwitcher component', () => {
stubs: {
GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
props: ['placeholder'],
methods: { focusInput: focusInputMock },
}),
ProjectsList: stubComponent(ProjectsList, {
props: ['username', 'viewAllLink', 'isSearch', 'searchResults'],
@ -118,9 +122,22 @@ describe('ContextSwitcher component', () => {
});
});
it('focuses the search input when focusInput is called', () => {
wrapper.vm.focusInput();
expect(focusInputMock).toHaveBeenCalledTimes(1);
});
it('does not trigger the search query on mount', () => {
expect(searchUserProjectsAndGroupsHandlerSuccess).not.toHaveBeenCalled();
});
it('shows a loading spinner when search query is typed in', async () => {
findSearchBox().vm.$emit('input', 'foo');
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
});
});
describe('item access tracking', () => {
@ -156,6 +173,10 @@ describe('ContextSwitcher component', () => {
expect(searchUserProjectsAndGroupsHandlerSuccess).toHaveBeenCalled();
});
it('hides the loading spinner', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
it('passes the projects to the frequent projects list', () => {
expect(findProjectsList().props('isSearch')).toBe(true);
expect(findProjectsList().props('searchResults')).toEqual(
@ -206,7 +227,7 @@ describe('ContextSwitcher component', () => {
jest.spyOn(Sentry, 'captureException');
});
it('captures exception if response is formatted incorrectly', async () => {
it('captures exception and shows an alert if response is formatted incorrectly', async () => {
createWrapper({
requestHandlers: {
searchUserProjectsAndGroupsQueryHandler: jest.fn().mockResolvedValue({
@ -217,9 +238,10 @@ describe('ContextSwitcher component', () => {
await triggerSearchQuery();
expect(Sentry.captureException).toHaveBeenCalled();
expect(findAlert().exists()).toBe(true);
});
it('captures exception if query fails', async () => {
it('captures exception and shows an alert if query fails', async () => {
createWrapper({
requestHandlers: {
searchUserProjectsAndGroupsQueryHandler: jest.fn().mockRejectedValue(),
@ -228,6 +250,7 @@ describe('ContextSwitcher component', () => {
await triggerSearchQuery();
expect(Sentry.captureException).toHaveBeenCalled();
expect(findAlert().exists()).toBe(true);
});
});
});

View File

@ -1,14 +1,18 @@
import { GlCollapse } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue';
import HelpCenter from '~/super_sidebar/components/help_center.vue';
import UserBar from '~/super_sidebar/components/user_bar.vue';
import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
import ContextSwitcher from '~/super_sidebar/components/context_switcher.vue';
import { isCollapsed } from '~/super_sidebar/super_sidebar_collapsed_state_manager';
import { stubComponent } from 'helpers/stub_component';
import { sidebarData } from '../mock_data';
jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager', () => ({
isCollapsed: jest.fn(),
}));
const focusInputMock = jest.fn();
describe('SuperSidebar component', () => {
let wrapper;
@ -24,6 +28,11 @@ describe('SuperSidebar component', () => {
sidebarData,
...props,
},
stubs: {
ContextSwitcher: stubComponent(ContextSwitcher, {
methods: { focusInput: focusInputMock },
}),
},
});
};
@ -56,5 +65,21 @@ describe('SuperSidebar component', () => {
createWrapper();
expect(findSidebarPortalTarget().exists()).toBe(true);
});
it("does not call the context switcher's focusInput method initially", () => {
expect(focusInputMock).not.toHaveBeenCalled();
});
});
describe('when opening the context switcher', () => {
beforeEach(() => {
createWrapper();
wrapper.findComponent(GlCollapse).vm.$emit('input', true);
wrapper.findComponent(GlCollapse).vm.$emit('shown');
});
it("calls the context switcher's focusInput method", () => {
expect(focusInputMock).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['CiConfigIncludeType'] do
it { expect(described_class.graphql_name).to eq('CiConfigIncludeType') }
it 'exposes all the existing include types' do
expect(described_class.values.keys).to match_array(%w[remote local file template])
expect(described_class.values.keys).to match_array(%w[remote local file template component])
end
end

View File

@ -18,6 +18,7 @@ RSpec.describe GitlabSchema.types['WorkItem'] do
id
iid
lock_version
namespace
project
state title
title_html

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaValidation::Inconsistency, feature_category: :database do
let(:validator) { Gitlab::Database::SchemaValidation::Validators::DifferentDefinitionIndexes }
let(:database_statement) { 'CREATE INDEX index_name ON public.achievements USING btree (namespace_id)' }
let(:structure_sql_statement) { 'CREATE INDEX index_name ON public.achievements USING btree (id)' }
let(:structure_stmt) { PgQuery.parse(structure_sql_statement).tree.stmts.first.stmt.index_stmt }
let(:database_stmt) { PgQuery.parse(database_statement).tree.stmts.first.stmt.index_stmt }
let(:structure_sql_object) { Gitlab::Database::SchemaValidation::Index.new(structure_stmt) }
let(:database_object) { Gitlab::Database::SchemaValidation::Index.new(database_stmt) }
subject(:inconsistency) { described_class.new(validator, structure_sql_object, database_object) }
describe '#object_name' do
it 'returns the index name' do
expect(inconsistency.object_name).to eq('index_name')
end
end
describe '#diff' do
it 'returns a diff between the structure.sql and the database' do
expect(inconsistency.diff).to be_a(Diffy::Diff)
expect(inconsistency.diff.string1).to eq("#{structure_sql_statement}\n")
expect(inconsistency.diff.string2).to eq("#{database_statement}\n")
end
end
describe '#error_message' do
it 'returns the error message' do
stub_const "#{validator}::ERROR_MESSAGE", 'error message %s'
expect(inconsistency.error_message).to eq('error message index_name')
end
end
describe '#type' do
it 'returns the type of the validator' do
expect(inconsistency.type).to eq('different_definition_indexes')
end
end
describe '#inspect' do
let(:expected_output) do
<<~MSG
------------------------------------------------------
The index_name index has a different statement between structure.sql and database
Diff:
\e[31m-CREATE INDEX index_name ON public.achievements USING btree (id)\e[0m
\e[32m+CREATE INDEX index_name ON public.achievements USING btree (namespace_id)\e[0m
------------------------------------------------------
MSG
end
it 'prints the inconsistency message' do
expect(inconsistency.inspect).to eql(expected_output)
end
end
end

View File

@ -28,7 +28,7 @@ RSpec.describe Gitlab::Database::SchemaValidation::Runner, feature_category: :da
subject(:inconsistencies) { described_class.new(structure_sql, database, validators: validators).execute }
let(:class_name) { 'Gitlab::Database::SchemaValidation::Validators::ExtraIndexes' }
let(:inconsistency_class_name) { 'Gitlab::Database::SchemaValidation::Validators::BaseValidator::Inconsistency' }
let(:inconsistency_class_name) { 'Gitlab::Database::SchemaValidation::Inconsistency' }
let(:extra_indexes) { class_double(class_name) }
let(:instace_extra_index) { instance_double(class_name, execute: [inconsistency]) }

View File

@ -13,7 +13,7 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::IndexInconsistenciesMet
end
let(:runner) { instance_double(Gitlab::Database::SchemaValidation::Runner, execute: inconsistencies) }
let(:inconsistency_class) { Gitlab::Database::SchemaValidation::Validators::BaseValidator::Inconsistency }
let(:inconsistency_class) { Gitlab::Database::SchemaValidation::Inconsistency }
let(:inconsistencies) do
[

View File

@ -6,11 +6,12 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
let_it_be(:user1) { build(:user, id: 1) }
let_it_be(:user2) { build(:user, id: 2) }
let_it_be(:user3) { build(:user, id: 3) }
let_it_be(:project) { build(:project) }
let_it_be(:project) { create(:project) }
let_it_be(:category) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_CATEGORY }
let_it_be(:event_action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION }
let_it_be(:event_label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL }
let(:original_params) { nil }
let(:event_property) { action }
let(:time) { Time.zone.now }
@ -67,6 +68,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue created actions' do
it_behaves_like 'daily tracked issuable snowplow and service ping events with project' do
let(:action) { described_class::ISSUE_CREATED }
let(:original_params) { { namespace: project.project_namespace.reload } }
def track_action(params)
described_class.track_issue_created_action(**params)

View File

@ -581,7 +581,7 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
end
end
describe 'metadata partitioning', :ci_partitioning do
describe 'metadata partitioning', :ci_partitionable do
let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) }
let(:bridge) do

View File

@ -74,7 +74,7 @@ RSpec.describe Ci::BuildNeed, model: true, feature_category: :continuous_integra
stub_current_partition_id
end
it 'creates build needs successfully', :aggregate_failures do
it 'creates build needs successfully', :aggregate_failures, :ci_partitionable do
ci_build.needs_attributes = [
{ name: "build", artifacts: true },
{ name: "build2", artifacts: true },

View File

@ -175,7 +175,7 @@ RSpec.describe Ci::BuildRunnerSession, model: true, feature_category: :continuou
end
end
describe 'partitioning' do
describe 'partitioning', :ci_partitionable do
include Ci::PartitioningHelpers
let(:new_pipeline) { create(:ci_pipeline) }

View File

@ -5781,7 +5781,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
describe 'metadata partitioning', :ci_partitioning do
describe 'metadata partitioning', :ci_partitionable do
let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) }
let(:build) do

Some files were not shown because too many files have changed in this diff Show More