Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b9ae930d02
commit
4eef6c2c97
|
|
@ -28,6 +28,9 @@ GITALY_SERVER_VERSION @project_278964_bot6 @gitlab-org/maintainers/rails-backend
|
|||
/doc/.vale/ @marcel.amirault @eread @aqualls @gitlab-org/tw-leadership
|
||||
/lib/tasks/gitlab/tw/codeowners.rake @aqualls @gitlab-org/tw-leadership
|
||||
|
||||
^[Source code editing]
|
||||
.solargraph.yml.example @igor.drozdov
|
||||
|
||||
^[Backend]
|
||||
*.rb @gitlab-org/maintainers/rails-backend
|
||||
*.rake @gitlab-org/maintainers/rails-backend
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ export default {
|
|||
setCookie(PREFERRED_LANGUAGE_COOKIE_KEY, code);
|
||||
window.location.reload();
|
||||
},
|
||||
itemTestSelector(locale) {
|
||||
return `language_switcher_lang_${locale}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -41,7 +44,10 @@ export default {
|
|||
@select="onLanguageSelected"
|
||||
>
|
||||
<template #list-item="{ item: locale }">
|
||||
<span :data-testid="`language_switcher_lang_${locale.value}`">
|
||||
<span
|
||||
:data-testid="itemTestSelector(locale.value)"
|
||||
:data-qa-selector="itemTestSelector(locale.value)"
|
||||
>
|
||||
{{ locale.text }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,31 +5,7 @@ export default {
|
|||
components: {
|
||||
ListboxInput,
|
||||
},
|
||||
inject: {
|
||||
label: {
|
||||
from: 'label',
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
from: 'name',
|
||||
},
|
||||
emails: {
|
||||
from: 'emails',
|
||||
default: () => [],
|
||||
},
|
||||
emptyValueText: {
|
||||
from: 'emptyValueText',
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
from: 'value',
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
from: 'disabled',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
inject: ['label', 'name', 'emails', 'emptyValueText', 'value', 'disabled'],
|
||||
data() {
|
||||
return {
|
||||
selected: this.value,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const initNotificationEmailListboxInputs = () => {
|
|||
const els = [...document.querySelectorAll('.js-notification-email-listbox-input')];
|
||||
|
||||
els.forEach((el, index) => {
|
||||
const { label, name, emptyValueText, value } = el.dataset;
|
||||
const { label, name, emptyValueText, value = '' } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
computed: {
|
||||
pypiPipCommand() {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
return `pip install ${this.packageEntity.name} --extra-index-url ${this.packageEntity.pypiUrl}`;
|
||||
return `pip install ${this.packageEntity.name} --index-url ${this.packageEntity.pypiUrl}`;
|
||||
},
|
||||
pypiSetupCommand() {
|
||||
return `[gitlab]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { debounce } from 'lodash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
|
||||
|
|
@ -191,6 +191,9 @@ export default {
|
|||
this.parentWorkItemType,
|
||||
);
|
||||
},
|
||||
showConfidentialityTooltip() {
|
||||
return this.isCreateForm && this.parentConfidential;
|
||||
},
|
||||
addOrCreateMethod() {
|
||||
return this.isCreateForm ? this.createChild : this.addChild;
|
||||
},
|
||||
|
|
@ -207,7 +210,10 @@ export default {
|
|||
return this.parentMilestone?.id;
|
||||
},
|
||||
isSubmitButtonDisabled() {
|
||||
return this.isCreateForm ? this.search.length === 0 : this.workItemsToAdd.length === 0;
|
||||
if (this.isCreateForm) {
|
||||
return this.search.length === 0;
|
||||
}
|
||||
return this.workItemsToAdd.length === 0 || !this.areWorkItemsToAddValid;
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.availableWorkItems.loading;
|
||||
|
|
@ -215,6 +221,32 @@ export default {
|
|||
addInputPlaceholder() {
|
||||
return sprintfWorkItem(I18N_WORK_ITEM_SEARCH_INPUT_PLACEHOLDER, this.childrenTypeName);
|
||||
},
|
||||
tokenSelectorContainerClass() {
|
||||
return !this.areWorkItemsToAddValid ? 'gl-inset-border-1-red-500!' : '';
|
||||
},
|
||||
invalidWorkItemsToAdd() {
|
||||
return this.parentConfidential
|
||||
? this.workItemsToAdd.filter((workItem) => !workItem.confidential)
|
||||
: [];
|
||||
},
|
||||
areWorkItemsToAddValid() {
|
||||
return this.invalidWorkItemsToAdd.length === 0;
|
||||
},
|
||||
showWorkItemsToAddInvalidMessage() {
|
||||
return !this.isCreateForm && !this.areWorkItemsToAddValid;
|
||||
},
|
||||
workItemsToAddInvalidMessage() {
|
||||
return sprintf(
|
||||
s__(
|
||||
'WorkItem|%{invalidWorkItemsList} cannot be added: Cannot assign a non-confidential %{childWorkItemType} to a confidential parent %{parentWorkItemType}. Make the selected %{childWorkItemType} confidential and try again.',
|
||||
),
|
||||
{
|
||||
invalidWorkItemsList: this.invalidWorkItemsToAdd.map(({ title }) => title).join(', '),
|
||||
childWorkItemType: this.childrenTypeName,
|
||||
parentWorkItemType: this.parentWorkItemType,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
||||
|
|
@ -334,6 +366,7 @@ export default {
|
|||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-checkbox
|
||||
v-if="isCreateForm"
|
||||
ref="confidentialityCheckbox"
|
||||
v-model="confidential"
|
||||
name="isConfidential"
|
||||
|
|
@ -342,35 +375,44 @@ export default {
|
|||
>{{ confidentialityCheckboxLabel }}</gl-form-checkbox
|
||||
>
|
||||
<gl-tooltip
|
||||
v-if="parentConfidential"
|
||||
v-if="showConfidentialityTooltip"
|
||||
:target="getConfidentialityTooltipTarget"
|
||||
triggers="hover"
|
||||
>{{ confidentialityCheckboxTooltip }}</gl-tooltip
|
||||
>
|
||||
<gl-token-selector
|
||||
v-if="!isCreateForm"
|
||||
v-model="workItemsToAdd"
|
||||
:dropdown-items="availableWorkItems"
|
||||
:loading="isLoading"
|
||||
:placeholder="addInputPlaceholder"
|
||||
menu-class="gl-dropdown-menu-wide dropdown-reduced-height gl-min-h-7!"
|
||||
class="gl-mb-4"
|
||||
data-testid="work-item-token-select-input"
|
||||
@text-input="debouncedSearchKeyUpdate"
|
||||
@focus="handleFocus"
|
||||
@mouseover.native="handleMouseOver"
|
||||
@mouseout.native="handleMouseOut"
|
||||
>
|
||||
<template #token-content="{ token }">
|
||||
{{ token.title }}
|
||||
</template>
|
||||
<template #dropdown-item-content="{ dropdownItem }">
|
||||
<div class="gl-display-flex">
|
||||
<div class="gl-text-secondary gl-mr-4">{{ getIdFromGraphQLId(dropdownItem.id) }}</div>
|
||||
<div class="gl-text-truncate">{{ dropdownItem.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</gl-token-selector>
|
||||
<div class="gl-mb-4">
|
||||
<gl-token-selector
|
||||
v-if="!isCreateForm"
|
||||
v-model="workItemsToAdd"
|
||||
:dropdown-items="availableWorkItems"
|
||||
:loading="isLoading"
|
||||
:placeholder="addInputPlaceholder"
|
||||
menu-class="gl-dropdown-menu-wide dropdown-reduced-height gl-min-h-7!"
|
||||
:container-class="tokenSelectorContainerClass"
|
||||
data-testid="work-item-token-select-input"
|
||||
@text-input="debouncedSearchKeyUpdate"
|
||||
@focus="handleFocus"
|
||||
@mouseover.native="handleMouseOver"
|
||||
@mouseout.native="handleMouseOut"
|
||||
>
|
||||
<template #token-content="{ token }">
|
||||
{{ token.title }}
|
||||
</template>
|
||||
<template #dropdown-item-content="{ dropdownItem }">
|
||||
<div class="gl-display-flex">
|
||||
<div class="gl-text-secondary gl-mr-4">{{ getIdFromGraphQLId(dropdownItem.id) }}</div>
|
||||
<div class="gl-text-truncate">{{ dropdownItem.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</gl-token-selector>
|
||||
<div
|
||||
v-if="showWorkItemsToAddInvalidMessage"
|
||||
class="gl-text-red-500"
|
||||
data-testid="work-items-invalid"
|
||||
>
|
||||
{{ workItemsToAddInvalidMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ query projectWorkItems(
|
|||
id
|
||||
title
|
||||
state
|
||||
confidential
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,21 @@ module Mutations
|
|||
null: true,
|
||||
description: 'Job after the mutation.'
|
||||
|
||||
argument :variables, [::Types::Ci::VariableInputType],
|
||||
required: false,
|
||||
default_value: [],
|
||||
replace_null_with_default: true,
|
||||
description: 'Variables to use when playing a manual job.'
|
||||
|
||||
authorize :update_build
|
||||
|
||||
def resolve(id:)
|
||||
def resolve(id:, variables:)
|
||||
job = authorized_find!(id: id)
|
||||
project = job.project
|
||||
variables = variables.map(&:to_h)
|
||||
|
||||
::Ci::PlayBuildService.new(project, current_user).execute(job, variables)
|
||||
|
||||
::Ci::PlayBuildService.new(project, current_user).execute(job)
|
||||
{
|
||||
job: job,
|
||||
errors: errors_on_object(job)
|
||||
|
|
|
|||
|
|
@ -3704,6 +3704,7 @@ Input type: `JobPlayInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationjobplayclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationjobplayid"></a>`id` | [`CiBuildID!`](#cibuildid) | ID of the job to mutate. |
|
||||
| <a id="mutationjobplayvariables"></a>`variables` | [`[CiVariableInput!]`](#civariableinput) | Variables to use when playing a manual job. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
|
|||
|
|
@ -143,30 +143,35 @@ For example: `gitlab-org/dast@1.0`.
|
|||
|
||||
### The component path
|
||||
|
||||
A component path must contain at least the metadata YAML and optionally a related `README.md` documentation file.
|
||||
A component path must contain at least the component YAML and optionally a related `README.md` documentation file.
|
||||
|
||||
The component path can be:
|
||||
|
||||
- A path to a project: `gitlab-org/dast`. In this case the 2 files are defined in the root directory of the repository.
|
||||
- A path to a project subdirectory: `gitlab-org/dast/api-scan`. In this case the 2 files are defined in the `api-scan` directory.
|
||||
- A path to a local directory: `/path/to/component`. This path must contain the metadata YAML that defines the component.
|
||||
- A path to a project: `gitlab-org/dast`. The default component is processed.
|
||||
- A path to an explicit component: `gitlab-org/dast/api-scan`. In this case the explicit `api-scan` component is processed.
|
||||
- A path to a local directory: `/path/to/component`. This path must contain the component YAML that defines the component.
|
||||
The path must start with `/` to indicate a full path in the repository.
|
||||
|
||||
The metadata YAML file follows the filename convention `gitlab-<component-type>.yml` where component type is one of:
|
||||
The component YAML file follows the filename convention `<type>.yml` where component type is one of:
|
||||
|
||||
| Component type | Context |
|
||||
| -------------- | ------- |
|
||||
| `template` | For components used under `include:` keyword |
|
||||
| `step` | For components used under `steps:` keyword |
|
||||
| `workflow` | For components used under `trigger:` keyword |
|
||||
|
||||
Based on the context where the component is used we fetch the correct YAML file.
|
||||
For example, if we are including a component `gitlab-org/dast@1.0` we expect a YAML file named `gitlab-template.yml` in the
|
||||
top level directory of `gitlab-org/dast` repository.
|
||||
For example:
|
||||
|
||||
A `gitlab-<component-type>.yml` file:
|
||||
- if we are including a component `gitlab-org/dast@1.0` we expect a YAML file named `template.yml` in the
|
||||
root directory of `gitlab-org/dast` repository.
|
||||
- if we are including a component `gitlab-org/dast/api-scan@1.0` we expect a YAML file named `template.yml` inside a
|
||||
directory `api-scan` of `gitlab-org/dast` repository.
|
||||
- if we are using a step component `gitlab-org/dast/api-scan@1.0` we expect a YAML file named `step.yml` inside a
|
||||
directory `api-scan` of `gitlab-org/dast` repository.
|
||||
|
||||
- Must have a **name** to be referenced to and **description** for extra details.
|
||||
A component YAML file:
|
||||
|
||||
- Must have a **name** to be referenced to.
|
||||
- Must specify its **type** in the filename, which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step).
|
||||
- Must define its **content** based on the type.
|
||||
- Must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables.
|
||||
|
|
@ -187,8 +192,8 @@ spec:
|
|||
content: { ... }
|
||||
```
|
||||
|
||||
Components that are released in the catalog must have a `README.md` file in the same directory as the
|
||||
metadata YAML file. The `README.md` represents the documentation for the specific component, hence it's recommended
|
||||
Components that are released in the catalog must have a `README.md` file at the root directory of the repository.
|
||||
The `README.md` represents the documentation for the specific component, hence it's recommended
|
||||
even when not releasing versions in the catalog.
|
||||
|
||||
### The component version
|
||||
|
|
@ -230,30 +235,28 @@ The following directory structure would support 1 component per project:
|
|||
|
||||
```plaintext
|
||||
.
|
||||
├── gitlab-<type>.yml
|
||||
├── template.yml
|
||||
├── README.md
|
||||
└── .gitlab-ci.yml
|
||||
```
|
||||
|
||||
The `.gitlab-ci.yml` is recommended for the project to ensure changes are verified accordingly.
|
||||
|
||||
The component is now identified by the path `myorg/rails-rspec`. In other words, this means that
|
||||
the `gitlab-<type>.yml` and `README.md` are located in the root directory of the repository.
|
||||
The component is now identified by the path `myorg/rails-rspec` and we expect a `template.yml` file
|
||||
and `README.md` located in the root directory of the repository.
|
||||
|
||||
The following directory structure would support multiple components per project:
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── .gitlab-ci.yml
|
||||
├── README.md
|
||||
├── unit/
|
||||
│ ├── gitlab-workflow.yml
|
||||
│ └── README.md
|
||||
│ └── template.yml
|
||||
├── integration/
|
||||
│ ├── gitlab-workflow.yml
|
||||
│ └── README.md
|
||||
│ └── template.yml
|
||||
└── feature/
|
||||
├── gitlab-workflow.yml
|
||||
└── README.md
|
||||
└── template.yml
|
||||
```
|
||||
|
||||
In this example we are defining multiple test profiles that are executed with RSpec.
|
||||
|
|
@ -266,18 +269,20 @@ This directory structure could also support both strategies:
|
|||
|
||||
```plaintext
|
||||
.
|
||||
├── gitlab-template.yml # myorg/rails-rspec
|
||||
├── template.yml # myorg/rails-rspec
|
||||
├── README.md
|
||||
├── LICENSE
|
||||
├── .gitlab-ci.yml
|
||||
├── unit/
|
||||
│ ├── gitlab-workflow.yml # myorg/rails-rspec/unit
|
||||
│ └── README.md
|
||||
│ └── template.yml # myorg/rails-rspec/unit
|
||||
├── integration/
|
||||
│ ├── gitlab-workflow.yml # myorg/rails-rspec/integration
|
||||
│ └── README.md
|
||||
└── feature/
|
||||
├── gitlab-workflow.yml # myorg/rails-rspec/feature
|
||||
└── README.md
|
||||
│ └── template.yml # myorg/rails-rspec/integration
|
||||
├── feature/
|
||||
│ └── template.yml # myorg/rails-rspec/feature
|
||||
└── report/
|
||||
├── step.yml # myorg/rails-rspec/report
|
||||
├── Dockerfile
|
||||
└── ... other files
|
||||
```
|
||||
|
||||
With the above structure we could have a top-level component that can be used as the
|
||||
|
|
@ -285,7 +290,7 @@ default component. For example, `myorg/rails-rspec` could run all the test profi
|
|||
However, more specific test profiles could be used separately (for example `myorg/rails-rspec/integration`).
|
||||
|
||||
NOTE:
|
||||
Any nesting more than 1 level is initially not permitted.
|
||||
Nesting of components is not permitted.
|
||||
This limitation encourages cohesion at project level and keeps complexity low.
|
||||
|
||||
## Input parameters `spec:inputs:` parameters
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ module API
|
|||
mount ::API::NugetProjectPackages
|
||||
mount ::API::PackageFiles
|
||||
mount ::API::Pages
|
||||
mount ::API::PagesDomains
|
||||
mount ::API::PersonalAccessTokens::SelfInformation
|
||||
mount ::API::PersonalAccessTokens
|
||||
mount ::API::ProjectClusters
|
||||
|
|
@ -319,7 +320,6 @@ module API
|
|||
mount ::API::Labels
|
||||
mount ::API::Notes
|
||||
mount ::API::NotificationSettings
|
||||
mount ::API::PagesDomains
|
||||
mount ::API::ProjectEvents
|
||||
mount ::API::ProjectMilestones
|
||||
mount ::API::ProtectedTags
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
|
||||
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user'
|
||||
end
|
||||
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
before do
|
||||
|
|
@ -63,6 +63,8 @@ module API
|
|||
|
||||
desc 'Get all pages domains' do
|
||||
success Entities::PagesDomain
|
||||
tags %w[pages_domains]
|
||||
is_array true
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@
|
|||
module Gitlab
|
||||
module Database
|
||||
module GitlabSchema
|
||||
UnknownSchemaError = Class.new(StandardError)
|
||||
|
||||
DICTIONARY_PATH = 'db/docs/'
|
||||
|
||||
def self.table_schemas(tables)
|
||||
tables.map { |table| table_schema(table) }.to_set
|
||||
def self.table_schemas(tables, undefined: true)
|
||||
tables.map { |table| table_schema(table, undefined: undefined) }.to_set
|
||||
end
|
||||
|
||||
def self.table_schema(name, undefined: true)
|
||||
|
|
@ -82,6 +84,13 @@ module Gitlab
|
|||
@views_and_tables_to_schema ||= self.tables_to_schema.merge(self.views_to_schema)
|
||||
end
|
||||
|
||||
def self.table_schema!(name)
|
||||
self.table_schema(name, undefined: false) || raise(
|
||||
UnknownSchemaError,
|
||||
"Could not find gitlab schema for table #{name}: Any new tables must be added to the database dictionary"
|
||||
)
|
||||
end
|
||||
|
||||
def self.deleted_views_and_tables_to_schema
|
||||
@deleted_views_and_tables_to_schema ||= self.deleted_tables_to_schema.merge(self.deleted_views_to_schema)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module Gitlab
|
|||
{
|
||||
column: config.fetch('column'),
|
||||
on_delete: config.fetch('on_delete').to_sym,
|
||||
gitlab_schema: GitlabSchema.table_schema(child_table_name)
|
||||
gitlab_schema: GitlabSchema.table_schema!(child_table_name)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,13 +22,16 @@ module Gitlab
|
|||
return unless allowed_schemas
|
||||
|
||||
invalid_schemas = table_schemas - allowed_schemas
|
||||
if invalid_schemas.any?
|
||||
message = "The query tried to access #{tables} (of #{table_schemas.to_a}) "
|
||||
message += "which is outside of allowed schemas (#{allowed_schemas}) "
|
||||
message += "for the current connection '#{Gitlab::Database.db_config_name(parsed.connection)}'"
|
||||
|
||||
raise CrossSchemaAccessError, message
|
||||
end
|
||||
return if invalid_schemas.empty?
|
||||
|
||||
schema_list = table_schemas.sort.join(',')
|
||||
|
||||
message = "The query tried to access #{tables} (of #{schema_list}) "
|
||||
message += "which is outside of allowed schemas (#{allowed_schemas}) "
|
||||
message += "for the current connection '#{Gitlab::Database.db_config_name(parsed.connection)}'"
|
||||
|
||||
raise CrossSchemaAccessError, message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -87,15 +87,15 @@ module Gitlab
|
|||
return if tables == ['schema_migrations']
|
||||
|
||||
context[:modified_tables_by_db][database].merge(tables)
|
||||
all_tables = context[:modified_tables_by_db].values.map(&:to_a).flatten
|
||||
all_tables = context[:modified_tables_by_db].values.flat_map(&:to_a)
|
||||
schemas = ::Gitlab::Database::GitlabSchema.table_schemas(all_tables)
|
||||
|
||||
schemas += ApplicationRecord.gitlab_transactions_stack
|
||||
|
||||
if schemas.many?
|
||||
message = "Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
|
||||
"a transaction modifying the '#{all_tables.to_a.join(", ")}' tables. " \
|
||||
"Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-cross-database-transactions for details on how to resolve this exception."
|
||||
"a transaction modifying the '#{all_tables.to_a.join(", ")}' tables. " \
|
||||
"Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-cross-database-transactions for details on how to resolve this exception."
|
||||
|
||||
if schemas.any? { |s| s.to_s.start_with?("undefined") }
|
||||
message += " The gitlab_schema was undefined for one or more of the tables in this transaction. Any new tables must be added to lib/gitlab/database/gitlab_schemas.yml ."
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module YourWork
|
||||
module Menus
|
||||
class ActivityMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
activity_dashboard_path
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Activity')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'history'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
!!context.current_user
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ path: 'dashboard#activity' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module YourWork
|
||||
module Menus
|
||||
class MilestonesMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
dashboard_milestones_path
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Milestones')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'clock'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
!!context.current_user
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: 'dashboard/milestones' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,6 +22,8 @@ module Sidebars
|
|||
|
||||
def add_menus
|
||||
add_menu(Sidebars::YourWork::Menus::ProjectsMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::MilestonesMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::ActivityMenu.new(context))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47093,6 +47093,9 @@ msgstr ""
|
|||
msgid "WorkItem|%{count} more assignees"
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|%{invalidWorkItemsList} cannot be added: Cannot assign a non-confidential %{childWorkItemType} to a confidential parent %{parentWorkItemType}. Make the selected %{childWorkItemType} confidential and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|%{workItemType} deleted"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ end
|
|||
# Show only cross-schema foreign keys
|
||||
if $options[:cross_schema]
|
||||
all_foreign_keys.select! do |definition|
|
||||
Gitlab::Database::GitlabSchema.table_schema(definition.from_table) != Gitlab::Database::GitlabSchema.table_schema(definition.to_table)
|
||||
Gitlab::Database::GitlabSchema.table_schema!(definition.from_table) != Gitlab::Database::GitlabSchema.table_schema!(definition.to_table)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -299,21 +299,21 @@ HELM_CMD=$(cat << EOF
|
|||
--set global.appConfig.sentry.dsn="${REVIEW_APPS_SENTRY_DSN}" \
|
||||
--set global.appConfig.sentry.environment="review" \
|
||||
--set gitlab.migrations.image.repository="${gitlab_toolbox_image_repository}" \
|
||||
--set gitlab.migrations.image.tag="${CI_COMMIT_REF_SLUG}" \
|
||||
--set gitlab.migrations.image.tag="${CI_COMMIT_SHA}" \
|
||||
--set gitlab.gitaly.image.repository="${gitlab_gitaly_image_repository}" \
|
||||
--set gitlab.gitaly.image.tag="${gitaly_image_tag}" \
|
||||
--set gitlab.gitlab-shell.image.repository="${gitlab_shell_image_repository}" \
|
||||
--set gitlab.gitlab-shell.image.tag="v${GITLAB_SHELL_VERSION}" \
|
||||
--set gitlab.sidekiq.annotations.commit="${CI_COMMIT_SHORT_SHA}" \
|
||||
--set gitlab.sidekiq.image.repository="${gitlab_sidekiq_image_repository}" \
|
||||
--set gitlab.sidekiq.image.tag="${CI_COMMIT_REF_SLUG}" \
|
||||
--set gitlab.sidekiq.image.tag="${CI_COMMIT_SHA}" \
|
||||
--set gitlab.webservice.annotations.commit="${CI_COMMIT_SHORT_SHA}" \
|
||||
--set gitlab.webservice.image.repository="${gitlab_webservice_image_repository}" \
|
||||
--set gitlab.webservice.image.tag="${CI_COMMIT_REF_SLUG}" \
|
||||
--set gitlab.webservice.image.tag="${CI_COMMIT_SHA}" \
|
||||
--set gitlab.webservice.workhorse.image="${gitlab_workhorse_image_repository}" \
|
||||
--set gitlab.webservice.workhorse.tag="${CI_COMMIT_REF_SLUG}" \
|
||||
--set gitlab.webservice.workhorse.tag="${CI_COMMIT_SHA}" \
|
||||
--set gitlab.toolbox.image.repository="${gitlab_toolbox_image_repository}" \
|
||||
--set gitlab.toolbox.image.tag="${CI_COMMIT_REF_SLUG}"
|
||||
--set gitlab.toolbox.image.tag="${CI_COMMIT_SHA}"
|
||||
EOF
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -184,6 +184,20 @@ module Trigger
|
|||
true
|
||||
end
|
||||
|
||||
def gitlab_ref_slug
|
||||
if ENV['CI_COMMIT_TAG']
|
||||
ENV['CI_COMMIT_REF_NAME']
|
||||
else
|
||||
ENV['CI_COMMIT_SHA']
|
||||
end
|
||||
end
|
||||
|
||||
def base_variables
|
||||
super.merge(
|
||||
'GITLAB_REF_SLUG' => gitlab_ref_slug
|
||||
)
|
||||
end
|
||||
|
||||
def extra_variables
|
||||
{
|
||||
"TRIGGER_BRANCH" => ref,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ RSpec.describe 'Dashboard > Activity', feature_category: :users do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a dashboard page with sidebar', :activity_dashboard_path, :activity
|
||||
|
||||
context 'tabs' do
|
||||
it 'shows Your Projects' do
|
||||
visit activity_dashboard_path
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ RSpec.describe 'Dashboard > Milestones', feature_category: :team_planning do
|
|||
visit dashboard_milestones_path
|
||||
end
|
||||
|
||||
it_behaves_like 'a dashboard page with sidebar', :dashboard_milestones_path, :milestones
|
||||
|
||||
it 'sees milestones' do
|
||||
expect(page).to have_current_path dashboard_milestones_path, ignore_query: true
|
||||
expect(page).to have_content(milestone.title)
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ exports[`PypiInstallation renders all the messages 1`] = `
|
|||
aria-live="polite"
|
||||
class="btn input-group-text btn-default btn-md gl-button btn-default-secondary btn-icon"
|
||||
data-clipboard-handle-tooltip="false"
|
||||
data-clipboard-text="pip install @gitlab-org/package-15 --extra-index-url http://__token__:<your_personal_token>@gdk.test:3000/api/v4/projects/1/packages/pypi/simple"
|
||||
data-clipboard-text="pip install @gitlab-org/package-15 --index-url http://__token__:<your_personal_token>@gdk.test:3000/api/v4/projects/1/packages/pypi/simple"
|
||||
id="clipboard-button-6"
|
||||
title="Copy Pip command"
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_PYPI };
|
|||
describe('PypiInstallation', () => {
|
||||
let wrapper;
|
||||
|
||||
const pipCommandStr = `pip install @gitlab-org/package-15 --extra-index-url ${packageEntity.pypiUrl}`;
|
||||
const pipCommandStr = `pip install @gitlab-org/package-15 --index-url ${packageEntity.pypiUrl}`;
|
||||
const pypiSetupStr = `[gitlab]
|
||||
repository = ${packageEntity.pypiSetupUrl}
|
||||
username = __token__
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import { GlForm, GlFormInput, GlFormCheckbox, GlTooltip, GlTokenSelector } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { sprintf } from '~/locale';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
@ -170,6 +170,16 @@ describe('WorkItemLinksForm', () => {
|
|||
});
|
||||
|
||||
describe('adding an existing work item', () => {
|
||||
const selectAvailableWorkItemTokens = async () => {
|
||||
findTokenSelector().vm.$emit(
|
||||
'input',
|
||||
availableWorkItemsResponse.data.workspace.workItems.nodes,
|
||||
);
|
||||
findTokenSelector().vm.$emit('blur', new FocusEvent({ relatedTarget: null }));
|
||||
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await createComponent({ formType: FORM_TYPES.add });
|
||||
});
|
||||
|
|
@ -179,6 +189,7 @@ describe('WorkItemLinksForm', () => {
|
|||
expect(findTokenSelector().exists()).toBe(true);
|
||||
expect(findAddChildButton().text()).toBe('Add task');
|
||||
expect(findInput().exists()).toBe(false);
|
||||
expect(findConfidentialCheckbox().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('searches for available work items as prop when typing in input', async () => {
|
||||
|
|
@ -190,13 +201,7 @@ describe('WorkItemLinksForm', () => {
|
|||
});
|
||||
|
||||
it('selects and adds children', async () => {
|
||||
findTokenSelector().vm.$emit(
|
||||
'input',
|
||||
availableWorkItemsResponse.data.workspace.workItems.nodes,
|
||||
);
|
||||
findTokenSelector().vm.$emit('blur', new FocusEvent({ relatedTarget: null }));
|
||||
|
||||
await waitForPromises();
|
||||
await selectAvailableWorkItemTokens();
|
||||
|
||||
expect(findAddChildButton().text()).toBe('Add tasks');
|
||||
findForm().vm.$emit('submit', {
|
||||
|
|
@ -205,6 +210,31 @@ describe('WorkItemLinksForm', () => {
|
|||
await waitForPromises();
|
||||
expect(updateMutationResolver).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows validation error when non-confidential child items are being added to confidential parent', async () => {
|
||||
await createComponent({ formType: FORM_TYPES.add, parentConfidential: true });
|
||||
|
||||
await selectAvailableWorkItemTokens();
|
||||
|
||||
const validationEl = wrapper.findByTestId('work-items-invalid');
|
||||
expect(validationEl.exists()).toBe(true);
|
||||
expect(validationEl.text().trim()).toBe(
|
||||
sprintf(
|
||||
s__(
|
||||
'WorkItem|%{invalidWorkItemsList} cannot be added: Cannot assign a non-confidential %{childWorkItemType} to a confidential parent %{parentWorkItemType}. Make the selected %{childWorkItemType} confidential and try again.',
|
||||
),
|
||||
{
|
||||
// Only non-confidential work items are shown in the error message
|
||||
invalidWorkItemsList: availableWorkItemsResponse.data.workspace.workItems.nodes
|
||||
.filter((wi) => !wi.confidential)
|
||||
.map((wi) => wi.title)
|
||||
.join(', '),
|
||||
childWorkItemType: 'Task',
|
||||
parentWorkItemType: 'Issue',
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('associate iteration with task', () => {
|
||||
|
|
|
|||
|
|
@ -1177,6 +1177,7 @@ export const availableWorkItemsResponse = {
|
|||
title: 'Task 1',
|
||||
state: 'OPEN',
|
||||
createdAt: '2022-08-03T12:41:54Z',
|
||||
confidential: false,
|
||||
__typename: 'WorkItem',
|
||||
},
|
||||
{
|
||||
|
|
@ -1184,6 +1185,15 @@ export const availableWorkItemsResponse = {
|
|||
title: 'Task 2',
|
||||
state: 'OPEN',
|
||||
createdAt: '2022-08-03T12:41:54Z',
|
||||
confidential: false,
|
||||
__typename: 'WorkItem',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/WorkItem/460',
|
||||
title: 'Task 3',
|
||||
state: 'OPEN',
|
||||
createdAt: '2022-08-03T12:41:54Z',
|
||||
confidential: true,
|
||||
__typename: 'WorkItem',
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -17,6 +17,25 @@ RSpec.shared_examples 'validate schema data' do |tables_and_views|
|
|||
end
|
||||
|
||||
RSpec.describe Gitlab::Database::GitlabSchema do
|
||||
shared_examples 'maps table name to table schema' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:name, :classification) do
|
||||
'ci_builds' | :gitlab_ci
|
||||
'my_schema.ci_builds' | :gitlab_ci
|
||||
'information_schema.columns' | :gitlab_internal
|
||||
'audit_events_part_5fc467ac26' | :gitlab_main
|
||||
'_test_gitlab_main_table' | :gitlab_main
|
||||
'_test_gitlab_ci_table' | :gitlab_ci
|
||||
'_test_my_table' | :gitlab_shared
|
||||
'pg_attribute' | :gitlab_internal
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(classification) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.deleted_views_and_tables_to_schema' do
|
||||
include_examples 'validate schema data', described_class.deleted_views_and_tables_to_schema
|
||||
end
|
||||
|
|
@ -97,25 +116,85 @@ RSpec.describe Gitlab::Database::GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.table_schema' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
describe '.table_schemas' do
|
||||
let(:tables) { %w[users projects ci_builds] }
|
||||
|
||||
where(:name, :classification) do
|
||||
'ci_builds' | :gitlab_ci
|
||||
'my_schema.ci_builds' | :gitlab_ci
|
||||
'information_schema.columns' | :gitlab_internal
|
||||
'audit_events_part_5fc467ac26' | :gitlab_main
|
||||
'_test_gitlab_main_table' | :gitlab_main
|
||||
'_test_gitlab_ci_table' | :gitlab_ci
|
||||
'_test_my_table' | :gitlab_shared
|
||||
'pg_attribute' | :gitlab_internal
|
||||
'my_other_table' | :undefined_my_other_table
|
||||
subject { described_class.table_schemas(tables) }
|
||||
|
||||
it 'returns the matched schemas' do
|
||||
expect(subject).to match_array %i[gitlab_main gitlab_ci].to_set
|
||||
end
|
||||
|
||||
with_them do
|
||||
subject { described_class.table_schema(name) }
|
||||
context 'when one of the tables does not have a matching table schema' do
|
||||
let(:tables) { %w[users projects unknown ci_builds] }
|
||||
|
||||
it { is_expected.to eq(classification) }
|
||||
context 'and undefined parameter is false' do
|
||||
subject { described_class.table_schemas(tables, undefined: false) }
|
||||
|
||||
it 'includes a nil value' do
|
||||
is_expected.to match_array [:gitlab_main, nil, :gitlab_ci].to_set
|
||||
end
|
||||
end
|
||||
|
||||
context 'and undefined parameter is true' do
|
||||
subject { described_class.table_schemas(tables, undefined: true) }
|
||||
|
||||
it 'includes "undefined_<table_name>"' do
|
||||
is_expected.to match_array [:gitlab_main, :undefined_unknown, :gitlab_ci].to_set
|
||||
end
|
||||
end
|
||||
|
||||
context 'and undefined parameter is not specified' do
|
||||
it 'includes a nil value' do
|
||||
is_expected.to match_array [:gitlab_main, :undefined_unknown, :gitlab_ci].to_set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.table_schema' do
|
||||
subject { described_class.table_schema(name) }
|
||||
|
||||
it_behaves_like 'maps table name to table schema'
|
||||
|
||||
context 'when mapping fails' do
|
||||
let(:name) { 'unknown_table' }
|
||||
|
||||
context "and parameter 'undefined' is set to true" do
|
||||
subject { described_class.table_schema(name, undefined: true) }
|
||||
|
||||
it { is_expected.to eq(:undefined_unknown_table) }
|
||||
end
|
||||
|
||||
context "and parameter 'undefined' is set to false" do
|
||||
subject { described_class.table_schema(name, undefined: false) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context "and parameter 'undefined' is not set" do
|
||||
subject { described_class.table_schema(name) }
|
||||
|
||||
it { is_expected.to eq(:undefined_unknown_table) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.table_schema!' do
|
||||
subject { described_class.table_schema!(name) }
|
||||
|
||||
it_behaves_like 'maps table name to table schema'
|
||||
|
||||
context 'when mapping fails' do
|
||||
let(:name) { 'non_existing_table' }
|
||||
|
||||
it "raises error" do
|
||||
expect { subject }.to raise_error(
|
||||
Gitlab::Database::GitlabSchema::UnknownSchemaError,
|
||||
"Could not find gitlab schema for table #{name}: " \
|
||||
"Any new tables must be added to the database dictionary"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -112,4 +112,31 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build_definition' do
|
||||
context 'when child table schema is not defined' do
|
||||
let(:loose_foreign_keys_yaml) do
|
||||
{
|
||||
'ci_unknown_table' => [
|
||||
{
|
||||
'table' => 'projects',
|
||||
'column' => 'project_id',
|
||||
'on_delete' => 'async_delete'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.definitions }
|
||||
|
||||
before do
|
||||
described_class.instance_variable_set(:@definitions, nil)
|
||||
described_class.instance_variable_set(:@loose_foreign_keys_yaml, loose_foreign_keys_yaml)
|
||||
end
|
||||
|
||||
it 'raises Gitlab::Database::GitlabSchema::UnknownSchemaError error' do
|
||||
expect { subject }.to raise_error(Gitlab::Database::GitlabSchema::UnknownSchemaError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
|
|||
gitlab_schemas: "gitlab_ci",
|
||||
db_config_name: "ci"
|
||||
}
|
||||
},
|
||||
"for query accessing gitlab_main and unknown schema" => {
|
||||
model: ApplicationRecord,
|
||||
sql: "SELECT 1 FROM projects LEFT JOIN not_in_schema ON not_in_schema.project_id=projects.id",
|
||||
expectations: {
|
||||
gitlab_schemas: "gitlab_main,undefined_not_in_schema",
|
||||
db_config_name: "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection
|
|||
sql: "SELECT 1 FROM ci_builds",
|
||||
expect_error: /The query tried to access \["ci_builds"\]/,
|
||||
setup: -> (_) { skip_if_multiple_databases_not_setup }
|
||||
},
|
||||
"for query accessing unknown gitlab_schema" => {
|
||||
model: ::ApplicationRecord,
|
||||
sql: "SELECT 1 FROM new_table",
|
||||
expect_error: /The query tried to access \["new_table"\] \(of undefined_new_table\)/,
|
||||
setup: -> (_) { skip_if_multiple_databases_not_setup }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,17 +8,25 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
|
||||
let_it_be(:job) { create(:ci_build, pipeline: pipeline, name: 'build') }
|
||||
let_it_be(:job) { create(:ci_build, :playable, pipeline: pipeline, name: 'build') }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
let(:variables) do
|
||||
{
|
||||
id: job.to_global_id.to_s
|
||||
}
|
||||
end
|
||||
|
||||
let(:mutation) do
|
||||
graphql_mutation(:job_play, variables,
|
||||
<<-QL
|
||||
errors
|
||||
job {
|
||||
id
|
||||
manualVariables {
|
||||
nodes {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
QL
|
||||
)
|
||||
|
|
@ -43,4 +51,29 @@ RSpec.describe 'JobPlay', feature_category: :continuous_integration do
|
|||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response['job']['id']).to eq(job_id)
|
||||
end
|
||||
|
||||
context 'when given variables' do
|
||||
let(:variables) do
|
||||
{
|
||||
id: job.to_global_id.to_s,
|
||||
variables: [
|
||||
{ key: 'MANUAL_VAR_1', value: 'test var' },
|
||||
{ key: 'MANUAL_VAR_2', value: 'test var 2' }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'provides those variables to the job', :aggregated_errors do
|
||||
expect_next_instance_of(Ci::PlayBuildService) do |instance|
|
||||
expect(instance).to receive(:execute).with(an_instance_of(Ci::Build), variables[:variables]).and_call_original
|
||||
end
|
||||
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response['job']['manualVariables']['nodes'].pluck('key')).to contain_exactly(
|
||||
'MANUAL_VAR_1', 'MANUAL_VAR_2'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ require 'rspec-parameterized'
|
|||
|
||||
require_relative '../../scripts/trigger-build'
|
||||
|
||||
RSpec.describe Trigger do
|
||||
RSpec.describe Trigger, feature_category: :tooling do
|
||||
let(:env) do
|
||||
{
|
||||
'CI_JOB_URL' => 'ci_job_url',
|
||||
|
|
@ -362,6 +362,28 @@ RSpec.describe Trigger do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GITLAB_REF_SLUG" do
|
||||
context 'when CI_COMMIT_TAG is set' do
|
||||
before do
|
||||
stub_env('CI_COMMIT_TAG', 'true')
|
||||
end
|
||||
|
||||
it 'sets GITLAB_REF_SLUG to CI_COMMIT_REF_NAME' do
|
||||
expect(subject.variables['GITLAB_REF_SLUG']).to eq(env['CI_COMMIT_REF_NAME'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CI_COMMIT_TAG is nil' do
|
||||
before do
|
||||
stub_env('CI_COMMIT_TAG', nil)
|
||||
end
|
||||
|
||||
it 'sets GITLAB_REF_SLUG to CI_COMMIT_SHA' do
|
||||
expect(subject.variables['GITLAB_REF_SLUG']).to eq(env['CI_COMMIT_SHA'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#version_param_value" do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
|||
|
|
@ -232,6 +232,14 @@ RSpec.shared_context 'dashboard navbar structure' do
|
|||
{
|
||||
nav_item: _("Projects"),
|
||||
nav_sub_items: []
|
||||
},
|
||||
{
|
||||
nav_item: _("Milestones"),
|
||||
nav_sub_items: []
|
||||
},
|
||||
{
|
||||
nav_item: _("Activity"),
|
||||
nav_sub_items: []
|
||||
}
|
||||
]
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue