Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-10 00:09:15 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-10 00:09:15 +0300
commitd8d0344cc3602e2625b7bbd79360b3e5b20b8870 (patch)
tree600a1eae6972365ac8a746054abf223318841068
parent72c331ebf56ba3d49a79ec799de84e790748adef (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue26
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue27
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/details_header.vue32
-rw-r--r--app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue6
-rw-r--r--app/assets/javascripts/registry/explorer/constants/common.js3
-rw-r--r--app/assets/javascripts/registry/explorer/constants/details.js8
-rw-r--r--app/assets/javascripts/registry/explorer/constants/index.js1
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue7
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue2
-rw-r--r--app/mailers/emails/pipelines.rb2
-rw-r--r--app/models/experiment.rb20
-rw-r--r--app/services/merge_requests/update_service.rb9
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml2
-rw-r--r--changelogs/unreleased/205484-08-project-settings-headers-repository-cleanup.yml5
-rw-r--r--changelogs/unreleased/292824-track-label-milestone-change.yml5
-rw-r--r--changelogs/unreleased/320901-container-page-says-image-repository-not-found.yml5
-rw-r--r--changelogs/unreleased/55412-typo-in-pipeline-status-email.yml5
-rw-r--r--config/feature_flags/development/usage_data_i_code_review_user_labels_changed.yml8
-rw-r--r--config/feature_flags/development/usage_data_i_code_review_user_milestone_changed.yml8
-rw-r--r--config/metrics/counts_28d/20210302110520_i_code_review_user_milestone_changed_monthly.yml20
-rw-r--r--config/metrics/counts_28d/20210302110607_i_code_review_user_labels_changed_monthly.yml20
-rw-r--r--config/metrics/counts_7d/20210302110403_i_code_review_user_milestone_changed_weekly.yml20
-rw-r--r--config/metrics/counts_7d/20210302110548_i_code_review_user_labels_changed_weekly.yml20
-rw-r--r--doc/api/oauth2.md10
-rw-r--r--doc/api/snippets.md8
-rw-r--r--doc/development/usage_ping/dictionary.md48
-rw-r--r--doc/user/group/epics/manage_epics.md2
-rw-r--r--doc/user/project/issues/associate_zoom_meeting.md2
-rw-r--r--doc/user/project/issues/managing_issues.md4
-rw-r--r--doc/user/project/merge_requests/drafts.md4
-rw-r--r--doc/user/project/merge_requests/getting_started.md2
-rw-r--r--doc/user/project/quick_actions.md200
-rw-r--r--lib/gitlab/experimentation/controller_concern.rb4
-rw-r--r--lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml8
-rw-r--r--lib/gitlab/usage_data_counters/known_events/code_review_events.yml10
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb10
-rw-r--r--locale/gitlab.pot17
-rw-r--r--spec/features/groups/container_registry_spec.rb8
-rw-r--r--spec/features/projects/container_registry_spec.rb8
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js8
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js31
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_header_spec.js51
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js11
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js22
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb16
-rw-r--r--spec/mailers/emails/pipelines_spec.rb2
-rw-r--r--spec/models/experiment_spec.rb157
-rw-r--r--spec/services/merge_requests/update_service_spec.rb18
52 files changed, 645 insertions, 285 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 2b795e2c604..0d61c12bd48 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-58bf16b78b3c99757a2f283a5befe57a2cb7f009
+3822772ed121b764fdcc5011d199c37ef76e06a9
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index 2ab49b6c760..47f1405c980 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -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
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index f8cc74511d9..666693e934f 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -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>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
index a4b4c08bc34..f46068acd68 100644
--- a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
+++ b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue
@@ -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" />
diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
index a09844b59bc..0373a84b271 100644
--- a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
@@ -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"
diff --git a/app/assets/javascripts/registry/explorer/constants/common.js b/app/assets/javascripts/registry/explorer/constants/common.js
new file mode 100644
index 00000000000..dc71ef8450b
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/constants/common.js
@@ -0,0 +1,3 @@
+import { s__ } from '~/locale';
+
+export const ROOT_IMAGE_TEXT = s__('ContainerRegistry|Root image');
diff --git a/app/assets/javascripts/registry/explorer/constants/details.js b/app/assets/javascripts/registry/explorer/constants/details.js
index 3f04538a18b..7220f9646db 100644
--- a/app/assets/javascripts/registry/explorer/constants/details.js
+++ b/app/assets/javascripts/registry/explorer/constants/details.js
@@ -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;
diff --git a/app/assets/javascripts/registry/explorer/constants/index.js b/app/assets/javascripts/registry/explorer/constants/index.js
index 10816e12ead..6886356d8e2 100644
--- a/app/assets/javascripts/registry/explorer/constants/index.js
+++ b/app/assets/javascripts/registry/explorer/constants/index.js
@@ -1,3 +1,4 @@
+export * from './common';
export * from './expiration_policies';
export * from './quick_start';
export * from './list';
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index 0403467468a..2f515356fa7 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -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) {
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 9d0631fbc01..4e59d0d81c1 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -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"
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 0b830f4ee5e..fb1f70723fd 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -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)
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index 354b1e0b6b9..ac8b6516d02 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -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
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index ae0d8bba9d4..f5e14797f7e 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -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
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index de9859230d5..df39e6297f6 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -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
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index f9af116fbff..b0112be0e3d 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -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
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 509092b64c9..425f76bd8d6 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -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
diff --git a/changelogs/unreleased/205484-08-project-settings-headers-repository-cleanup.yml b/changelogs/unreleased/205484-08-project-settings-headers-repository-cleanup.yml
new file mode 100644
index 00000000000..a0f79c935da
--- /dev/null
+++ b/changelogs/unreleased/205484-08-project-settings-headers-repository-cleanup.yml
@@ -0,0 +1,5 @@
+---
+title: Project Settings Repository Cleanup header expands/collapses on click / tap
+merge_request: 55232
+author: Daniel Schömer
+type: changed
diff --git a/changelogs/unreleased/292824-track-label-milestone-change.yml b/changelogs/unreleased/292824-track-label-milestone-change.yml
new file mode 100644
index 00000000000..4430cbda898
--- /dev/null
+++ b/changelogs/unreleased/292824-track-label-milestone-change.yml
@@ -0,0 +1,5 @@
+---
+title: Add tracking to merge request labels/milestone changes
+merge_request: 55484
+author:
+type: other
diff --git a/changelogs/unreleased/320901-container-page-says-image-repository-not-found.yml b/changelogs/unreleased/320901-container-page-says-image-repository-not-found.yml
new file mode 100644
index 00000000000..a9494b43b2e
--- /dev/null
+++ b/changelogs/unreleased/320901-container-page-says-image-repository-not-found.yml
@@ -0,0 +1,5 @@
+---
+title: Use Root Image for images with missing name
+merge_request: 54693
+author:
+type: changed
diff --git a/changelogs/unreleased/55412-typo-in-pipeline-status-email.yml b/changelogs/unreleased/55412-typo-in-pipeline-status-email.yml
new file mode 100644
index 00000000000..7fc2fb9416e
--- /dev/null
+++ b/changelogs/unreleased/55412-typo-in-pipeline-status-email.yml
@@ -0,0 +1,5 @@
+---
+title: Fix typo in pipeline status email
+merge_request: 55412
+author: Gabriel Berke-Williams
+type: fixed
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_labels_changed.yml b/config/feature_flags/development/usage_data_i_code_review_user_labels_changed.yml
new file mode 100644
index 00000000000..d70c6c963e8
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_labels_changed.yml
@@ -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
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_milestone_changed.yml b/config/feature_flags/development/usage_data_i_code_review_user_milestone_changed.yml
new file mode 100644
index 00000000000..0f64b7639c5
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_milestone_changed.yml
@@ -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
diff --git a/config/metrics/counts_28d/20210302110520_i_code_review_user_milestone_changed_monthly.yml b/config/metrics/counts_28d/20210302110520_i_code_review_user_milestone_changed_monthly.yml
new file mode 100644
index 00000000000..4edc596dca1
--- /dev/null
+++ b/config/metrics/counts_28d/20210302110520_i_code_review_user_milestone_changed_monthly.yml
@@ -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
diff --git a/config/metrics/counts_28d/20210302110607_i_code_review_user_labels_changed_monthly.yml b/config/metrics/counts_28d/20210302110607_i_code_review_user_labels_changed_monthly.yml
new file mode 100644
index 00000000000..713473cc832
--- /dev/null
+++ b/config/metrics/counts_28d/20210302110607_i_code_review_user_labels_changed_monthly.yml
@@ -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
diff --git a/config/metrics/counts_7d/20210302110403_i_code_review_user_milestone_changed_weekly.yml b/config/metrics/counts_7d/20210302110403_i_code_review_user_milestone_changed_weekly.yml
new file mode 100644
index 00000000000..2f158edf0af
--- /dev/null
+++ b/config/metrics/counts_7d/20210302110403_i_code_review_user_milestone_changed_weekly.yml
@@ -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
diff --git a/config/metrics/counts_7d/20210302110548_i_code_review_user_labels_changed_weekly.yml b/config/metrics/counts_7d/20210302110548_i_code_review_user_labels_changed_weekly.yml
new file mode 100644
index 00000000000..14710eb040a
--- /dev/null
+++ b/config/metrics/counts_7d/20210302110548_i_code_review_user_labels_changed_weekly.yml
@@ -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
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 92d109cac5f..ab106b4fb44 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -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).
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index 3b7184c9933..cf1cfa567db 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.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
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 6f021ae5ae7..ad95e041c2f 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -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
diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md
index 98aed0b52a1..3c5e140965a 100644
--- a/doc/user/group/epics/manage_epics.md
+++ b/doc/user/group/epics/manage_epics.md
@@ -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.
diff --git a/doc/user/project/issues/associate_zoom_meeting.md b/doc/user/project/issues/associate_zoom_meeting.md
index d81fe19c5b9..f98e94c66ae 100644
--- a/doc/user/project/issues/associate_zoom_meeting.md
+++ b/doc/user/project/issues/associate_zoom_meeting.md
@@ -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:
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index f1739726cf8..cfb22881431 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -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.
diff --git a/doc/user/project/merge_requests/drafts.md b/doc/user/project/merge_requests/drafts.md
index 522fef0be71..45348c3ae66 100644
--- a/doc/user/project/merge_requests/drafts.md
+++ b/doc/user/project/merge_requests/drafts.md
@@ -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.
diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md
index 3cc69ef82f5..cf1a359c56f 100644
--- a/doc/user/project/merge_requests/getting_started.md
+++ b/doc/user/project/merge_requests/getting_started.md
@@ -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
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index d7a6d412511..284deabb33c 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -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 for issues, merge requests and epics
-
-The following quick actions are applicable to descriptions, discussions and threads in:
-
-- Issues
-- Merge requests
-- Epics **(PREMIUM)**
-
-| 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
+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.
+
+Be sure to enter each quick action on a separate line to allow GitLab to
+properly detect and execute the commands.
+
+## Parameters
+
+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.
+
+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
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index 2b38b12c914..32f9f1b3157 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -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)
diff --git a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
index 7857c1b3b1d..4c2355d526a 100644
--- a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
+++ b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml
@@ -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
diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
index 5a7084f346e..18c5dc73de2 100644
--- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
@@ -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
diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
index a5122aa6ee0..eb28a387a97 100644
--- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
@@ -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)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 51c127effed..0e524204aaa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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"
diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb
index cacabdda22d..65374263f45 100644
--- a/spec/features/groups/container_registry_spec.rb
+++ b/spec/features/groups/container_registry_spec.rb
@@ -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
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index d0ad6668c07..40d0260eafd 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -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
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 028133c54ea..5ca5d855038 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -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.");
});
});
});
diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
index 8beadf12a36..f362aace1df 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -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);
});
});
});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
index 3fa3a2ae1de..b50ed87a563 100644
--- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
@@ -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', () => {
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index d6ee871341b..6c897b983f7 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -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();
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index 65c58bf9874..76baf4f72c9 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -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', () => {
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index 1cebe37bea5..bb9d273995a 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index d0c3456aa26..6486a5a22ba 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -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
diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb
index 3ac68721357..a29835f3439 100644
--- a/spec/mailers/emails/pipelines_spec.rb
+++ b/spec/mailers/emails/pipelines_spec.rb
@@ -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
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
index 22bbf2df8fd..09dd1766acc 100644
--- a/spec/models/experiment_spec.rb
+++ b/spec/models/experiment_spec.rb
@@ -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
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 86007c9cc72..7a7f684c6d0 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -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