Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-07-21 12:08:33 +00:00
parent c1cea595b6
commit 6c44b67631
46 changed files with 709 additions and 328 deletions

View File

@ -61,6 +61,7 @@ export default {
<gl-button
variant="confirm"
class="gl-mt-3"
data-testid="create_new_ci_button"
data-qa-selector="create_new_ci_button"
@click="createEmptyConfigFile"
>

View File

@ -1,4 +1,3 @@
import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import { TREE_TYPE } from '../constants';
export const getLowestSingleFolder = (folder) => {
@ -28,7 +27,7 @@ export const getLowestSingleFolder = (folder) => {
const { path, tree } = getFolder(folder, [folder.name]);
return {
path: truncatePathMiddleToLength(path.join('/'), 40),
path: path.join('/'),
treeAcc: tree.length ? tree[tree.length - 1].tree : null,
};
};

View File

@ -167,36 +167,6 @@ export const truncateWidth = (string, options = {}) => {
*/
export const truncateSha = (sha) => sha.substring(0, 8);
const ELLIPSIS_CHAR = '…';
export const truncatePathMiddleToLength = (text, maxWidth) => {
let returnText = text;
let ellipsisCount = 0;
while (returnText.length >= maxWidth) {
const textSplit = returnText.split('/').filter((s) => s !== ELLIPSIS_CHAR);
if (textSplit.length === 0) {
// There are n - 1 path separators for n segments, so 2n - 1 <= maxWidth
const maxSegments = Math.floor((maxWidth + 1) / 2);
return new Array(maxSegments).fill(ELLIPSIS_CHAR).join('/');
}
const middleIndex = Math.floor(textSplit.length / 2);
returnText = textSplit
.slice(0, middleIndex)
.concat(
new Array(ellipsisCount + 1).fill().map(() => ELLIPSIS_CHAR),
textSplit.slice(middleIndex + 1),
)
.join('/');
ellipsisCount += 1;
}
return returnText;
};
/**
* Capitalizes first character
*

View File

@ -16,8 +16,6 @@ import DynamicContent from './dynamic_content.vue';
import StatusIcon from './status_icon.vue';
import ActionButtons from './action_buttons.vue';
const FETCH_TYPE_COLLAPSED = 'collapsed';
const FETCH_TYPE_EXPANDED = 'expanded';
const WIDGET_PREFIX = 'Widget';
const MISSING_RESPONSE_HEADERS =
'MR Widget: raesponse object should contain status and headers object. Make sure to include that in your `fetchCollapsedData` and `fetchExpandedData` functions.';
@ -49,15 +47,6 @@ export default {
SafeHtml,
},
props: {
/**
* @param {value.collapsed} Object
* @param {value.expanded} Object
*/
value: {
type: Object,
required: false,
default: () => ({}),
},
loadingText: {
type: String,
required: false,
@ -238,7 +227,7 @@ export default {
try {
if (this.fetchCollapsedData) {
await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED);
await this.fetch(this.fetchCollapsedData);
}
} catch {
this.summaryError = this.errorText;
@ -271,7 +260,7 @@ export default {
this.contentError = null;
try {
await this.fetch(this.fetchExpandedData, FETCH_TYPE_EXPANDED);
await this.fetch(this.fetchExpandedData);
} catch {
this.contentError = this.errorText;
@ -282,7 +271,7 @@ export default {
this.isLoadingExpandedContent = false;
},
fetch(handler, dataType) {
fetch(handler) {
const requests = this.multiPolling ? handler() : [handler];
const promises = requests.map((request) => {
@ -319,9 +308,7 @@ export default {
});
});
return Promise.all(promises).then((data) => {
this.$emit('input', { ...this.value, [dataType]: this.multiPolling ? data : data[0] });
});
return Promise.all(promises);
},
},
failedStatusIcon: EXTENSION_ICONS.failed,

View File

@ -110,7 +110,7 @@ module Projects
update_pending_builds
post_update_hooks(project)
post_update_hooks(project, @old_group)
rescue Exception # rubocop:disable Lint/RescueException
rollback_side_effects
raise
@ -119,7 +119,7 @@ module Projects
end
# Overridden in EE
def post_update_hooks(project)
def post_update_hooks(project, _old_group)
ensure_personal_project_owner_membership(project)
invalidate_personal_projects_counts

View File

@ -1,3 +1,4 @@
.form-group
= f.label s_('SubgroupCreationLevel|Roles allowed to create subgroups'), class: 'label-bold'
= f.select :subgroup_creation_level, options_for_select(::Gitlab::Access.subgroup_creation_options, group.subgroup_creation_level), {}, class: 'form-control'
- ::Gitlab::Access.subgroup_creation_options.each do |label, action|
= f.gitlab_ui_radio_component :subgroup_creation_level, action, label

View File

@ -0,0 +1,8 @@
---
name: ci_interpolation_inputs_refactor
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125632
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/418198
milestone: '16.3'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -371,6 +371,11 @@ module.exports = {
include: /node_modules/,
loader: 'babel-loader',
},
{
test: /gridstack\/.*\.js$/,
include: /node_modules/,
loader: 'babel-loader',
},
{
test: /_worker\.js$/,
resourceQuery: /worker/,

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class EnsureBackfillForCiPipelineVariablesPipelineIdIsFinished < Gitlab::Database::Migration[2.1]
include Gitlab::Database::MigrationHelpers::ConvertToBigint
restrict_gitlab_migration gitlab_schema: :gitlab_ci
disable_ddl_transaction!
TABLE_NAME = :ci_pipeline_variables
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
table_name: TABLE_NAME,
column_name: 'pipeline_id',
job_arguments: [['pipeline_id'], ['id_convert_to_bigint']]
)
end
def down
# no-op
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class CreateAsyncIndexForCiPiplineVariablesPipelineId < Gitlab::Database::Migration[2.1]
TABLE_NAME = :ci_pipeline_variables
INDEX_NAME = "index_ci_pipeline_variables_on_pipeline_id_bigint_and_key"
def up
prepare_async_index TABLE_NAME, [:pipeline_id_convert_to_bigint, :key], unique: true, name: INDEX_NAME
end
def down
unprepare_async_index TABLE_NAME, [:pipeline_id_convert_to_bigint, :key], unique: true, name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
e2a7487d4da68819a1bdde6626b32ef8399a917a75d4cbc8ecf2fa140ba1ff16

View File

@ -0,0 +1 @@
54ac5a22e121379b1ffcefc7b4c1f26cadd15a9b0cabfd0d9e3cba3886777d46

View File

@ -28,7 +28,7 @@ is recommended when [FIPS mode](../../development/fips_compliance.md) is enabled
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.
Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point)
Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point)
usually supplies this URL.
```plaintext

View File

@ -36,7 +36,7 @@ gRPC would carry messages between modules.
## Use Packwerk to enforce module boundaries
Packwerk is a static analyzer that helps defining and enforcing module boundaries
in Ruby.
in Ruby.
[In this PoC merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98801)
we demonstrate a possible directory structure of the monolith broken down into separate

View File

@ -367,7 +367,7 @@ This action is not reversible.
Any existing CI template, that you share with other projects via `include:` syntax, can be converted to a CI component.
1. Decide whether you want the component to be part of an existing [components repository](#components-repository),
1. Decide whether you want the component to be part of an existing [components repository](#components-repository),
if you want to logically group components together. Create and setup a [components repository](#components-repository) otherwise.
1. Create a YAML file in the components repository according to the expected [directory structure](#directory-structure).
1. Copy the content of the template YAML file into the new component YAML file.

View File

@ -154,7 +154,7 @@ to interact with your application, so we need to install and run them.
Furthermore, WebdriverIO uses Selenium as a common interface to control different browsers,
so we need to install and run Selenium as well. Luckily, the Selenium project provides the Docker images for Firefox
[standalone-firefox](https://hub.docker.com/r/selenium/standalone-firefox/) and
and for Chrome [standalone-chrome](https://hub.docker.com/r/selenium/standalone-chrome/).
and for Chrome [standalone-chrome](https://hub.docker.com/r/selenium/standalone-chrome/).
(Since Safari and Internet Explorer/Edge are not open source and
not available for Linux, we are unfortunately unable to use those in GitLab CI/CD).

View File

@ -52,9 +52,9 @@ during indexing and searching operations. Some of the benefits and tradeoffs to
- Routing is not used if too many shards would be hit for global and group scoped searches.
- Shard size imbalance might occur.
## Existing Analyzers/Tokenizers/Filters
## Existing analyzers and tokenizers
These are all defined in [`ee/lib/elastic/latest/config.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/elastic/latest/config.rb)
The following analyzers and tokenizers are defined in [`ee/lib/elastic/latest/config.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/elastic/latest/config.rb).
### Analyzers
@ -72,7 +72,7 @@ Please see the `sha_tokenizer` explanation later below for an example.
#### `code_analyzer`
Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the filters: [`code`](#code), `lowercase`, and `asciifolding`
Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the [`word_delimiter_graph`](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-word-delimiter-graph-tokenfilter.html), `lowercase`, and `asciifolding` filters.
The `whitespace` tokenizer was selected to have more control over how tokens are split. For example the string `Foo::bar(4)` needs to generate tokens like `Foo` and `bar(4)` to be properly searched.
@ -81,10 +81,6 @@ Please see the `code` filter for an explanation on how tokens are split.
NOTE:
The [Elasticsearch `code_analyzer` doesn't account for all code cases](../integration/advanced_search/elasticsearch_troubleshooting.md#elasticsearch-code_analyzer-doesnt-account-for-all-code-cases).
#### `code_search_analyzer`
Not directly used for indexing, but rather used to transform a search input. Uses the `whitespace` tokenizer and the `lowercase` and `asciifolding` filters.
### Tokenizers
#### `sha_tokenizer`
@ -115,27 +111,10 @@ Example:
- `'path/application.js'`
- `'application.js'`
### Filters
#### `code`
Uses a [Pattern Capture token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pattern-capture-tokenfilter.html) to split tokens into more easily searched versions of themselves.
Patterns:
- `"(\\p{Ll}+|\\p{Lu}\\p{Ll}+|\\p{Lu}+)"`: captures CamelCase and lowerCamelCase strings as separate tokens
- `"(\\d+)"`: extracts digits
- `"(?=([\\p{Lu}]+[\\p{L}]+))"`: captures CamelCase strings recursively. For example: `ThisIsATest` => `[ThisIsATest, IsATest, ATest, Test]`
- `'"((?:\\"|[^"]|\\")*)"'`: captures terms inside quotes, removing the quotes
- `"'((?:\\'|[^']|\\')*)'"`: same as above, for single-quotes
- `'\.([^.]+)(?=\.|\s|\Z)'`: separate terms with periods in-between
- `'([\p{L}_.-]+)'`: some common chars in file names to keep the whole filename intact (for example `my_file-ñame.txt`)
- `'([\p{L}\d_]+)'`: letters, numbers and underscores are the most common tokens in programming. Always capture them greedily regardless of context.
## Gotchas
- Searches can have their own analyzers. Remember to check when editing analyzers
- `Character` filters (as opposed to token filters) always replace the original character, so they're not a good choice as they can hinder exact searches
- Searches can have their own analyzers. Remember to check when editing analyzers.
- `Character` filters (as opposed to token filters) always replace the original character. These filters can hinder exact searches.
## Zero downtime reindexing with multiple indices

View File

@ -45,7 +45,7 @@ For versioning and upgrade details, see our [Release and Maintenance policy](../
GitLab self-managed packages are semantically versioned and follow our [maintenance policy](../../policy/maintenance.md). This process applies to features and APIs that are generally available, not beta or experimental.
This maintenance policy is in place to allow our customers to prepare for disruptive changes by establishing a clear and predictable pattern that is widely used in the software industry. For many of our customers, GitLab is a business-critical application and surprising changes can cause damages and erode trust.
This maintenance policy is in place to allow our customers to prepare for disruptive changes by establishing a clear and predictable pattern that is widely used in the software industry. For many of our customers, GitLab is a business-critical application and surprising changes can cause damages and erode trust.
Introducing breaking changes in minor releases is against policy because it can disrupt our customers and introduces an element of randomness that requires customers to check for breaking changes every minor release to ensure that their business is not impacted. This does not align with our goal [to make it as easy as possible for customers to do business with GitLab](https://about.gitlab.com/company/yearlies/#fy24-yearlies) and is strongly discouraged.

View File

@ -47,7 +47,7 @@ In general, for topic titles:
- Follow [capitalization](../styleguide/index.md#topic-titles) guidelines.
- Do not repeat text from earlier topic titles. For example, if the page is about merge requests,
instead of `Troubleshooting merge requests`, use only `Troubleshooting`.
- Avoid using hyphens to separate information.
- Avoid using hyphens to separate information.
For example, instead of `Internal analytics - Architecture`, use `Internal analytics architecture` or `Architecture of internal analytics`.
See also [guidelines for heading levels in Markdown](../styleguide/index.md#heading-levels-in-markdown).

View File

@ -13,7 +13,7 @@ We extract libraries from our codebase when their functionality
is highly isolated and we want to use them in other applications
ourselves or we think it would benefit the wider community.
Extracting code to a gem also ensures that the gem does not contain any hidden
Extracting code to a gem also ensures that the gem does not contain any hidden
dependencies on our application code.
Gems should always be used when implementing functionality that can be considered isolated,
@ -36,7 +36,7 @@ You can always start by creating a new Gem [in the same repo](#in-the-same-repo)
to be used by a wider community.
WARNING:
To prevent malicious actors from name-squatting the extracted Gems, follow the instructions
To prevent malicious actors from name-squatting the extracted Gems, follow the instructions
to [reserve a gem name](#reserve-a-gem-name).
## Advantages of using Gems
@ -81,7 +81,7 @@ and prevents complexity (coordinating changes across repos, new permissions, mul
Gems stored in the same repo should be referenced in `Gemfile` with the `path:` syntax.
WARNING:
To prevent malicious actors from name-squatting the extracted Gems, follow the instructions
To prevent malicious actors from name-squatting the extracted Gems, follow the instructions
to [reserve a gem name](#reserve-a-gem-name).
### Create and use a new Gem

View File

@ -680,7 +680,7 @@ on, check out our [self-service framework](geo/framework.md).
After triggering a successful [e2e:package-and-test-ee](testing_guide/end_to_end/index.md#using-the-package-and-test-job) pipeline, you can manually trigger a job named `GET:Geo`:
1. In the [GitLab project](https://gitlab.com/gitlab-org/gitlab), select the **Pipelines** tab of a merge request.
1. In the [GitLab project](https://gitlab.com/gitlab-org/gitlab), select the **Pipelines** tab of a merge request.
1. Select the `Stage: qa` stage on the latest pipeline to expand and list all the related jobs.
1. Select `trigger-omnibus` to view the [Omnibus GitLab Mirror](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) pipeline corresponding to the merge request.
1. The `GET:Geo` job can be found and triggered under the `trigger-qa` stage.

View File

@ -577,7 +577,7 @@ All other attributes are optional.
##### SAST
The `location` of a SAST vulnerability must have a `file` that gives the path of the affected file and
The `location` of a SAST vulnerability must have a `file` that gives the path of the affected file and
a `start_line` field with the affected line number.
It may also have an `end_line`, a `class`, and a `method`.

View File

@ -42,7 +42,7 @@ or even a result of a public holiday in some regions of the world with a larger
1. Check (via [Grafana explore tab](https://dashboards.gitlab.net/explore) ) following Prometheus counters `gitlab_snowplow_events_total`, `gitlab_snowplow_failed_events_total` and `gitlab_snowplow_successful_events_total` to see how many events were fired correctly from GitLab.com. Example query to use `sum(rate(gitlab_snowplow_successful_events_total{env="gprd"}[5m])) / sum(rate(gitlab_snowplow_events_total{env="gprd"}[5m]))` would chart rate at which number of good events rose in comparison to events sent in total. If number drops from 1 it means that problem might be in communication between GitLab and AWS collectors fleet.
1. Check [logs in Kibana](https://log.gprd.gitlab.net/app/discover#) and filter with `{ "query": { "match_phrase": { "json.message": "failed to be reported to collector at" } } }` if there are some failed events logged
We conducted an investigation into an unexpected drop in snowplow events volume.
We conducted an investigation into an unexpected drop in snowplow events volume.
GitLab team members can view more information in this confidential issue: `https://gitlab.com/gitlab-org/gitlab/-/issues/335206`

View File

@ -30,7 +30,7 @@ A new subscription must be purchased and applied as needed.
## Choose a GitLab tier
Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose
the features that fit your budget.
the features that fit your budget.
For more details, see
[a comparison of self-managed features available in each tier](https://about.gitlab.com/pricing/feature-comparison/).

View File

@ -501,7 +501,7 @@ To run an existing on-demand scan:
1. In the scan's row, select **Run scan**.
If the branch saved in the scan no longer exists, you must:
1. [Edit the scan](#edit-an-on-demand-scan).
1. Select a new branch.
1. Save the edited scan.

View File

@ -22,7 +22,7 @@ GitLab analyzers make an effort to fit the severity descriptions below, but they
## Critical severity
Vulnerabilities identified at the Critical Severity level should be investigated immediately. Vulnerabilities at this level assume exploitation of the flaw could lead to full system or data compromise. Examples of critical severity flaws are Command/Code Injection and SQL Injection. Typically these flaws are rated with CVSS 3.1 between 9.0-10.0.
Vulnerabilities identified at the Critical Severity level should be investigated immediately. Vulnerabilities at this level assume exploitation of the flaw could lead to full system or data compromise. Examples of critical severity flaws are Command/Code Injection and SQL Injection. Typically these flaws are rated with CVSS 3.1 between 9.0-10.0.
## High severity

View File

@ -63,6 +63,24 @@ This page shows groups that you are a member of:
- Through membership of a subgroup's parent group.
- Through direct or inherited membership of a project in the group or subgroup.
## View group activity
To view the activity of a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
1. Select **Manage > Activity**.
1. Optional. To filter activity by contribution type, select a tab:
- **All**: All contributions by group members in the group and group's projects.
- **Push events**: Push events in the group's projects.
- **Merge events**: Accepted merge requests in the group's projects.
- **Issue events**: Issues opened and closed in the group's projects.
- **Epic events**: Epics opened and closed in the group.
- **Comments**: Comments posted by group members in the group's projects.
- **Wiki**: Updates to wiki pages in the group.
- **Designs**: Designs added, updated, and removed in the group's projects.
- **Team**: Group members who joined and left the group's projects.
## Create a group
To create a group:

View File

@ -354,7 +354,16 @@ To view your activity:
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Your work**.
1. Select **Activity**.
1. Optional. To filter your activity by contribution type, in the **Your Activity** tab, select a tab.
1. Optional. To filter your activity by contribution type, in the **Your Activity** tab, select a tab:
- **All**: All contributions you made in your groups and projects.
- **Push events**: Push events you made in your projects.
- **Merge events**: Merge requests you accepted in your projects.
- **Issue events**: Issues you opened and closed in your projects.
- **Comments**: Comments you posted in your projects.
- **Wiki**: Wiki pages you created and updated in your projects.
- **Designs**: Designs you added, updated, and removed in your projects.
- **Team**: Projects you joined and left.
## Session duration

View File

@ -178,7 +178,15 @@ To view the activity of a project:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Manage > Activity**.
1. Select a tab to view the type of project activity.
1. Optional. To filter activity by contribution type, select a tab:
- **All**: All contributions by project members.
- **Push events**: Push events in the project.
- **Merge events**: Accepted merge requests in the project.
- **Issue events**: Issues opened and closed in the project.
- **Comments**: Comments posted by project members.
- **Designs**: Designs added, updated, and removed in the project.
- **Team**: Members who joined and left the project.
## Search in projects

View File

@ -203,6 +203,7 @@ module.exports = (path, options = {}) => {
'@gitlab/favicon-overlay',
'@gitlab/cluster-client',
'bootstrap-vue',
'gridstack',
'three',
'monaco-editor',
'monaco-yaml',

View File

@ -73,7 +73,11 @@ module Gitlab
end
def inputs
@inputs ||= Ci::Input::Inputs.new(spec, args)
@inputs ||= if Feature.enabled?(:ci_interpolation_inputs_refactor)
Ci::Interpolation::Inputs.new(spec, args)
else
Ci::Input::Inputs.new(spec, args)
end
end
def context

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Interpolation
# Interpolation inputs provided by the user.
class Inputs
UnknownInputTypeError = Class.new(StandardError)
TYPES = [
StringInput
].freeze
def initialize(specs, args)
@specs = specs.to_h
@args = args.to_h
@inputs = []
@errors = []
validate!
fabricate!
end
def errors
@errors + @inputs.flat_map(&:errors)
end
def valid?
errors.none?
end
def to_hash
@inputs.inject({}) do |hash, input|
hash.merge(input.to_hash)
end
end
private
def validate!
unknown_inputs = @args.keys - @specs.keys
return if unknown_inputs.empty?
@errors.push("unknown input arguments: #{unknown_inputs.join(', ')}")
end
def fabricate!
@specs.each do |input_name, spec|
input_type = TYPES.find { |klass| klass.matches?(spec) }
unless input_type
@errors.push(
"unknown input specification for `#{input_name}` (valid types: #{valid_type_names.join(', ')})")
next
end
@inputs.push(input_type.new(
name: input_name,
spec: spec,
value: @args[input_name]))
end
end
def valid_type_names
TYPES.map(&:type_name)
end
end
end
end
end

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Interpolation
class Inputs
##
# This is a common abstraction for all input types
class BaseInput
ArgumentNotValidError = Class.new(StandardError)
# Checks whether the class matches the type in the specification
def self.matches?(spec)
raise NotImplementedError
end
# Human readable type used in error messages
def self.type_name
raise NotImplementedError
end
# Checks whether the provided value is of the given type
def valid_value?(value)
raise NotImplementedError
end
attr_reader :errors, :name, :spec, :value
def initialize(name:, spec:, value:)
@name = name
@errors = []
# Treat minimal spec definition (nil) as a valid hash:
# spec:
# inputs:
# website:
@spec = spec || {} # specification from input definition
@value = value # actual value provided by the user
validate!
end
def to_hash
raise ArgumentNotValidError unless valid?
{ name => actual_value }
end
def valid?
@errors.none?
end
private
def validate!
return error('required value has not been provided') if required_input? && value.nil?
# validate default value
return error("default value is not a #{self.class.type_name}") if !required_input? && !valid_value?(default)
# validate provided value
error("provided value is not a #{self.class.type_name}") unless valid_value?(value)
end
def error(message)
@errors.push("`#{name}` input: #{message}")
end
def actual_value
# nil check is to support boolean values.
value.nil? ? default : value
end
# An input specification without a default value is required.
# For example:
# ```yaml
# spec:
# inputs:
# website:
# ```
def required_input?
!spec.key?(:default)
end
def default
spec[:default]
end
end
end
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Interpolation
class Inputs
class StringInput < BaseInput
def self.matches?(spec)
# The input spec can be `nil` when using a minimal specification
# and also when `type` is not specified.
#
# ```yaml
# spec:
# inputs:
# foo:
# ```
spec.nil? || (spec.is_a?(Hash) && [nil, type_name].include?(spec[:type]))
end
def self.type_name
'string'
end
def valid_value?(value)
value.nil? || value.is_a?(String)
end
end
end
end
end
end

View File

@ -51441,6 +51441,9 @@ msgstr ""
msgid "Vulnerability|Try it out"
msgstr ""
msgid "Vulnerability|URL:"
msgstr ""
msgid "Vulnerability|Unmodified Response"
msgstr ""

View File

@ -145,7 +145,7 @@
"gettext-parser": "^6.0.0",
"graphql": "^15.7.2",
"graphql-tag": "^2.11.0",
"gridstack": "^7.3.0",
"gridstack": "^8.3.0",
"highlight.js": "^11.8.0",
"immer": "^9.0.15",
"ipaddr.js": "^1.9.1",

View File

@ -27,13 +27,6 @@ module QA
Page::Project::PipelineEditor::New.perform(&:create_new_ci)
Page::Project::PipelineEditor::Show.perform do |show|
# Editor should display default content when project does not have CI file yet
# New MR checkbox should not be rendered when a new target branch is yet to be provided
aggregate_failures 'check editor default conditions' do
expect(show.editing_content).not_to be_empty
expect(show).to have_no_new_mr_checkbox
end
# The new MR checkbox is visible after a new branch name is set
show.set_source_branch(SecureRandom.hex(10))
expect(show).to have_new_mr_checkbox

View File

@ -11,6 +11,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
let(:default_branch) { 'main' }
let(:other_branch) { 'test' }
let(:branch_with_invalid_ci) { 'despair' }
let(:branch_without_ci) { 'empty' }
let(:default_content) { 'Default' }
@ -45,6 +46,7 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
project.repository.create_file(user, project.ci_config_path_or_default, default_content, message: 'Create CI file for main', branch_name: default_branch)
project.repository.create_file(user, project.ci_config_path_or_default, valid_content, message: 'Create CI file for test', branch_name: other_branch)
project.repository.create_file(user, project.ci_config_path_or_default, invalid_content, message: 'Create CI file for test', branch_name: branch_with_invalid_ci)
project.repository.create_file(user, 'index.js', "file", message: 'New js file', branch_name: branch_without_ci)
visit project_ci_pipeline_editor_path(project)
wait_for_requests
@ -62,6 +64,31 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
end
end
describe 'when there are no CI config file' do
before do
visit project_ci_pipeline_editor_path(project, branch_name: branch_without_ci)
end
it 'renders the empty page', :aggregate_failures do
expect(page).to have_content 'Optimize your workflow with CI/CD Pipelines'
expect(page).to have_selector '[data-testid="create_new_ci_button"]'
end
context 'when clicking on the create new CI button' do
before do
click_button 'Configure pipeline'
end
it 'renders the source editor with default content', :aggregate_failures do
expect(page).to have_selector('#source-editor-')
page.within('#source-editor-') do
expect(page).to have_content('This file is a template, and might need editing before it works on your project.')
end
end
end
end
describe 'When CI yml has valid syntax' do
before do
visit project_ci_pipeline_editor_path(project, branch_name: other_branch)
@ -149,15 +176,6 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
end
shared_examples 'default branch switcher behavior' do
def switch_to_branch(branch)
find('[data-testid="branch-selector"]').click
page.within '[data-testid="branch-selector"]' do
click_button branch
wait_for_requests
end
end
it 'displays current branch' do
page.within('[data-testid="branch-selector"]') do
expect(page).to have_content(default_branch)
@ -195,12 +213,20 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
end
describe 'Branch Switcher' do
def switch_to_branch(branch)
# close button for the popover
find('[data-testid="close-button"]').click
find('[data-testid="branch-selector"]').click
page.within '[data-testid="branch-selector"]' do
click_button branch
wait_for_requests
end
end
before do
visit project_ci_pipeline_editor_path(project)
wait_for_requests
# close button for the popover
find('[data-testid="close-button"]').click
end
it_behaves_like 'default branch switcher behavior'
@ -262,6 +288,24 @@ RSpec.describe 'Pipeline Editor', :js, feature_category: :pipeline_composition d
end
describe 'Commit Form' do
context 'when targetting the main branch' do
it 'does not show the option to create a Merge request', :aggregate_failures do
expect(page).not_to have_selector('[data-testid="new-mr-checkbox"]')
expect(page).not_to have_content('Start a new merge request with these changes')
end
end
context 'when targetting any non-main branch' do
before do
find('#source-branch-field').set('new_branch', clear: :backspace)
end
it 'shows the option to create a Merge request', :aggregate_failures do
expect(page).to have_selector('[data-testid="new-mr-checkbox"]')
expect(page).to have_content('Start a new merge request with these changes')
end
end
it 'is preserved when changing tabs' do
find('#commit-message').set('message', clear: :backspace)
find('#source-branch-field').set('new_branch', clear: :backspace)

View File

@ -382,7 +382,7 @@ describe('~/diffs/utils/tree_worker_utils', () => {
},
{
type: 'tree',
name: 'ee/lib/…/…/…/longtreenametomakepath',
name: 'ee/lib/ee/gitlab/checks/longtreenametomakepath',
tree: [
{
name: 'diff_check.rb',

View File

@ -238,36 +238,6 @@ describe('text_utility', () => {
});
});
describe('truncatePathMiddleToLength', () => {
it('does not truncate text', () => {
expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test');
});
it('truncates middle of the path', () => {
expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff');
});
it('truncates multiple times in the middle of the path', () => {
expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual(
'app/…/…/diff',
);
});
describe('given a path too long for the maxWidth', () => {
it.each`
path | maxWidth | result
${'aa/bb/cc'} | ${1} | ${'…'}
${'aa/bb/cc'} | ${2} | ${'…'}
${'aa/bb/cc'} | ${3} | ${'…/…'}
${'aa/bb/cc'} | ${4} | ${'…/…'}
${'aa/bb/cc'} | ${5} | ${'…/…/…'}
`('truncates ($path, $maxWidth) to $result', ({ path, maxWidth, result }) => {
expect(result.length).toBeLessThanOrEqual(maxWidth);
expect(textUtils.truncatePathMiddleToLength(path, maxWidth)).toEqual(result);
});
});
});
describe('slugifyWithUnderscore', () => {
it('should replaces whitespaces with underscore and convert to lower case', () => {
expect(textUtils.slugifyWithUnderscore('My Input String')).toEqual('my_input_string');

View File

@ -121,14 +121,15 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
describe('fetch', () => {
it('sets the data.collapsed property after a successfull call - multiPolling: false', async () => {
it('calls fetchCollapsedData properly when multiPolling is false', async () => {
const mockData = { headers: {}, status: HTTP_STATUS_OK, data: { vulnerabilities: [] } };
createComponent({ propsData: { fetchCollapsedData: () => Promise.resolve(mockData) } });
const fetchCollapsedData = jest.fn().mockResolvedValue(mockData);
createComponent({ propsData: { fetchCollapsedData } });
await waitForPromises();
expect(wrapper.emitted('input')[0][0]).toEqual({ collapsed: mockData.data, expanded: null });
expect(fetchCollapsedData).toHaveBeenCalledTimes(1);
});
it('sets the data.collapsed property after a successfull call - multiPolling: true', async () => {
it('calls fetchCollapsedData properly when multiPolling is true', async () => {
const mockData1 = {
headers: {},
status: HTTP_STATUS_OK,
@ -140,22 +141,22 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
data: { vulnerabilities: [{ vuln: 2 }] },
};
const fetchCollapsedData = [
jest.fn().mockResolvedValue(mockData1),
jest.fn().mockResolvedValue(mockData2),
];
createComponent({
propsData: {
multiPolling: true,
fetchCollapsedData: () => [
() => Promise.resolve(mockData1),
() => Promise.resolve(mockData2),
],
fetchCollapsedData: () => fetchCollapsedData,
},
});
await waitForPromises();
expect(wrapper.emitted('input')[0][0]).toEqual({
collapsed: [mockData1.data, mockData2.data],
expanded: null,
});
expect(fetchCollapsedData[0]).toHaveBeenCalledTimes(1);
expect(fetchCollapsedData[1]).toHaveBeenCalledTimes(1);
});
it('throws an error when the handler does not include headers or status objects', async () => {
@ -328,11 +329,12 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
};
const fetchExpandedData = jest.fn().mockResolvedValue(mockDataExpanded);
const fetchCollapsedData = jest.fn().mockResolvedValue(mockDataCollapsed);
await createComponent({
propsData: {
isCollapsible: true,
fetchCollapsedData: () => Promise.resolve(mockDataCollapsed),
fetchCollapsedData,
fetchExpandedData,
},
});
@ -340,17 +342,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
findToggleButton().vm.$emit('click');
await waitForPromises();
// First fetches the collapsed data
expect(wrapper.emitted('input')[0][0]).toEqual({
collapsed: mockDataCollapsed.data,
expanded: null,
});
// Then fetches the expanded data
expect(wrapper.emitted('input')[1][0]).toEqual({
collapsed: null,
expanded: mockDataExpanded.data,
});
expect(fetchCollapsedData).toHaveBeenCalledTimes(1);
expect(fetchExpandedData).toHaveBeenCalledTimes(1);
// Triggering a click does not call the expanded data again
findToggleButton().vm.$emit('click');
@ -371,14 +364,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
findToggleButton().vm.$emit('click');
await waitForPromises();
// First fetches the collapsed data
expect(wrapper.emitted('input')[0][0]).toEqual({
collapsed: undefined,
expanded: null,
});
expect(fetchExpandedData).toHaveBeenCalledTimes(1);
expect(wrapper.emitted('input')).toHaveLength(1); // Should not an emit an input call because request failed
findToggleButton().vm.$emit('click');
await waitForPromises();

View File

@ -9,151 +9,9 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
subject { described_class.new(result, arguments) }
context 'when input data is valid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).to be_valid
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
end
context 'when config has a syntax error' do
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') }
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'Invalid configuration format'
end
end
context 'when spec header is invalid' do
let(:header) do
{ spec: { arguments: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid header' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include('header:spec config contains unknown keys: arguments')
end
end
context 'when interpolation block is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.abc ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.errors).to include 'unknown interpolation key: `abc`'
expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
end
end
context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: ['gitlab.com'] }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'unsupported value in input argument `website`'
end
end
context 'when multiple interpolation blocks are invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
end
end
describe '#to_hash' do
context 'when interpolation is not used' do
let(:result) do
::Gitlab::Ci::Config::Yaml::Result.new(config: content)
end
let(:content) do
{ test: 'deploy production' }
end
let(:arguments) { nil }
it 'returns original content' do
subject.interpolate!
expect(subject).not_to be_interpolated
expect(subject.to_hash).to eq(content)
end
end
context 'when interpolation is available' do
# Remove shared examples when ci_interpolation_inputs_refactor is removed.
shared_examples 'interpolator' do
context 'when input data is valid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
@ -166,12 +24,189 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
{ website: 'gitlab.com' }
end
it 'correctly interpolates content' do
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).to be_interpolated
expect(subject).to be_valid
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
end
context 'when config has a syntax error' do
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') }
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'Invalid configuration format'
end
end
context 'when spec header is invalid' do
let(:header) do
{ spec: { arguments: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid header' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include('header:spec config contains unknown keys: arguments')
end
end
context 'when interpolation block is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.abc ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.errors).to include 'unknown interpolation key: `abc`'
expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
end
end
context 'when multiple interpolation blocks are invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message)
.to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
end
end
describe '#to_hash' do
context 'when interpolation is not used' do
let(:result) do
::Gitlab::Ci::Config::Yaml::Result.new(config: content)
end
let(:content) do
{ test: 'deploy production' }
end
let(:arguments) { nil }
it 'returns original content' do
subject.interpolate!
expect(subject.to_hash).to eq(content)
end
end
context 'when interpolation is available' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates content' do
subject.interpolate!
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
end
end
end
it_behaves_like 'interpolator' do
context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: ['gitlab.com'] }
end
it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include '`website` input: provided value is not a string'
end
end
end
context 'when feature flag ci_interpolation_inputs_refactor is disabled' do
before do
stub_feature_flags(ci_interpolation_inputs_refactor: false)
end
it_behaves_like 'interpolator'
context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: ['gitlab.com'] }
end
it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'unsupported value in input argument `website`'
end
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do
describe '.matches?' do
it 'is not implemented' do
expect { described_class.matches?(double) }.to raise_error(NotImplementedError)
end
end
describe '.type_name' do
it 'is not implemented' do
expect { described_class.type_name }.to raise_error(NotImplementedError)
end
end
describe '#valid_value?' do
it 'is not implemented' do
expect do
described_class.new(
name: 'website', spec: { website: nil }, value: { website: 'example.com' }
).valid_value?('test')
end.to raise_error(NotImplementedError)
end
end
end

View File

@ -0,0 +1,97 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Interpolation::Inputs, feature_category: :pipeline_composition do
let(:inputs) { described_class.new(specs, args) }
let(:specs) { { foo: { default: 'bar' } } }
let(:args) { {} }
context 'when inputs are valid' do
where(:specs, :args, :merged) do
[
[
{ foo: { default: 'bar' } }, {},
{ foo: 'bar' }
],
[
{ foo: { default: 'bar' } }, { foo: 'test' },
{ foo: 'test' }
],
[
{ foo: nil }, { foo: 'bar' },
{ foo: 'bar' }
],
[
{ foo: { type: 'string' } }, { foo: 'bar' },
{ foo: 'bar' }
],
[
{ foo: { type: 'string', default: 'bar' } }, { foo: 'test' },
{ foo: 'test' }
],
[
{ foo: { type: 'string', default: 'bar' } }, {},
{ foo: 'bar' }
],
[
{ foo: { default: 'bar' }, baz: nil }, { baz: 'test' },
{ foo: 'bar', baz: 'test' }
]
]
end
with_them do
it 'contains the merged inputs' do
expect(inputs).to be_valid
expect(inputs.to_hash).to eq(merged)
end
end
end
context 'when inputs are invalid' do
where(:specs, :args, :errors) do
[
[
{ foo: nil }, { foo: 'bar', test: 'bar' },
['unknown input arguments: test']
],
[
{ foo: nil }, { test: 'bar', gitlab: '1' },
['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided']
],
[
{ foo: 123 }, {},
['unknown input specification for `foo` (valid types: string)']
],
[
{ a: nil, foo: 123 }, { a: '123' },
['unknown input specification for `foo` (valid types: string)']
],
[
{ foo: nil }, {},
['`foo` input: required value has not been provided']
],
[
{ foo: { default: 123 } }, { foo: 'test' },
['`foo` input: default value is not a string']
],
[
{ foo: { default: 'test' } }, { foo: 123 },
['`foo` input: provided value is not a string']
],
[
{ foo: nil }, { foo: 123 },
['`foo` input: provided value is not a string']
]
]
end
with_them do
it 'contains the merged inputs', :aggregate_failures do
expect(inputs).not_to be_valid
expect(inputs.errors).to contain_exactly(*errors)
end
end
end
end

View File

@ -117,6 +117,10 @@ module.exports = function storybookWebpackConfig({ config }) {
config.resolve.extensions = Array.from(
new Set([...config.resolve.extensions, ...gitlabWebpackConfig.resolve.extensions]),
);
config.resolve.alias = {
...config.resolve.alias,
gridstack: require.resolve('gridstack/dist/es5/gridstack.js'),
};
// Replace any Storybook-defined CSS loaders with our custom one.
config.module.rules = [

View File

@ -6746,10 +6746,10 @@ graphql@^15.7.2:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef"
integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A==
gridstack@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-7.3.0.tgz#7b32395edcd885bc39b84068ac86f2831f7a2451"
integrity sha512-JKZgsHzm1ljkn1NnBZpf8j4NDOBCXTuw0m1ZC0sr6NKUh0BFWzXAONIxtX1hWGUVeKLj5l1VcmnTwCXw5ypDNw==
gridstack@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-8.3.0.tgz#4c79f8b8c4cffeb3664266108e38ed91b3d0f7b4"
integrity sha512-RcL2xskAYKOpakvpSwHdKheG7C7YgNY7777C5m+T1JMjSgcmEc3qPBM573l0NuyjMz4Errx1/3p+rMgUfF4+mw==
gzip-size@^6.0.0:
version "6.0.0"