Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-01 21:11:09 +00:00
parent 69f0d90aad
commit 11b7785066
46 changed files with 357 additions and 323 deletions

View file

@ -90,6 +90,17 @@ RSpec/FilePath:
- 'ee/spec/frontend/fixtures/*'
- 'spec/requests/api/v3/*'
# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 28
AllowSubject: true
Exclude:
- 'spec/migrations/**/*.rb'
- 'spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb'
- 'spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb'
- 'ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb'
- 'ee/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb'
Naming/FileName:
ExpectMatchingDefinition: true
Exclude:

View file

@ -463,11 +463,6 @@ RSpec/ExpectChange:
RSpec/ExpectInHook:
Enabled: false
# Offense count: 16403
# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 40
# Offense count: 2352
# Cop supports --auto-correct.
# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers.

View file

@ -1,3 +1,4 @@
import Activities from '~/activities';
document.addEventListener('DOMContentLoaded', () => new Activities());
// eslint-disable-next-line no-new
new Activities();

View file

@ -0,0 +1,34 @@
<script>
import { GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlSprintf,
},
i18n: {
title: __('Optimize your workflow with CI/CD Pipelines'),
body: __(
'Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started.',
),
},
inject: {
emptyStateIllustrationPath: {
default: '',
},
},
};
</script>
<template>
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
<img :src="emptyStateIllustrationPath" />
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
<p>
<gl-sprintf :message="$options.i18n.body">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
</div>
</template>

View file

@ -5,7 +5,6 @@ export const COMMIT_FAILURE = 'COMMIT_FAILURE';
export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export const CREATE_TAB = 'CREATE_TAB';

View file

@ -25,6 +25,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
// Add to provide/inject API for static values
ciConfigPath,
defaultBranch,
emptyStateIllustrationPath,
lintHelpPagePath,
newMergeRequestPath,
projectFullPath,
@ -51,6 +52,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
provide: {
ciConfigPath,
defaultBranch,
emptyStateIllustrationPath,
lintHelpPagePath,
newMergeRequestPath,
projectFullPath,

View file

@ -1,17 +1,12 @@
<script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import httpStatusCodes from '~/lib/utils/http_status';
import { __, s__, sprintf } from '~/locale';
import { __, s__ } from '~/locale';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue';
import {
COMMIT_FAILURE,
COMMIT_SUCCESS,
DEFAULT_FAILURE,
LOAD_FAILURE_NO_FILE,
LOAD_FAILURE_UNKNOWN,
} from './constants';
import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue';
import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } from './constants';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';
@ -21,6 +16,7 @@ export default {
ConfirmUnsavedChangesDialog,
GlAlert,
GlLoadingIcon,
PipelineEditorEmptyState,
PipelineEditorHome,
},
inject: {
@ -40,6 +36,7 @@ export default {
// Success and failure state
failureType: null,
failureReasons: [],
hasNoCiConfigFile: false,
initialCiFileContent: '',
lastCommittedContent: '',
currentCiFileContent: '',
@ -102,21 +99,11 @@ export default {
isBlobContentLoading() {
return this.$apollo.queries.initialCiFileContent.loading;
},
isBlobContentError() {
return this.failureType === LOAD_FAILURE_NO_FILE;
},
isCiConfigDataLoading() {
return this.$apollo.queries.ciConfigData.loading;
},
failure() {
switch (this.failureType) {
case LOAD_FAILURE_NO_FILE:
return {
text: sprintf(this.$options.errorTexts[LOAD_FAILURE_NO_FILE], {
filePath: this.ciConfigPath,
}),
variant: 'danger',
};
case LOAD_FAILURE_UNKNOWN:
return {
text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN],
@ -154,9 +141,6 @@ export default {
errorTexts: {
[COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
[DEFAULT_FAILURE]: __('Something went wrong on our end.'),
[LOAD_FAILURE_NO_FILE]: s__(
'Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again.',
),
[LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'),
},
successTexts: {
@ -173,7 +157,7 @@ export default {
response?.status === httpStatusCodes.NOT_FOUND ||
response?.status === httpStatusCodes.BAD_REQUEST
) {
this.reportFailure(LOAD_FAILURE_NO_FILE);
this.hasNoCiConfigFile = true;
} else {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
}
@ -216,18 +200,19 @@ export default {
</script>
<template>
<div class="gl-mt-4">
<gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess">
{{ success.text }}
</gl-alert>
<gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="dismissFailure">
{{ failure.text }}
<ul v-if="failureReasons.length" class="gl-mb-0">
<li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
</ul>
</gl-alert>
<div class="gl-mt-4 gl-relative">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<div v-else-if="!isBlobContentError" class="gl-mt-4">
<pipeline-editor-empty-state v-else-if="hasNoCiConfigFile" />
<div v-else>
<gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess">
{{ success.text }}
</gl-alert>
<gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="dismissFailure">
{{ failure.text }}
<ul v-if="failureReasons.length" class="gl-mb-0">
<li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
</ul>
</gl-alert>
<pipeline-editor-home
:is-ci-config-data-loading="isCiConfigDataLoading"
:ci-config-data="ciConfigData"
@ -237,7 +222,7 @@ export default {
@showError="showErrorAlert"
@updateCiConfig="updateCiConfig"
/>
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
</template>

View file

@ -38,7 +38,7 @@ $dark-cp: #969896;
$dark-c1: #969896;
$dark-cs: #969896;
$dark-gd: #c66;
$dark-gh: #c5c8c6;
$dark-gh: #8abeb7;
$dark-gi: #b5bd68;
$dark-gp: #969896;
$dark-gu: #8abeb7;

View file

@ -87,6 +87,7 @@ $monokai-il: #ae81ff;
$monokai-gu: #75715e;
$monokai-gd: #f92672;
$monokai-gi: #a6e22e;
$monokai-gh: #75715e;
.code.monokai {
// Line numbers
@ -281,4 +282,5 @@ $monokai-gi: #a6e22e;
.gu { color: $monokai-gu; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: $monokai-gd; } /* Generic.Deleted & Diff Deleted */
.gi { color: $monokai-gi; } /* Generic.Inserted & Diff Inserted */
.gh { color: $monokai-gh; } /* Generic.Heading */
}

View file

@ -24,7 +24,7 @@ $white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gh: #800080;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
@ -280,7 +280,9 @@ span.highlight_word {
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gh { color: $white-gh;
font-weight: $gl-font-weight-bold; }
.gi {
color: $white-gi;

View file

@ -11,7 +11,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
feature_category :pipeline_authoring
def show
render_404 unless ::Gitlab::Ci::Features.ci_pipeline_editor_page_enabled?(@project)
end
private

View file

@ -5,8 +5,7 @@ module Ci
include ChecksCollaboration
def can_view_pipeline_editor?(project)
can_collaborate_with_project?(project) &&
Gitlab::Ci::Features.ci_pipeline_editor_page_enabled?(project)
can_collaborate_with_project?(project)
end
end
end

View file

@ -72,52 +72,6 @@ module NotificationsHelper
end
end
def notification_list_item(level, setting)
title = notification_title(level)
data = {
notification_level: level,
notification_title: title
}
content_tag(:li, role: "menuitem") do
link_to '#', class: "update-notification #{('is-active' if setting.level == level)}", data: data do
link_output = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
link_output << content_tag(:span, notification_description(level), class: 'dropdown-menu-inner-content')
end
end
end
# Identifier to trigger individually dropdowns and custom settings modals in the same view
def notifications_menu_identifier(type, notification_setting)
"#{type}-#{notification_setting.user_id}-#{notification_setting.source_id}-#{notification_setting.source_type}"
end
# Create hidden field to send notification setting source to controller
def hidden_setting_source_input(notification_setting)
return unless notification_setting.source_type
hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id
end
def notification_event_name(event)
# All values from NotificationSetting.email_events
case event
when :success_pipeline
s_('NotificationEvent|Successful pipeline')
else
event_name = "NotificationEvent|#{event.to_s.humanize}"
s_(event_name)
end
end
def notification_setting_icon(notification_setting = nil)
sprite_icon(
!notification_setting.present? || notification_setting.disabled? ? "notifications-off" : "notifications",
css_class: "icon notifications-icon js-notifications-icon"
)
end
def show_unsubscribe_title?(noteable)
can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
end

View file

@ -1,12 +1,13 @@
- page_title s_('Pipelines|Pipeline Editor')
#js-pipeline-editor{ data: { "ci-config-path": @project.ci_config_path_or_default,
"commit-sha" => @project.commit ? @project.commit.sha : '',
"default-branch" => @project.default_branch,
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"new-merge-request-path" => namespace_project_new_merge_request_path,
"project-path" => @project.path,
"project-full-path" => @project.full_path,
"project-namespace" => @project.namespace.full_path,
"default-branch" => @project.default_branch,
"commit-sha" => @project.commit ? @project.commit.sha : '',
"new-merge-request-path" => namespace_project_new_merge_request_path,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"yml-help-page-path" => help_page_path('ci/yaml/README'),
} }

View file

@ -0,0 +1,5 @@
---
title: Align heading style with subheadings in markdown
merge_request: 55284
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Add empty state to pipeline editor section
merge_request: 55227
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Remove pipeline editor feature flag
merge_request: 54971
author:
type: other

View file

@ -1,8 +0,0 @@
---
name: ci_pipeline_editor_page
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46580
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/270059
milestone: '13.6'
type: development
group: group::pipeline authoring
default_enabled: true

View file

@ -0,0 +1,14 @@
---
# Warning: gitlab.DefaultBranch
#
# Do not refer to the default branch as the "master" branch, if possible.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Use "default branch" or `main` instead of `master`, when possible.'
level: warning
ignorecase: true
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html
scope: raw
raw:
- '\`master\`'

View file

@ -337,38 +337,41 @@ attribute.
### Examples
For these examples we use the project ID 42, and assume the project is hosted on
GitLab.com. The example API token we use is `token`. We use
[curl](https://curl.se/) to perform the HTTP requests.
These examples use [cURL](https://curl.se/) to perform HTTP requests.
The example commands use these values:
Let's start with a basic example:
- **Project ID**: 42
- **Location**: hosted on GitLab.com
- **Example API token**: `token`
This command generates a changelog for version `1.0.0`.
The commit range:
- Starts with the tag of the last release.
- Ends with the last commit on the target branch. The default target branch is the project's default branch.
If the last tag is `v0.9.0` and the default branch is `main`, the range of commits
included in this example is `v0.9.0..main`:
```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0" "https://gitlab.com/api/v4/projects/42/repository/changelog"
```
This generates a changelog for version `1.0.0`. The start of the range of
commits to include is the tag of the last release. The end of the range is the
last commit on the target branch, which defaults to the project's default
branch. So if the last tag is `v0.9.0`, and the default branch is `main`, this
means the range of commits is `v0.9.0..main`.
If you want to generate the data on a different branch, you can do so as
follows:
To generate the data on a different branch, specify the `branch` parameter. This
command generates data from the `foo` branch:
```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&branch=foo" "https://gitlab.com/api/v4/projects/42/repository/changelog"
```
This generates the data on the `foo` branch.
A different trailer to use is specified as follows:
To use a different trailer, use the `trailer` parameter:
```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&trailer=Type" "https://gitlab.com/api/v4/projects/42/repository/changelog"
```
Or perhaps you want to store the results in a different file:
To store the results in a different file, use the `file` parameter:
```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&file=NEWS" "https://gitlab.com/api/v4/projects/42/repository/changelog"

View file

@ -74,15 +74,15 @@ runs by enabling the [Skip outdated deployment jobs](../pipelines/settings.md#sk
Example of a problematic pipeline flow **before** enabling Skip outdated deployment jobs:
1. Pipeline-A is created on the `master` branch.
1. Later, Pipeline-B is created on the `master` branch (with a newer commit SHA).
1. Pipeline-A is created on the default branch.
1. Later, Pipeline-B is created on the default branch (with a newer commit SHA).
1. The `deploy` job in Pipeline-B finishes first, and deploys the newer code.
1. The `deploy` job in Pipeline-A finished later, and deploys the older code, **overwriting** the newer (latest) deployment.
The improved pipeline flow **after** enabling Skip outdated deployment jobs:
1. Pipeline-A is created on the `master` branch.
1. Later, Pipeline-B is created on the `master` branch (with a newer SHA).
1. Pipeline-A is created on the default branch.
1. Later, Pipeline-B is created on the default branch (with a newer SHA).
1. The `deploy` job in Pipeline-B finishes first, and deploys the newer code.
1. The `deploy` job in Pipeline-A is automatically cancelled, so that it doesn't overwrite the deployment from the newer pipeline.

View file

@ -180,7 +180,7 @@ $ vault write auth/jwt/config \
For the full list of available configuration options, see Vault's [API documentation](https://www.vaultproject.io/api/auth/jwt#configure).
The following job, when run for the `master` branch, is able to read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`:
The following job, when run for the default branch, is able to read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`:
```yaml
read_secrets:

View file

@ -122,7 +122,7 @@ Therefore, for a production environment we use additional steps to ensure that a
Since this was a WordPress project, I gave real life code snippets. Some further ideas you can pursue:
- Having a slightly different script for `master` branch allows you to deploy to a production server from that branch and to a stage server from any other branches.
- Having a slightly different script for the default branch allows you to deploy to a production server from that branch and to a stage server from any other branches.
- Instead of pushing it live, you can push it to WordPress official repository (with creating a SVN commit, etc.).
- You could generate i18n text domains on the fly.

View file

@ -126,7 +126,7 @@ Test the pipeline by creating a commit with a message like:
fix: testing patch releases
```
Push the commit to `master`. The pipeline should create a new release (`v1.0.0`) on the project's **Releases** page and publish a new version of the package to the project's **Package Registry** page.
Push the commit to the default branch. The pipeline should create a new release (`v1.0.0`) on the project's **Releases** page and publish a new version of the package to the project's **Package Registry** page.
To create a minor release, use a commit message like:

View file

@ -8,10 +8,7 @@ type: reference
# Pipeline Editor **(FREE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4540) in GitLab 13.8.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-pipeline-editor). **(FREE SELF)**
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/270059) in GitLab 13.10.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
@ -30,7 +27,7 @@ From the pipeline editor page you can:
NOTE:
You must already have [a `.gitlab-ci.yml` file](../quick_start/index.md#create-a-gitlab-ciyml-file)
on the default branch (usually `master`) of your project to use the editor.
on the default branch of your project to use the editor.
## Validate CI configuration
@ -68,7 +65,6 @@ reflected in the CI lint. It displays the same results as the existing [CI Lint
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
It is not accessible if the [pipeline editor is disabled](#enable-or-disable-pipeline-editor).
To view a visualization of your `gitlab-ci.yml` configuration, in your project,
go to **CI/CD > Editor**, and then select the **Visualize** tab. The
@ -150,22 +146,3 @@ If you enter a new branch name, the **Start a new merge request with these chang
checkbox appears. Select it to start a new merge request after you commit the changes.
![The commit form with a new branch](img/pipeline_editor_commit_v13_8.png)
## Enable or disable pipeline editor **(FREE SELF)**
The pipeline editor is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can disable it.
To disable it:
```ruby
Feature.disable(:ci_pipeline_editor_page)
```
To enable it:
```ruby
Feature.enable(:ci_pipeline_editor_page)
```

View file

@ -66,7 +66,7 @@ In this file, you define:
- The decisions the runner should make when specific conditions are encountered.
For example, you might want to run a suite of tests when you commit to
any branch except `master`. When you commit to `master`, you want
any branch except the default branch. When you commit to the default branch, you want
to run the same suite, but also publish your application.
All of this is defined in the `.gitlab-ci.yml` file.

View file

@ -31,8 +31,8 @@ In the above example:
- A Review App is built every time a commit is pushed to `topic branch`.
- The reviewer fails two reviews before passing the third review.
- After the review has passed, `topic branch` is merged into `master` where it is deployed to staging.
- After having been approved in staging, the changes that were merged into `master` are deployed in to production.
- After the review passes, `topic branch` is merged into the default branch, where it's deployed to staging.
- After its approval in staging, the changes that were merged into the default branch are deployed to production.
## How Review Apps work

View file

@ -164,7 +164,7 @@ a branch to its remote repository. To illustrate the problem, suppose you've had
1. A user creates a feature branch named `example` and pushes it to a remote repository.
1. A new pipeline starts running on the `example` branch.
1. A user rebases the `example` branch on the latest `master` branch and force-pushes it to its remote repository.
1. A user rebases the `example` branch on the latest default branch and force-pushes it to its remote repository.
1. A new pipeline starts running on the `example` branch again, however,
the previous pipeline (2) fails because of `fatal: reference is not a tree:` error.

View file

@ -28,7 +28,7 @@ in the pipeline detail view.
Consider the following workflow:
1. Your `master` branch is rock solid, your project is using GitLab CI/CD and
1. Your default branch is rock solid, your project is using GitLab CI/CD and
your pipelines indicate that there isn't anything broken.
1. Someone from your team submits a merge request, a test fails and the pipeline
gets the known red icon. To investigate more, you have to go through the job
@ -44,7 +44,7 @@ First, GitLab Runner uploads all [JUnit report format XML files](https://www.ibm
as [artifacts](pipelines/job_artifacts.md#artifactsreportsjunit) to GitLab. Then, when you visit a merge request, GitLab starts
comparing the head and base branch's JUnit report format XML files, where:
- The base branch is the target branch (usually `master`).
- The base branch is the target branch (usually the default branch).
- The head branch is the source branch (the latest pipeline in each merge request).
The reports panel has a summary showing how many tests failed, how many had errors

View file

@ -247,7 +247,7 @@ include:
```
The [`MergeRequest-Pipelines` template](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/Workflows/MergeRequest-Pipelines.gitlab-ci.yml)
makes your pipelines run for the default branch (usually `master`), tags, and
makes your pipelines run for the default branch, tags, and
all types of merge request pipelines. Use this template if you use any of the
the [Pipelines for Merge Requests features](../merge_request_pipelines/), as mentioned
above.
@ -1260,9 +1260,9 @@ Other commonly used variables for `if` clauses:
- `if: $CI_COMMIT_TAG`: If changes are pushed for a tag.
- `if: $CI_COMMIT_BRANCH`: If changes are pushed to any branch.
- `if: '$CI_COMMIT_BRANCH == "master"'`: If changes are pushed to `master`.
- `if: '$CI_COMMIT_BRANCH == "main"'`: If changes are pushed to `main`.
- `if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'`: If changes are pushed to the default
branch (usually `master`). Use when you want to have the same configuration in multiple
branch. Use when you want to have the same configuration in multiple
projects with different default branches.
- `if: '$CI_COMMIT_BRANCH =~ /regex-expression/'`: If the commit branch matches a regular expression.
- `if: '$CUSTOM_VARIABLE !~ /regex-expression/'`: If the [custom variable](../variables/README.md#custom-cicd-variables)

View file

@ -1607,34 +1607,31 @@ displayed for the page or feature.
#### Version text in the **Version History**
If all content in a section is related, add version text after the header
for the section. The version information must be surrounded by blank lines, and
each entry should be on its own line.
If all content in a section is related, add version text after the header for
the section. The version information must:
Add the version history information as a blockquote:
- Be surrounded by blank lines.
- Start with `>`.
- Version histories with more than one entry should have each entry on its own
line (long lines are okay). Start each line with `> -` to get unordered list
formatting.
- Whenever possible, have a link to the completed issue, merge request, or epic
that introduced the feature. An issue is preferred over a merge request, and
a merge request is preferred over an epic.
```markdown
## Feature name
> Introduced in GitLab 11.3.
> [Introduced](<link-to-issue>) in GitLab 11.3.
This feature does something.
```
Whenever possible, version text should have a link to the completed issue, merge
request, or epic that introduced the feature. An issue is preferred over a merge
request, and a merge request is preferred over an epic. For example:
## Feature name 2
```markdown
> [Introduced](<link-to-issue>) in GitLab 11.3.
```
If you're adding information about new features or changes in a release, update
the blockquote to use a bulleted list:
```markdown
> - [Introduced](<link-to-issue>) in GitLab 11.3.
> - Enabled by default in GitLab 11.4.
> - [Enabled by default](<link-to-issue>) in GitLab 11.4.
This feature does something else.
```
If a feature is moved to another tier:

View file

@ -41,8 +41,8 @@ On your profile page, you can see the following information:
- Personal projects: your personal projects (respecting the project's visibility level)
- Starred projects: projects you starred
- Snippets: your personal code [snippets](../snippets.md#personal-snippets)
- Followers: people following you
- Following: people you are following
- Followers: people [following](../index.md#user-activity) you
- Following: people you are [following](../index.md#user-activity)
Profile page with active Following view:

View file

@ -19,6 +19,10 @@ module Gitlab
raise NotImplementedError
end
def markdown_description
self.class.name
end
def self.identifier
raise NotImplementedError
end

View file

@ -55,10 +55,6 @@ module Gitlab
::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
end
def self.ci_pipeline_editor_page_enabled?(project)
::Feature.enabled?(:ci_pipeline_editor_page, project, default_enabled: :yaml)
end
def self.validate_build_dependencies?(project)
::Feature.enabled?(:ci_validate_build_dependencies, project, default_enabled: :yaml) &&
::Feature.disabled?(:ci_validate_build_dependencies_override, project)

View file

@ -8560,6 +8560,9 @@ msgstr ""
msgid "Create a merge request"
msgstr ""
msgid "Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started."
msgstr ""
msgid "Create a new branch"
msgstr ""
@ -9073,6 +9076,18 @@ msgstr ""
msgid "Cycle Time"
msgstr ""
msgid "CycleAnalyticsEvent|%{label_reference} label was added to the issue"
msgstr ""
msgid "CycleAnalyticsEvent|%{label_reference} label was added to the merge request"
msgstr ""
msgid "CycleAnalyticsEvent|%{label_reference} label was removed from the issue"
msgstr ""
msgid "CycleAnalyticsEvent|%{label_reference} label was removed from the merge request"
msgstr ""
msgid "CycleAnalyticsEvent|Issue closed"
msgstr ""
@ -21229,6 +21244,9 @@ msgstr ""
msgid "OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses."
msgstr ""
msgid "Optimize your workflow with CI/CD Pipelines"
msgstr ""
msgid "Optional"
msgstr ""
@ -22102,9 +22120,6 @@ msgstr ""
msgid "Pipelines|There are currently no pipelines."
msgstr ""
msgid "Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again."
msgstr ""
msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team."
msgstr ""

View file

@ -36,18 +36,5 @@ RSpec.describe Projects::Ci::PipelineEditorController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when ci_pipeline_editor_page feature flag is disabled' do
before do
stub_feature_flags(ci_pipeline_editor_page: false)
project.add_developer(user)
get :show, params: { namespace_id: project.namespace, project_id: project }
end
it 'responds with 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View file

@ -23,15 +23,21 @@ FactoryBot.define do
end
trait :with_aggregation_schedule do
association :aggregation_schedule, factory: :namespace_aggregation_schedules
after(:create) do |namespace|
create(:namespace_aggregation_schedules, namespace: namespace)
end
end
trait :with_root_storage_statistics do
association :root_storage_statistics, factory: :namespace_root_storage_statistics
after(:create) do |namespace|
create(:namespace_root_storage_statistics, namespace: namespace)
end
end
trait :with_namespace_settings do
association :namespace_settings, factory: :namespace_settings
after(:create) do |namespace|
create(:namespace_settings, namespace: namespace)
end
end
trait :shared_runners_disabled do

View file

@ -0,0 +1,42 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
describe('Pipeline editor empty state', () => {
let wrapper;
const defaultProvide = {
emptyStateIllustrationPath: 'my/svg/path',
};
const createComponent = () => {
wrapper = shallowMount(PipelineEditorEmptyState, {
provide: defaultProvide,
});
};
const findSvgImage = () => wrapper.find('img');
const findTitle = () => wrapper.find('h1');
const findDescription = () => wrapper.findComponent(GlSprintf);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders an svg image', () => {
expect(findSvgImage().exists()).toBe(true);
});
it('renders a title', () => {
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title);
});
it('renders a description', () => {
expect(findDescription().exists()).toBe(true);
expect(findDescription().html()).toContain(wrapper.vm.$options.i18n.body);
});
});

View file

@ -7,6 +7,7 @@ import httpStatusCodes from '~/lib/utils/http_status';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
@ -92,6 +93,7 @@ describe('Pipeline editor app component', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findEditorHome = () => wrapper.findComponent(PipelineEditorHome);
const findTextEditor = () => wrapper.findComponent(TextEditor);
const findEmptyState = () => wrapper.findComponent(PipelineEditorEmptyState);
beforeEach(() => {
mockBlobContentData = jest.fn();
@ -146,45 +148,51 @@ describe('Pipeline editor app component', () => {
});
});
describe('when no file exists', () => {
const noFileAlertMsg =
'There is no .gitlab-ci.yml file in this repository, please add one and visit the Pipeline Editor again.';
describe('when no CI config file exists', () => {
describe('in a project without a repository', () => {
it('shows an empty state and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.BAD_REQUEST,
},
});
createComponentWithApollo();
it('shows a 404 error message and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
},
await waitForPromises();
expect(findEmptyState().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
expect(findEditorHome().exists()).toBe(false);
});
createComponentWithApollo();
await waitForPromises();
expect(findAlert().text()).toBe(noFileAlertMsg);
expect(findEditorHome().exists()).toBe(false);
});
it('shows a 400 error message and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.BAD_REQUEST,
},
describe('in a project with a repository', () => {
it('shows an empty state and does not show editor home component', async () => {
mockBlobContentData.mockRejectedValueOnce({
response: {
status: httpStatusCodes.NOT_FOUND,
},
});
createComponentWithApollo();
await waitForPromises();
expect(findEmptyState().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
expect(findEditorHome().exists()).toBe(false);
});
createComponentWithApollo();
await waitForPromises();
expect(findAlert().text()).toBe(noFileAlertMsg);
expect(findEditorHome().exists()).toBe(false);
});
it('shows a unkown error message', async () => {
mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
createComponentWithApollo();
await waitForPromises();
describe('because of a fetching error', () => {
it('shows a unkown error message', async () => {
mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
createComponentWithApollo();
await waitForPromises();
expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[LOAD_FAILURE_UNKNOWN]);
expect(findEditorHome().exists()).toBe(true);
expect(findEmptyState().exists()).toBe(false);
expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[LOAD_FAILURE_UNKNOWN]);
expect(findEditorHome().exists()).toBe(true);
});
});
});

View file

@ -19,12 +19,5 @@ RSpec.describe Ci::PipelineEditorHelper do
expect(subject).to be false
end
it 'user can not view editor if feature is disabled' do
allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
stub_feature_flags(ci_pipeline_editor_page: false)
expect(subject).to be false
end
end
end

View file

@ -19,22 +19,6 @@ RSpec.describe NotificationsHelper do
it { expect(notification_title(:global)).to match('Global') }
end
describe '#notification_event_name' do
context 'for success_pipeline' do
it 'returns the custom name' do
expect(FastGettext).to receive(:cached_find).with('NotificationEvent|Successful pipeline')
expect(notification_event_name(:success_pipeline)).to eq('Successful pipeline')
end
end
context 'for everything else' do
it 'returns a humanized name' do
expect(FastGettext).to receive(:cached_find).with('NotificationEvent|Failed pipeline')
expect(notification_event_name(:failed_pipeline)).to eq('Failed pipeline')
end
end
end
describe '#notification_icon_level' do
let(:user) { create(:user) }
let(:global_setting) { user.global_notification_setting }

View file

@ -134,4 +134,14 @@ RSpec.describe Gitlab::Ci::Status::Factory do
it_behaves_like 'compound decorator factory'
end
end
context 'behaviour of FactoryBot traits that create associations' do
context 'creating a namespace with an associated aggregation_schedule record' do
it 'creates only one Namespace record and one Namespace::AggregationSchedule record' do
expect { create(:namespace, :with_aggregation_schedule) }
.to change { Namespace.count }.by(1)
.and change { Namespace::AggregationSchedule.count }.by(1)
end
end
end
end

View file

@ -2,6 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/MultipleMemoizedHelpers
RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
context "with worker attribution" do
subject { described_class.new }
@ -287,3 +288,4 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
end
end
end
# rubocop: enable RSpec/MultipleMemoizedHelpers

View file

@ -4,40 +4,41 @@ require 'spec_helper'
RSpec.describe Boards::Issues::ListService do
describe '#execute' do
let_it_be(:user) { create(:user) }
context 'when parent is a project' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:board) { create(:board, project: project) }
let(:m1) { create(:milestone, project: project) }
let(:m2) { create(:milestone, project: project) }
let_it_be(:m1) { create(:milestone, project: project) }
let_it_be(:m2) { create(:milestone, project: project) }
let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
let_it_be(:development) { create(:label, project: project, name: 'Development') }
let_it_be(:testing) { create(:label, project: project, name: 'Testing') }
let_it_be(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
let_it_be(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let_it_be(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
let!(:backlog) { create(:backlog_list, board: board) }
let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board) }
let_it_be(:backlog) { create(:backlog_list, board: board) }
let_it_be(:list1) { create(:list, board: board, label: development, position: 0) }
let_it_be(:list2) { create(:list, board: board, label: testing, position: 1) }
let_it_be(:closed) { create(:closed_list, board: board) }
let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) }
let!(:reopened_issue1) { create(:issue, :opened, project: project, title: 'Reopened Issue 1' ) }
let_it_be(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
let_it_be(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) }
let_it_be(:reopened_issue1) { create(:issue, :opened, project: project, title: 'Reopened Issue 1' ) }
let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, development]) }
let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
let!(:list1_issue3) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1]) }
let!(:list2_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [testing]) }
let_it_be(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, development]) }
let_it_be(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) }
let_it_be(:list1_issue3) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1]) }
let_it_be(:list2_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [testing]) }
let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug], closed_at: 1.day.ago) }
let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3], closed_at: 2.days.ago) }
let!(:closed_issue3) { create(:issue, :closed, project: project, closed_at: 1.week.ago) }
let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1], closed_at: 1.year.ago) }
let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development], closed_at: 2.years.ago) }
let_it_be(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug], closed_at: 1.day.ago) }
let_it_be(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3], closed_at: 2.days.ago) }
let_it_be(:closed_issue3) { create(:issue, :closed, project: project, closed_at: 1.week.ago) }
let_it_be(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1], closed_at: 1.year.ago) }
let_it_be(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development], closed_at: 2.years.ago) }
let(:parent) { project }
@ -48,14 +49,16 @@ RSpec.describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
context 'when project is archived' do
let(:project) { create(:project, :archived) }
before do
project.update!(archived: true)
end
it_behaves_like 'issues list service'
end
end
# rubocop: disable RSpec/MultipleMemoizedHelpers
context 'when parent is a group' do
let(:user) { create(:user) }
let(:project) { create(:project, :empty_repo, namespace: group) }
let(:project1) { create(:project, :empty_repo, namespace: group) }
let(:project_archived) { create(:project, :empty_repo, :archived, namespace: group) }
@ -104,7 +107,7 @@ RSpec.describe Boards::Issues::ListService do
group.add_developer(user)
end
context 'and group has no parent' do
context 'when the group has no parent' do
let(:parent) { group }
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
@ -112,7 +115,7 @@ RSpec.describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
end
context 'and group is an ancestor' do
context 'when the group is an ancestor' do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
let!(:backlog) { create(:backlog_list, board: board) }
@ -125,5 +128,6 @@ RSpec.describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
end
end
# rubocop: enable RSpec/MultipleMemoizedHelpers
end
end

View file

@ -4,9 +4,9 @@ require 'spec_helper'
RSpec.describe Labels::PromoteService do
describe '#execute' do
let!(:user) { create(:user) }
let_it_be(:user) { create(:user) }
context 'project without group' do
context 'without a group' do
let!(:project_1) { create(:project) }
let!(:project_label_1_1) { create(:label, project: project_1) }
@ -18,40 +18,40 @@ RSpec.describe Labels::PromoteService do
end
end
context 'project with group' do
let!(:promoted_label_name) { "Promoted Label" }
let!(:untouched_label_name) { "Untouched Label" }
let!(:promoted_description) { "Promoted Description" }
let!(:promoted_color) { "#0000FF" }
let!(:label_2_1_priority) { 1 }
let!(:label_3_1_priority) { 2 }
context 'with a group' do
let_it_be(:promoted_label_name) { "Promoted Label" }
let_it_be(:untouched_label_name) { "Untouched Label" }
let_it_be(:promoted_description) { "Promoted Description" }
let_it_be(:promoted_color) { "#0000FF" }
let_it_be(:label_2_1_priority) { 1 }
let_it_be(:label_3_1_priority) { 2 }
let!(:group_1) { create(:group) }
let!(:group_2) { create(:group) }
let_it_be(:group_1) { create(:group) }
let_it_be(:group_2) { create(:group) }
let!(:project_1) { create(:project, namespace: group_1) }
let!(:project_2) { create(:project, namespace: group_1) }
let!(:project_3) { create(:project, namespace: group_1) }
let!(:project_4) { create(:project, namespace: group_2) }
let_it_be(:project_1) { create(:project, :repository, namespace: group_1) }
let_it_be(:project_2) { create(:project, :repository, namespace: group_1) }
let_it_be(:project_3) { create(:project, :repository, namespace: group_1) }
let_it_be(:project_4) { create(:project, :repository, namespace: group_2) }
# Labels/issues can't be lazily created so we might as well eager initialize
# all other objects too since we use them inside
let!(:project_label_1_1) { create(:label, project: project_1, name: promoted_label_name, color: promoted_color, description: promoted_description) }
let!(:project_label_1_2) { create(:label, project: project_1, name: untouched_label_name) }
let!(:project_label_2_1) { create(:label, project: project_2, priority: label_2_1_priority, name: promoted_label_name, color: "#FF0000") }
let!(:project_label_3_1) { create(:label, project: project_3, priority: label_3_1_priority, name: promoted_label_name) }
let!(:project_label_3_2) { create(:label, project: project_3, priority: 1, name: untouched_label_name) }
let!(:project_label_4_1) { create(:label, project: project_4, name: promoted_label_name) }
let_it_be(:project_label_1_1) { create(:label, project: project_1, name: promoted_label_name, color: promoted_color, description: promoted_description) }
let_it_be(:project_label_1_2) { create(:label, project: project_1, name: untouched_label_name) }
let_it_be(:project_label_2_1) { create(:label, project: project_2, priority: label_2_1_priority, name: promoted_label_name, color: "#FF0000") }
let_it_be(:project_label_3_1) { create(:label, project: project_3, priority: label_3_1_priority, name: promoted_label_name) }
let_it_be(:project_label_3_2) { create(:label, project: project_3, priority: 1, name: untouched_label_name) }
let_it_be(:project_label_4_1) { create(:label, project: project_4, name: promoted_label_name) }
let!(:issue_1_1) { create(:labeled_issue, project: project_1, labels: [project_label_1_1, project_label_1_2]) }
let!(:issue_1_2) { create(:labeled_issue, project: project_1, labels: [project_label_1_2]) }
let!(:issue_2_1) { create(:labeled_issue, project: project_2, labels: [project_label_2_1]) }
let!(:issue_4_1) { create(:labeled_issue, project: project_4, labels: [project_label_4_1]) }
let_it_be(:issue_1_1) { create(:labeled_issue, project: project_1, labels: [project_label_1_1, project_label_1_2]) }
let_it_be(:issue_1_2) { create(:labeled_issue, project: project_1, labels: [project_label_1_2]) }
let_it_be(:issue_2_1) { create(:labeled_issue, project: project_2, labels: [project_label_2_1]) }
let_it_be(:issue_4_1) { create(:labeled_issue, project: project_4, labels: [project_label_4_1]) }
let!(:merge_3_1) { create(:labeled_merge_request, source_project: project_3, target_project: project_3, labels: [project_label_3_1, project_label_3_2]) }
let_it_be(:merge_3_1) { create(:labeled_merge_request, source_project: project_3, target_project: project_3, labels: [project_label_3_1, project_label_3_2]) }
let!(:issue_board_2_1) { create(:board, project: project_2) }
let!(:issue_board_list_2_1) { create(:list, board: issue_board_2_1, label: project_label_2_1) }
let_it_be(:issue_board_2_1) { create(:board, project: project_2) }
let_it_be(:issue_board_list_2_1) { create(:list, board: issue_board_2_1, label: project_label_2_1) }
let(:new_label) { group_1.labels.find_by(title: promoted_label_name) }
@ -82,8 +82,8 @@ RSpec.describe Labels::PromoteService do
expect { service.execute(project_label_1_1) }.to change { Subscription.count }.from(4).to(3)
expect(new_label.subscribed?(user)).to be_truthy
expect(new_label.subscribed?(user2)).to be_truthy
expect(new_label).to be_subscribed(user)
expect(new_label).to be_subscribed(user2)
end
it 'recreates priorities' do
@ -165,12 +165,12 @@ RSpec.describe Labels::PromoteService do
service.execute(project_label_1_1)
Label.reflect_on_all_associations.each do |association|
expect(project_label_1_1.send(association.name).any?).to be_falsey
expect(project_label_1_1.send(association.name).reset).not_to be_any
end
end
end
context 'if there is an existing identical group label' do
context 'when there is an existing identical group label' do
let!(:existing_group_label) { create(:group_label, group: group_1, title: project_label_1_1.title ) }
it 'uses the existing group label' do
@ -187,7 +187,7 @@ RSpec.describe Labels::PromoteService do
it_behaves_like 'promoting a project label to a group label'
end
context 'if there is no existing identical group label' do
context 'when there is no existing identical group label' do
let(:existing_group_label) { nil }
it 'recreates the label as a group label' do

View file

@ -8,6 +8,7 @@ RSpec.shared_examples_for 'value stream analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
it { expect(instance).to respond_to(:markdown_description) }
it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do