Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7f521d2781
commit
97a128c1d1
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 })"
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,9 @@
|
|||
&[aria-expanded='true'] {
|
||||
background-color: $t-gray-a-08;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include gl-focus($inset: true); }
|
||||
}
|
||||
|
||||
.btn-with-notification {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
48fadefd7b9ba4383a2e69715e21f5f19d10cc524174a463299b49ae736e6d2f
|
||||
|
|
@ -0,0 +1 @@
|
|||
e6bd8bddb33ee72a183da7381bc599b98db03fdf3de6bc20860fe3b119d5a6a2
|
||||
|
|
@ -0,0 +1 @@
|
|||
65380616067b8dc968e5d200092ebbb58e6c8bc8967ac9ed84282c450aefe016
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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}`)]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ describe('Frequent Items Dropdown Store Actions', () => {
|
|||
|
||||
mockedState.namespace = mockNamespace;
|
||||
mockedState.storageKey = mockStorageKey;
|
||||
gon.features = { fullPathProjectSearch: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ describe('ImportHistoryApp', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
gon.api_version = 'v4';
|
||||
gon.features = { fullPathProjectSearch: true };
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ RSpec.describe GitlabSchema.types['WorkItem'] do
|
|||
id
|
||||
iid
|
||||
lock_version
|
||||
namespace
|
||||
project
|
||||
state title
|
||||
title_html
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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]) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue