Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-29 18:13:42 +00:00
parent 51a7dc1b9d
commit 7504274091
31 changed files with 815 additions and 161 deletions

View File

@ -0,0 +1,200 @@
<script>
import {
GlDropdown,
GlDropdownItem,
GlModal,
GlModalDirective,
GlSprintf,
GlFormGroup,
GlFormInput,
} from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import { DELETE_AGENT_MODAL_ID } from '../constants';
import deleteAgent from '../graphql/mutations/delete_agent.mutation.graphql';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import { removeAgentFromStore } from '../graphql/cache_update';
export default {
i18n: {
dropdownText: __('More options'),
deleteButton: s__('ClusterAgents|Delete agent'),
modalTitle: __('Are you sure?'),
modalBody: s__(
'ClusterAgents|Are you sure you want to delete this agent? You cannot undo this.',
),
modalInputLabel: s__('ClusterAgents|To delete the agent, type %{name} to confirm:'),
modalAction: s__('ClusterAgents|Delete'),
modalCancel: __('Cancel'),
successMessage: s__('ClusterAgents|%{name} successfully deleted'),
defaultError: __('An error occurred. Please try again.'),
},
components: {
GlDropdown,
GlDropdownItem,
GlModal,
GlSprintf,
GlFormGroup,
GlFormInput,
},
directives: {
GlModalDirective,
},
inject: ['projectPath'],
props: {
agent: {
required: true,
type: Object,
validator: (value) => ['id', 'name'].every((prop) => value[prop]),
},
defaultBranchName: {
default: '.noBranch',
required: false,
type: String,
},
maxAgents: {
default: null,
required: false,
type: Number,
},
},
data() {
return {
loading: false,
error: null,
deleteConfirmText: null,
agentName: this.agent.name,
};
},
computed: {
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
first: this.maxAgents,
last: null,
projectPath: this.projectPath,
};
},
modalId() {
return sprintf(DELETE_AGENT_MODAL_ID, {
agentName: this.agent.name,
});
},
primaryModalProps() {
return {
text: this.$options.i18n.modalAction,
attributes: [
{ disabled: this.loading || this.disableModalSubmit, loading: this.loading },
{ variant: 'danger' },
],
};
},
cancelModalProps() {
return {
text: this.$options.i18n.modalCancel,
attributes: [],
};
},
disableModalSubmit() {
return this.deleteConfirmText !== this.agent.name;
},
},
methods: {
async deleteAgent() {
if (this.disableModalSubmit || this.loading) {
return;
}
this.loading = true;
this.error = null;
try {
const { errors } = await this.deleteAgentMutation();
if (errors.length) {
throw new Error(errors[0]);
}
} catch (error) {
this.error = error?.message || this.$options.i18n.defaultError;
} finally {
this.loading = false;
const successMessage = sprintf(this.$options.i18n.successMessage, { name: this.agentName });
this.$toast.show(this.error || successMessage);
this.$refs.modal.hide();
}
},
deleteAgentMutation() {
return this.$apollo
.mutate({
mutation: deleteAgent,
variables: {
input: {
id: this.agent.id,
},
},
update: (store) => {
const deleteClusterAgent = this.agent;
removeAgentFromStore(
store,
deleteClusterAgent,
getAgentsQuery,
this.getAgentsQueryVariables,
);
},
})
.then(({ data: { clusterAgentDelete } }) => {
return clusterAgentDelete;
});
},
hideModal() {
this.loading = false;
this.error = null;
this.deleteConfirmText = null;
},
},
};
</script>
<template>
<div>
<gl-dropdown
icon="ellipsis_v"
right
:disabled="loading"
:text="$options.i18n.dropdownText"
text-sr-only
category="tertiary"
no-caret
>
<gl-dropdown-item v-gl-modal-directive="modalId">
{{ $options.i18n.deleteButton }}
</gl-dropdown-item>
</gl-dropdown>
<gl-modal
ref="modal"
:modal-id="modalId"
:title="$options.i18n.modalTitle"
:action-primary="primaryModalProps"
:action-cancel="cancelModalProps"
size="sm"
@primary="deleteAgent"
@hide="hideModal"
>
<p>{{ $options.i18n.modalBody }}</p>
<gl-form-group>
<template #label>
<gl-sprintf :message="$options.i18n.modalInputLabel">
<template #name>
<code>{{ agent.name }}</code>
</template>
</gl-sprintf>
</template>
<gl-form-input v-model="deleteConfirmText" @keydown.enter="deleteAgent" />
</gl-form-group>
</gl-modal>
</div>
</template>

View File

@ -1,21 +1,23 @@
<script>
import {
GlLink,
GlModalDirective,
GlTable,
GlIcon,
GlSprintf,
GlTooltip,
GlPopover,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import { GlLink, GlTable, GlIcon, GlSprintf, GlTooltip, GlPopover } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { helpPagePath } from '~/helpers/help_page_helper';
import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
import { AGENT_STATUSES } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
import AgentOptions from './agent_options.vue';
export default {
i18n: {
nameLabel: s__('ClusterAgents|Name'),
statusLabel: s__('ClusterAgents|Connection status'),
lastContactLabel: s__('ClusterAgents|Last contact'),
configurationLabel: s__('ClusterAgents|Configuration'),
optionsLabel: __('Options'),
troubleshootingText: s__('ClusterAgents|Learn how to troubleshoot'),
neverConnectedText: s__('ClusterAgents|Never'),
},
components: {
GlLink,
GlTable,
@ -24,14 +26,10 @@ export default {
GlTooltip,
GlPopover,
TimeAgoTooltip,
},
directives: {
GlModalDirective,
AgentOptions,
},
mixins: [timeagoMixin],
INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
troubleshooting_link: helpPagePath('user/clusters/agent/index', {
anchor: 'troubleshooting',
}),
@ -40,6 +38,16 @@ export default {
required: true,
type: Array,
},
defaultBranchName: {
default: '.noBranch',
required: false,
type: String,
},
maxAgents: {
default: null,
required: false,
type: Number,
},
},
computed: {
fields() {
@ -47,22 +55,27 @@ export default {
return [
{
key: 'name',
label: s__('ClusterAgents|Name'),
label: this.$options.i18n.nameLabel,
tdClass,
},
{
key: 'status',
label: s__('ClusterAgents|Connection status'),
label: this.$options.i18n.statusLabel,
tdClass,
},
{
key: 'lastContact',
label: s__('ClusterAgents|Last contact'),
label: this.$options.i18n.lastContactLabel,
tdClass,
},
{
key: 'configuration',
label: s__('ClusterAgents|Configuration'),
label: this.$options.i18n.configurationLabel,
tdClass,
},
{
key: 'options',
label: this.$options.i18n.optionsLabel,
tdClass,
},
];
@ -118,7 +131,7 @@ export default {
</p>
<p class="gl-mb-0">
<gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
{{ s__('ClusterAgents|Learn how to troubleshoot') }}</gl-link
{{ $options.i18n.troubleshootingText }}</gl-link
>
</p>
</gl-popover>
@ -127,7 +140,7 @@ export default {
<template #cell(lastContact)="{ item }">
<span data-testid="cluster-agent-last-contact">
<time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
<span v-else>{{ s__('ClusterAgents|Never') }}</span>
<span v-else>{{ $options.i18n.neverConnectedText }}</span>
</span>
</template>
@ -140,5 +153,13 @@ export default {
<span v-else>{{ getAgentConfigPath(item.name) }}</span>
</span>
</template>
<template #cell(options)="{ item }">
<agent-options
:agent="item"
:default-branch-name="defaultBranchName"
:max-agents="maxAgents"
/>
</template>
</gl-table>
</template>

View File

@ -151,7 +151,11 @@ export default {
<section v-else-if="agentList">
<div v-if="agentList.length">
<agent-table :agents="agentList" />
<agent-table
:agents="agentList"
:default-branch-name="defaultBranchName"
:max-agents="cursor.first"
/>
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />

View File

@ -242,3 +242,5 @@ export const EVENT_ACTIONS_CHANGE = 'change_tab';
export const MODAL_TYPE_EMPTY = 'empty_state';
export const MODAL_TYPE_REGISTER = 'agent_registration';
export const DELETE_AGENT_MODAL_ID = 'delete-agent-modal-%{agentName}';

View File

@ -63,3 +63,25 @@ export function addAgentConfigToStore(
});
}
}
export function removeAgentFromStore(store, deleteClusterAgent, query, variables) {
if (!hasErrors(deleteClusterAgent)) {
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, (draftData) => {
draftData.project.clusterAgents.nodes = draftData.project.clusterAgents.nodes.filter(
({ id }) => id !== deleteClusterAgent.id,
);
draftData.project.clusterAgents.count -= 1;
});
store.writeQuery({
query,
variables,
data,
});
}
}

View File

@ -0,0 +1,5 @@
mutation deleteClusterAgent($input: ClusterAgentDeleteInput!) {
clusterAgentDelete(input: $input) {
errors
}
}

View File

@ -1,8 +1,10 @@
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadClusters from './load_clusters';
import loadMainView from './load_main_view';
Vue.use(GlToast);
Vue.use(VueApollo);
export default () => {

View File

@ -155,7 +155,7 @@ module Repositories
end
def should_auto_link?
return false unless Feature.enabled?(:lfs_auto_link_fork_source, project)
return false unless Feature.enabled?(:lfs_auto_link_fork_source, project, default_enabled: :yaml)
return false unless project.forked?
# Sanity check in case for some reason the user doesn't have access to the parent

View File

@ -20,7 +20,14 @@ class OnboardingProgress < ApplicationRecord
:issue_created,
:issue_auto_closed,
:repository_imported,
:repository_mirrored
:repository_mirrored,
:secure_dependency_scanning_run,
:secure_container_scanning_run,
:secure_dast_run,
:secure_secret_detection_run,
:secure_coverage_fuzzing_run,
:secure_api_fuzzing_run,
:secure_cluster_image_scanning_run
].freeze
scope :incomplete_actions, -> (actions) do
@ -52,12 +59,19 @@ class OnboardingProgress < ApplicationRecord
where(namespace: namespace).any?
end
def register(namespace, action)
return unless root_namespace?(namespace) && ACTIONS.include?(action)
def register(namespace, actions)
actions = Array(actions)
return unless root_namespace?(namespace) && actions.difference(ACTIONS).empty?
action_column = column_name(action)
onboarding_progress = find_by(namespace: namespace, action_column => nil)
onboarding_progress&.update!(action_column => Time.current)
onboarding_progress = find_by(namespace: namespace)
return unless onboarding_progress
now = Time.current
nil_actions = actions.select { |action| onboarding_progress[column_name(action)].nil? }
return if nil_actions.empty?
updates = nil_actions.inject({}) { |sum, action| sum.merge!({ column_name(action) => now }) }
onboarding_progress.update!(updates)
end
def completed?(namespace, action)

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348243
milestone: '14.6'
type: development
group: group::source code
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddSecureScanningActionsToOnboardingProgresses < Gitlab::Database::Migration[1.0]
def change
change_table(:onboarding_progresses, bulk: true) do |t|
t.column :secure_dependency_scanning_run_at, :datetime_with_timezone
t.column :secure_container_scanning_run_at, :datetime_with_timezone
t.column :secure_dast_run_at, :datetime_with_timezone
t.column :secure_secret_detection_run_at, :datetime_with_timezone
t.column :secure_coverage_fuzzing_run_at, :datetime_with_timezone
t.column :secure_cluster_image_scanning_run_at, :datetime_with_timezone
t.column :secure_api_fuzzing_run_at, :datetime_with_timezone
end
end
end

View File

@ -0,0 +1 @@
80c1ad5815ef68ab1a7d63566d478683b3f9a5169ed15ecd6f44f7f542d40dc8

View File

@ -16778,7 +16778,14 @@ CREATE TABLE onboarding_progresses (
issue_auto_closed_at timestamp with time zone,
repository_imported_at timestamp with time zone,
repository_mirrored_at timestamp with time zone,
issue_created_at timestamp with time zone
issue_created_at timestamp with time zone,
secure_dependency_scanning_run_at timestamp with time zone,
secure_container_scanning_run_at timestamp with time zone,
secure_dast_run_at timestamp with time zone,
secure_secret_detection_run_at timestamp with time zone,
secure_coverage_fuzzing_run_at timestamp with time zone,
secure_cluster_image_scanning_run_at timestamp with time zone,
secure_api_fuzzing_run_at timestamp with time zone
);
CREATE SEQUENCE onboarding_progresses_id_seq

View File

@ -671,7 +671,7 @@ Data that was created on the primary while the secondary was paused is lost.
If you are running GitLab 14.5 and later:
1. SSH to every Sidekiq, PostgresSQL, and Gitaly node in the **secondary** site and run one of the following commands:
1. For each node outside of the **secondary** Kubernetes cluster using Omnibus such as PostgreSQL or Gitaly, SSH into the node and run one of the following commands:
- To promote the secondary node to primary:
@ -685,19 +685,17 @@ If you are running GitLab 14.5 and later:
sudo gitlab-ctl geo promote --force
```
1. SSH into each Rails node on your **secondary** site and run one of the following commands:
1. Find the `toolbox` pod:
- To promote the secondary node to primary:
```shell
kubectl --namespace gitlab get pods -lapp=toolbox
```
```shell
sudo gitlab-ctl geo promote
```
1. Promote the secondary:
- To promote the secondary node to primary **without any further confirmation**:
```shell
sudo gitlab-ctl geo promote --force
```
```shell
kubectl --namespace gitlab exec -ti gitlab-geo-toolbox-XXX -- gitlab-rake geo:set_secondary_as_primary
```
If you are running GitLab 14.4 and earlier:
@ -708,8 +706,6 @@ If you are running GitLab 14.4 and earlier:
sudo gitlab-ctl promote-db
```
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
1. Edit `/etc/gitlab/gitlab.rb` on the database node in the **secondary** site to
reflect its new status as **primary** by removing any lines that enabled the
`geo_secondary_role`:

View File

@ -10,8 +10,8 @@ description: "Set and configure Git protocol v2"
> [Re-enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/27828) in GitLab 12.8.
Git protocol v2 improves the v1 wire protocol in several ways and is
enabled by default in GitLab for HTTP requests. In order to enable SSH,
further configuration is needed by the administrator.
enabled by default in GitLab for HTTP requests. To enable SSH, additional
configuration is required by an administrator.
More details about the new features and improvements are available in
the [Google Open Source Blog](https://opensource.googleblog.com/2018/05/introducing-git-protocol-version-2.html)
@ -48,7 +48,7 @@ sudo systemctl restart ssh
## Instructions
In order to use the new protocol, clients need to either pass the configuration
To use the new protocol, clients need to either pass the configuration
`-c protocol.version=2` to the Git command, or set it globally:
```shell

View File

@ -1,7 +1,7 @@
---
stage: Create
group: Source Code
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"
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
---
# Commits API **(FREE)**
@ -502,9 +502,8 @@ Example response:
Adds a comment to a commit.
In order to post a comment in a particular line of a particular file, you must
specify the full commit SHA, the `path`, the `line` and `line_type` should be
`new`.
To post a comment in a particular line of a particular file, you must specify
the full commit SHA, the `path`, the `line`, and `line_type` should be `new`.
The comment is added at the end of the last commit if at least one of the
cases below is valid:

View File

@ -3837,13 +3837,13 @@ The following keywords are deprecated.
### Globally-defined `types`
WARNING:
`types` is deprecated, and could be removed in a future release.
`types` is deprecated, and is [scheduled to be removed in GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/346823).
Use [`stages`](#stages) instead.
### Job-defined `type`
WARNING:
`type` is deprecated, and could be removed in one of the future releases.
`type` is deprecated, and is [scheduled to be removed in GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/346823).
Use [`stage`](#stage) instead.
### Globally-defined `image`, `services`, `cache`, `before_script`, `after_script`

View File

@ -1,15 +1,14 @@
---
stage: Create
group: Source Code
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"
type: howto, reference
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
---
# Edit files through the command line **(FREE)**
When [working with Git from the command line](start-using-git.md), you need to
use more than just the Git commands. There are several basic commands that you should
learn, in order to make full use of the command line.
learn to make full use of the command line.
## Start working on your project

View File

@ -1,7 +1,7 @@
---
stage: Create
group: Source Code
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"
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
disqus_identifier: 'https://docs.gitlab.com/ee/workflow/gitlab_flow.html'
---
@ -47,9 +47,11 @@ For a video introduction of how this works in GitLab, see [GitLab Flow](https://
<!-- vale gitlab.Spelling = YES -->
Git flow was one of the first proposals to use Git branches, and it has received a lot of attention.
It suggests a `main` branch and a separate `develop` branch, as well as supporting branches for features, releases, and hotfixes.
The development happens on the `develop` branch, moves to a release branch, and is finally merged into the `main` branch.
Git flow was one of the first proposals to use Git branches, and it has received
a lot of attention. It suggests a `main` branch and a separate `develop` branch,
with supporting branches for features, releases, and hotfixes. The development
happens on the `develop` branch, moves to a release branch, and is finally merged
into the `main` branch.
Git flow is a well-defined standard, but its complexity introduces two problems.
The first problem is that developers must use the `develop` branch and not `main`. `main` is reserved for code that is released to production.

View File

@ -2,7 +2,6 @@
stage: none
group: unassigned
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
type: reference
---
# Sign-in restrictions **(FREE SELF)**
@ -21,8 +20,11 @@ To access sign-in restriction settings:
You can restrict the password authentication for web interface and Git over HTTP(S):
- **Web interface**: When this feature is disabled, the **Standard** sign-in tab is removed and an [external authentication provider](../../../administration/auth/index.md) must be used.
- **Git over HTTP(S)**: When this feature is disabled, a [Personal Access Token](../../profile/personal_access_tokens.md) must be used to authenticate.
- **Web interface**: When this feature is disabled, the **Standard** sign-in tab
is removed and an [external authentication provider](../../../administration/auth/index.md)
must be used.
- **Git over HTTP(S)**: When this feature is disabled, a [Personal Access Token](../../profile/personal_access_tokens.md)
or LDAP password must be used to authenticate.
In the event of an external authentication provider outage, use the [GitLab Rails console](../../../administration/operations/rails_console.md) to [re-enable the standard web sign-in form](../../../administration/troubleshooting/gitlab_rails_cheat_sheet.md#re-enable-standard-web-sign-in-form). This configuration can also be changed over the [Application settings REST API](../../../api/settings.md#change-application-settings) while authenticating with an administrator account's personal access token.

View File

@ -136,7 +136,22 @@ with the following differences:
## Remove an agent
1. Get the `<cluster-agent-id>` and the `<cluster-agent-token-id>` from a query in the interactive GraphQL explorer.
You can remove an agent using the [GitLab UI](#remove-an-agent-through-the-gitlab-ui) or through the [GraphQL API](#remove-an-agent-with-the-gitlab-graphql-api).
### Remove an agent through the GitLab UI
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323055) in GitLab 14.7.
To remove an agent from the UI:
1. Go to your agent's configuration repository.
1. From your project's sidebar, select **Infrastructure > Kubernetes clusters**.
1. Select your agent from the table, and then in the **Options** column, click the vertical ellipsis
(**{ellipsis_v}**) button and select **Delete agent**.
### Remove an agent with the GitLab GraphQL API
1. Get the `<cluster-agent-token-id>` from a query in the interactive GraphQL explorer.
For GitLab.com, go to <https://gitlab.com/-/graphql-explorer> to open GraphQL Explorer.
For self-managed GitLab instances, go to `https://gitlab.example.com/-/graphql-explorer`, replacing `gitlab.example.com` with your own instance's URL.
@ -157,7 +172,7 @@ For self-managed GitLab instances, go to `https://gitlab.example.com/-/graphql-e
}
```
1. Remove an Agent record with GraphQL by deleting the `clusterAgent` and the `clusterAgentToken`.
1. Remove an agent record with GraphQL by deleting the `clusterAgentToken`.
```graphql
mutation deleteAgent {

View File

@ -84,7 +84,7 @@ Badges directly associated with a project can be configured on the
## Placeholders
The URL a badge points to, as well as the image URL, can contain placeholders
Both the URL a badge points to and the image URL can contain placeholders
which are evaluated when displaying the badge. The following placeholders
are available:

View File

@ -639,6 +639,9 @@ You can then see the issue's status in the issues list and the epic tree.
After an issue is closed, its health status can't be edited and the **Edit** button becomes disabled
until the issue is reopened.
You can also set and clear health statuses using the `/health_status` and `/clear_health_status`
[quick actions](../quick_actions.md#issues-merge-requests-and-epics).
## Publish an issue **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.1.

View File

@ -2,13 +2,10 @@
stage: Create
group: Source Code
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
type: reference, concepts
---
# Squash and merge **(FREE)**
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18956) from GitLab Premium to GitLab Free in 11.0.
With squash and merge you can combine all your merge request's commits into one
and retain a clean history.
@ -75,7 +72,7 @@ can be either selected or unselected:
![Squash commits checkbox on accept merge request form](img/squash_mr_widget.png)
Note that Squash and Merge might not be available depending on the project's configuration
Squash and Merge might not be available depending on the project's configuration
for [Squash Commit Options](#squash-commits-options).
## Commit metadata for squashed commits
@ -88,9 +85,9 @@ The squashed commit has the following metadata:
## Squash and fast-forward merge
When a project has the [fast-forward merge setting enabled](fast_forward_merge.md#enabling-fast-forward-merges), the merge
request must be able to be fast-forwarded without squashing in order to squash
it. This is because squashing is only available when accepting a merge request,
When a project has the [fast-forward merge setting enabled](fast_forward_merge.md#enabling-fast-forward-merges),
the merge request must be able to be fast-forwarded without squashing to squash
it. This is because squashing is available only when accepting a merge request,
so a merge request may need to be rebased before squashing, even though
squashing can itself be considered equivalent to rebasing.

View File

@ -47,76 +47,78 @@ threads. Some quick actions might not be available to all subscription tiers.
<!-- Keep this table sorted alphabetically -->
| Command | Issue | Merge request | Epic | Action |
|:--------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------|
| `/add_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). |
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. |
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users. |
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself. |
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users as reviewers. |
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. |
| `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award. |
| `/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 <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. Learn more about [time tracking](time_tracking.md). |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. |
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced 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/pipelines/merge_trains.md). |
| `/milestone %milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set milestone. |
| `/move <path/to/project>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Move this issue to another project. |
| `/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. |
| `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced in GitLab 14.5](https://gitlab.com/gitlab-org/gitlab/-/issues/296787)). |
| `/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. |
| `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. |
| `/rebase` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Rebase source branch. This schedules a background task that attempts to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` is ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. If there are merge conflicts, GitLab displays a message that a rebase cannot be scheduled. Rebase failures are displayed with the merge request status. |
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. |
| `/relate #issue1 #issue2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark issues as related. |
| `/remove_child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). |
| `/remove_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). |
| `/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. |
| `/severity <severity>` | **{check-circle}** Yes | **{check-circle}** No | **{check-circle}** No | Set the severity. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced in GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/334045). |
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. |
| `/spend <time> [<date>]` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add or subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend 1mo 2w 3d 4h 5m 2018-08-26` or `/spend -1h 30m`. Learn more about [time tracking](time_tracking.md). |
| `/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. |
| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced in GitLab 14.3](https://gitlab.com/gitlab-org/gitlab/-/issues/8103)|
| `/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. |
| `/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)). |
| Command | Issue | Merge request | Epic | Action |
|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `/add_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). |
| `/approve` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Approve the merge request. |
| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users. |
| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself. |
| `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users as reviewers. |
| `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. |
| `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle emoji award. |
| `/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_health_status` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear [health status](issues/managing_issues.md#health-status) ([introduced in GitLab 14.7](https://gitlab.com/gitlab-org/gitlab/-/issues/213814)). |
| `/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 <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. Learn more about [time tracking](time_tracking.md). |
| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk` ([introduced in GitLab 14.7](https://gitlab.com/gitlab-org/gitlab/-/issues/213814)). |
| `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. |
| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced 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/pipelines/merge_trains.md). |
| `/milestone %milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set milestone. |
| `/move <path/to/project>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Move this issue to another project. |
| `/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. |
| `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced in GitLab 14.5](https://gitlab.com/gitlab-org/gitlab/-/issues/296787)). |
| `/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. |
| `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. |
| `/rebase` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Rebase source branch. This schedules a background task that attempts to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` is ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. If there are merge conflicts, GitLab displays a message that a rebase cannot be scheduled. Rebase failures are displayed with the merge request status. |
| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. |
| `/relate #issue1 #issue2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark issues as related. |
| `/remove_child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). |
| `/remove_contacts email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove one or more [CRM contacts](../crm/index.md) ([introduced in GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73413)). |
| `/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. |
| `/severity <severity>` | **{check-circle}** Yes | **{check-circle}** No | **{check-circle}** No | Set the severity. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced in GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/334045). |
| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. |
| `/spend <time> [<date>]` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add or subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend 1mo 2w 3d 4h 5m 2018-08-26` or `/spend -1h 30m`. Learn more about [time tracking](time_tracking.md). |
| `/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. |
| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced in GitLab 14.3](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) |
| `/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. |
| `/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

View File

@ -39,6 +39,10 @@
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_clear_health_status
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_clone
category: quickactions
redis_slot: quickactions
@ -263,6 +267,10 @@
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_health_status
category: quickactions
redis_slot: quickactions
aggregation: weekly
- name: i_quickactions_wip
category: quickactions
redis_slot: quickactions

View File

@ -7329,6 +7329,9 @@ msgstr ""
msgid "Clear due date"
msgstr ""
msgid "Clear health status"
msgstr ""
msgid "Clear recent searches"
msgstr ""
@ -7347,9 +7350,15 @@ msgstr ""
msgid "Clear weight"
msgstr ""
msgid "Cleared health status."
msgstr ""
msgid "Cleared weight."
msgstr ""
msgid "Clears health status."
msgstr ""
msgid "Clears weight."
msgstr ""
@ -7503,6 +7512,9 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
msgid "ClusterAgents|%{name} successfully deleted"
msgstr ""
msgid "ClusterAgents|%{number} of %{total} agents"
msgstr ""
@ -7560,6 +7572,9 @@ msgstr ""
msgid "ClusterAgents|An unknown error occurred. Please try again."
msgstr ""
msgid "ClusterAgents|Are you sure you want to delete this agent? You cannot undo this."
msgstr ""
msgid "ClusterAgents|Certificate"
msgstr ""
@ -7605,6 +7620,12 @@ msgstr ""
msgid "ClusterAgents|Date created"
msgstr ""
msgid "ClusterAgents|Delete"
msgstr ""
msgid "ClusterAgents|Delete agent"
msgstr ""
msgid "ClusterAgents|Deprecated"
msgstr ""
@ -7718,6 +7739,9 @@ msgstr[1] ""
msgid "ClusterAgents|This agent has no tokens"
msgstr ""
msgid "ClusterAgents|To delete the agent, type %{name} to confirm:"
msgstr ""
msgid "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}What's the agent's configuration file?%{linkEnd}"
msgstr ""
@ -23038,6 +23062,9 @@ msgstr ""
msgid "More information."
msgstr ""
msgid "More options"
msgstr ""
msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr ""
@ -32296,6 +32323,12 @@ msgstr ""
msgid "Set due date"
msgstr ""
msgid "Set health status"
msgstr ""
msgid "Set health status to %{health_status}."
msgstr ""
msgid "Set iteration"
msgstr ""
@ -32452,6 +32485,9 @@ msgstr ""
msgid "Sets %{epic_ref} as parent epic."
msgstr ""
msgid "Sets health status to %{health_status}."
msgstr ""
msgid "Sets target branch to %{branch_name}."
msgstr ""

View File

@ -0,0 +1,211 @@
import { GlDropdown, GlDropdownItem, GlModal, GlFormInput } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { ENTER_KEY } from '~/lib/utils/keys';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import deleteAgentMutation from '~/clusters_list/graphql/mutations/delete_agent.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import AgentOptions from '~/clusters_list/components/agent_options.vue';
import { MAX_LIST_COUNT } from '~/clusters_list/constants';
import { getAgentResponse, mockDeleteResponse, mockErrorDeleteResponse } from '../mocks/apollo';
Vue.use(VueApollo);
const projectPath = 'path/to/project';
const defaultBranchName = 'default';
const maxAgents = MAX_LIST_COUNT;
const agent = {
id: 'agent-id',
name: 'agent-name',
webPath: 'agent-webPath',
};
describe('AgentOptions', () => {
let wrapper;
let toast;
let apolloProvider;
let deleteResponse;
const findModal = () => wrapper.findComponent(GlModal);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDeleteBtn = () => wrapper.findComponent(GlDropdownItem);
const findInput = () => wrapper.findComponent(GlFormInput);
const findPrimaryAction = () => findModal().props('actionPrimary');
const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr];
const createMockApolloProvider = ({ mutationResponse }) => {
deleteResponse = jest.fn().mockResolvedValue(mutationResponse);
return createMockApollo([[deleteAgentMutation, deleteResponse]]);
};
const writeQuery = () => {
apolloProvider.clients.defaultClient.cache.writeQuery({
query: getAgentsQuery,
variables: {
projectPath,
defaultBranchName,
first: maxAgents,
last: null,
},
data: getAgentResponse.data,
});
};
const createWrapper = ({ mutationResponse = mockDeleteResponse } = {}) => {
apolloProvider = createMockApolloProvider({ mutationResponse });
const provide = {
projectPath,
};
const propsData = {
defaultBranchName,
maxAgents,
agent,
};
toast = jest.fn();
wrapper = shallowMountExtended(AgentOptions, {
apolloProvider,
provide,
propsData,
mocks: { $toast: { show: toast } },
stubs: { GlModal },
});
wrapper.vm.$refs.modal.hide = jest.fn();
writeQuery();
return wrapper.vm.$nextTick();
};
const submitAgentToDelete = async () => {
findDeleteBtn().vm.$emit('click');
findInput().vm.$emit('input', agent.name);
await findModal().vm.$emit('primary');
};
beforeEach(() => {
return createWrapper({});
});
afterEach(() => {
wrapper.destroy();
apolloProvider = null;
deleteResponse = null;
toast = null;
});
describe('delete agent action', () => {
it('displays a delete button', () => {
expect(findDeleteBtn().text()).toBe('Delete agent');
});
describe('when clicking the delete button', () => {
beforeEach(() => {
findDeleteBtn().vm.$emit('click');
});
it('displays a delete confirmation modal', () => {
expect(findModal().isVisible()).toBe(true);
});
});
describe.each`
condition | agentName | isDisabled | mutationCalled
${'the input with agent name is missing'} | ${''} | ${true} | ${false}
${'the input with agent name is incorrect'} | ${'wrong-name'} | ${true} | ${false}
${'the input with agent name is correct'} | ${agent.name} | ${false} | ${true}
`('when $condition', ({ agentName, isDisabled, mutationCalled }) => {
beforeEach(() => {
findDeleteBtn().vm.$emit('click');
findInput().vm.$emit('input', agentName);
});
it(`${isDisabled ? 'disables' : 'enables'} the modal primary button`, () => {
expect(findPrimaryActionAttributes('disabled')).toBe(isDisabled);
});
describe('when user clicks the modal primary button', () => {
beforeEach(async () => {
await findModal().vm.$emit('primary');
});
if (mutationCalled) {
it('calls the delete mutation', () => {
expect(deleteResponse).toHaveBeenCalledWith({ input: { id: agent.id } });
});
} else {
it("doesn't call the delete mutation", () => {
expect(deleteResponse).not.toHaveBeenCalled();
});
}
});
describe('when user presses the enter button', () => {
beforeEach(async () => {
await findInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
});
if (mutationCalled) {
it('calls the delete mutation', () => {
expect(deleteResponse).toHaveBeenCalledWith({ input: { id: agent.id } });
});
} else {
it("doesn't call the delete mutation", () => {
expect(deleteResponse).not.toHaveBeenCalled();
});
}
});
});
describe('when agent was deleted successfully', () => {
beforeEach(async () => {
await submitAgentToDelete();
});
it('calls the toast action', () => {
expect(toast).toHaveBeenCalledWith(`${agent.name} successfully deleted`);
});
});
});
describe('when getting an error deleting agent', () => {
beforeEach(async () => {
await createWrapper({ mutationResponse: mockErrorDeleteResponse });
submitAgentToDelete();
});
it('displays the error message', () => {
expect(toast).toHaveBeenCalledWith('could not delete agent');
});
});
describe('when the delete modal was closed', () => {
beforeEach(async () => {
const loadingResponse = new Promise(() => {});
await createWrapper({ mutationResponse: loadingResponse });
submitAgentToDelete();
});
it('reenables the options dropdown', async () => {
expect(findPrimaryActionAttributes('loading')).toBe(true);
expect(findDropdown().attributes('disabled')).toBe('true');
await findModal().vm.$emit('hide');
expect(findPrimaryActionAttributes('loading')).toBe(false);
expect(findDropdown().attributes('disabled')).toBeUndefined();
});
it('clears the agent name input', async () => {
expect(findInput().attributes('value')).toBe(agent.name);
await findModal().vm.$emit('hide');
expect(findInput().attributes('value')).toBeUndefined();
});
});
});

View File

@ -1,16 +1,22 @@
import { GlLink, GlIcon } from '@gitlab/ui';
import AgentTable from '~/clusters_list/components/agent_table.vue';
import AgentOptions from '~/clusters_list/components/agent_options.vue';
import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import timeagoMixin from '~/vue_shared/mixins/timeago';
const connectedTimeNow = new Date();
const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME);
const provideData = {
projectPath: 'path/to/project',
};
const propsData = {
agents: [
{
name: 'agent-1',
id: 'agent-1-id',
configFolder: {
webPath: '/agent/full/path',
},
@ -21,6 +27,7 @@ const propsData = {
},
{
name: 'agent-2',
id: 'agent-2-id',
webPath: '/agent-2',
status: 'active',
lastContact: connectedTimeNow.getTime(),
@ -34,6 +41,7 @@ const propsData = {
},
{
name: 'agent-3',
id: 'agent-3-id',
webPath: '/agent-3',
status: 'inactive',
lastContact: connectedTimeInactive.getTime(),
@ -48,6 +56,10 @@ const propsData = {
],
};
const AgentOptionsStub = stubComponent(AgentOptions, {
template: `<div></div>`,
});
describe('AgentTable', () => {
let wrapper;
@ -57,15 +69,21 @@ describe('AgentTable', () => {
const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at);
const findConfiguration = (at) =>
wrapper.findAllByTestId('cluster-agent-configuration-link').at(at);
const findAgentOptions = () => wrapper.findAllComponents(AgentOptions);
beforeEach(() => {
wrapper = mountExtended(AgentTable, { propsData });
wrapper = mountExtended(AgentTable, {
propsData,
provide: provideData,
stubs: {
AgentOptions: AgentOptionsStub,
},
});
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
@ -108,5 +126,9 @@ describe('AgentTable', () => {
expect(findLink.exists()).toBe(hasLink);
expect(findConfiguration(lineNumber).text()).toBe(agentPath);
});
it('displays actions menu for each agent', () => {
expect(findAgentOptions()).toHaveLength(3);
});
});
});

View File

@ -75,3 +75,15 @@ export const getAgentResponse = {
},
},
};
export const mockDeleteResponse = {
data: { clusterAgentDelete: { errors: [] } },
};
export const mockErrorDeleteResponse = {
data: {
clusterAgentDelete: {
errors: ['could not delete agent'],
},
},
};

View File

@ -131,29 +131,86 @@ RSpec.describe OnboardingProgress do
end
describe '.register' do
subject(:register_action) { described_class.register(namespace, action) }
context 'for a single action' do
subject(:register_action) { described_class.register(namespace, action) }
context 'when the namespace was onboarded' do
before do
described_class.onboard(namespace)
context 'when the namespace was onboarded' do
before do
described_class.onboard(namespace)
end
it 'registers the action for the namespace' do
expect { register_action }.to change { described_class.completed?(namespace, action) }.from(false).to(true)
end
it 'does not override timestamp', :aggregate_failures do
expect(described_class.find_by_namespace_id(namespace.id).subscription_created_at).to be_nil
register_action
expect(described_class.find_by_namespace_id(namespace.id).subscription_created_at).not_to be_nil
expect { described_class.register(namespace, action) }.not_to change { described_class.find_by_namespace_id(namespace.id).subscription_created_at }
end
context 'when the action does not exist' do
let(:action) { :foo }
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(nil)
end
end
end
it 'registers the action for the namespace' do
expect { register_action }.to change { described_class.completed?(namespace, action) }.from(false).to(true)
end
context 'when the action does not exist' do
let(:action) { :foo }
context 'when the namespace was not onboarded' do
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(nil)
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(false)
end
end
end
context 'when the namespace was not onboarded' do
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(false)
context 'for multiple actions' do
let(:action1) { :security_scan_enabled }
let(:action2) { :secure_dependency_scanning_run }
let(:actions) { [action1, action2] }
subject(:register_action) { described_class.register(namespace, actions) }
context 'when the namespace was onboarded' do
before do
described_class.onboard(namespace)
end
it 'registers the actions for the namespace' do
expect { register_action }.to change {
[described_class.completed?(namespace, action1), described_class.completed?(namespace, action2)]
}.from([false, false]).to([true, true])
end
it 'does not override timestamp', :aggregate_failures do
described_class.register(namespace, [action1])
expect(described_class.find_by_namespace_id(namespace.id).security_scan_enabled_at).not_to be_nil
expect(described_class.find_by_namespace_id(namespace.id).secure_dependency_scanning_run_at).to be_nil
expect { described_class.register(namespace, [action1, action2]) }.not_to change {
described_class.find_by_namespace_id(namespace.id).security_scan_enabled_at
}
expect(described_class.find_by_namespace_id(namespace.id).secure_dependency_scanning_run_at).not_to be_nil
end
context 'when one of the actions does not exist' do
let(:action2) { :foo }
it 'does not register any action for the namespace' do
expect { register_action }.not_to change {
[described_class.completed?(namespace, action1), described_class.completed?(namespace, action2)]
}.from([false, nil])
end
end
end
context 'when the namespace was not onboarded' do
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action1) }.from(false)
expect { described_class.register(namespace, action) }.not_to change { described_class.completed?(namespace, action2) }.from(false)
end
end
end
end