Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-24 21:09:08 +00:00
parent 8015f09545
commit e09f6bdfd1
52 changed files with 340 additions and 179 deletions

View File

@ -38,9 +38,9 @@ export const toggleContainerClasses = (containerEl, classList) => {
/**
* Return a object mapping element dataset names to booleans.
*
* This is useful for data- attributes whose presence represent
* a truthiness, no matter the value of the attribute. The absence of the
* attribute represents falsiness.
* This is useful for data- attributes whose presense represent
* a truthiness, no matter the value of the attribute. The absense of the
* attribute represents falsiness.
*
* This can be useful when Rails-provided boolean-like values are passed
* directly to the HAML template, rather than cast to a string.

View File

@ -4,9 +4,10 @@ import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, LICENSE_ULTIMATE } from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
import UpgradeBanner from './upgrade_banner.vue';
@ -50,6 +51,17 @@ export default {
TrainingProviderList,
},
inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
apollo: {
currentLicensePlan: {
query: currentLicenseQuery,
update({ currentLicense }) {
return currentLicense?.plan;
},
error() {
this.hasCurrentLicenseFetchError = true;
},
},
},
props: {
augmentedSecurityFeatures: {
type: Array,
@ -84,15 +96,13 @@ export default {
required: false,
default: '',
},
securityTrainingEnabled: {
type: Boolean,
required: true,
},
},
data() {
return {
autoDevopsEnabledAlertDismissedProjects: [],
errorMessage: '',
currentLicensePlan: '',
hasCurrentLicenseFetchError: false,
};
},
computed: {
@ -113,6 +123,12 @@ export default {
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath)
);
},
shouldShowVulnerabilityManagementTab() {
// if the query fails (if the plan is `null` also means an error has occurred) we still want to show the feature
const hasQueryError = this.hasCurrentLicenseFetchError || this.currentLicensePlan === null;
return hasQueryError || this.currentLicensePlan === LICENSE_ULTIMATE;
},
},
methods: {
dismissAutoDevopsEnabledAlert() {
@ -237,9 +253,9 @@ export default {
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
<gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">
{{ $options.i18n.configurationHistory }}
</gl-link>
<gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{
$options.i18n.configurationHistory
}}</gl-link>
</p>
</template>
<template #features>
@ -254,18 +270,20 @@ export default {
</section-layout>
</gl-tab>
<gl-tab
v-if="securityTrainingEnabled"
v-if="shouldShowVulnerabilityManagementTab"
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
query-param-value="vulnerability-management"
>
<section-layout :heading="$options.i18n.securityTraining">
<template #description>
<p>{{ $options.i18n.securityTrainingDescription }}</p>
<p>
<gl-link :href="vulnerabilityTrainingDocsPath">
{{ $options.i18n.securityTrainingDoc }}
</gl-link>
{{ $options.i18n.securityTrainingDescription }}
</p>
<p>
<gl-link :href="vulnerabilityTrainingDocsPath">{{
$options.i18n.securityTrainingDoc
}}</gl-link>
</p>
</template>
<template #features>

View File

@ -310,3 +310,7 @@ export const TEMP_PROVIDER_URLS = {
Kontra: 'https://application.security/',
[__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
};
export const LICENSE_ULTIMATE = 'ultimate';
export const LICENSE_FREE = 'free';
export const LICENSE_PREMIUM = 'premium';

View File

@ -0,0 +1,6 @@
query getCurrentLicensePlan {
currentLicense {
id
plan
}
}

View File

@ -56,7 +56,6 @@ export const initSecurityConfiguration = (el) => {
'gitlabCiPresent',
'autoDevopsEnabled',
'canEnableAutoDevops',
'securityTrainingEnabled',
]),
},
});

View File

@ -155,7 +155,7 @@ export default {
requests.forEach((request) => {
const poll = new Poll({
resource: {
fetchData: () => request(this.$props),
fetchData: () => request(this),
},
method: 'fetchData',
successCallback: (response) => {
@ -180,7 +180,7 @@ export default {
initExtensionPolling() {
const poll = new Poll({
resource: {
fetchData: () => this.fetchCollapsedData(this.$props),
fetchData: () => this.fetchCollapsedData(this),
},
method: 'fetchData',
successCallback: ({ data }) => {
@ -208,7 +208,7 @@ export default {
this.initExtensionPolling();
}
} else {
this.fetchCollapsedData(this.$props)
this.fetchCollapsedData(this)
.then((data) => {
this.setCollapsedData(data);
})
@ -231,7 +231,7 @@ export default {
this.loadingState = LOADING_STATES.expandedLoading;
this.fetchFullData(this.$props)
this.fetchFullData(this)
.then((data) => {
this.loadingState = null;
this.fullData = data.map((x, i) => ({ id: i, ...x }));

View File

@ -34,13 +34,7 @@ export default {
{ ...extension },
{
props: {
...extension.props.reduce(
(acc, key) => ({
...acc,
[key]: this.mr[key],
}),
{},
),
mr: this.mr,
},
},
),

View File

@ -10,12 +10,26 @@ export const registerExtension = (extension) => {
registeredExtensions.extensions.push({
extends: ExtensionBase,
name: extension.name,
props: extension.props,
props: {
mr: {
type: Object,
required: true,
},
},
i18n: extension.i18n,
expandEvent: extension.expandEvent,
enablePolling: extension.enablePolling,
modalComponent: extension.modalComponent,
computed: {
...extension.props.reduce(
(acc, propKey) => ({
...acc,
[propKey]() {
return this.mr[propKey];
},
}),
{},
),
...Object.keys(extension.computed).reduce(
(acc, computedKey) => ({
...acc,

View File

@ -49,7 +49,7 @@ export default {
]"
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
>
<gl-loading-icon v-if="isLoading" size="lg" inline class="gl-display-block" />
<gl-loading-icon v-if="isLoading" size="sm" inline class="gl-display-block" />
<gl-icon
v-else
:name="$options.EXTENSION_ICON_NAMES[iconName]"

View File

@ -8,8 +8,7 @@
a {
color: $gl-text-color;
&.link,
&.gl-link {
&.link {
color: $blue-600;
}
}

View File

@ -246,13 +246,13 @@ module MergeRequestsHelper
''
end
link_to branch, branch_path, title: branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
link_to branch, branch_path, title: branch, class: 'gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
end
def merge_request_header(project, merge_request)
link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold', avatar: false)
copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy')
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mb-n2'
_('%{author} requested to merge %{source_branch} %{copy_button} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe }
end

View File

@ -118,7 +118,7 @@ class Label < ApplicationRecord
| # Integer-based label ID, or
(?<label_name>
# String-based single-word label title, or
[A-Za-z0-9_\-\?\.&]+
#{Gitlab::Regex.sep_by_1(/:{1,2}/, /[A-Za-z0-9_\-\?\.&]+/)}
(?<!\.|\?)
|
# String-based multi-word label surrounded in quotes

View File

@ -2906,10 +2906,6 @@ class Project < ApplicationRecord
build_artifacts_size_refresh&.started?
end
def security_training_available?
licensed_feature_available?(:security_training)
end
private
# overridden in EE

View File

@ -24,8 +24,7 @@ module Projects
gitlab_ci_history_path: gitlab_ci_history_path,
auto_fix_enabled: autofix_enabled,
can_toggle_auto_fix_settings: can_toggle_autofix,
auto_fix_user_path: auto_fix_user_path,
security_training_enabled: project.security_training_available?
auto_fix_user_path: auto_fix_user_path
}
end

View File

@ -37,7 +37,7 @@ instance of the [GraphiQL explorer](https://gitlab.com/-/graphql-explorer):
1. Open the [GraphiQL Explorer](https://gitlab.com/-/graphql-explorer) page.
1. Paste the `query` listed above into the left window of your GraphiQL explorer tool.
1. Click Play to get the result shown here:
1. Select Play to get the result shown here:
![GraphiQL explorer search for boards](img/sample_issue_boards_v13_2.png)

View File

@ -48,7 +48,7 @@ GET /runners?tag_list=tag1,tag2
|------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online`, `offline` and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
@ -113,7 +113,7 @@ GET /runners/all?tag_list=tag1,tag2
|------------|--------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
@ -464,7 +464,7 @@ GET /projects/:id/runners?tag_list=tag1,tag2
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
@ -580,7 +580,7 @@ GET /groups/:id/runners?tag_list=tag1,tag2
|------------|----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of the group owned by the authenticated user |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type`. The `project_type` value is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351466) and will be removed in GitLab 15.0 |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `status` | string | no | The status of runners to show, one of: `online`, `offline`, `stale`, and `never_contacted`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 16.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |

View File

@ -100,7 +100,7 @@ Supported attributes:
| Attribute | Type | Required | Description |
|-----------------|----------------|------------------------|-------------|
| `project_id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `name` | string | **{check-circle}** Yes | The `name` of the file being uploaded. The file name must be unique within the project. |
| `name` | string | **{check-circle}** Yes | The `name` of the file being uploaded. The filename must be unique within the project. |
| `file` | file | **{check-circle}** Yes | The `file` being uploaded (5 MB limit). |
Example request:

View File

@ -36,9 +36,9 @@ environments are not displayed.
To add a project to the dashboard:
1. Click the **Add projects** button in the home screen of the dashboard.
1. Select **Add projects** in the home screen of the dashboard.
1. Search and add one or more projects using the **Search your projects** field.
1. Click the **Add projects** button.
1. Select **Add projects**.
Once added, you can see a summary of each project's environment operational
health, including the latest commit, pipeline status, and deployment time.

View File

@ -63,7 +63,7 @@ rollout 10%:
ROLLOUT_PERCENTAGE: 10
```
When the jobs are built, a **play** button appears next to the job's name. Click the **play** button
When the jobs are built, a **play** button appears next to the job's name. Select **play**
to release each stage of pods. You can also rollback by running a lower percentage job. Once 100%
is reached, you cannot roll back using this method. It is still possible to roll back by redeploying
the old version using the **Rollback** button on the environment page.

View File

@ -92,7 +92,7 @@ For more information, see [Deployment safety](../environments/deployment_safety.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211339) in GitLab 13.6.
A deployment job can fail because a newer one has run. If you retry the failed deployment job, the
environment could be overwritten with older source code. If you click **Retry**, a modal warns you
environment could be overwritten with older source code. If you select **Retry**, a modal warns you
about this and asks for confirmation.
For more information, see [Deployment safety](../environments/deployment_safety.md).

View File

@ -85,9 +85,9 @@ To protect or unprotect a runner:
1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
1. Find the runner you want to protect or unprotect. Make sure it's enabled.
1. Click the pencil button.
1. Select the pencil button.
1. Check the **Protected** option.
1. Click **Save changes**.
1. Select **Save changes**.
![specific runners edit icon](img/protected_runners_check_box_v14_1.png)
@ -113,7 +113,7 @@ To reset the registration token:
1. Go to the project's **Settings > CI/CD**.
1. Expand the **General pipelines settings** section.
1. Find the **Runner token** form field and click the **Reveal value** button.
1. Find the **Runner token** form field and select **Reveal value**.
1. Delete the value and save the form.
1. After the page is refreshed, expand the **Runners settings** section
and check the registration token - it should be changed.
@ -193,9 +193,9 @@ To make a runner pick untagged jobs:
1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
1. Find the runner you want to pick untagged jobs and make sure it's enabled.
1. Click the pencil button.
1. Select the pencil button.
1. Check the **Run untagged jobs** option.
1. Click the **Save changes** button for the changes to take effect.
1. Select **Save changes** for the changes to take effect.
NOTE:
The runner tags list can not be empty when it's not allowed to pick untagged jobs.

View File

@ -84,7 +84,7 @@ To disable shared runners for a group:
1. Go to the group's **Settings > CI/CD** and expand the **Runners** section.
1. In the **Shared runners** area, turn off the **Enable shared runners for this group** toggle.
1. Optionally, to allow shared runners to be enabled for individual projects or subgroups,
click **Allow projects and subgroups to override the group setting**.
select **Allow projects and subgroups to override the group setting**.
NOTE:
To re-enable the shared runners for a group, turn on the
@ -200,11 +200,11 @@ You must have the Owner role for the group.
1. Go to the group you want to remove or pause the runner for.
1. On the left sidebar, select **CI/CD > Runners**.
1. Click **Pause** or **Remove runner**.
1. Select **Pause** or **Remove runner**.
- If you pause a group runner that is used by multiple projects, the runner pauses for all projects.
- From the group view, you cannot remove a runner that is assigned to more than one project.
You must remove it from each project first.
1. On the confirmation dialog, click **OK**.
1. On the confirmation dialog, select **OK**.
## Specific runners

View File

@ -34,8 +34,8 @@ Additional features and capabilities are planned.
To add a secure file to a project:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > CI/CD**.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > CI/CD**.
1. In the **Secure Files** section, select **Manage**.
1. Select **Upload File**.
1. Find the file to upload, select **Open**, and the file upload begins immediately.

View File

@ -115,6 +115,6 @@ There are several ways you can contribute to Cloud Seed:
and [share feedback](https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/feedback/-/issues/new?template=general_feedback).
- If you are familiar with Ruby on Rails or Vue.js,
consider [contributing to GitLab](../development/contributing/index.md) as a developer.
- Much of Cloud Seed is an internal module within the GitLab code base.
- Much of Cloud Seed is an internal module within the GitLab codebase.
- If you are familiar with GitLab pipelines, consider contributing to
the [Cloud Seed Library](https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/library) project.

View File

@ -226,9 +226,9 @@ Download Ruby and compile it:
```shell
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.5.tar.gz"
echo '2755b900a21235b443bb16dadd9032f784d4a88f143d852bc5d154f22b8781f1 ruby-2.7.5.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.5.tar.gz
cd ruby-2.7.5
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.6.tar.gz"
echo 'e7203b0cc09442ed2c08936d483f8ac140ec1c72e37bb5c401646b7866cb5d10 ruby-2.7.6.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.6.tar.gz
cd ruby-2.7.6
./configure --disable-install-rdoc --enable-shared
make

View File

@ -97,6 +97,6 @@ application.
[restart GitLab](../administration/restart_gitlab.md#installations-from-source).
On the sign-in page there should now be an Auth0 icon below the regular sign-in
form. Click the icon to begin the authentication process. Auth0 asks the
form. Select the icon to begin the authentication process. Auth0 asks the
user to sign in and authorize the GitLab application. If the user authenticates
successfully, the user is returned to GitLab and signed in.

View File

@ -261,7 +261,7 @@ To enable languages support:
1. On the left sidebar, select **Settings > Advanced Search**.
1. Locate **Custom analyzers: language support**.
1. Enable plugins support for **Indexing**.
1. Click **Save changes** for the changes to take effect.
1. Select **Save changes** for the changes to take effect.
1. Trigger [Zero downtime reindexing](#zero-downtime-reindexing) or reindex everything from scratch to create a new index with updated mappings.
1. Enable plugins support for **Searching** after the previous step is completed.
@ -435,7 +435,7 @@ Some migrations are built with a retry limit. If the migration cannot finish wit
it is halted and a notification is displayed in the Advanced Search integration settings.
It is recommended to check the [`elasticsearch.log` file](../administration/logs.md#elasticsearchlog) to
debug why the migration was halted and make any changes before retrying the migration. Once you believe you've
fixed the cause of the failure, click "Retry migration", and the migration is scheduled to be retried
fixed the cause of the failure, select "Retry migration", and the migration is scheduled to be retried
in the background.
If you cannot get the migration to succeed, you may
@ -611,7 +611,7 @@ Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
This enqueues a Sidekiq job for each project that needs to be indexed.
You can view the jobs in **Menu > Admin > Monitoring > Background Jobs > Queues Tab**
and click `elastic_commit_indexer`, or you can query indexing status using a Rake task:
and select `elastic_commit_indexer`, or you can query indexing status using a Rake task:
```shell
# Omnibus installations

View File

@ -91,7 +91,7 @@ To change your personal details, including name, billing address, and email addr
1. Select **My account > Account details**.
1. Expand the **Personal details** section.
1. Edit your personal details.
1. Click **Save changes**.
1. Select **Save changes**.
### Change your company details
@ -101,7 +101,7 @@ To change your company details, including company name and VAT number:
1. Select **My account > Account details**.
1. Expand the **Company details** section.
1. Edit the company details.
1. Click **Save changes**.
1. Select **Save changes**.
### Change your payment method
@ -117,7 +117,7 @@ To change your payment method:
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Select **My account > Payment methods**.
1. **Edit** an existing payment method's information or **Add new payment method**.
1. Click **Save Changes**.
1. Select **Save Changes**.
#### Set a default payment method
@ -127,7 +127,7 @@ method as the default:
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Select **My account > Payment methods**.
1. **Edit** the selected payment method and check the **Make default payment method** checkbox.
1. Click **Save Changes**.
1. Select **Save Changes**.
### Change the linked account
@ -137,8 +137,8 @@ To change the GitLab.com account linked to your Customers Portal account:
[Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. In a separate browser tab, go to [GitLab SaaS](https://gitlab.com) and ensure you
are not logged in.
1. On the Customers Portal page, click **My account > Account details**.
1. Under **Your GitLab.com account**, click **Change linked account**.
1. On the Customers Portal page, select **My account > Account details**.
1. Under **Your GitLab.com account**, select **Change linked account**.
1. Log in to the [GitLab SaaS](https://gitlab.com) account you want to link to the Customers Portal
account.
@ -164,9 +164,9 @@ Only one namespace can be linked to a subscription.
To change the password for this customers portal account:
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Select the **My account** drop-down and click on **Account details**.
1. Select the **My account** drop-down and select on **Account details**.
1. Make the required changes to the **Your password** section.
1. Click **Save changes**.
1. Select **Save changes**.
## Community program subscriptions

View File

@ -69,9 +69,9 @@ Download Ruby and compile it:
```shell
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.5.tar.gz"
echo '2755b900a21235b443bb16dadd9032f784d4a88f143d852bc5d154f22b8781f1 ruby-2.7.5.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.5.tar.gz
cd ruby-2.7.5
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.6.tar.gz"
echo 'e7203b0cc09442ed2c08936d483f8ac140ec1c72e37bb5c401646b7866cb5d10 ruby-2.7.6.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.6.tar.gz
cd ruby-2.7.6
./configure --disable-install-rdoc --enable-shared
make

View File

@ -235,7 +235,7 @@ v1.0, 2019-01-01
NOTE:
[Wiki pages](project/wiki/index.md#create-a-new-wiki-page) created with the AsciiDoc
format are saved with the file extension `.asciidoc`. When working with AsciiDoc wiki
pages, change the file name from `.adoc` to `.asciidoc`.
pages, change the filename from `.adoc` to `.asciidoc`.
```plaintext
include::basics.adoc[]

View File

@ -49,7 +49,7 @@ In order to:
After you have successful deployments to your group-level or instance-level cluster:
1. Navigate to your group's **Kubernetes** page.
1. Click on the **Environments** tab.
1. Select the **Environments** tab.
Only successful deployments to the cluster are included in this page.
Non-cluster environments aren't included.

View File

@ -98,7 +98,7 @@ To enable the Prometheus integration for your cluster:
**Kubernetes** page.
1. Select the **Integrations** tab.
1. Check the **Enable Prometheus integration** checkbox.
1. Click **Save changes**.
1. Select **Save changes**.
1. Go to the **Health** tab to see your cluster's metrics.
## Elastic Stack cluster integration **(FREE SELF)**
@ -165,5 +165,5 @@ To enable the Elastic Stack integration for your cluster:
**Kubernetes** page.
1. Select the **Integrations** tab.
1. Check the **Enable Elastic Stack integration** checkbox.
1. Click **Save changes**.
1. Select **Save changes**.
1. Go to the **Health** tab to see your cluster's metrics.

View File

@ -49,7 +49,7 @@ Select the desired period from the calendar dropdown.
## Sorting by different factors
Contributions per group member are also presented in tabular format. Click a column header to sort the table by that column:
Contributions per group member are also presented in tabular format. Select a column header to sort the table by that column:
- Member name
- Number of pushed events

View File

@ -115,7 +115,7 @@ To set a limit on how long personal access tokens are valid for users in a group
1. Navigate to the **Settings > General** page in your group's sidebar.
1. Expand the **Permissions and group features** section.
1. Fill in the **Maximum allowable lifetime for access tokens (days)** field.
1. Click **Save changes**.
1. Select **Save changes**.
Once a lifetime for personal access tokens is set:

View File

@ -577,7 +577,7 @@ To generate a SAML Response:
- [SAML-tracer](https://addons.mozilla.org/en-US/firefox/addon/saml-tracer/) for Firefox.
1. Open a new browser tab.
1. Open the SAML tracer console:
- Chrome: Right click on the page, select **Inspect**, then click on the SAML tab in the opened developer console.
- Chrome: Right click on the page, select **Inspect**, then select the SAML tab in the opened developer console.
- Firefox: Select the SAML-tracer icon located on the browser toolbar.
1. Go to the GitLab single sign-on URL for the group in the same browser tab with the SAML tracer open.
1. Select **Authorize** or attempt to log in. A SAML response is displayed in the tracer console that resembles this

View File

@ -82,7 +82,7 @@ table, use the Azure defaults. For a list of required attributes, refer to the [
For guidance, you can view [an example configuration in the troubleshooting reference](../../../administration/troubleshooting/group_saml_scim.md#azure-active-directory).
1. Below the mapping list click on **Show advanced options > Edit attribute list for AppName**.
1. Below the mapping list select **Show advanced options > Edit attribute list for AppName**.
1. Ensure the `id` is the primary and required field, and `externalId` is also required.
NOTE:

View File

@ -23,9 +23,9 @@ To add a project to the dashboard:
1. Ensure your alerts populate the `gitlab_environment_name` label on the alerts you set up in Prometheus.
The value of this should match the name of your environment in GitLab.
In GitLab 13.9, you can display alerts for the `production` environment only.
1. Click the **Add projects** button in the home screen of the dashboard.
1. Select **Add projects** in the home screen of the dashboard.
1. Search and add one or more projects using the **Search your projects** field.
1. Click the **Add projects** button.
1. Select **Add projects**.
Once added, the dashboard displays the project's number of active alerts,
last commit, pipeline status, and when it was last deployed.

View File

@ -57,7 +57,7 @@ To download and run a container image hosted in the GitLab Container Registry:
1. Copy the link to your container image:
- Go to your project or group's **Packages & Registries > Container Registry**
and find the image you want.
- Next to the image name, click the **Copy** button.
- Next to the image name, select **Copy**.
![Container Registry image URL](img/container_registry_hover_path_13_4.png)
@ -404,7 +404,7 @@ To delete images from within GitLab:
by clicking the red **{remove}** **Trash** icon next to the tag you want
to delete.
1. In the dialog box, click **Remove tag**.
1. In the dialog box, select **Remove tag**.
### Delete images using the API
@ -506,7 +506,7 @@ You can, however, remove the Container Registry for a project:
1. Go to your project's **Settings > General** page.
1. Expand the **Visibility, project features, permissions** section
and disable **Container Registry**.
1. Click **Save changes**.
1. Select **Save changes**.
The **Packages & Registries > Container Registry** entry is removed from the project's sidebar.

View File

@ -109,12 +109,12 @@ To create a cleanup policy in the UI:
| **Remove tags older than** | Remove only tags older than X days. |
| **Remove tags matching** | The regex pattern that determines which tags to remove. This value cannot be blank. For all tags, use `.*`. See other [regex pattern examples](#regex-pattern-examples). |
1. Click **Save**.
1. Select **Save**.
Depending on the interval you chose, the policy is scheduled to run.
NOTE:
If you edit the policy and click **Save** again, the interval is reset.
If you edit the policy and select **Save** again, the interval is reset.
### Regex pattern examples

View File

@ -701,7 +701,7 @@ dependencies {
For your project, go to **Packages & Registries > Package Registry**.
To remove a package, click the red trash icon or, from the package details, the **Delete** button.
To remove a package, select the red trash icon or, from the package details, the **Delete** button.
## Create Maven packages with GitLab CI/CD

View File

@ -171,7 +171,7 @@ To use the [project-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet en
![Visual Studio Adding a NuGet source](img/visual_studio_adding_nuget_source.png)
1. Click **Save**.
1. Select **Save**.
The source is displayed in your list.
@ -200,7 +200,7 @@ To use the [group-level](#use-the-gitlab-endpoint-for-nuget-packages) NuGet endp
![Visual Studio Adding a NuGet source](img/visual_studio_adding_nuget_source.png)
1. Click **Save**.
1. Select **Save**.
The source is displayed in your list.

View File

@ -31,7 +31,7 @@ To delete a package in the UI, from your group or project:
1. Go to **Packages & Registries > Package Registry**.
1. Find the name of the package you want to delete.
1. Click **Delete**.
1. Select **Delete**.
The package is permanently deleted.

View File

@ -33,7 +33,7 @@ Advanced Search searches default project branches only.
| Use | Description | Example |
|--------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `filename:` | File name | [`filename:*spec.rb`](https://gitlab.com/search?snippets=&scope=blobs&repository_ref=&search=filename%3A*spec.rb&group_id=9970&project_id=278964) |
| `filename:` | Filename | [`filename:*spec.rb`](https://gitlab.com/search?snippets=&scope=blobs&repository_ref=&search=filename%3A*spec.rb&group_id=9970&project_id=278964) |
| `path:` | Repository location | [`path:spec/workers/`](https://gitlab.com/search?group_id=9970&project_id=278964&repository_ref=&scope=blobs&search=path%3Aspec%2Fworkers&snippets=) |
| `extension:` | File extension, without the `.` | [`extension:js`](https://gitlab.com/search?group_id=9970&project_id=278964&repository_ref=&scope=blobs&search=extension%3Ajs&snippets=) |
| `blob:` | Git object ID | [`blob:998707*`](https://gitlab.com/search?snippets=false&scope=blobs&repository_ref=&search=blob%3A998707*&group_id=9970) |

View File

@ -181,7 +181,7 @@ Search history is available for issues and merge requests, and is stored locally
in your browser. To run a search from history:
1. In the top menu, select **Issues** or **Merge requests**.
1. To the left of the search bar, click **Recent searches**, and select a search from the list.
1. To the left of the search bar, select **Recent searches**, and select a search from the list.
## Removing search filters

View File

@ -27,7 +27,7 @@ sent within five minutes, with a link for users to re-confirm the subject email
## Do the confirmation emails expire?
The links in these re-confirmation emails expire after one day by default. Users who click an expired link are asked to request a new re-confirmation email. Any user can request a new re-confirmation email from `http://gitlab.example.com/users/confirmation/new`.
The links in these re-confirmation emails expire after one day by default. Users who select an expired link are asked to request a new re-confirmation email. Any user can request a new re-confirmation email from `http://gitlab.example.com/users/confirmation/new`.
## Generate a list of affected users

View File

@ -481,6 +481,11 @@ module Gitlab
"can contain only lowercase letters, digits, '_' and '-'. " \
"Must start with a letter, and cannot end with '-' or '_'"
end
# One or more `part`s, separated by separator
def sep_by_1(separator, part)
%r(#{part} (#{separator} #{part})*)x
end
end
end

View File

@ -35,7 +35,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Contacts'),
link: group_crm_contacts_path(context.group),
active_routes: { path: 'groups/crm#contacts' },
active_routes: { controller: 'groups/crm/contacts' },
item_id: :crm_contacts
)
end
@ -44,7 +44,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Organizations'),
link: group_crm_organizations_path(context.group),
active_routes: { path: 'groups/crm#organizations' },
active_routes: { controller: 'groups/crm/organizations' },
item_id: :crm_organizations
)
end

View File

@ -2,6 +2,7 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlTab, GlTabs, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
@ -19,14 +20,22 @@ import {
LICENSE_COMPLIANCE_DESCRIPTION,
LICENSE_COMPLIANCE_HELP_PATH,
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
LICENSE_ULTIMATE,
LICENSE_PREMIUM,
LICENSE_FREE,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import {
REPORT_TYPE_LICENSE_COMPLIANCE,
REPORT_TYPE_SAST,
} from '~/vue_shared/security_reports/constants';
import { getCurrentLicensePlanResponse } from '../mock_data';
const upgradePath = '/upgrade';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
@ -41,40 +50,31 @@ Vue.use(VueApollo);
describe('App component', () => {
let wrapper;
let userCalloutDismissSpy;
let mockApollo;
const securityFeaturesMock = [
{
name: SAST_NAME,
shortName: SAST_SHORT_NAME,
description: SAST_DESCRIPTION,
helpPath: SAST_HELP_PATH,
configurationHelpPath: SAST_CONFIG_HELP_PATH,
type: REPORT_TYPE_SAST,
available: true,
},
];
const complianceFeaturesMock = [
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH,
},
];
const createComponent = ({ shouldShowCallout = true, ...propsData } = {}) => {
const createComponent = ({
shouldShowCallout = true,
licenseQueryResponse = LICENSE_ULTIMATE,
...propsData
}) => {
userCalloutDismissSpy = jest.fn();
mockApollo = createMockApollo([
[
currentLicenseQuery,
jest
.fn()
.mockResolvedValue(
licenseQueryResponse instanceof Error
? licenseQueryResponse
: getCurrentLicensePlanResponse(licenseQueryResponse),
),
],
]);
wrapper = extendedWrapper(
mount(SecurityConfigurationApp, {
propsData: {
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
securityTrainingEnabled: true,
...propsData,
},
propsData,
provide: {
upgradePath,
autoDevopsHelpPagePath,
@ -82,6 +82,7 @@ describe('App component', () => {
projectFullPath,
vulnerabilityTrainingDocsPath,
},
apolloProvider: mockApollo,
stubs: {
...stubChildren(SecurityConfigurationApp),
GlLink: false,
@ -132,13 +133,40 @@ describe('App component', () => {
const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert);
const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab');
const securityFeaturesMock = [
{
name: SAST_NAME,
shortName: SAST_SHORT_NAME,
description: SAST_DESCRIPTION,
helpPath: SAST_HELP_PATH,
configurationHelpPath: SAST_CONFIG_HELP_PATH,
type: REPORT_TYPE_SAST,
available: true,
},
];
const complianceFeaturesMock = [
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH,
},
];
afterEach(() => {
wrapper.destroy();
mockApollo = null;
});
describe('basic structure', () => {
beforeEach(() => {
createComponent();
beforeEach(async () => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
await waitForPromises();
});
it('renders main-heading with correct text', () => {
@ -198,7 +226,10 @@ describe('App component', () => {
describe('Manage via MR Error Alert', () => {
beforeEach(() => {
createComponent();
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
});
describe('on initial load', () => {
@ -234,6 +265,8 @@ describe('App component', () => {
describe('given the right props', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled: false,
gitlabCiPresent: false,
canEnableAutoDevops: true,
@ -255,7 +288,10 @@ describe('App component', () => {
describe('given the wrong props', () => {
beforeEach(() => {
createComponent();
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
});
it('should not show AutoDevopsAlert', () => {
expect(findAutoDevopsAlert().exists()).toBe(false);
@ -279,7 +315,11 @@ describe('App component', () => {
);
}
createComponent({ autoDevopsEnabled });
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled,
});
});
it(shouldRender ? 'renders' : 'does not render', () => {
@ -305,7 +345,12 @@ describe('App component', () => {
);
}
createComponent({ autoDevopsEnabled: true });
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled: true,
});
findAutoDevopsEnabledAlert().vm.$emit('dismiss');
});
@ -330,6 +375,7 @@ describe('App component', () => {
describe('given at least one unavailable feature', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)),
});
});
@ -350,6 +396,7 @@ describe('App component', () => {
describe('given at least one unavailable feature, but banner is already dismissed', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)),
shouldShowCallout: false,
});
@ -376,7 +423,11 @@ describe('App component', () => {
describe('when given latestPipelinePath props', () => {
beforeEach(() => {
createComponent({ latestPipelinePath: 'test/path' });
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
latestPipelinePath: 'test/path',
});
});
it('should show latest pipeline info on the security tab with correct link when latestPipelinePath is defined', () => {
@ -401,6 +452,8 @@ describe('App component', () => {
describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
gitlabCiPresent: true,
gitlabCiHistoryPath,
});
@ -415,36 +468,48 @@ describe('App component', () => {
});
});
describe('Vulnerability management tab', () => {
it('does not show tab if security training is disabled', () => {
createComponent({ securityTrainingEnabled: false });
expect(findVulnerabilityManagementTab().exists()).toBe(false);
describe('Vulnerability management', () => {
beforeEach(async () => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
await waitForPromises();
});
describe('security training enabled', () => {
beforeEach(() => {
createComponent();
});
it('shows the tab if security training is enabled', () => {
expect(findVulnerabilityManagementTab().exists()).toBe(true);
});
it('renders TrainingProviderList component', () => {
expect(findTrainingProviderList().exists()).toBe(true);
});
it('renders security training description', () => {
expect(findVulnerabilityManagementTab().text()).toContain(i18n.securityTrainingDescription);
});
it('renders link to help docs', () => {
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
});
it('renders TrainingProviderList component', () => {
expect(findTrainingProviderList().exists()).toBe(true);
});
it('renders security training description', () => {
expect(findVulnerabilityManagementTab().text()).toContain(i18n.securityTrainingDescription);
});
it('renders link to help docs', () => {
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
});
it.each`
licenseQueryResponse | display
${LICENSE_ULTIMATE} | ${true}
${LICENSE_PREMIUM} | ${false}
${LICENSE_FREE} | ${false}
${null} | ${true}
${new Error()} | ${true}
`(
'displays $display for license $licenseQueryResponse',
async ({ licenseQueryResponse, display }) => {
createComponent({
licenseQueryResponse,
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
await waitForPromises();
expect(findVulnerabilityManagementTab().exists()).toBe(display);
},
);
});
});

View File

@ -111,3 +111,12 @@ export const tempProviderLogos = {
svg: `<svg>${[testProviderName[1]]}</svg>`,
},
};
export const getCurrentLicensePlanResponse = (plan) => ({
data: {
currentLicense: {
id: 'gid://gitlab/License/1',
plan,
},
},
});

View File

@ -21,8 +21,8 @@ describe('MR widget extension registering', () => {
expect.objectContaining({
extends: ExtensionBase,
name: 'Test',
props: ['helloWorld'],
computed: {
helloWorld: expect.any(Function),
test: expect.any(Function),
},
methods: {

View File

@ -8323,14 +8323,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#security_training_available?' do
subject { build(:project) }
it 'returns false' do
expect(subject.security_training_available?).to eq false
end
end
private
def finish_job(export_job)

View File

@ -122,7 +122,7 @@ RSpec.describe QuickActions::InterpretService do
inprogress # populate the label
_, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [bug.id, inprogress.id])
expect(updates).to match(add_label_ids: contain_exactly(bug.id, inprogress.id))
end
it 'returns the label message' do
@ -130,7 +130,10 @@ RSpec.describe QuickActions::InterpretService do
inprogress # populate the label
_, _, message = service.execute(content, issuable)
expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.")
# Compare message without making assumptions about ordering.
expect(message).to match %r{Added ~".*" ~".*" labels.}
expect(message).to include(bug.to_reference(format: :name))
expect(message).to include(inprogress.to_reference(format: :name))
end
end
@ -1196,6 +1199,64 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
context 'with a colon label' do
let(:bug) { create(:label, project: project, title: 'Category:Bug') }
let(:inprogress) { create(:label, project: project, title: 'status:in:progress') }
context 'when quoted' do
let(:content) { %(/label ~"#{inprogress.title}" ~"#{bug.title}" ~unknown) }
it_behaves_like 'label command' do
let(:issuable) { merge_request }
end
it_behaves_like 'label command' do
let(:issuable) { issue }
end
end
context 'when unquoted' do
let(:content) { %(/label ~#{inprogress.title} ~#{bug.title} ~unknown) }
it_behaves_like 'label command' do
let(:issuable) { merge_request }
end
it_behaves_like 'label command' do
let(:issuable) { issue }
end
end
end
context 'with a scoped label' do
let(:bug) { create(:label, :scoped, project: project) }
let(:inprogress) { create(:label, project: project, title: 'three::part::label') }
context 'when quoted' do
let(:content) { %(/label ~"#{inprogress.title}" ~"#{bug.title}" ~unknown) }
it_behaves_like 'label command' do
let(:issuable) { merge_request }
end
it_behaves_like 'label command' do
let(:issuable) { issue }
end
end
context 'when unquoted' do
let(:content) { %(/label ~#{inprogress.title} ~#{bug.title} ~unknown) }
it_behaves_like 'label command' do
let(:issuable) { merge_request }
end
it_behaves_like 'label command' do
let(:issuable) { issue }
end
end
end
it_behaves_like 'multiple label command' do
let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) }
let(:issuable) { issue }