Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
72c331ebf5
commit
d8d0344cc3
52 changed files with 641 additions and 281 deletions
|
@ -1 +1 @@
|
|||
58bf16b78b3c99757a2f283a5befe57a2cb7f009
|
||||
3822772ed121b764fdcc5011d199c37ef76e06a9
|
||||
|
|
|
@ -76,7 +76,7 @@ export default {
|
|||
const inviteTo = this.isProject ? 'toProject' : 'toGroup';
|
||||
|
||||
return sprintf(this.$options.labels[this.inviteeType][inviteTo].introText, {
|
||||
name: this.name.toUpperCase(),
|
||||
name: this.name,
|
||||
});
|
||||
},
|
||||
toastOptions() {
|
||||
|
@ -215,10 +215,14 @@ export default {
|
|||
searchField: s__('InviteMembersModal|GitLab member or Email address'),
|
||||
placeHolder: s__('InviteMembersModal|Search for members to invite'),
|
||||
toGroup: {
|
||||
introText: s__("InviteMembersModal|You're inviting members to the %{name} group"),
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
),
|
||||
},
|
||||
toProject: {
|
||||
introText: s__("InviteMembersModal|You're inviting members to the %{name} project"),
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
),
|
||||
},
|
||||
},
|
||||
group: {
|
||||
|
@ -226,10 +230,14 @@ export default {
|
|||
searchField: s__('InviteMembersModal|Select a group to invite'),
|
||||
placeHolder: s__('InviteMembersModal|Search for a group to invite'),
|
||||
toGroup: {
|
||||
introText: s__("InviteMembersModal|You're inviting a group to the %{name} group"),
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
),
|
||||
},
|
||||
toProject: {
|
||||
introText: s__("InviteMembersModal|You're inviting a group to the %{name} project"),
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
),
|
||||
},
|
||||
},
|
||||
accessLevel: s__('InviteMembersModal|Choose a role permission'),
|
||||
|
@ -253,7 +261,13 @@ export default {
|
|||
:header-close-label="$options.labels.headerCloseLabel"
|
||||
>
|
||||
<div>
|
||||
<p ref="introText">{{ introText }}</p>
|
||||
<p ref="introText">
|
||||
<gl-sprintf :message="introText">
|
||||
<template #strong="{ content }">
|
||||
<strong>{{ content }}</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<label :id="$options.membersTokenSelectLabelId" class="gl-font-weight-bold gl-mt-5">{{
|
||||
$options.labels[inviteeType].searchField
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<script>
|
||||
import { GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLink,
|
||||
GlIcon,
|
||||
},
|
||||
components: { GlButton },
|
||||
props: {
|
||||
displayText: {
|
||||
type: String,
|
||||
|
@ -24,6 +21,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
|
@ -34,10 +36,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-link :class="classes" data-qa-selector="invite_members_button" @click="openModal">
|
||||
<div v-if="icon" class="nav-icon-container">
|
||||
<gl-icon :size="16" :name="icon" />
|
||||
</div>
|
||||
<span class="nav-item-name"> {{ displayText }} </span>
|
||||
</gl-link>
|
||||
<gl-button
|
||||
:class="classes"
|
||||
:icon="icon"
|
||||
:variant="variant"
|
||||
data-qa-selector="invite_members_button"
|
||||
@click="openModal"
|
||||
>
|
||||
{{ displayText }}
|
||||
</gl-button>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { GlSprintf, GlButton } from '@gitlab/ui';
|
||||
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { sprintf, n__ } from '~/locale';
|
||||
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import {
|
||||
DETAILS_PAGE_TITLE,
|
||||
UPDATED_AT,
|
||||
CLEANUP_UNSCHEDULED_TEXT,
|
||||
CLEANUP_SCHEDULED_TEXT,
|
||||
|
@ -20,11 +19,16 @@ import {
|
|||
UNSCHEDULED_STATUS,
|
||||
SCHEDULED_STATUS,
|
||||
ONGOING_STATUS,
|
||||
ROOT_IMAGE_TEXT,
|
||||
ROOT_IMAGE_TOOLTIP,
|
||||
} from '../../constants/index';
|
||||
|
||||
export default {
|
||||
name: 'DetailsHeader',
|
||||
components: { GlSprintf, GlButton, TitleArea, MetadataItem },
|
||||
components: { GlButton, GlIcon, TitleArea, MetadataItem },
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
image: {
|
||||
|
@ -73,9 +77,12 @@ export default {
|
|||
deleteButtonDisabled() {
|
||||
return this.disabled || !this.image.canDelete;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
DETAILS_PAGE_TITLE,
|
||||
rootImageTooltip() {
|
||||
return !this.image.name ? ROOT_IMAGE_TOOLTIP : '';
|
||||
},
|
||||
imageName() {
|
||||
return this.image.name || ROOT_IMAGE_TEXT;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -84,12 +91,15 @@ export default {
|
|||
<title-area :metadata-loading="metadataLoading">
|
||||
<template #title>
|
||||
<span data-testid="title">
|
||||
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
|
||||
<template #imageName>
|
||||
{{ image.name }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
{{ imageName }}
|
||||
</span>
|
||||
<gl-icon
|
||||
v-if="rootImageTooltip"
|
||||
v-gl-tooltip="rootImageTooltip"
|
||||
class="gl-text-blue-600"
|
||||
name="information-o"
|
||||
:aria-label="rootImageTooltip"
|
||||
/>
|
||||
</template>
|
||||
<template #metadata-tags-count>
|
||||
<metadata-item icon="tag" :text="tagCountText" data-testid="tags-count" />
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
IMAGE_DELETE_SCHEDULED_STATUS,
|
||||
IMAGE_FAILED_DELETED_STATUS,
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '../../constants/index';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
|
||||
|
@ -74,6 +75,9 @@ export default {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
imageName() {
|
||||
return this.item.name ? this.item.path : `${this.item.path}/ ${ROOT_IMAGE_TEXT}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -95,7 +99,7 @@ export default {
|
|||
data-qa-selector="registry_image_content"
|
||||
:to="{ name: 'details', params: { id } }"
|
||||
>
|
||||
{{ item.path }}
|
||||
{{ imageName }}
|
||||
</router-link>
|
||||
<clipboard-button
|
||||
v-if="item.location"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const ROOT_IMAGE_TEXT = s__('ContainerRegistry|Root image');
|
|
@ -2,7 +2,6 @@ import { helpPagePath } from '~/helpers/help_page_helper';
|
|||
import { s__, __ } from '~/locale';
|
||||
|
||||
// Translations strings
|
||||
export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
|
||||
export const DELETE_TAG_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while marking the tag for deletion.',
|
||||
);
|
||||
|
@ -53,7 +52,8 @@ export const MISSING_OR_DELETED_IMAGE_TITLE = s__(
|
|||
export const MISSING_OR_DELETED_IMAGE_MESSAGE = s__(
|
||||
'ContainerRegistry|The requested image repository does not exist or has been deleted. If you think this is an error, try refreshing the page.',
|
||||
);
|
||||
export const MISSING_OR_DELETE_IMAGE_BREADCRUMB = s__(
|
||||
|
||||
export const MISSING_OR_DELETED_IMAGE_BREADCRUMB = s__(
|
||||
'ContainerRegistry|Image repository not found',
|
||||
);
|
||||
|
||||
|
@ -112,6 +112,10 @@ export const FAILED_DELETION_STATUS_MESSAGE = s__(
|
|||
'ContainerRegistry|This image repository has failed to be deleted',
|
||||
);
|
||||
|
||||
export const ROOT_IMAGE_TOOLTIP = s__(
|
||||
'ContainerRegistry|Image repository with no name located at the project URL.',
|
||||
);
|
||||
|
||||
// Parameters
|
||||
|
||||
export const DEFAULT_PAGE = 1;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './common';
|
||||
export * from './expiration_policies';
|
||||
export * from './quick_start';
|
||||
export * from './list';
|
||||
|
|
|
@ -24,7 +24,8 @@ import {
|
|||
GRAPHQL_PAGE_SIZE,
|
||||
FETCH_IMAGES_LIST_ERROR_MESSAGE,
|
||||
UNFINISHED_STATUS,
|
||||
MISSING_OR_DELETE_IMAGE_BREADCRUMB,
|
||||
MISSING_OR_DELETED_IMAGE_BREADCRUMB,
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '../constants/index';
|
||||
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
|
||||
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
|
||||
|
@ -116,7 +117,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
updateBreadcrumb() {
|
||||
const name = this.image?.name || MISSING_OR_DELETE_IMAGE_BREADCRUMB;
|
||||
const name = this.image?.id
|
||||
? this.image?.name || ROOT_IMAGE_TEXT
|
||||
: MISSING_OR_DELETED_IMAGE_BREADCRUMB;
|
||||
this.breadCrumbState.updateName(name);
|
||||
},
|
||||
deleteTags(toBeDeleted) {
|
||||
|
|
|
@ -184,7 +184,7 @@ export default {
|
|||
<slot name="sub-heading"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="action-buttons"></slot>
|
||||
<slot name="action-buttons" :is-collapsible="isCollapsible"></slot>
|
||||
|
||||
<button
|
||||
v-if="isCollapsible"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Emails
|
||||
module Pipelines
|
||||
def pipeline_success_email(pipeline, recipients)
|
||||
pipeline_mail(pipeline, recipients, 'Succesful')
|
||||
pipeline_mail(pipeline, recipients, 'Successful')
|
||||
end
|
||||
|
||||
def pipeline_failed_email(pipeline, recipients)
|
||||
|
|
|
@ -14,22 +14,30 @@ class Experiment < ApplicationRecord
|
|||
find_or_create_by!(name: name).record_group_and_variant!(group, variant)
|
||||
end
|
||||
|
||||
def self.record_conversion_event(name, user)
|
||||
find_or_create_by!(name: name).record_conversion_event_for_user(user)
|
||||
def self.record_conversion_event(name, user, context = {})
|
||||
find_or_create_by!(name: name).record_conversion_event_for_user(user, context)
|
||||
end
|
||||
|
||||
# Create or update the recorded experiment_user row for the user in this experiment.
|
||||
def record_user_and_group(user, group_type, context = {})
|
||||
experiment_user = experiment_users.find_or_initialize_by(user: user)
|
||||
merged_context = experiment_user.context.deep_merge(context.deep_stringify_keys)
|
||||
experiment_user.update!(group_type: group_type, context: merged_context)
|
||||
experiment_user.update!(group_type: group_type, context: merged_context(experiment_user, context))
|
||||
end
|
||||
|
||||
def record_conversion_event_for_user(user)
|
||||
experiment_users.find_by(user: user, converted_at: nil)&.touch(:converted_at)
|
||||
def record_conversion_event_for_user(user, context = {})
|
||||
experiment_user = experiment_users.find_by(user: user)
|
||||
return unless experiment_user
|
||||
|
||||
experiment_user.update!(converted_at: Time.current, context: merged_context(experiment_user, context))
|
||||
end
|
||||
|
||||
def record_group_and_variant!(group, variant)
|
||||
experiment_subjects.find_or_initialize_by(group: group).update!(variant: variant)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merged_context(experiment_user, new_context)
|
||||
experiment_user.context.deep_merge(new_context.deep_stringify_keys)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,6 +50,7 @@ module MergeRequests
|
|||
track_title_and_desc_edits(changed_fields)
|
||||
track_discussion_lock_toggle(merge_request, changed_fields)
|
||||
track_time_estimate_and_spend_edits(merge_request, old_timelogs, changed_fields)
|
||||
track_labels_change(merge_request, old_labels)
|
||||
|
||||
notify_if_labels_added(merge_request, old_labels)
|
||||
notify_if_mentions_added(merge_request, old_mentioned_users)
|
||||
|
@ -113,6 +114,12 @@ module MergeRequests
|
|||
merge_request_activity_counter.track_time_spent_changed_action(user: current_user) if old_timelogs != merge_request.timelogs
|
||||
end
|
||||
|
||||
def track_labels_change(merge_request, old_labels)
|
||||
return if Set.new(merge_request.labels) == Set.new(old_labels)
|
||||
|
||||
merge_request_activity_counter.track_labels_changed_action(user: current_user)
|
||||
end
|
||||
|
||||
def notify_if_labels_added(merge_request, old_labels)
|
||||
added_labels = merge_request.labels - old_labels
|
||||
|
||||
|
@ -191,6 +198,8 @@ module MergeRequests
|
|||
|
||||
return unless merge_request.previous_changes.include?('milestone_id')
|
||||
|
||||
merge_request_activity_counter.track_milestone_changed_action(user: current_user)
|
||||
|
||||
if merge_request.milestone.nil?
|
||||
notification_service.async.removed_milestone_merge_request(merge_request, current_user)
|
||||
else
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
.gl-w-half.gl-xs-w-full
|
||||
.gl-display-flex.gl-flex-wrap.gl-justify-content-end.gl-mb-3
|
||||
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
|
||||
.js-invite-members-trigger{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
= render 'groups/invite_members_modal', group: @group
|
||||
- if can_manage_members && !can_invite_members_for_group?(@group)
|
||||
%hr.gl-mt-4
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4= _('Repository cleanup')
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Repository cleanup')
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
.col-md-12.col-lg-6
|
||||
.gl-display-flex.gl-flex-wrap.gl-justify-content-end
|
||||
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
|
||||
.js-invite-members-trigger{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
|
||||
- else
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Project Settings Repository Cleanup header expands/collapses on click / tap
|
||||
merge_request: 55232
|
||||
author: Daniel Schömer
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add tracking to merge request labels/milestone changes
|
||||
merge_request: 55484
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use Root Image for images with missing name
|
||||
merge_request: 54693
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix typo in pipeline status email
|
||||
merge_request: 55412
|
||||
author: Gabriel Berke-Williams
|
||||
type: fixed
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: usage_data_i_code_review_user_labels_changed
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
rollout_issue_url:
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: true
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: usage_data_i_code_review_user_milestone_changed
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
rollout_issue_url:
|
||||
milestone: '13.10'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: true
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_milestone_changed_monthly
|
||||
description: Count of unique users per month who changed milestone of a MR
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_labels_changed_monthly
|
||||
description: Count of unique users per month who changed labels of a MR
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_milestone_changed_weekly
|
||||
description: Count of unique users per week who changed milestone of a MR
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_labels_changed_weekly
|
||||
description: Count of unique users per week who changed labels of a MR
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.10"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55484
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -354,3 +354,13 @@ These are aliases for `scope` and `expires_in` respectively, and have been inclu
|
|||
prevent breaking changes introduced in [doorkeeper 5.0.2](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions#from-4x-to-5x).
|
||||
|
||||
Don't rely on these fields as they will be removed in a later release.
|
||||
|
||||
## OAuth2 tokens and GitLab registries
|
||||
|
||||
Standard OAuth2 tokens support different degrees of access to GitLab registries, as they:
|
||||
|
||||
- Do not allow users to authenticate to:
|
||||
- The GitLab [Container registry](../user/packages/container_registry/index.md#authenticate-with-the-container-registry).
|
||||
- Packages listed in the GitLab [Package registry](../user/packages/package_registry/index.md).
|
||||
- Allow users to get, list, and delete registries through
|
||||
the [Container registry API](container_registry.md).
|
||||
|
|
|
@ -9,7 +9,9 @@ type: reference, api
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/6373) in GitLab 8.15.
|
||||
|
||||
Snippets API operates on [snippets](../user/snippets.md).
|
||||
Snippets API operates on [snippets](../user/snippets.md). Related APIs exist for
|
||||
[project snippets](project_snippets.md) and
|
||||
[moving snippets between storages](snippet_repository_storage_moves.md).
|
||||
|
||||
## Snippet visibility level
|
||||
|
||||
|
@ -289,14 +291,12 @@ Parameters:
|
|||
| `content` | string | no | Deprecated: Use `files` instead. Content of a snippet |
|
||||
| `description` | string | no | Description of a snippet |
|
||||
| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level) |
|
||||
| `files` | array of hashes | no | An array of snippet files |
|
||||
| `files` | array of hashes | sometimes | An array of snippet files. Required when updating snippets with multiple files. |
|
||||
| `files:action` | string | yes | Type of action to perform on the file, one of: 'create', 'update', 'delete', 'move' |
|
||||
| `files:file_path` | string | no | File path of the snippet file |
|
||||
| `files:previous_path` | string | no | Previous path of the snippet file |
|
||||
| `files:content` | string | no | Content of the snippet file |
|
||||
|
||||
Updates to snippets with multiple files *must* use the `files` attribute.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
|
|
|
@ -8468,6 +8468,30 @@ Status: `data_available`
|
|||
|
||||
Tiers:
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_labels_changed_monthly`
|
||||
|
||||
Count of unique users per month who changed labels of a MR
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210302110607_i_code_review_user_labels_changed_monthly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_labels_changed_weekly`
|
||||
|
||||
Count of unique users per week who changed labels of a MR
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210302110548_i_code_review_user_labels_changed_weekly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_marked_as_draft_monthly`
|
||||
|
||||
Missing description
|
||||
|
@ -8516,6 +8540,30 @@ Status: `data_available`
|
|||
|
||||
Tiers:
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_milestone_changed_monthly`
|
||||
|
||||
Count of unique users per month who changed milestone of a MR
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210302110520_i_code_review_user_milestone_changed_monthly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_milestone_changed_weekly`
|
||||
|
||||
Count of unique users per week who changed milestone of a MR
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210302110403_i_code_review_user_milestone_changed_weekly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_mr_discussion_locked_monthly`
|
||||
|
||||
Count of unique users per month who locked a MR
|
||||
|
|
|
@ -246,7 +246,7 @@ To move an issue to another epic:
|
|||
|
||||
If you have the necessary [permissions](../../permissions.md) to close an issue and create an
|
||||
epic in the immediate parent group, you can promote an issue to an epic with the `/promote`
|
||||
[quick action](../../project/quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
|
||||
[quick action](../../project/quick_actions.md#issues-merge-requests-and-epics).
|
||||
Only issues from projects that are in groups can be promoted. When you attempt to promote a confidential
|
||||
issue, a warning is displayed. Promoting a confidential issue to an epic makes all information
|
||||
related to the issue public as epics are public to group members.
|
||||
|
|
|
@ -17,7 +17,7 @@ team members can join swiftly without requesting a link.
|
|||
## Adding a Zoom meeting to an issue
|
||||
|
||||
To associate a Zoom meeting with an issue, you can use GitLab
|
||||
[quick actions](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
|
||||
[quick actions](../quick_actions.md#issues-merge-requests-and-epics).
|
||||
|
||||
In an issue, leave a comment using the `/zoom` quick action followed by a valid Zoom link:
|
||||
|
||||
|
|
|
@ -298,7 +298,7 @@ To promote an issue to an epic:
|
|||
1. In an issue, select the vertical ellipsis (**{ellipsis_v}**) button.
|
||||
1. Select **Promote to epic**.
|
||||
|
||||
Alternatively, you can use the `/promote` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
|
||||
Alternatively, you can use the `/promote` [quick action](../quick_actions.md#issues-merge-requests-and-epics).
|
||||
|
||||
Read more about promoting an issue to an epic on the [Manage epics page](../../group/epics/manage_epics.md#promote-an-issue-to-an-epic).
|
||||
|
||||
|
@ -313,5 +313,5 @@ To add an issue to an [iteration](../../group/iterations/index.md):
|
|||
1. Click an iteration you'd like to associate this issue with.
|
||||
|
||||
You can also use the `/iteration`
|
||||
[quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
|
||||
[quick action](../quick_actions.md#issues-merge-requests-and-epics)
|
||||
in a comment or description field.
|
||||
|
|
|
@ -27,7 +27,7 @@ There are several ways to flag a merge request as a draft:
|
|||
the beginning of the merge request's title, or click **Start the title with Draft:**
|
||||
below the **Title** field.
|
||||
- **Commenting in an existing merge request**: Add the `/draft`
|
||||
[quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
|
||||
[quick action](../quick_actions.md#issues-merge-requests-and-epics)
|
||||
in a comment. This quick action is a toggle, and can be repeated to change the status
|
||||
again. This quick action discards any other text in the comment.
|
||||
- **Creating a commit**: Add `draft:`, `Draft:`, `fixup!`, or `Fixup!` to the
|
||||
|
@ -53,7 +53,7 @@ When a merge request is ready to be merged, you can remove the `Draft` flag in s
|
|||
from the beginning of the title, or click **Remove the Draft: prefix from the title**
|
||||
below the **Title** field.
|
||||
- **Commenting in an existing merge request**: Add the `/draft`
|
||||
[quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
|
||||
[quick action](../quick_actions.md#issues-merge-requests-and-epics)
|
||||
in a comment in the merge request. This quick action is a toggle, and can be repeated
|
||||
to change the status back. This quick action discards any other text in the comment.
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ dropdown menu.
|
|||
It is also possible to manage multiple assignees:
|
||||
|
||||
- When creating a merge request.
|
||||
- Using [quick actions](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
|
||||
- Using [quick actions](../quick_actions.md#issues-merge-requests-and-epics).
|
||||
|
||||
### Reviewer
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ group: Project Management
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# GitLab Quick Actions
|
||||
# GitLab quick actions **(FREE)**
|
||||
|
||||
> - Introduced in [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/26672):
|
||||
> once an action is executed, an alert appears when a quick action is successfully applied.
|
||||
|
@ -15,115 +15,113 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> `/` into a description or comment field, all available quick actions are displayed in a scrollable list.
|
||||
> - The rebase quick action was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49800) in GitLab 13.8.
|
||||
|
||||
Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
|
||||
and commits that are usually done by clicking buttons or dropdowns in the GitLab UI.
|
||||
You can enter these commands in the description or in comments of issues, epics, merge requests, and commits.
|
||||
Each command should be on a separate line in order to be properly detected and executed.
|
||||
Quick actions are text-based shortcuts for common actions that are usually done
|
||||
by selecting buttons or dropdowns in the GitLab user interface. You can enter
|
||||
these commands in the descriptions or comments of issues, epics, merge requests,
|
||||
and commits.
|
||||
|
||||
## Quick Actions for issues, merge requests and epics
|
||||
Be sure to enter each quick action on a separate line to allow GitLab to
|
||||
properly detect and execute the commands.
|
||||
|
||||
The following quick actions are applicable to descriptions, discussions and threads in:
|
||||
## Parameters
|
||||
|
||||
- Issues
|
||||
- Merge requests
|
||||
- Epics **(PREMIUM)**
|
||||
Many quick actions require a parameter. For example, the `/assign` quick action
|
||||
requires a username. GitLab uses [autocomplete characters](autocomplete_characters.md)
|
||||
with quick actions to help users enter parameters, by providing a list of
|
||||
available values.
|
||||
|
||||
| Command | Issue | Merge request | Epic | Action |
|
||||
| :------------------------------------ | :---- | :------------ | :--- | :------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `/approve` | | ✓ | | Approve the merge request. **(STARTER)** |
|
||||
| `/assign @user` | ✓ | ✓ | | Assign one user. |
|
||||
| `/assign @user1 @user2` | ✓ | ✓ | | Assign multiple users. **(STARTER)** |
|
||||
| `/assign me` | ✓ | ✓ | | Assign yourself. |
|
||||
| `/assign_reviewer @user` or `/reviewer @user` or `/request_review @user` | | ✓ | | Assign one user as a reviewer. |
|
||||
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | | ✓ | | Assign multiple users as reviewers. **(STARTER)** |
|
||||
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | | ✓ | | Assign yourself as a reviewer. |
|
||||
| `/award :emoji:` | ✓ | ✓ | ✓ | Toggle emoji award. |
|
||||
| `/child_epic <epic>` | | | ✓ | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). **(ULTIMATE)** |
|
||||
| `/clear_weight` | ✓ | | | Clear weight. **(STARTER)** |
|
||||
| `/clone <path/to/project> [--with_notes]`| ✓ | | | 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` | ✓ | ✓ | ✓ | Close. |
|
||||
| `/confidential` | ✓ | | | Make confidential. |
|
||||
| `/copy_metadata <!merge_request>` | ✓ | ✓ | | Copy labels and milestone from another merge request in the project. |
|
||||
| `/copy_metadata <#issue>` | ✓ | ✓ | | Copy labels and milestone from another issue in the project. |
|
||||
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue. |
|
||||
| `/done` | ✓ | ✓ | ✓ | Mark to do as done. |
|
||||
| `/draft` | | ✓ | | Toggle the draft status. |
|
||||
| `/due <date>` | ✓ | | | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. |
|
||||
| `/duplicate <#issue>` | ✓ | | | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. **(STARTER)** |
|
||||
| `/epic <epic>` | ✓ | | | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. **(PREMIUM)** |
|
||||
| `/estimate <<W>w <DD>d <hh>h <mm>m>` | ✓ | ✓ | | Set time estimate. For example, `/estimate 1w 3d 2h 14m`. |
|
||||
| `/invite_email email1 email2` | ✓ | | | Add up to 6 e-mail participants. This action is behind feature flag `issue_email_participants` |
|
||||
| `/iteration *iteration:"iteration name"` | ✓ | | | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). **(STARTER)** |
|
||||
| `/label ~label1 ~label2` | ✓ | ✓ | ✓ | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
|
||||
| `/lock` | ✓ | ✓ | | Lock the discussions. |
|
||||
| `/merge` | | ✓ | | Merge changes. Depending on the project setting, this may be [when the pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md), adding to a [Merge Train](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md), etc. |
|
||||
| `/milestone %milestone` | ✓ | ✓ | | Set milestone. |
|
||||
| `/move <path/to/project>` | ✓ | | | Move this issue to another project. |
|
||||
| `/parent_epic <epic>` | | | ✓ | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/-/issues/10556)). **(ULTIMATE)** |
|
||||
| `/promote` | ✓ | | | Promote issue to epic. **(PREMIUM)** |
|
||||
| `/publish` | ✓ | | | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) **(ULTIMATE)** |
|
||||
| `/reassign @user1 @user2` | ✓ | ✓ | | Replace current assignees with those specified. **(STARTER)** |
|
||||
| `/rebase` | | ✓ | | 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. |
|
||||
| `/reassign_reviewer @user1 @user2` | | ✓ | | Replace current reviewers with those specified. **(STARTER)** |
|
||||
| `/relabel ~label1 ~label2` | ✓ | ✓ | ✓ | Replace current labels with those specified. |
|
||||
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related. **(STARTER)** |
|
||||
| `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). **(ULTIMATE)** |
|
||||
| `/remove_due_date` | ✓ | | | Remove due date. |
|
||||
| `/remove_epic` | ✓ | | | Remove from epic. **(PREMIUM)** |
|
||||
| `/remove_estimate` | ✓ | ✓ | | Remove time estimate. |
|
||||
| `/remove_iteration` | ✓ | | | Remove iteration ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)) **(STARTER)** |
|
||||
| `/remove_milestone` | ✓ | ✓ | | Remove milestone. |
|
||||
| `/remove_parent_epic` | | | ✓ | Remove parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/-/issues/10556)). **(ULTIMATE)** |
|
||||
| `/remove_time_spent` | ✓ | ✓ | | Remove time spent. |
|
||||
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue ([introduced in GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16609)). |
|
||||
| `/reopen` | ✓ | ✓ | ✓ | Reopen. |
|
||||
| `/shrug <comment>` | ✓ | ✓ | ✓ | Append the comment with `¯\_(ツ)_/¯`. |
|
||||
| `/spend <time(-<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | ✓ | ✓ | | Subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend time(-1h 30m)` or `/spend time(-1h 30m) date(2018-08-26)`. |
|
||||
| `/spend <time(<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | ✓ | ✓ | | Add spent time. Optionally, specify the date that time was spent on. For example, `/spend time(1h 30m)` or `/spend time(1h 30m) date(2018-08-26)`. |
|
||||
| `/submit_review` | | ✓ | | Submit a pending review ([introduced in GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/issues/8041)). **(PREMIUM)** |
|
||||
| `/subscribe` | ✓ | ✓ | ✓ | Subscribe to notifications. |
|
||||
| `/tableflip <comment>` | ✓ | ✓ | ✓ | Append the comment with `(╯°□°)╯︵ ┻━┻`. |
|
||||
| `/target_branch <local branch name>` | | ✓ | | Set target branch. |
|
||||
| `/title <new title>` | ✓ | ✓ | ✓ | Change title. |
|
||||
| `/todo` | ✓ | ✓ | ✓ | Add a to-do item. |
|
||||
| `/unassign @user1 @user2` | ✓ | ✓ | | Remove specific assignees. **(STARTER)** |
|
||||
| `/unassign` | | ✓ | | Remove all assignees. |
|
||||
| `/unassign_reviewer @user1 @user2` or `/remove_reviewer @user1 @user2` | | ✓ | | Remove specific reviewers. **(STARTER)** |
|
||||
| `/unassign_reviewer` or `/remove_reviewer` | | ✓ | | Remove all reviewers. |
|
||||
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | ✓ | ✓ | ✓ | Remove specified labels. |
|
||||
| `/unlabel` or `/remove_label` | ✓ | ✓ | ✓ | Remove all labels. |
|
||||
| `/unlock` | ✓ | ✓ | | Unlock the discussions. |
|
||||
| `/unsubscribe` | ✓ | ✓ | ✓ | Unsubscribe from notifications. |
|
||||
| `/weight <value>` | ✓ | | | Set weight. Valid options for `<value>` include `0`, `1`, `2`, and so on. **(STARTER)** |
|
||||
| `/wip` | | ✓ | | Toggle the draft status. |
|
||||
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue ([introduced in GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16609)). |
|
||||
|
||||
## Autocomplete characters
|
||||
|
||||
Many quick actions require a parameter, for example: username, milestone, and
|
||||
label. [Autocomplete characters](autocomplete_characters.md) can make it easier
|
||||
to enter a parameter, compared to selecting items from a list.
|
||||
|
||||
## Quick actions parameters
|
||||
|
||||
The easiest way to set parameters for quick actions is to use autocomplete. If
|
||||
you manually enter a parameter, it must be enclosed in double quotation marks
|
||||
If you manually enter a parameter, it must be enclosed in double quotation marks
|
||||
(`"`), unless it contains only these characters:
|
||||
|
||||
1. ASCII letters.
|
||||
1. Numerals (0-9).
|
||||
1. Underscore (`_`), hyphen (`-`), question mark (`?`), dot (`.`), or ampersand (`&`).
|
||||
- ASCII letters
|
||||
- Numbers (0-9)
|
||||
- Underscore (`_`), hyphen (`-`), question mark (`?`), dot (`.`), or ampersand (`&`)
|
||||
|
||||
Parameters are also case-sensitive. Autocomplete handles this, and the insertion
|
||||
Parameters are case-sensitive. Autocomplete handles this, and the insertion
|
||||
of quotation marks, automatically.
|
||||
|
||||
## Quick actions for commit messages
|
||||
## Issues, merge requests, and epics
|
||||
|
||||
The following quick actions are applicable to descriptions, discussions, and
|
||||
threads. Some quick actions might not be available to all subscription tiers.
|
||||
|
||||
| Command | Issue | Merge request | Epic | Action |
|
||||
|:--------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------|
|
||||
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. |
|
||||
| `/assign @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one user. |
|
||||
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign multiple users. |
|
||||
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself. |
|
||||
| `/assign_reviewer @user` or `/reviewer @user` or `/request_review @user` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one user as a reviewer. |
|
||||
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign multiple 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. |
|
||||
| `/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 in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). |
|
||||
| `/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 | **{dotted-circle}** No | Make confidential. |
|
||||
| `/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 as done. |
|
||||
| `/draft` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Toggle the draft status. |
|
||||
| `/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`. |
|
||||
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. |
|
||||
| `/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 <<W>w <DD>d <hh>h <mm>m>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1w 3d 2h 14m`. |
|
||||
| `/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`. |
|
||||
| `/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 in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). |
|
||||
| `/label ~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. |
|
||||
| `/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/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.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. |
|
||||
| `/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 in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/-/issues/10556)). |
|
||||
| `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic. |
|
||||
| `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) |
|
||||
| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Replace current assignees 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. |
|
||||
| `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. |
|
||||
| `/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 in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). |
|
||||
| `/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` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time estimate. |
|
||||
| `/remove_iteration` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove iteration ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). |
|
||||
| `/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 in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/-/issues/10556)). |
|
||||
| `/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 ([introduced in GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16609)). |
|
||||
| `/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 `¯\_(ツ)_/¯`. |
|
||||
| `/spend <time(-<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend time(-1h 30m)` or `/spend time(-1h 30m) date(2018-08-26)`. |
|
||||
| `/spend <time(<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add spent time. Optionally, specify the date that time was spent on. For example, `/spend time(1h 30m)` or `/spend time(1h 30m) date(2018-08-26)`. |
|
||||
| `/submit_review` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Submit a pending review ([introduced in GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/issues/8041)). |
|
||||
| `/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. |
|
||||
| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. |
|
||||
| `/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` 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. |
|
||||
| `/wip` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Toggle the draft status. |
|
||||
| `/zoom <Zoom URL>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add Zoom meeting to this issue ([introduced in GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16609)). |
|
||||
|
||||
## Commit messages
|
||||
|
||||
The following quick actions are applicable for commit messages:
|
||||
|
||||
| Command | Action |
|
||||
| :---------------------- | :---------------------------------------- |
|
||||
| `/tag v1.2.3 <message>` | Tags this commit with an optional message |
|
||||
|:----------------------- |:------------------------------------------|
|
||||
| `/tag v1.2.3 <message>` | Tags the commit with an optional message. |
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
|
|
|
@ -72,12 +72,12 @@ module Gitlab
|
|||
::Experiment.add_user(experiment_key, tracking_group(experiment_key, nil, subject: subject), current_user, context)
|
||||
end
|
||||
|
||||
def record_experiment_conversion_event(experiment_key)
|
||||
def record_experiment_conversion_event(experiment_key, context = {})
|
||||
return if dnt_enabled?
|
||||
return unless current_user
|
||||
return unless Experimentation.active?(experiment_key)
|
||||
|
||||
::Experiment.record_conversion_event(experiment_key, current_user)
|
||||
::Experiment.record_conversion_event(experiment_key, current_user, context)
|
||||
end
|
||||
|
||||
def experiment_tracking_category_and_group(experiment_key, subject: nil)
|
||||
|
|
|
@ -49,7 +49,9 @@
|
|||
'i_code_review_user_time_estimate_changed',
|
||||
'i_code_review_user_time_spent_changed',
|
||||
'i_code_review_user_assignees_changed',
|
||||
'i_code_review_user_reviewers_changed'
|
||||
'i_code_review_user_reviewers_changed',
|
||||
'i_code_review_user_milestone_changed',
|
||||
'i_code_review_user_labels_changed'
|
||||
]
|
||||
- name: code_review_category_monthly_active_users
|
||||
operator: OR
|
||||
|
@ -92,7 +94,9 @@
|
|||
'i_code_review_user_time_estimate_changed',
|
||||
'i_code_review_user_time_spent_changed',
|
||||
'i_code_review_user_assignees_changed',
|
||||
'i_code_review_user_reviewers_changed'
|
||||
'i_code_review_user_reviewers_changed',
|
||||
'i_code_review_user_milestone_changed',
|
||||
'i_code_review_user_labels_changed'
|
||||
]
|
||||
- name: code_review_extension_category_monthly_active_users
|
||||
operator: OR
|
||||
|
|
|
@ -194,3 +194,13 @@
|
|||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_code_review_user_reviewers_changed
|
||||
- name: i_code_review_user_milestone_changed
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_code_review_user_milestone_changed
|
||||
- name: i_code_review_user_labels_changed
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_code_review_user_labels_changed
|
||||
|
|
|
@ -42,6 +42,8 @@ module Gitlab
|
|||
MR_ASSIGNEES_CHANGED_ACTION = 'i_code_review_user_assignees_changed'
|
||||
MR_REVIEWERS_CHANGED_ACTION = 'i_code_review_user_reviewers_changed'
|
||||
MR_INCLUDING_CI_CONFIG_ACTION = 'o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile'
|
||||
MR_MILESTONE_CHANGED_ACTION = 'i_code_review_user_milestone_changed'
|
||||
MR_LABELS_CHANGED_ACTION = 'i_code_review_user_labels_changed'
|
||||
|
||||
class << self
|
||||
def track_mr_diffs_action(merge_request:)
|
||||
|
@ -191,6 +193,14 @@ module Gitlab
|
|||
track_unique_action_by_user(MR_INCLUDING_CI_CONFIG_ACTION, user)
|
||||
end
|
||||
|
||||
def track_milestone_changed_action(user:)
|
||||
track_unique_action_by_user(MR_MILESTONE_CHANGED_ACTION, user)
|
||||
end
|
||||
|
||||
def track_labels_changed_action(user:)
|
||||
track_unique_action_by_user(MR_LABELS_CHANGED_ACTION, user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_action_by_merge_request(action, merge_request)
|
||||
|
|
|
@ -7955,9 +7955,6 @@ msgid_plural "ContainerRegistry|%{count} Tags"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "ContainerRegistry|%{imageName} tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|%{strongStart}Disabled%{strongEnd} - Tags will not be automatically deleted."
|
||||
msgstr ""
|
||||
|
||||
|
@ -8060,6 +8057,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Image repository will be deleted"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Image repository with no name located at the project URL."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Image tags"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8125,6 +8125,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Remove these tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Root image"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Run cleanup:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16681,16 +16684,16 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Some of the members could not be added"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You're inviting a group to the %{name} group"
|
||||
msgid "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You're inviting a group to the %{name} project"
|
||||
msgid "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} project."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You're inviting members to the %{name} group"
|
||||
msgid "InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} group."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|You're inviting members to the %{name} project"
|
||||
msgid "InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembers|Invite a group"
|
||||
|
|
|
@ -67,7 +67,13 @@ RSpec.describe 'Container Registry', :js do
|
|||
end
|
||||
|
||||
it 'shows the image title' do
|
||||
expect(page).to have_content 'my/image tags'
|
||||
expect(page).to have_content 'my/image'
|
||||
end
|
||||
|
||||
it 'shows the image tags' do
|
||||
expect(page).to have_content 'Image tags'
|
||||
first_tag = first('[data-testid="name"]')
|
||||
expect(first_tag).to have_content 'latest'
|
||||
end
|
||||
|
||||
it 'user removes a specific tag from container repository' do
|
||||
|
|
|
@ -82,7 +82,13 @@ RSpec.describe 'Container Registry', :js do
|
|||
end
|
||||
|
||||
it 'shows the image title' do
|
||||
expect(page).to have_content 'my/image tags'
|
||||
expect(page).to have_content 'my/image'
|
||||
end
|
||||
|
||||
it 'shows the image tags' do
|
||||
expect(page).to have_content 'Image tags'
|
||||
first_tag = first('[data-testid="name"]')
|
||||
expect(first_tag).to have_content '1'
|
||||
end
|
||||
|
||||
it 'user removes a specific tag from container repository' do
|
||||
|
|
|
@ -129,7 +129,7 @@ describe('InviteMembersModal', () => {
|
|||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
wrapper = createInviteMembersToProjectWrapper();
|
||||
|
||||
expect(findIntroText()).toBe("You're inviting members to the TEST NAME project");
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name project.");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -137,7 +137,7 @@ describe('InviteMembersModal', () => {
|
|||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
wrapper = createInviteGroupToProjectWrapper();
|
||||
|
||||
expect(findIntroText()).toBe("You're inviting a group to the TEST NAME project");
|
||||
expect(findIntroText()).toBe("You're inviting a group to the test name project.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -147,7 +147,7 @@ describe('InviteMembersModal', () => {
|
|||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
wrapper = createInviteMembersToGroupWrapper();
|
||||
|
||||
expect(wrapper.html()).toContain("You're inviting members to the TEST NAME group");
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name group.");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -155,7 +155,7 @@ describe('InviteMembersModal', () => {
|
|||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
wrapper = createInviteGroupToGroupWrapper();
|
||||
|
||||
expect(wrapper.html()).toContain("You're inviting a group to the TEST NAME group");
|
||||
expect(findIntroText()).toBe("You're inviting a group to the test name group.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
|
||||
|
||||
const displayText = 'Invite team members';
|
||||
const icon = 'plus';
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
return shallowMount(InviteMembersTrigger, {
|
||||
|
@ -23,36 +22,14 @@ describe('InviteMembersTrigger', () => {
|
|||
});
|
||||
|
||||
describe('displayText', () => {
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
it('includes the correct displayText for the link', () => {
|
||||
expect(findLink().text()).toBe(displayText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('icon', () => {
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
|
||||
it('includes the correct icon when an icon is sent', () => {
|
||||
wrapper = createComponent({ icon });
|
||||
|
||||
expect(findIcon().attributes('name')).toBe(icon);
|
||||
});
|
||||
|
||||
it('does not include an icon when icon is not sent', () => {
|
||||
wrapper = createComponent();
|
||||
|
||||
expect(findIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not include an icon when empty string is sent', () => {
|
||||
wrapper = createComponent({ icon: '' });
|
||||
|
||||
expect(findIcon().exists()).toBe(false);
|
||||
it('includes the correct displayText for the button', () => {
|
||||
expect(findButton().text()).toBe(displayText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { GlSprintf, GlButton } from '@gitlab/ui';
|
||||
import { GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import component from '~/registry/explorer/components/details_page/details_header.vue';
|
||||
import {
|
||||
DETAILS_PAGE_TITLE,
|
||||
UNSCHEDULED_STATUS,
|
||||
SCHEDULED_STATUS,
|
||||
ONGOING_STATUS,
|
||||
|
@ -13,6 +13,8 @@ import {
|
|||
CLEANUP_SCHEDULED_TOOLTIP,
|
||||
CLEANUP_ONGOING_TOOLTIP,
|
||||
CLEANUP_UNFINISHED_TOOLTIP,
|
||||
ROOT_IMAGE_TEXT,
|
||||
ROOT_IMAGE_TOOLTIP,
|
||||
} from '~/registry/explorer/constants';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
|
||||
|
@ -41,6 +43,7 @@ describe('Details Header', () => {
|
|||
const findTagsCount = () => findByTestId('tags-count');
|
||||
const findCleanup = () => findByTestId('cleanup');
|
||||
const findDeleteButton = () => wrapper.find(GlButton);
|
||||
const findInfoIcon = () => wrapper.find(GlIcon);
|
||||
|
||||
const waitForMetadataItems = async () => {
|
||||
// Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
|
||||
|
@ -51,8 +54,10 @@ describe('Details Header', () => {
|
|||
const mountComponent = (propsData = { image: defaultImage }) => {
|
||||
wrapper = shallowMount(component, {
|
||||
propsData,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
TitleArea,
|
||||
},
|
||||
});
|
||||
|
@ -62,15 +67,41 @@ describe('Details Header', () => {
|
|||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
describe('image name', () => {
|
||||
describe('missing image name', () => {
|
||||
it('root image ', () => {
|
||||
mountComponent({ image: { ...defaultImage, name: '' } });
|
||||
|
||||
it('has the correct title ', () => {
|
||||
mountComponent({ image: { ...defaultImage, name: '' } });
|
||||
expect(findTitle().text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
|
||||
});
|
||||
expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT);
|
||||
});
|
||||
|
||||
it('shows imageName in the title', () => {
|
||||
mountComponent();
|
||||
expect(findTitle().text()).toContain('foo');
|
||||
it('has an icon', () => {
|
||||
mountComponent({ image: { ...defaultImage, name: '' } });
|
||||
|
||||
expect(findInfoIcon().exists()).toBe(true);
|
||||
expect(findInfoIcon().props('name')).toBe('information-o');
|
||||
});
|
||||
|
||||
it('has a tooltip', () => {
|
||||
mountComponent({ image: { ...defaultImage, name: '' } });
|
||||
|
||||
const tooltip = getBinding(findInfoIcon().element, 'gl-tooltip');
|
||||
expect(tooltip.value).toBe(ROOT_IMAGE_TOOLTIP);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with image name present', () => {
|
||||
it('shows image.name ', () => {
|
||||
mountComponent();
|
||||
expect(findTitle().text()).toContain('foo');
|
||||
});
|
||||
|
||||
it('has no icon', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findInfoIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete button', () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
IMAGE_DELETE_SCHEDULED_STATUS,
|
||||
IMAGE_FAILED_DELETED_STATUS,
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '~/registry/explorer/constants';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
@ -73,8 +74,8 @@ describe('Image List Row', () => {
|
|||
mountComponent();
|
||||
|
||||
const link = findDetailsLink();
|
||||
expect(link.html()).toContain(item.path);
|
||||
expect(link.props('to')).toMatchObject({
|
||||
expect(link.text()).toBe(item.path);
|
||||
expect(findDetailsLink().props('to')).toMatchObject({
|
||||
name: 'details',
|
||||
params: {
|
||||
id: getIdFromGraphQLId(item.id),
|
||||
|
@ -82,6 +83,12 @@ describe('Image List Row', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`when the image has no name appends ${ROOT_IMAGE_TEXT} to the path`, () => {
|
||||
mountComponent({ item: { ...item, name: '' } });
|
||||
|
||||
expect(findDetailsLink().text()).toBe(`${item.path}/ ${ROOT_IMAGE_TEXT}`);
|
||||
});
|
||||
|
||||
it('contains a clipboard button', () => {
|
||||
mountComponent();
|
||||
const button = findClipboardButton();
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
UNFINISHED_STATUS,
|
||||
DELETE_SCHEDULED,
|
||||
ALERT_DANGER_IMAGE,
|
||||
MISSING_OR_DELETED_IMAGE_BREADCRUMB,
|
||||
ROOT_IMAGE_TEXT,
|
||||
} from '~/registry/explorer/constants';
|
||||
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
|
||||
import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
|
||||
|
@ -515,6 +517,26 @@ describe('Details Page', () => {
|
|||
|
||||
expect(breadCrumbState.updateName).toHaveBeenCalledWith(containerRepositoryMock.name);
|
||||
});
|
||||
|
||||
it(`when the image is missing set the breadcrumb to ${MISSING_OR_DELETED_IMAGE_BREADCRUMB}`, async () => {
|
||||
mountComponent({ resolver: jest.fn().mockResolvedValue(graphQLEmptyImageDetailsMock) });
|
||||
|
||||
await waitForApolloRequestRender();
|
||||
|
||||
expect(breadCrumbState.updateName).toHaveBeenCalledWith(MISSING_OR_DELETED_IMAGE_BREADCRUMB);
|
||||
});
|
||||
|
||||
it(`when the image has no name set the breadcrumb to ${ROOT_IMAGE_TEXT}`, async () => {
|
||||
mountComponent({
|
||||
resolver: jest
|
||||
.fn()
|
||||
.mockResolvedValue(graphQLImageDetailsMock({ ...containerRepositoryMock, name: null })),
|
||||
});
|
||||
|
||||
await waitForApolloRequestRender();
|
||||
|
||||
expect(breadCrumbState.updateName).toHaveBeenCalledWith(ROOT_IMAGE_TEXT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the image has a status different from null', () => {
|
||||
|
|
|
@ -534,7 +534,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
|
|||
end
|
||||
|
||||
it 'records the conversion event for the experiment & user' do
|
||||
expect(::Experiment).to receive(:record_conversion_event).with(:test_experiment, user)
|
||||
expect(::Experiment).to receive(:record_conversion_event).with(:test_experiment, user, {})
|
||||
record_conversion_event
|
||||
end
|
||||
|
||||
|
|
|
@ -370,4 +370,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
|
|||
it_behaves_like 'not tracked merge request unique event'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.track_milestone_changed_action' do
|
||||
subject { described_class.track_milestone_changed_action(user: user) }
|
||||
|
||||
it_behaves_like 'a tracked merge request unique event' do
|
||||
let(:action) { described_class::MR_MILESTONE_CHANGED_ACTION }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.track_labels_changed_action' do
|
||||
subject { described_class.track_labels_changed_action(user: user) }
|
||||
|
||||
it_behaves_like 'a tracked merge request unique event' do
|
||||
let(:action) { described_class::MR_LABELS_CHANGED_ACTION }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -89,7 +89,7 @@ RSpec.describe Emails::Pipelines do
|
|||
let(:sha) { project.commit(ref).sha }
|
||||
|
||||
it_behaves_like 'correct pipeline information' do
|
||||
let(:status) { 'Succesful' }
|
||||
let(:status) { 'Successful' }
|
||||
let(:status_text) { "Pipeline ##{pipeline.id} has passed!" }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,10 +98,11 @@ RSpec.describe Experiment do
|
|||
|
||||
describe '.record_conversion_event' do
|
||||
let_it_be(:user) { build(:user) }
|
||||
let_it_be(:context) { { a: 42 } }
|
||||
|
||||
let(:experiment_key) { :test_experiment }
|
||||
|
||||
subject(:record_conversion_event) { described_class.record_conversion_event(experiment_key, user) }
|
||||
subject(:record_conversion_event) { described_class.record_conversion_event(experiment_key, user, context) }
|
||||
|
||||
context 'when no matching experiment exists' do
|
||||
it 'creates the experiment and uses it' do
|
||||
|
@ -127,22 +128,79 @@ RSpec.describe Experiment do
|
|||
|
||||
it 'sends record_conversion_event_for_user to the experiment instance' do
|
||||
expect_next_found_instance_of(described_class) do |experiment|
|
||||
expect(experiment).to receive(:record_conversion_event_for_user).with(user)
|
||||
expect(experiment).to receive(:record_conversion_event_for_user).with(user, context)
|
||||
end
|
||||
record_conversion_event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'experiment user with context' do
|
||||
let_it_be(:context) { { a: 42, 'b' => 34, 'c': { c1: 100, c2: 'c2', e: :e }, d: [1, 3] } }
|
||||
let_it_be(:initial_expected_context) { { 'a' => 42, 'b' => 34, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [1, 3] } }
|
||||
|
||||
before do
|
||||
subject
|
||||
experiment.record_user_and_group(user, :experimental, {})
|
||||
end
|
||||
|
||||
it 'has an initial context with stringified keys' do
|
||||
expect(ExperimentUser.last.context).to eq(initial_expected_context)
|
||||
end
|
||||
|
||||
context 'when updated' do
|
||||
before do
|
||||
subject
|
||||
experiment.record_user_and_group(user, :experimental, new_context)
|
||||
end
|
||||
|
||||
context 'with an empty context' do
|
||||
let_it_be(:new_context) { {} }
|
||||
|
||||
it 'keeps the initial context' do
|
||||
expect(ExperimentUser.last.context).to eq(initial_expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with string keys' do
|
||||
let_it_be(:new_context) { { f: :some_symbol } }
|
||||
|
||||
it 'adds new symbols stringified' do
|
||||
expected_context = initial_expected_context.merge('f' => 'some_symbol')
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with atomic values or array values' do
|
||||
let_it_be(:new_context) { { b: 97, d: [99] } }
|
||||
|
||||
it 'overrides the values' do
|
||||
expected_context = { 'a' => 42, 'b' => 97, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [99] }
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with nested hashes' do
|
||||
let_it_be(:new_context) { { c: { g: 107 } } }
|
||||
|
||||
it 'inserts nested additional values in the same keys' do
|
||||
expected_context = initial_expected_context.deep_merge('c' => { 'g' => 107 })
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#record_conversion_event_for_user' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:experiment) { create(:experiment) }
|
||||
let_it_be(:context) { { a: 42 } }
|
||||
|
||||
subject(:record_conversion_event_for_user) { experiment.record_conversion_event_for_user(user) }
|
||||
subject { experiment.record_conversion_event_for_user(user, context) }
|
||||
|
||||
context 'when no existing experiment_user record exists for the given user' do
|
||||
it 'does not update or create an experiment_user record' do
|
||||
expect { record_conversion_event_for_user }.not_to change { ExperimentUser.all.to_a }
|
||||
expect { subject }.not_to change { ExperimentUser.all.to_a }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -151,7 +209,13 @@ RSpec.describe Experiment do
|
|||
let!(:experiment_user) { create(:experiment_user, experiment: experiment, user: user, converted_at: 2.days.ago) }
|
||||
|
||||
it 'does not update the converted_at value' do
|
||||
expect { record_conversion_event_for_user }.not_to change { experiment_user.converted_at }
|
||||
expect { subject }.not_to change { experiment_user.converted_at }
|
||||
end
|
||||
|
||||
it_behaves_like 'experiment user with context' do
|
||||
before do
|
||||
experiment.record_user_and_group(user, :experimental, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,7 +223,13 @@ RSpec.describe Experiment do
|
|||
let(:experiment_user) { create(:experiment_user, experiment: experiment, user: user) }
|
||||
|
||||
it 'updates the converted_at value' do
|
||||
expect { record_conversion_event_for_user }.to change { experiment_user.reload.converted_at }
|
||||
expect { subject }.to change { experiment_user.reload.converted_at }
|
||||
end
|
||||
|
||||
it_behaves_like 'experiment user with context' do
|
||||
before do
|
||||
experiment.record_user_and_group(user, :experimental, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -196,24 +266,25 @@ RSpec.describe Experiment do
|
|||
describe '#record_user_and_group' do
|
||||
let_it_be(:experiment) { create(:experiment) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { :control }
|
||||
let_it_be(:context) { { a: 42 } }
|
||||
|
||||
let(:group) { :control }
|
||||
let(:context) { { a: 42 } }
|
||||
|
||||
subject(:record_user_and_group) { experiment.record_user_and_group(user, group, context) }
|
||||
subject { experiment.record_user_and_group(user, group, context) }
|
||||
|
||||
context 'when an experiment_user does not yet exist for the given user' do
|
||||
it 'creates a new experiment_user record' do
|
||||
expect { record_user_and_group }.to change(ExperimentUser, :count).by(1)
|
||||
expect { subject }.to change(ExperimentUser, :count).by(1)
|
||||
end
|
||||
|
||||
it 'assigns the correct group_type to the experiment_user' do
|
||||
record_user_and_group
|
||||
subject
|
||||
|
||||
expect(ExperimentUser.last.group_type).to eq('control')
|
||||
end
|
||||
|
||||
it 'adds the correct context to the experiment_user' do
|
||||
record_user_and_group
|
||||
subject
|
||||
|
||||
expect(ExperimentUser.last.context).to eq({ 'a' => 42 })
|
||||
end
|
||||
end
|
||||
|
@ -225,72 +296,18 @@ RSpec.describe Experiment do
|
|||
end
|
||||
|
||||
it 'does not create a new experiment_user record' do
|
||||
expect { record_user_and_group }.not_to change(ExperimentUser, :count)
|
||||
expect { subject }.not_to change(ExperimentUser, :count)
|
||||
end
|
||||
|
||||
context 'but the group_type and context has changed' do
|
||||
let(:group) { :experimental }
|
||||
|
||||
it 'updates the existing experiment_user record with group_type' do
|
||||
expect { record_user_and_group }.to change { ExperimentUser.last.group_type }
|
||||
expect { subject }.to change { ExperimentUser.last.group_type }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a context already exists' do
|
||||
let_it_be(:context) { { a: 42, 'b' => 34, 'c': { c1: 100, c2: 'c2', e: :e }, d: [1, 3] } }
|
||||
let_it_be(:initial_expected_context) { { 'a' => 42, 'b' => 34, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [1, 3] } }
|
||||
|
||||
before do
|
||||
record_user_and_group
|
||||
experiment.record_user_and_group(user, :control, {})
|
||||
end
|
||||
|
||||
it 'has an initial context with stringified keys' do
|
||||
expect(ExperimentUser.last.context).to eq(initial_expected_context)
|
||||
end
|
||||
|
||||
context 'when updated' do
|
||||
before do
|
||||
record_user_and_group
|
||||
experiment.record_user_and_group(user, :control, new_context)
|
||||
end
|
||||
|
||||
context 'with an empty context' do
|
||||
let_it_be(:new_context) { {} }
|
||||
|
||||
it 'keeps the initial context' do
|
||||
expect(ExperimentUser.last.context).to eq(initial_expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with string keys' do
|
||||
let_it_be(:new_context) { { f: :some_symbol } }
|
||||
|
||||
it 'adds new symbols stringified' do
|
||||
expected_context = initial_expected_context.merge('f' => 'some_symbol')
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with atomic values or array values' do
|
||||
let_it_be(:new_context) { { b: 97, d: [99] } }
|
||||
|
||||
it 'overrides the values' do
|
||||
expected_context = { 'a' => 42, 'b' => 97, 'c' => { 'c1' => 100, 'c2' => 'c2', 'e' => 'e' }, 'd' => [99] }
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with nested hashes' do
|
||||
let_it_be(:new_context) { { c: { g: 107 } } }
|
||||
|
||||
it 'inserts nested additional values in the same keys' do
|
||||
expected_context = initial_expected_context.deep_merge('c' => { 'g' => 107 })
|
||||
expect(ExperimentUser.last.context).to eq(expected_context)
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'experiment user with context'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -187,6 +187,24 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
|
|||
MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
it 'tracks milestone change' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_milestone_changed_action).once.with(user: user)
|
||||
|
||||
opts[:milestone] = milestone
|
||||
|
||||
MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
it 'track labels change' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_labels_changed_action).once.with(user: user)
|
||||
|
||||
opts[:label_ids] = [label2.id]
|
||||
|
||||
MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
context 'assignees' do
|
||||
context 'when assignees changed' do
|
||||
it 'tracks assignees changed event' do
|
||||
|
|
Loading…
Reference in a new issue