Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
125c8a6a81
commit
23f57fb31f
|
@ -2455,7 +2455,7 @@ Database/MultipleDatabases:
|
|||
- 'lib/gitlab/gitlab_import/importer.rb'
|
||||
- 'lib/gitlab/health_checks/db_check.rb'
|
||||
- 'lib/gitlab/import_export/base/relation_factory.rb'
|
||||
- 'lib/gitlab/import_export/relation_tree_restorer.rb'
|
||||
- 'lib/gitlab/import_export/group/relation_tree_restorer.rb'
|
||||
- 'lib/gitlab/legacy_github_import/importer.rb'
|
||||
- 'lib/gitlab/metrics/samplers/database_sampler.rb'
|
||||
- 'lib/gitlab/seeder.rb'
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { GlButton, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import {
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { mapActions } from 'vuex';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
@ -8,6 +15,9 @@ import { s__ } from '~/locale';
|
|||
export default {
|
||||
name: 'ManualVariablesForm',
|
||||
components: {
|
||||
GlFormInputGroup,
|
||||
GlInputGroupText,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
|
@ -32,6 +42,9 @@ export default {
|
|||
value: 'value',
|
||||
},
|
||||
i18n: {
|
||||
header: s__('CiVariables|Variables'),
|
||||
keyLabel: s__('CiVariables|Key'),
|
||||
valueLabel: s__('CiVariables|Value'),
|
||||
keyPlaceholder: s__('CiVariables|Input variable key'),
|
||||
valuePlaceholder: s__('CiVariables|Input variable value'),
|
||||
formHelpText: s__(
|
||||
|
@ -40,9 +53,13 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
variables: [],
|
||||
key: '',
|
||||
secretValue: '',
|
||||
variables: [
|
||||
{
|
||||
key: '',
|
||||
secretValue: '',
|
||||
id: uniqueId(),
|
||||
},
|
||||
],
|
||||
triggerBtnDisabled: false,
|
||||
};
|
||||
},
|
||||
|
@ -50,40 +67,32 @@ export default {
|
|||
variableSettings() {
|
||||
return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
key(newVal) {
|
||||
this.handleValueChange(newVal, this.$options.inputTypes.key);
|
||||
},
|
||||
secretValue(newVal) {
|
||||
this.handleValueChange(newVal, this.$options.inputTypes.value);
|
||||
preparedVariables() {
|
||||
// we need to ensure no empty variables are passed to the API
|
||||
// and secretValue should be snake_case when passed to the API
|
||||
return this.variables
|
||||
.filter((variable) => variable.key !== '')
|
||||
.map(({ key, secretValue }) => ({ key, secret_value: secretValue }));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['triggerManualJob']),
|
||||
handleValueChange(newValue, type) {
|
||||
if (newValue !== '') {
|
||||
this.createNewVariable(type);
|
||||
this.resetForm();
|
||||
canRemove(index) {
|
||||
return index < this.variables.length - 1;
|
||||
},
|
||||
addEmptyVariable() {
|
||||
const lastVar = this.variables[this.variables.length - 1];
|
||||
|
||||
if (lastVar.key === '') {
|
||||
return;
|
||||
}
|
||||
},
|
||||
createNewVariable(type) {
|
||||
const newVariable = {
|
||||
key: this.key,
|
||||
secret_value: this.secretValue,
|
||||
|
||||
this.variables.push({
|
||||
key: '',
|
||||
secret_value: '',
|
||||
id: uniqueId(),
|
||||
};
|
||||
|
||||
this.variables.push(newVariable);
|
||||
|
||||
return this.$nextTick().then(() => {
|
||||
this.$refs[`${this.$options.inputTypes[type]}-${newVariable.id}`][0].focus();
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
this.key = '';
|
||||
this.secretValue = '';
|
||||
},
|
||||
deleteVariable(id) {
|
||||
this.variables.splice(
|
||||
this.variables.findIndex((el) => el.id === id),
|
||||
|
@ -93,112 +102,92 @@ export default {
|
|||
trigger() {
|
||||
this.triggerBtnDisabled = true;
|
||||
|
||||
this.triggerManualJob(this.variables);
|
||||
this.triggerManualJob(this.preparedVariables);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="col-12" data-testid="manual-vars-form">
|
||||
<label>{{ s__('CiVariables|Variables') }}</label>
|
||||
|
||||
<div class="ci-table">
|
||||
<div class="gl-responsive-table-row table-row-header pb-0 pt-0 border-0" role="row">
|
||||
<div class="table-section section-50" role="rowheader">{{ s__('CiVariables|Key') }}</div>
|
||||
<div class="table-section section-50" role="rowheader">{{ s__('CiVariables|Value') }}</div>
|
||||
</div>
|
||||
<div class="row gl-justify-content-center">
|
||||
<div class="col-10" data-testid="manual-vars-form">
|
||||
<label>{{ $options.i18n.header }}</label>
|
||||
|
||||
<div
|
||||
v-for="variable in variables"
|
||||
v-for="(variable, index) in variables"
|
||||
:key="variable.id"
|
||||
class="gl-responsive-table-row"
|
||||
class="gl-display-flex gl-align-items-center gl-mb-4"
|
||||
data-testid="ci-variable-row"
|
||||
>
|
||||
<div class="table-section section-50">
|
||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
|
||||
<div class="table-mobile-content gl-mr-3">
|
||||
<input
|
||||
:ref="`${$options.inputTypes.key}-${variable.id}`"
|
||||
v-model="variable.key"
|
||||
:placeholder="$options.i18n.keyPlaceholder"
|
||||
class="ci-variable-body-item form-control"
|
||||
data-testid="ci-variable-key"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<gl-form-input-group class="gl-mr-4 gl-flex-grow-1">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ $options.i18n.keyLabel }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="`${$options.inputTypes.key}-${variable.id}`"
|
||||
v-model="variable.key"
|
||||
:placeholder="$options.i18n.keyPlaceholder"
|
||||
data-testid="ci-variable-key"
|
||||
@change="addEmptyVariable"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<div class="table-section section-50">
|
||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
|
||||
<div class="table-mobile-content gl-mr-3">
|
||||
<input
|
||||
:ref="`${$options.inputTypes.value}-${variable.id}`"
|
||||
v-model="variable.secret_value"
|
||||
:placeholder="$options.i18n.valuePlaceholder"
|
||||
class="ci-variable-body-item form-control"
|
||||
data-testid="ci-variable-value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<gl-form-input-group class="gl-flex-grow-2">
|
||||
<template #prepend>
|
||||
<gl-input-group-text>
|
||||
{{ $options.i18n.valueLabel }}
|
||||
</gl-input-group-text>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:ref="`${$options.inputTypes.value}-${variable.id}`"
|
||||
v-model="variable.secretValue"
|
||||
:placeholder="$options.i18n.valuePlaceholder"
|
||||
data-testid="ci-variable-value"
|
||||
/>
|
||||
</gl-form-input-group>
|
||||
|
||||
<div class="table-section section-10">
|
||||
<div class="table-mobile-header" role="rowheader"></div>
|
||||
<div class="table-mobile-content justify-content-end">
|
||||
<gl-button
|
||||
category="tertiary"
|
||||
icon="clear"
|
||||
:aria-label="__('Delete variable')"
|
||||
data-testid="delete-variable-btn"
|
||||
@click="deleteVariable(variable.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- delete variable button placeholder to not break flex layout -->
|
||||
<div
|
||||
v-if="!canRemove(index)"
|
||||
class="gl-w-7 gl-mr-3"
|
||||
data-testid="delete-variable-btn-placeholder"
|
||||
></div>
|
||||
|
||||
<gl-button
|
||||
v-if="canRemove(index)"
|
||||
class="gl-flex-grow-0 gl-flex-basis-0"
|
||||
category="tertiary"
|
||||
variant="danger"
|
||||
icon="clear"
|
||||
:aria-label="__('Delete variable')"
|
||||
data-testid="delete-variable-btn"
|
||||
@click="deleteVariable(variable.id)"
|
||||
/>
|
||||
</div>
|
||||
<div class="gl-responsive-table-row">
|
||||
<div class="table-section section-50">
|
||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
|
||||
<div class="table-mobile-content gl-mr-3">
|
||||
<input
|
||||
ref="inputKey"
|
||||
v-model="key"
|
||||
class="js-input-key form-control"
|
||||
:placeholder="$options.i18n.keyPlaceholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-section section-50">
|
||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
|
||||
<div class="table-mobile-content gl-mr-3">
|
||||
<input
|
||||
ref="inputSecretValue"
|
||||
v-model="secretValue"
|
||||
class="ci-variable-body-item form-control"
|
||||
:placeholder="$options.i18n.valuePlaceholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gl-text-center gl-mt-5">
|
||||
<gl-sprintf :message="$options.i18n.formHelpText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="variableSettings" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-justify-content-center gl-mt-5">
|
||||
<gl-button
|
||||
class="gl-mt-5"
|
||||
variant="info"
|
||||
category="primary"
|
||||
:aria-label="__('Trigger manual job')"
|
||||
:disabled="triggerBtnDisabled"
|
||||
data-testid="trigger-manual-job-btn"
|
||||
@click="trigger"
|
||||
>
|
||||
{{ action.button_title }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gl-text-center gl-mt-3">
|
||||
<gl-sprintf :message="$options.i18n.formHelpText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="variableSettings" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<gl-button
|
||||
variant="info"
|
||||
category="primary"
|
||||
:aria-label="__('Trigger manual job')"
|
||||
:disabled="triggerBtnDisabled"
|
||||
data-testid="trigger-manual-job-btn"
|
||||
@click="trigger"
|
||||
>
|
||||
{{ action.button_title }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::HookLogsController < Admin::ApplicationController
|
||||
include HooksExecution
|
||||
include ::Integrations::HooksExecution
|
||||
|
||||
before_action :hook, only: [:show, :retry]
|
||||
before_action :hook_log, only: [:show, :retry]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::HooksController < Admin::ApplicationController
|
||||
include HooksExecution
|
||||
include ::Integrations::HooksExecution
|
||||
|
||||
before_action :hook_logs, only: :edit
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::IntegrationsController < Admin::ApplicationController
|
||||
include IntegrationsActions
|
||||
include ::Integrations::Actions
|
||||
|
||||
before_action :not_found, unless: -> { instance_level_integrations? }
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IntegrationsActions
|
||||
module Integrations::Actions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module HooksExecution
|
||||
module Integrations::HooksExecution
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
|
@ -68,6 +68,15 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def topic
|
||||
load_topic
|
||||
|
||||
return render_404 unless @topic
|
||||
|
||||
params[:topic] = @topic.name
|
||||
@projects = load_projects
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_project_counts
|
||||
|
@ -86,6 +95,10 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
prepare_projects_for_rendering(projects)
|
||||
end
|
||||
|
||||
def load_topic
|
||||
@topic = Projects::Topic.find_by_name(params[:topic_name])
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def preload_associations(projects)
|
||||
projects.includes(:route, :creator, :group, :project_feature, :topics, namespace: [:route, :owner])
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Groups
|
||||
module Settings
|
||||
class IntegrationsController < Groups::ApplicationController
|
||||
include IntegrationsActions
|
||||
include ::Integrations::Actions
|
||||
|
||||
before_action :authorize_admin_group!
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::HookLogsController < Projects::ApplicationController
|
||||
include HooksExecution
|
||||
include ::Integrations::HooksExecution
|
||||
|
||||
before_action :authorize_admin_project!
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Projects::HooksController < Projects::ApplicationController
|
||||
include HooksExecution
|
||||
include ::Integrations::HooksExecution
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Security
|
||||
module LatestPipelineInformation
|
||||
private
|
||||
|
||||
def scanner_enabled?(scan_type)
|
||||
latest_builds_reports.include?(scan_type)
|
||||
end
|
||||
|
||||
def latest_builds_reports(only_successful_builds: false)
|
||||
strong_memoize("latest_builds_reports_#{only_successful_builds}") do
|
||||
builds = latest_security_builds
|
||||
builds = builds.select { |build| build.status == 'success' } if only_successful_builds
|
||||
builds.flat_map do |build|
|
||||
build.options[:artifacts][:reports].keys
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def latest_security_builds
|
||||
return [] unless latest_default_branch_pipeline
|
||||
|
||||
::Security::SecurityJobsFinder.new(pipeline: latest_default_branch_pipeline).execute +
|
||||
::Security::LicenseComplianceJobsFinder.new(pipeline: latest_default_branch_pipeline).execute
|
||||
end
|
||||
|
||||
def latest_default_branch_pipeline
|
||||
strong_memoize(:pipeline) { latest_pipeline }
|
||||
end
|
||||
|
||||
def auto_devops_source?
|
||||
latest_default_branch_pipeline&.auto_devops_source?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
- @hide_top_links = false
|
||||
- @no_container = true
|
||||
- page_title @topic.name, _("Topics")
|
||||
- max_topic_name_length = 50
|
||||
|
||||
= render_dashboard_ultimate_trial(current_user)
|
||||
|
||||
.gl-text-center.gl-bg-gray-10.gl-pb-2.gl-pt-6
|
||||
.gl-pb-5.gl-align-items-center.gl-justify-content-center.gl-display-flex
|
||||
.avatar-container.s60.gl-flex-shrink-0
|
||||
= topic_icon(@topic, alt: _('Topic avatar'), class: 'avatar topic-avatar s60')
|
||||
- if @topic.name.length > max_topic_name_length
|
||||
%h1.gl-mt-3.str-truncated.has-tooltip{ title: @topic.name }
|
||||
= truncate(@topic.name, length: max_topic_name_length)
|
||||
- else
|
||||
%h1.gl-mt-3
|
||||
= @topic.name
|
||||
- if @topic.description.present?
|
||||
.topic-description.gl-ml-4.gl-mr-4
|
||||
= markdown(@topic.description)
|
||||
|
||||
%div{ class: container_class }
|
||||
.gl-py-5.gl-border-gray-100.gl-border-b-solid.gl-border-b-1
|
||||
%h3.gl-m-0= _('Projects with this topic')
|
||||
.top-area.gl-pt-2.gl-pb-2
|
||||
.nav-controls
|
||||
= render 'shared/projects/search_form'
|
||||
= render 'shared/projects/dropdown'
|
||||
= render 'filter'
|
||||
|
||||
= render 'projects', projects: @projects
|
|
@ -8,7 +8,7 @@
|
|||
= sprite_icon('tag', css_class: 'icon gl-relative gl-mr-2')
|
||||
|
||||
- project.topics_to_show.each do |topic|
|
||||
- explore_project_topic_path = explore_projects_path(topic: topic)
|
||||
- explore_project_topic_path = topic_explore_projects_path(topic_name: topic)
|
||||
- if topic.length > max_project_topic_length
|
||||
%a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' }
|
||||
= truncate(topic, length: max_project_topic_length)
|
||||
|
@ -21,7 +21,7 @@
|
|||
- content = capture do
|
||||
%span.gl-display-inline-flex.gl-flex-wrap
|
||||
- project.topics_not_shown.each do |topic|
|
||||
- explore_project_topic_path = explore_projects_path(topic: topic)
|
||||
- explore_project_topic_path = topic_explore_projects_path(topic_name: topic)
|
||||
- if topic.length > max_project_topic_length
|
||||
%a{ class: "#{ project_topics_classes } gl-mb-3 str-truncated has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' }
|
||||
= truncate(topic, length: max_project_topic_length)
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace :explore do
|
|||
collection do
|
||||
get :trending
|
||||
get :starred
|
||||
get 'topics/:topic_name', action: :topic, as: :topic, constraints: { topic_name: /.+/ }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -38,7 +38,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
|
||||
namespace :security do
|
||||
resource :configuration, only: [:show], controller: :configuration
|
||||
resource :configuration, only: [:show], controller: :configuration do
|
||||
resource :sast, only: [:show], controller: :sast_configuration
|
||||
end
|
||||
end
|
||||
|
||||
resources :artifacts, only: [:index, :destroy]
|
||||
|
|
|
@ -533,6 +533,7 @@ Plan.default.actual_limits.update!(pages_file_entries: 100)
|
|||
The total number of registered runners is limited at the group and project levels. Each time a new runner is registered,
|
||||
GitLab checks these limits against runners that have been active in the last 3 months.
|
||||
A runner's registration fails if it exceeds the limit for the scope determined by the runner registration token.
|
||||
If the limit value is set to zero, the limit is disabled.
|
||||
|
||||
- GitLab SaaS subscribers have different limits defined per plan, affecting all projects using that plan.
|
||||
- Self-managed GitLab Premium and Ultimate limits are defined by a default plan that affects all projects:
|
||||
|
|
|
@ -1034,6 +1034,27 @@ Input type: `ConfigureSecretDetectionInput`
|
|||
| <a id="mutationconfiguresecretdetectionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationconfiguresecretdetectionsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
|
||||
|
||||
### `Mutation.corpusCreate`
|
||||
|
||||
Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
|
||||
|
||||
Input type: `CorpusCreateInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcorpuscreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcorpuscreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the corpus belongs to. |
|
||||
| <a id="mutationcorpuscreatepackageid"></a>`packageId` | [`PackagesPackageID!`](#packagespackageid) | ID of the corpus package. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcorpuscreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcorpuscreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.createAlertIssue`
|
||||
|
||||
Input type: `CreateAlertIssueInput`
|
||||
|
|
|
@ -422,22 +422,36 @@ configurations. Local configurations in the `.gitlab-ci.yml` file override inclu
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/284883) in GitLab 13.8.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/294294) in GitLab 13.9.
|
||||
> - [Support for project, group, and instance variables added](https://gitlab.com/gitlab-org/gitlab/-/issues/219065) in GitLab 14.2.
|
||||
> - [Support for pipeline variables added](https://gitlab.com/gitlab-org/gitlab/-/issues/337633) in GitLab 14.5.
|
||||
|
||||
In `include` sections in your `.gitlab-ci.yml` file, you can use:
|
||||
|
||||
- `$CI_COMMIT_REF_NAME` [predefined variable](../variables/predefined_variables.md) in GitLab 14.2
|
||||
and later.
|
||||
- When used in `include`, the `CI_COMMIT_REF_NAME` variable returns the full
|
||||
ref path, like `refs/heads/branch-name`. In other cases, this variable returns only
|
||||
the branch name, like `branch-name`.
|
||||
|
||||
To use `CI_COMMIT_REF_NAME` in `include:rules`, you might need to use `if: $CI_COMMIT_REF_NAME =~ /main/`
|
||||
(not `== main`). [An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/337633)
|
||||
to align `CI_COMMIT_REF_NAME` behavior in all cases.
|
||||
- [Project variables](../variables/index.md#add-a-cicd-variable-to-a-project)
|
||||
- [Group variables](../variables/index.md#add-a-cicd-variable-to-a-group)
|
||||
- [Instance variables](../variables/index.md#add-a-cicd-variable-to-an-instance)
|
||||
- Project [predefined variables](../variables/predefined_variables.md).
|
||||
- Project [predefined variables](../variables/predefined_variables.md)
|
||||
- In GitLab 14.2 and later, the `$CI_COMMIT_REF_NAME` [predefined variable](../variables/predefined_variables.md).
|
||||
|
||||
When used in `include`, the `CI_COMMIT_REF_NAME` variable returns the full
|
||||
ref path, like `refs/heads/branch-name`. In `include:rules`, you might need to use
|
||||
`if: $CI_COMMIT_REF_NAME =~ /main/` (not `== main`). This behavior is resolved in GitLab 14.5.
|
||||
|
||||
In GitLab 14.5 and later, you can also use:
|
||||
|
||||
- [Trigger variables](../triggers/index.md#making-use-of-trigger-variables).
|
||||
- [Scheduled pipeline variables](../pipelines/schedules.md#using-variables).
|
||||
- [Manual pipeline run variables](../variables/index.md#override-a-variable-when-running-a-pipeline-manually).
|
||||
- Pipeline [predefined variables](../variables/predefined_variables.md).
|
||||
|
||||
YAML files are parsed before the pipeline is created, so the following pipeline predefined variables
|
||||
are **not** available:
|
||||
|
||||
- `CI_PIPELINE_ID`
|
||||
- `CI_PIPELINE_URL`
|
||||
- `CI_PIPELINE_IID`
|
||||
- `CI_PIPELINE_CREATED_AT`
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
@ -448,9 +462,6 @@ include:
|
|||
For an example of how you can include these predefined variables, and the variables' impact on CI/CD jobs,
|
||||
see this [CI/CD variable demo](https://youtu.be/4XR8gw3Pkos).
|
||||
|
||||
There is a [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/337633)
|
||||
that proposes expanding this feature to support more variables.
|
||||
|
||||
#### `rules` with `include`
|
||||
|
||||
> - Introduced in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
|
||||
|
@ -468,6 +479,9 @@ include:
|
|||
- local: builds.yml
|
||||
rules:
|
||||
- if: '$INCLUDE_BUILDS == "true"'
|
||||
- local: deploys.yml
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
|
||||
test:
|
||||
stage: test
|
||||
|
|
|
@ -74,6 +74,13 @@ Possible version history entries are:
|
|||
> - [Generally available](issue-link) in GitLab X.Y. [Feature flag <flag name>](issue-link) removed.
|
||||
```
|
||||
|
||||
You can combine entries if they happened in the same release:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
|
||||
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.3.
|
||||
```
|
||||
|
||||
## Feature flag documentation examples
|
||||
|
||||
The following examples show the progression of a feature flag.
|
||||
|
|
|
@ -128,5 +128,7 @@ To remove a blocked IP:
|
|||
keys *rack::attack*
|
||||
```
|
||||
|
||||
By default, the [`keys` command is disabled](https://docs.gitlab.com/omnibus/settings/redis.html#renamed-commands).
|
||||
|
||||
1. Optionally, add [the IP to the allowlist](https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-rack-attack)
|
||||
to prevent it being denylisted again.
|
||||
|
|
|
@ -125,7 +125,7 @@ is rejected.
|
|||
|
||||
NOTE:
|
||||
The repository size limit includes repository files and LFS, but does not include artifacts, uploads,
|
||||
wiki, packages, or snippets.
|
||||
wiki, packages, or snippets. The repository size limit applies to both private and public projects.
|
||||
|
||||
For details on manually purging files, see [reducing the repository size using Git](../../project/repository/reducing_the_repo_size_using_git.md).
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ If you are near or over the repository size limit, you can either
|
|||
NOTE:
|
||||
`git push` and GitLab project imports are limited to 5 GB per request through
|
||||
Cloudflare. Git LFS and imports other than a file upload are not affected by
|
||||
this limit.
|
||||
this limit. Repository limits apply to both public and private projects.
|
||||
|
||||
## IP range
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Custom group-level project templates **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.6.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6861) in GitLab 11.6.
|
||||
|
||||
[Group owners](../permissions.md#group-members-permissions) can set a subgroup to
|
||||
be the source of project templates that are selectable when a new project is created
|
||||
|
|
|
@ -19,23 +19,23 @@ Using GitLab Group Migration, you can migrate existing top-level groups from Git
|
|||
|
||||
The following resources are migrated to the target instance:
|
||||
|
||||
- Groups ([Introduced in 13.7](https://gitlab.com/groups/gitlab-org/-/epics/4374))
|
||||
- Groups ([Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4374) in 13.7)
|
||||
- description
|
||||
- attributes
|
||||
- subgroups
|
||||
- avatar ([Introduced in 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/322904))
|
||||
- Group Labels ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/292429))
|
||||
- avatar ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322904) in 14.0)
|
||||
- Group Labels ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292429) in 13.9)
|
||||
- title
|
||||
- description
|
||||
- color
|
||||
- created_at ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300007))
|
||||
- updated_at ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300007))
|
||||
- Members ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/299415))
|
||||
- created_at ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/300007) in 13.10)
|
||||
- updated_at ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/300007) in 13.10)
|
||||
- Members ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299415) in 13.9)
|
||||
Group members are associated with the imported group if:
|
||||
- The user already exists in the target GitLab instance and
|
||||
- The user has a public email in the source GitLab instance that matches a
|
||||
confirmed email in the target GitLab instance
|
||||
- Epics ([Introduced in 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/250281))
|
||||
- Epics ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250281) in 13.7)
|
||||
- title
|
||||
- description
|
||||
- state (open / closed)
|
||||
|
@ -43,12 +43,12 @@ The following resources are migrated to the target instance:
|
|||
- due date
|
||||
- epic order on boards
|
||||
- confidentiality
|
||||
- labels ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/297460))
|
||||
- author ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/298745))
|
||||
- parent epic ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/297459))
|
||||
- emoji award ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/297466))
|
||||
- events ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/297465))
|
||||
- Milestones ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/292427))
|
||||
- labels ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/297460) in 13.9)
|
||||
- author ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298745) in 13.9)
|
||||
- parent epic ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/297459) in 13.9)
|
||||
- emoji award ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/297466) in 13.9)
|
||||
- events ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/297465) in 13.10)
|
||||
- Milestones ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292427) in 13.10)
|
||||
- title
|
||||
- description
|
||||
- state (active / closed)
|
||||
|
@ -56,8 +56,8 @@ The following resources are migrated to the target instance:
|
|||
- due date
|
||||
- created at
|
||||
- updated at
|
||||
- iid ([Introduced in 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/326157))
|
||||
- Iterations ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/292428))
|
||||
- iid ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326157) in 13.11)
|
||||
- Iterations ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292428) in 13.10)
|
||||
- iid
|
||||
- title
|
||||
- description
|
||||
|
@ -66,7 +66,7 @@ The following resources are migrated to the target instance:
|
|||
- due date
|
||||
- created at
|
||||
- updated at
|
||||
- Badges ([Introduced in 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/292431))
|
||||
- Badges ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292431) in 13.11)
|
||||
- name
|
||||
- link URL
|
||||
- image URL
|
||||
|
|
|
@ -51,8 +51,8 @@ The following items are exported:
|
|||
- Subgroups (including all the aforementioned data)
|
||||
- Epics
|
||||
- Events
|
||||
- [Wikis](../../project/wiki/group.md) **(PREMIUM SELF)**
|
||||
(Introduced in [GitLab 13.9](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53247))
|
||||
- [Wikis](../../project/wiki/group.md)
|
||||
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53247) in GitLab 13.9)
|
||||
|
||||
The following items are **not** exported:
|
||||
|
||||
|
|
|
@ -135,11 +135,11 @@ The Package Registry supports the following formats:
|
|||
|
||||
| Package type | GitLab version | Status |
|
||||
| ------------ | -------------- |------- |
|
||||
| [Maven](../maven_repository/index.md) | 11.3+ | Stable |
|
||||
| [npm](../npm_registry/index.md) | 11.7+ | Stable |
|
||||
| [NuGet](../nuget_repository/index.md) | 12.8+ | Stable |
|
||||
| [PyPI](../pypi_repository/index.md) | 12.10+ | Stable |
|
||||
| [Generic packages](../generic_packages/index.md) | 13.5+ | Stable |
|
||||
| [Maven](../maven_repository/index.md) | 11.3+ | GA |
|
||||
| [npm](../npm_registry/index.md) | 11.7+ | GA |
|
||||
| [NuGet](../nuget_repository/index.md) | 12.8+ | GA |
|
||||
| [PyPI](../pypi_repository/index.md) | 12.10+ | GA |
|
||||
| [Generic packages](../generic_packages/index.md) | 13.5+ | GA |
|
||||
| [Composer](../composer_repository/index.md) | 13.2+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6817) |
|
||||
| [Conan](../conan_repository/index.md) | 12.6+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6816) |
|
||||
| [Helm](../helm_repository/index.md) | 14.1+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6366) |
|
||||
|
@ -147,11 +147,11 @@ The Package Registry supports the following formats:
|
|||
| [Go](../go_proxy/index.md) | 13.1+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3043) |
|
||||
| [Ruby gems](../rubygems_registry/index.md) | 13.10+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3200) |
|
||||
|
||||
Status:
|
||||
[Status](https://about.gitlab.com/handbook/product/gitlab-the-product/#generally-available-ga):
|
||||
|
||||
- Alpha: behind a feature flag and not officially supported.
|
||||
- Beta: several known issues that may prevent expected use.
|
||||
- Stable: ready for production use.
|
||||
- GA (Generally Available): ready for production use at any scale.
|
||||
|
||||
You can also use the [API](../../../api/packages.md) to administer the Package Registry.
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module BulkImports
|
||||
module Projects
|
||||
module Pipelines
|
||||
class ExternalPullRequestsPipeline
|
||||
include NdjsonPipeline
|
||||
|
||||
relation_name 'external_pull_requests'
|
||||
|
||||
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,6 +31,10 @@ module BulkImports
|
|||
pipeline: BulkImports::Projects::Pipelines::MergeRequestsPipeline,
|
||||
stage: 4
|
||||
},
|
||||
external_pull_requests: {
|
||||
pipeline: BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline,
|
||||
stage: 4
|
||||
},
|
||||
uploads: {
|
||||
pipeline: BulkImports::Common::Pipelines::UploadsPipeline,
|
||||
stage: 5
|
||||
|
|
|
@ -19,11 +19,12 @@ module Gitlab
|
|||
|
||||
attr_reader :root, :context, :source_ref_path, :source
|
||||
|
||||
def initialize(config, project: nil, sha: nil, user: nil, parent_pipeline: nil, source_ref_path: nil, source: nil)
|
||||
@context = build_context(project: project, sha: sha, user: user, parent_pipeline: parent_pipeline, ref: source_ref_path)
|
||||
def initialize(config, project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, source: nil)
|
||||
@source_ref_path = pipeline&.source_ref_path
|
||||
|
||||
@context = build_context(project: project, pipeline: pipeline, sha: sha, user: user, parent_pipeline: parent_pipeline)
|
||||
@context.set_deadline(TIMEOUT_SECONDS)
|
||||
|
||||
@source_ref_path = source_ref_path
|
||||
@source = source
|
||||
|
||||
@config = expand_config(config)
|
||||
|
@ -108,16 +109,16 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def build_context(project:, sha:, user:, parent_pipeline:, ref:)
|
||||
def build_context(project:, pipeline:, sha:, user:, parent_pipeline:)
|
||||
Config::External::Context.new(
|
||||
project: project,
|
||||
sha: sha || find_sha(project),
|
||||
user: user,
|
||||
parent_pipeline: parent_pipeline,
|
||||
variables: build_variables(project: project, ref: ref))
|
||||
variables: build_variables(project: project, pipeline: pipeline))
|
||||
end
|
||||
|
||||
def build_variables(project:, ref:)
|
||||
def build_variables(project:, pipeline:)
|
||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||
break variables unless project
|
||||
|
||||
|
@ -126,18 +127,12 @@ module Gitlab
|
|||
#
|
||||
# See more detail in the docs: https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
|
||||
variables.concat(project.predefined_variables)
|
||||
variables.concat(pipeline_predefined_variables(ref: ref))
|
||||
variables.concat(project.ci_instance_variables_for(ref: ref))
|
||||
variables.concat(project.group.ci_variables_for(ref, project)) if project.group
|
||||
variables.concat(project.ci_variables_for(ref: ref))
|
||||
end
|
||||
end
|
||||
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/337633 aims to add all predefined variables
|
||||
# to this list, but only CI_COMMIT_REF_NAME is available right now to support compliance pipelines.
|
||||
def pipeline_predefined_variables(ref:)
|
||||
Gitlab::Ci::Variables::Collection.new.tap do |v|
|
||||
v.append(key: 'CI_COMMIT_REF_NAME', value: ref)
|
||||
variables.concat(pipeline.predefined_variables) if pipeline
|
||||
variables.concat(project.ci_instance_variables_for(ref: source_ref_path))
|
||||
variables.concat(project.group.ci_variables_for(source_ref_path, project)) if project.group
|
||||
variables.concat(project.ci_variables_for(ref: source_ref_path))
|
||||
variables.concat(pipeline.variables) if pipeline
|
||||
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline&.pipeline_schedule
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module Gitlab
|
|||
result = ::Gitlab::Ci::YamlProcessor.new(
|
||||
@command.config_content, {
|
||||
project: project,
|
||||
source_ref_path: @pipeline.source_ref_path,
|
||||
pipeline: @pipeline,
|
||||
sha: @pipeline.sha,
|
||||
source: @pipeline.source,
|
||||
user: current_user,
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module ImportExport
|
||||
module Group
|
||||
class RelationTreeRestorer
|
||||
def initialize( # rubocop:disable Metrics/ParameterLists
|
||||
user:,
|
||||
shared:,
|
||||
relation_reader:,
|
||||
members_mapper:,
|
||||
object_builder:,
|
||||
relation_factory:,
|
||||
reader:,
|
||||
importable:,
|
||||
importable_attributes:,
|
||||
importable_path:
|
||||
)
|
||||
@user = user
|
||||
@shared = shared
|
||||
@importable = importable
|
||||
@relation_reader = relation_reader
|
||||
@members_mapper = members_mapper
|
||||
@object_builder = object_builder
|
||||
@relation_factory = relation_factory
|
||||
@reader = reader
|
||||
@importable_attributes = importable_attributes
|
||||
@importable_path = importable_path
|
||||
end
|
||||
|
||||
def restore
|
||||
ActiveRecord::Base.uncached do
|
||||
ActiveRecord::Base.no_touching do
|
||||
update_params!
|
||||
|
||||
BulkInsertableAssociations.with_bulk_insert(enabled: bulk_insert_enabled) do
|
||||
fix_ci_pipelines_not_sorted_on_legacy_project_json!
|
||||
create_relations!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ensure that we have latest version of the restore
|
||||
@importable.reload # rubocop:disable Cop/ActiveRecordAssociationReload
|
||||
|
||||
true
|
||||
rescue StandardError => e
|
||||
@shared.error(e)
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bulk_insert_enabled
|
||||
false
|
||||
end
|
||||
|
||||
# Loops through the tree of models defined in import_export.yml and
|
||||
# finds them in the imported JSON so they can be instantiated and saved
|
||||
# in the DB. The structure and relationships between models are guessed from
|
||||
# the configuration yaml file too.
|
||||
# Finally, it updates each attribute in the newly imported project/group.
|
||||
def create_relations!
|
||||
relations.each do |relation_key, relation_definition|
|
||||
process_relation!(relation_key, relation_definition)
|
||||
end
|
||||
end
|
||||
|
||||
def process_relation!(relation_key, relation_definition)
|
||||
@relation_reader.consume_relation(@importable_path, relation_key).each do |data_hash, relation_index|
|
||||
process_relation_item!(relation_key, relation_definition, relation_index, data_hash)
|
||||
end
|
||||
end
|
||||
|
||||
def process_relation_item!(relation_key, relation_definition, relation_index, data_hash)
|
||||
relation_object = build_relation(relation_key, relation_definition, relation_index, data_hash)
|
||||
return unless relation_object
|
||||
return if relation_invalid_for_importable?(relation_object)
|
||||
|
||||
relation_object.assign_attributes(importable_class_sym => @importable)
|
||||
|
||||
import_failure_service.with_retry(action: 'relation_object.save!', relation_key: relation_key, relation_index: relation_index) do
|
||||
relation_object.save!
|
||||
log_relation_creation(@importable, relation_key, relation_object)
|
||||
end
|
||||
rescue StandardError => e
|
||||
import_failure_service.log_import_failure(
|
||||
source: 'process_relation_item!',
|
||||
relation_key: relation_key,
|
||||
relation_index: relation_index,
|
||||
exception: e)
|
||||
end
|
||||
|
||||
def import_failure_service
|
||||
@import_failure_service ||= ImportFailureService.new(@importable)
|
||||
end
|
||||
|
||||
def relations
|
||||
@relations ||=
|
||||
@reader
|
||||
.attributes_finder
|
||||
.find_relations_tree(importable_class_sym)
|
||||
.deep_stringify_keys
|
||||
end
|
||||
|
||||
def update_params!
|
||||
params = @importable_attributes.except(*relations.keys.map(&:to_s))
|
||||
params = params.merge(present_override_params)
|
||||
|
||||
# Cleaning all imported and overridden params
|
||||
params = Gitlab::ImportExport::AttributeCleaner.clean(
|
||||
relation_hash: params,
|
||||
relation_class: importable_class,
|
||||
excluded_keys: excluded_keys_for_relation(importable_class_sym))
|
||||
|
||||
@importable.assign_attributes(params)
|
||||
|
||||
modify_attributes
|
||||
|
||||
Gitlab::Timeless.timeless(@importable) do
|
||||
@importable.save!
|
||||
end
|
||||
end
|
||||
|
||||
def present_override_params
|
||||
# we filter out the empty strings from the overrides
|
||||
# keeping the default values configured
|
||||
override_params&.transform_values do |value|
|
||||
value.is_a?(String) ? value.presence : value
|
||||
end&.compact
|
||||
end
|
||||
|
||||
def override_params
|
||||
@importable_override_params ||= importable_override_params
|
||||
end
|
||||
|
||||
def importable_override_params
|
||||
if @importable.respond_to?(:import_data)
|
||||
@importable.import_data&.data&.fetch('override_params', nil) || {}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def modify_attributes
|
||||
# no-op to be overridden on inheritance
|
||||
end
|
||||
|
||||
def build_relations(relation_key, relation_definition, relation_index, data_hashes)
|
||||
data_hashes
|
||||
.map { |data_hash| build_relation(relation_key, relation_definition, relation_index, data_hash) }
|
||||
.tap { |entries| entries.compact! }
|
||||
end
|
||||
|
||||
def build_relation(relation_key, relation_definition, relation_index, data_hash)
|
||||
# TODO: This is hack to not create relation for the author
|
||||
# Rather make `RelationFactory#set_note_author` to take care of that
|
||||
return data_hash if relation_key == 'author' || already_restored?(data_hash)
|
||||
|
||||
# create relation objects recursively for all sub-objects
|
||||
relation_definition.each do |sub_relation_key, sub_relation_definition|
|
||||
transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index)
|
||||
end
|
||||
|
||||
relation = @relation_factory.create(**relation_factory_params(relation_key, relation_index, data_hash))
|
||||
|
||||
if relation && !relation.valid?
|
||||
@shared.logger.warn(
|
||||
message: "[Project/Group Import] Invalid object relation built",
|
||||
relation_key: relation_key,
|
||||
relation_index: relation_index,
|
||||
relation_class: relation.class.name,
|
||||
error_messages: relation.errors.full_messages.join(". ")
|
||||
)
|
||||
end
|
||||
|
||||
relation
|
||||
end
|
||||
|
||||
# Since we update the data hash in place as we restore relation items,
|
||||
# and since we also de-duplicate items, we might encounter items that
|
||||
# have already been restored in a previous iteration.
|
||||
def already_restored?(relation_item)
|
||||
!relation_item.is_a?(Hash)
|
||||
end
|
||||
|
||||
def transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index)
|
||||
sub_data_hash = data_hash[sub_relation_key]
|
||||
return unless sub_data_hash
|
||||
|
||||
# if object is a hash we can create simple object
|
||||
# as it means that this is 1-to-1 vs 1-to-many
|
||||
current_item =
|
||||
if sub_data_hash.is_a?(Array)
|
||||
build_relations(
|
||||
sub_relation_key,
|
||||
sub_relation_definition,
|
||||
relation_index,
|
||||
sub_data_hash).presence
|
||||
else
|
||||
build_relation(
|
||||
sub_relation_key,
|
||||
sub_relation_definition,
|
||||
relation_index,
|
||||
sub_data_hash)
|
||||
end
|
||||
|
||||
if current_item
|
||||
data_hash[sub_relation_key] = current_item
|
||||
else
|
||||
data_hash.delete(sub_relation_key)
|
||||
end
|
||||
end
|
||||
|
||||
def relation_invalid_for_importable?(_relation_object)
|
||||
false
|
||||
end
|
||||
|
||||
def excluded_keys_for_relation(relation)
|
||||
@reader.attributes_finder.find_excluded_keys(relation)
|
||||
end
|
||||
|
||||
def importable_class
|
||||
@importable.class
|
||||
end
|
||||
|
||||
def importable_class_sym
|
||||
importable_class.to_s.downcase.to_sym
|
||||
end
|
||||
|
||||
def relation_factory_params(relation_key, relation_index, data_hash)
|
||||
{
|
||||
relation_index: relation_index,
|
||||
relation_sym: relation_key.to_sym,
|
||||
relation_hash: data_hash,
|
||||
importable: @importable,
|
||||
members_mapper: @members_mapper,
|
||||
object_builder: @object_builder,
|
||||
user: @user,
|
||||
excluded_keys: excluded_keys_for_relation(relation_key)
|
||||
}
|
||||
end
|
||||
|
||||
# Temporary fix for https://gitlab.com/gitlab-org/gitlab/-/issues/27883 when import from legacy project.json
|
||||
# This should be removed once legacy JSON format is deprecated.
|
||||
# Ndjson export file will fix the order during project export.
|
||||
def fix_ci_pipelines_not_sorted_on_legacy_project_json!
|
||||
return unless @relation_reader.legacy?
|
||||
|
||||
@relation_reader.sort_ci_pipelines_by_id
|
||||
end
|
||||
|
||||
# Enable logging of each top-level relation creation when Importing
|
||||
# into a Group if feature flag is enabled
|
||||
def log_relation_creation(importable, relation_key, relation_object)
|
||||
root_ancestor_group = importable.try(:root_ancestor)
|
||||
|
||||
return unless root_ancestor_group
|
||||
return unless root_ancestor_group.instance_of?(::Group)
|
||||
return unless Feature.enabled?(:log_import_export_relation_creation, root_ancestor_group)
|
||||
|
||||
@shared.logger.info(
|
||||
importable_type: importable.class.to_s,
|
||||
importable_id: importable.id,
|
||||
relation_key: relation_key,
|
||||
relation_id: relation_object.id,
|
||||
author_id: relation_object.try(:author_id),
|
||||
message: '[Project/Group Import] Created new object relation'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module ImportExport
|
||||
module Project
|
||||
class RelationTreeRestorer < ImportExport::Group::RelationTreeRestorer
|
||||
# Relations which cannot be saved at project level (and have a group assigned)
|
||||
GROUP_MODELS = [GroupLabel, Milestone, Epic].freeze
|
||||
|
||||
private
|
||||
|
||||
def bulk_insert_enabled
|
||||
true
|
||||
end
|
||||
|
||||
def modify_attributes
|
||||
@importable.reconcile_shared_runners_setting!
|
||||
@importable.drop_visibility_level!
|
||||
end
|
||||
|
||||
def relation_invalid_for_importable?(relation_object)
|
||||
GROUP_MODELS.include?(relation_object.class) && relation_object.group_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
module ImportExport
|
||||
module Project
|
||||
module Sample
|
||||
class RelationTreeRestorer < ImportExport::RelationTreeRestorer
|
||||
class RelationTreeRestorer < ImportExport::Project::RelationTreeRestorer
|
||||
def initialize(...)
|
||||
super(...)
|
||||
|
||||
|
@ -18,10 +18,10 @@ module Gitlab
|
|||
end
|
||||
|
||||
def dates
|
||||
return [] if relation_reader.legacy?
|
||||
return [] if @relation_reader.legacy?
|
||||
|
||||
RelationFactory::DATE_MODELS.flat_map do |tag|
|
||||
relation_reader.consume_relation(@importable_path, tag, mark_as_consumed: false).map do |model|
|
||||
@relation_reader.consume_relation(@importable_path, tag, mark_as_consumed: false).map do |model|
|
||||
model.first['due_date']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module ImportExport
|
||||
class RelationTreeRestorer
|
||||
# Relations which cannot be saved at project level (and have a group assigned)
|
||||
GROUP_MODELS = [GroupLabel, Milestone, Epic].freeze
|
||||
|
||||
attr_reader :user
|
||||
attr_reader :shared
|
||||
attr_reader :importable
|
||||
attr_reader :relation_reader
|
||||
|
||||
def initialize( # rubocop:disable Metrics/ParameterLists
|
||||
user:, shared:, relation_reader:,
|
||||
members_mapper:, object_builder:,
|
||||
relation_factory:,
|
||||
reader:,
|
||||
importable:,
|
||||
importable_attributes:,
|
||||
importable_path:
|
||||
)
|
||||
@user = user
|
||||
@shared = shared
|
||||
@importable = importable
|
||||
@relation_reader = relation_reader
|
||||
@members_mapper = members_mapper
|
||||
@object_builder = object_builder
|
||||
@relation_factory = relation_factory
|
||||
@reader = reader
|
||||
@importable_attributes = importable_attributes
|
||||
@importable_path = importable_path
|
||||
end
|
||||
|
||||
def restore
|
||||
ActiveRecord::Base.uncached do
|
||||
ActiveRecord::Base.no_touching do
|
||||
update_params!
|
||||
|
||||
BulkInsertableAssociations.with_bulk_insert(enabled: project?) do
|
||||
fix_ci_pipelines_not_sorted_on_legacy_project_json!
|
||||
create_relations!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ensure that we have latest version of the restore
|
||||
@importable.reload # rubocop:disable Cop/ActiveRecordAssociationReload
|
||||
|
||||
true
|
||||
rescue StandardError => e
|
||||
@shared.error(e)
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project?
|
||||
@importable.instance_of?(::Project)
|
||||
end
|
||||
|
||||
# Loops through the tree of models defined in import_export.yml and
|
||||
# finds them in the imported JSON so they can be instantiated and saved
|
||||
# in the DB. The structure and relationships between models are guessed from
|
||||
# the configuration yaml file too.
|
||||
# Finally, it updates each attribute in the newly imported project/group.
|
||||
def create_relations!
|
||||
relations.each do |relation_key, relation_definition|
|
||||
process_relation!(relation_key, relation_definition)
|
||||
end
|
||||
end
|
||||
|
||||
def process_relation!(relation_key, relation_definition)
|
||||
@relation_reader.consume_relation(@importable_path, relation_key).each do |data_hash, relation_index|
|
||||
process_relation_item!(relation_key, relation_definition, relation_index, data_hash)
|
||||
end
|
||||
end
|
||||
|
||||
def process_relation_item!(relation_key, relation_definition, relation_index, data_hash)
|
||||
relation_object = build_relation(relation_key, relation_definition, relation_index, data_hash)
|
||||
return unless relation_object
|
||||
return if project? && group_model?(relation_object)
|
||||
|
||||
relation_object.assign_attributes(importable_class_sym => @importable)
|
||||
|
||||
import_failure_service.with_retry(action: 'relation_object.save!', relation_key: relation_key, relation_index: relation_index) do
|
||||
relation_object.save!
|
||||
log_relation_creation(@importable, relation_key, relation_object)
|
||||
end
|
||||
rescue StandardError => e
|
||||
import_failure_service.log_import_failure(
|
||||
source: 'process_relation_item!',
|
||||
relation_key: relation_key,
|
||||
relation_index: relation_index,
|
||||
exception: e)
|
||||
end
|
||||
|
||||
def import_failure_service
|
||||
@import_failure_service ||= ImportFailureService.new(@importable)
|
||||
end
|
||||
|
||||
def relations
|
||||
@relations ||=
|
||||
@reader
|
||||
.attributes_finder
|
||||
.find_relations_tree(importable_class_sym)
|
||||
.deep_stringify_keys
|
||||
end
|
||||
|
||||
def update_params!
|
||||
params = @importable_attributes.except(*relations.keys.map(&:to_s))
|
||||
params = params.merge(present_override_params)
|
||||
|
||||
# Cleaning all imported and overridden params
|
||||
params = Gitlab::ImportExport::AttributeCleaner.clean(
|
||||
relation_hash: params,
|
||||
relation_class: importable_class,
|
||||
excluded_keys: excluded_keys_for_relation(importable_class_sym))
|
||||
|
||||
@importable.assign_attributes(params)
|
||||
|
||||
modify_attributes
|
||||
|
||||
Gitlab::Timeless.timeless(@importable) do
|
||||
@importable.save!
|
||||
end
|
||||
end
|
||||
|
||||
def present_override_params
|
||||
# we filter out the empty strings from the overrides
|
||||
# keeping the default values configured
|
||||
override_params&.transform_values do |value|
|
||||
value.is_a?(String) ? value.presence : value
|
||||
end&.compact
|
||||
end
|
||||
|
||||
def override_params
|
||||
@importable_override_params ||= importable_override_params
|
||||
end
|
||||
|
||||
def importable_override_params
|
||||
if @importable.respond_to?(:import_data)
|
||||
@importable.import_data&.data&.fetch('override_params', nil) || {}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def modify_attributes
|
||||
return unless project?
|
||||
|
||||
@importable.reconcile_shared_runners_setting!
|
||||
@importable.drop_visibility_level!
|
||||
end
|
||||
|
||||
def build_relations(relation_key, relation_definition, relation_index, data_hashes)
|
||||
data_hashes
|
||||
.map { |data_hash| build_relation(relation_key, relation_definition, relation_index, data_hash) }
|
||||
.tap { |entries| entries.compact! }
|
||||
end
|
||||
|
||||
def build_relation(relation_key, relation_definition, relation_index, data_hash)
|
||||
# TODO: This is hack to not create relation for the author
|
||||
# Rather make `RelationFactory#set_note_author` to take care of that
|
||||
return data_hash if relation_key == 'author' || already_restored?(data_hash)
|
||||
|
||||
# create relation objects recursively for all sub-objects
|
||||
relation_definition.each do |sub_relation_key, sub_relation_definition|
|
||||
transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index)
|
||||
end
|
||||
|
||||
relation = @relation_factory.create(**relation_factory_params(relation_key, relation_index, data_hash))
|
||||
|
||||
if relation && !relation.valid?
|
||||
@shared.logger.warn(
|
||||
message: "[Project/Group Import] Invalid object relation built",
|
||||
relation_key: relation_key,
|
||||
relation_index: relation_index,
|
||||
relation_class: relation.class.name,
|
||||
error_messages: relation.errors.full_messages.join(". ")
|
||||
)
|
||||
end
|
||||
|
||||
relation
|
||||
end
|
||||
|
||||
# Since we update the data hash in place as we restore relation items,
|
||||
# and since we also de-duplicate items, we might encounter items that
|
||||
# have already been restored in a previous iteration.
|
||||
def already_restored?(relation_item)
|
||||
!relation_item.is_a?(Hash)
|
||||
end
|
||||
|
||||
def transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index)
|
||||
sub_data_hash = data_hash[sub_relation_key]
|
||||
return unless sub_data_hash
|
||||
|
||||
# if object is a hash we can create simple object
|
||||
# as it means that this is 1-to-1 vs 1-to-many
|
||||
current_item =
|
||||
if sub_data_hash.is_a?(Array)
|
||||
build_relations(
|
||||
sub_relation_key,
|
||||
sub_relation_definition,
|
||||
relation_index,
|
||||
sub_data_hash).presence
|
||||
else
|
||||
build_relation(
|
||||
sub_relation_key,
|
||||
sub_relation_definition,
|
||||
relation_index,
|
||||
sub_data_hash)
|
||||
end
|
||||
|
||||
if current_item
|
||||
data_hash[sub_relation_key] = current_item
|
||||
else
|
||||
data_hash.delete(sub_relation_key)
|
||||
end
|
||||
end
|
||||
|
||||
def group_model?(relation_object)
|
||||
GROUP_MODELS.include?(relation_object.class) && relation_object.group_id
|
||||
end
|
||||
|
||||
def excluded_keys_for_relation(relation)
|
||||
@reader.attributes_finder.find_excluded_keys(relation)
|
||||
end
|
||||
|
||||
def importable_class
|
||||
@importable.class
|
||||
end
|
||||
|
||||
def importable_class_sym
|
||||
importable_class.to_s.downcase.to_sym
|
||||
end
|
||||
|
||||
def relation_factory_params(relation_key, relation_index, data_hash)
|
||||
{
|
||||
relation_index: relation_index,
|
||||
relation_sym: relation_key.to_sym,
|
||||
relation_hash: data_hash,
|
||||
importable: @importable,
|
||||
members_mapper: @members_mapper,
|
||||
object_builder: @object_builder,
|
||||
user: @user,
|
||||
excluded_keys: excluded_keys_for_relation(relation_key)
|
||||
}
|
||||
end
|
||||
|
||||
# Temporary fix for https://gitlab.com/gitlab-org/gitlab/-/issues/27883 when import from legacy project.json
|
||||
# This should be removed once legacy JSON format is deprecated.
|
||||
# Ndjson export file will fix the order during project export.
|
||||
def fix_ci_pipelines_not_sorted_on_legacy_project_json!
|
||||
return unless relation_reader.legacy?
|
||||
|
||||
relation_reader.sort_ci_pipelines_by_id
|
||||
end
|
||||
|
||||
# Enable logging of each top-level relation creation when Importing
|
||||
# into a Group if feature flag is enabled
|
||||
def log_relation_creation(importable, relation_key, relation_object)
|
||||
root_ancestor_group = importable.try(:root_ancestor)
|
||||
|
||||
return unless root_ancestor_group
|
||||
return unless root_ancestor_group.instance_of?(::Group)
|
||||
return unless Feature.enabled?(:log_import_export_relation_creation, root_ancestor_group)
|
||||
|
||||
@shared.logger.info(
|
||||
importable_type: importable.class.to_s,
|
||||
importable_id: importable.id,
|
||||
relation_key: relation_key,
|
||||
relation_id: relation_object.id,
|
||||
author_id: relation_object.try(:author_id),
|
||||
message: '[Project/Group Import] Created new object relation'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25525,9 +25525,6 @@ msgstr ""
|
|||
msgid "Pipeline|In progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Manual"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25618,9 +25615,6 @@ msgstr ""
|
|||
msgid "Pipeline|Triggerer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipeline|Variables"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27391,6 +27385,9 @@ msgstr ""
|
|||
msgid "Projects with no vulnerabilities and security scanning enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with this topic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Projects with write access"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
"apollo-upload-client": "^13.0.0",
|
||||
"autosize": "^5.0.1",
|
||||
"aws-sdk": "^2.637.0",
|
||||
"axios": "^0.20.0",
|
||||
"axios": "^0.24.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"bootstrap": "4.5.3",
|
||||
|
@ -219,8 +219,8 @@
|
|||
"docdash": "^1.0.2",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-import-resolver-jest": "3.0.2",
|
||||
"eslint-import-resolver-webpack": "0.13.1",
|
||||
"eslint-plugin-no-jquery": "2.6.0",
|
||||
"eslint-import-resolver-webpack": "0.13.2",
|
||||
"eslint-plugin-no-jquery": "2.7.0",
|
||||
"gettext-extractor": "^3.5.3",
|
||||
"gettext-extractor-vue": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
|
|
|
@ -9,7 +9,7 @@ RSpec.describe Admin::IntegrationsController do
|
|||
sign_in(admin)
|
||||
end
|
||||
|
||||
it_behaves_like IntegrationsActions do
|
||||
it_behaves_like Integrations::Actions do
|
||||
let(:integration_attributes) { { instance: true, project: nil } }
|
||||
|
||||
let(:routing_params) do
|
||||
|
|
|
@ -74,6 +74,28 @@ RSpec.describe Explore::ProjectsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #topic' do
|
||||
context 'when topic does not exist' do
|
||||
it 'renders a 404 error' do
|
||||
get :topic, params: { topic_name: 'topic1' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
context 'when topic exists' do
|
||||
before do
|
||||
create(:topic, name: 'topic1')
|
||||
end
|
||||
|
||||
it 'renders the template' do
|
||||
get :topic, params: { topic_name: 'topic1' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template('topic')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "blocks high page numbers" do
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe Groups::Settings::IntegrationsController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like IntegrationsActions do
|
||||
it_behaves_like Integrations::Actions do
|
||||
let(:integration_attributes) { { group: group, project: nil } }
|
||||
|
||||
let(:routing_params) do
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe Projects::ServicesController do
|
|||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like IntegrationsActions do
|
||||
it_behaves_like Integrations::Actions do
|
||||
let(:integration_attributes) { { project: project } }
|
||||
|
||||
let(:routing_params) do
|
||||
|
|
|
@ -204,7 +204,7 @@ RSpec.describe 'Dashboard Projects' do
|
|||
visit dashboard_projects_path
|
||||
|
||||
expect(page).to have_selector('[data-testid="project_topic_list"]')
|
||||
expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
|
||||
expect(page).to have_link('topic1', href: topic_explore_projects_path(topic_name: 'topic1'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User triggers manual job with variables', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:user_access_level) { :developer }
|
||||
let(:project) { create(:project, :repository, namespace: user.namespace) }
|
||||
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
|
||||
let!(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.enable_ci
|
||||
|
||||
sign_in(user)
|
||||
|
||||
visit(project_job_path(project, build))
|
||||
end
|
||||
|
||||
it 'passes values correctly' do
|
||||
page.within(find("[data-testid='ci-variable-row']")) do
|
||||
find("[data-testid='ci-variable-key']").set('key_name')
|
||||
find("[data-testid='ci-variable-value']").set('key_value')
|
||||
end
|
||||
|
||||
find("[data-testid='trigger-manual-job-btn']").click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(build.job_variables.as_json).to contain_exactly(
|
||||
hash_including('key' => 'key_name', 'value' => 'key_value'))
|
||||
end
|
||||
end
|
|
@ -133,7 +133,7 @@ RSpec.describe 'Project' do
|
|||
visit path
|
||||
|
||||
expect(page).to have_selector('[data-testid="project_topic_list"]')
|
||||
expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
|
||||
expect(page).to have_link('topic1', href: topic_explore_projects_path(topic_name: 'topic1'))
|
||||
end
|
||||
|
||||
it 'shows up to 3 project topics' do
|
||||
|
@ -142,9 +142,9 @@ RSpec.describe 'Project' do
|
|||
visit path
|
||||
|
||||
expect(page).to have_selector('[data-testid="project_topic_list"]')
|
||||
expect(page).to have_link('topic1', href: explore_projects_path(topic: 'topic1'))
|
||||
expect(page).to have_link('topic2', href: explore_projects_path(topic: 'topic2'))
|
||||
expect(page).to have_link('topic3', href: explore_projects_path(topic: 'topic3'))
|
||||
expect(page).to have_link('topic1', href: topic_explore_projects_path(topic_name: 'topic1'))
|
||||
expect(page).to have_link('topic2', href: topic_explore_projects_path(topic_name: 'topic2'))
|
||||
expect(page).to have_link('topic3', href: topic_explore_projects_path(topic_name: 'topic3'))
|
||||
expect(page).to have_content('+ 1 more')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Topic show page' do
|
||||
let_it_be(:topic) { create(:topic, name: 'my-topic', description: 'This is **my** topic https://google.com/ :poop: ```\ncode\n```', avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
|
||||
|
||||
context 'when topic does not exist' do
|
||||
let(:path) { topic_explore_projects_path(topic_name: 'non-existing') }
|
||||
|
||||
it 'renders 404' do
|
||||
visit path
|
||||
|
||||
expect(status_code).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when topic exists' do
|
||||
before do
|
||||
visit topic_explore_projects_path(topic_name: topic.name)
|
||||
end
|
||||
|
||||
it 'shows name, avatar and description as markdown' do
|
||||
expect(page).to have_content(topic.name)
|
||||
expect(page).to have_selector('.avatar-container > img.topic-avatar')
|
||||
expect(find('.topic-description')).to have_selector('p > strong')
|
||||
expect(find('.topic-description')).to have_selector('p > a[rel]')
|
||||
expect(find('.topic-description')).to have_selector('p > gl-emoji')
|
||||
expect(find('.topic-description')).to have_selector('p > code')
|
||||
end
|
||||
|
||||
context 'with associated projects' do
|
||||
let!(:project) { create(:project, :public, topic_list: topic.name) }
|
||||
|
||||
it 'shows project list' do
|
||||
visit topic_explore_projects_path(topic_name: topic.name)
|
||||
|
||||
expect(find('.projects-list .project-name')).to have_content(project.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without associated projects' do
|
||||
it 'shows correct empty state message' do
|
||||
expect(page).to have_content('Explore public groups to find projects to contribute to.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,8 +4,6 @@ import { TEST_HOST } from 'helpers/test_constants';
|
|||
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
const { CancelToken } = axios;
|
||||
|
||||
describe('custom metrics form fields component', () => {
|
||||
let wrapper;
|
||||
let mockAxios;
|
||||
|
@ -116,14 +114,14 @@ describe('custom metrics form fields component', () => {
|
|||
|
||||
it('receives and validates a persisted value', () => {
|
||||
const query = 'persistedQuery';
|
||||
const axiosPost = jest.spyOn(axios, 'post');
|
||||
const source = CancelToken.source();
|
||||
jest.spyOn(axios, 'post');
|
||||
|
||||
mountComponent({ metricPersisted: true, ...makeFormData({ query }) });
|
||||
|
||||
expect(axiosPost).toHaveBeenCalledWith(
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
validateQueryPath,
|
||||
{ query },
|
||||
{ cancelToken: source.token },
|
||||
expect.objectContaining({ cancelToken: expect.anything() }),
|
||||
);
|
||||
expect(getNamedInput(queryInputName).value).toBe(query);
|
||||
jest.runAllTimers();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import Form from '~/jobs/components/manual_variables_form.vue';
|
||||
import ManualVariablesForm from '~/jobs/components/manual_variables_form.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
|
@ -21,7 +21,7 @@ describe('Manual Variables Form', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
|
||||
const createComponent = (props = {}) => {
|
||||
store = new Vuex.Store({
|
||||
actions: {
|
||||
triggerManualJob: jest.fn(),
|
||||
|
@ -29,7 +29,7 @@ describe('Manual Variables Form', () => {
|
|||
});
|
||||
|
||||
wrapper = extendedWrapper(
|
||||
mountFn(localVue.extend(Form), {
|
||||
mount(localVue.extend(ManualVariablesForm), {
|
||||
propsData: { ...requiredProps, ...props },
|
||||
localVue,
|
||||
store,
|
||||
|
@ -40,88 +40,120 @@ describe('Manual Variables Form', () => {
|
|||
);
|
||||
};
|
||||
|
||||
const findInputKey = () => wrapper.findComponent({ ref: 'inputKey' });
|
||||
const findInputValue = () => wrapper.findComponent({ ref: 'inputSecretValue' });
|
||||
const findHelpText = () => wrapper.findComponent(GlSprintf);
|
||||
const findHelpLink = () => wrapper.findComponent(GlLink);
|
||||
|
||||
const findTriggerBtn = () => wrapper.findByTestId('trigger-manual-job-btn');
|
||||
const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
|
||||
const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
|
||||
const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
|
||||
const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key');
|
||||
const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key');
|
||||
const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value');
|
||||
const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row');
|
||||
|
||||
const setCiVariableKey = () => {
|
||||
findCiVariableKey().setValue('new key');
|
||||
findCiVariableKey().vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
|
||||
const setCiVariableKeyByPosition = (position, value) => {
|
||||
findAllCiVariableKeys().at(position).setValue(value);
|
||||
findAllCiVariableKeys().at(position).vm.$emit('change');
|
||||
nextTick();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('shallowMount', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
it('creates a new variable when user enters a new key value', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
it('renders empty form with correct placeholders', () => {
|
||||
expect(findInputKey().attributes('placeholder')).toBe('Input variable key');
|
||||
expect(findInputValue().attributes('placeholder')).toBe('Input variable value');
|
||||
});
|
||||
await setCiVariableKey();
|
||||
|
||||
it('renders help text with provided link', () => {
|
||||
expect(findHelpText().exists()).toBe(true);
|
||||
expect(findHelpLink().attributes('href')).toBe(
|
||||
'/help/ci/variables/index#add-a-cicd-variable-to-a-project',
|
||||
);
|
||||
});
|
||||
|
||||
describe('when adding a new variable', () => {
|
||||
it('creates a new variable when user types a new key and resets the form', async () => {
|
||||
await findInputKey().setValue('new key');
|
||||
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
expect(findCiVariableKey().element.value).toBe('new key');
|
||||
expect(findInputKey().attributes('value')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('creates a new variable when user types a new value and resets the form', async () => {
|
||||
await findInputValue().setValue('new value');
|
||||
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
expect(findCiVariableValue().element.value).toBe('new value');
|
||||
expect(findInputValue().attributes('value')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ mountFn: mount });
|
||||
});
|
||||
it('does not create extra empty variables', async () => {
|
||||
expect(findAllVariables()).toHaveLength(1);
|
||||
|
||||
describe('when deleting a variable', () => {
|
||||
it('removes the variable row', async () => {
|
||||
await wrapper.setData({
|
||||
variables: [
|
||||
{
|
||||
key: 'new key',
|
||||
secret_value: 'value',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
await setCiVariableKey();
|
||||
|
||||
findDeleteVarBtn().trigger('click');
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findAllVariables()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
expect(findAllVariables()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('trigger button is disabled after trigger action', async () => {
|
||||
expect(findTriggerBtn().props('disabled')).toBe(false);
|
||||
it('removes the correct variable row', async () => {
|
||||
const variableKeyNameOne = 'key-one';
|
||||
const variableKeyNameThree = 'key-three';
|
||||
|
||||
await findTriggerBtn().trigger('click');
|
||||
await setCiVariableKeyByPosition(0, variableKeyNameOne);
|
||||
|
||||
expect(findTriggerBtn().props('disabled')).toBe(true);
|
||||
});
|
||||
await setCiVariableKeyByPosition(1, 'key-two');
|
||||
|
||||
await setCiVariableKeyByPosition(2, variableKeyNameThree);
|
||||
|
||||
expect(findAllVariables()).toHaveLength(4);
|
||||
|
||||
await findAllDeleteVarBtns().at(1).trigger('click');
|
||||
|
||||
expect(findAllVariables()).toHaveLength(3);
|
||||
|
||||
expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne);
|
||||
expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree);
|
||||
expect(findAllCiVariableKeys().at(2).element.value).toBe('');
|
||||
});
|
||||
|
||||
it('trigger button is disabled after trigger action', async () => {
|
||||
expect(findTriggerBtn().props('disabled')).toBe(false);
|
||||
|
||||
await findTriggerBtn().trigger('click');
|
||||
|
||||
expect(findTriggerBtn().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('delete variable button should only show when there is more than one variable', async () => {
|
||||
expect(findDeleteVarBtn().exists()).toBe(false);
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
expect(findDeleteVarBtn().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('delete variable button placeholder should only exist when a user cannot remove', async () => {
|
||||
expect(findDeleteVarBtnPlaceholder().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders help text with provided link', () => {
|
||||
expect(findHelpText().exists()).toBe(true);
|
||||
expect(findHelpLink().attributes('href')).toBe(
|
||||
'/help/ci/variables/index#add-a-cicd-variable-to-a-project',
|
||||
);
|
||||
});
|
||||
|
||||
it('passes variables in correct format', async () => {
|
||||
jest.spyOn(store, 'dispatch');
|
||||
|
||||
await setCiVariableKey();
|
||||
|
||||
await findCiVariableValue().setValue('new value');
|
||||
|
||||
await findTriggerBtn().trigger('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('triggerManualJob', [
|
||||
{
|
||||
key: 'new key',
|
||||
secret_value: 'new value',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import) }
|
||||
let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project, bulk_import: bulk_import) }
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
let(:attributes) { {} }
|
||||
let(:external_pr) { project.external_pull_requests.last }
|
||||
let(:external_pull_request) do
|
||||
{
|
||||
'pull_request_iid' => 4,
|
||||
'source_branch' => 'feature',
|
||||
'target_branch' => 'main',
|
||||
'source_repository' => 'repository',
|
||||
'target_repository' => 'repository',
|
||||
'source_sha' => 'abc',
|
||||
'target_sha' => 'xyz',
|
||||
'status' => 'open',
|
||||
'created_at' => '2019-12-24T14:04:50.053Z',
|
||||
'updated_at' => '2019-12-24T14:05:18.138Z'
|
||||
}.merge(attributes)
|
||||
end
|
||||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
describe '#run' do
|
||||
before do
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:remove_tmp_dir)
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[external_pull_request, 0]]))
|
||||
end
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
it 'imports external pull request', :aggregate_failures do
|
||||
expect(external_pr.pull_request_iid).to eq(external_pull_request['pull_request_iid'])
|
||||
expect(external_pr.source_branch).to eq(external_pull_request['source_branch'])
|
||||
expect(external_pr.target_branch).to eq(external_pull_request['target_branch'])
|
||||
expect(external_pr.status).to eq(external_pull_request['status'])
|
||||
expect(external_pr.created_at).to eq(external_pull_request['created_at'])
|
||||
expect(external_pr.updated_at).to eq(external_pull_request['updated_at'])
|
||||
end
|
||||
|
||||
context 'when status is closed' do
|
||||
let(:attributes) { { 'status' => 'closed' } }
|
||||
|
||||
it 'imports closed external pull request' do
|
||||
expect(external_pr.status).to eq(attributes['status'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when from fork' do
|
||||
let(:attributes) { { 'source_repository' => 'source' } }
|
||||
|
||||
it 'does not create external pull request' do
|
||||
expect(external_pr).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,7 @@ RSpec.describe BulkImports::Projects::Stage do
|
|||
[3, BulkImports::Projects::Pipelines::IssuesPipeline],
|
||||
[4, BulkImports::Common::Pipelines::BoardsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline],
|
||||
[5, BulkImports::Common::Pipelines::UploadsPipeline],
|
||||
[6, BulkImports::Common::Pipelines::EntityFinisher]
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
|
||||
let(:config) do
|
||||
described_class.new(yml, project: nil, sha: nil, user: nil)
|
||||
described_class.new(yml, project: nil, pipeline: nil, sha: nil, user: nil)
|
||||
end
|
||||
|
||||
context 'when config is valid' do
|
||||
|
@ -286,9 +286,12 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
|
||||
context "when using 'include' directive" do
|
||||
let(:group) { create(:group) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let(:project) { create(:project, :repository, group: group) }
|
||||
let(:main_project) { create(:project, :repository, :public, group: group) }
|
||||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
|
||||
let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
||||
let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
|
||||
|
||||
|
@ -327,7 +330,7 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
|
||||
let(:config) do
|
||||
described_class.new(gitlab_ci_yml, project: project, sha: '12345', user: user)
|
||||
described_class.new(gitlab_ci_yml, project: project, pipeline: pipeline, sha: '12345', user: user)
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -724,7 +727,7 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
end
|
||||
|
||||
context "when an 'include' has rules" do
|
||||
context "when an 'include' has rules with a project variable" do
|
||||
let(:gitlab_ci_yml) do
|
||||
<<~HEREDOC
|
||||
include:
|
||||
|
@ -751,5 +754,30 @@ RSpec.describe Gitlab::Ci::Config do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when an 'include' has rules with a pipeline variable" do
|
||||
let(:gitlab_ci_yml) do
|
||||
<<~HEREDOC
|
||||
include:
|
||||
- local: #{local_location}
|
||||
rules:
|
||||
- if: $CI_COMMIT_SHA == "#{project.commit.sha}"
|
||||
HEREDOC
|
||||
end
|
||||
|
||||
context 'when a pipeline is passed' do
|
||||
it 'includes the file' do
|
||||
expect(config.to_hash).to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a pipeline is not passed' do
|
||||
let(:pipeline) { nil }
|
||||
|
||||
it 'does not include the file' do
|
||||
expect(config.to_hash).not_to include(local_location_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This spec is a lightweight version of:
|
||||
# * project/tree_restorer_spec.rb
|
||||
#
|
||||
# In depth testing is being done in the above specs.
|
||||
# This spec tests that restore project works
|
||||
# but does not have 100% relation coverage.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::Group::RelationTreeRestorer do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:importable) { create(:group, parent: group) }
|
||||
|
||||
include_context 'relation tree restorer shared context' do
|
||||
let(:importable_name) { nil }
|
||||
end
|
||||
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json' }
|
||||
let(:relation_reader) do
|
||||
Gitlab::ImportExport::Json::LegacyReader::File.new(
|
||||
path,
|
||||
relation_names: reader.group_relation_names)
|
||||
end
|
||||
|
||||
let(:reader) do
|
||||
Gitlab::ImportExport::Reader.new(
|
||||
shared: shared,
|
||||
config: Gitlab::ImportExport::Config.new(config: Gitlab::ImportExport.legacy_group_config_file).to_h
|
||||
)
|
||||
end
|
||||
|
||||
let(:relation_tree_restorer) do
|
||||
described_class.new(
|
||||
user: user,
|
||||
shared: shared,
|
||||
relation_reader: relation_reader,
|
||||
object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
|
||||
members_mapper: members_mapper,
|
||||
relation_factory: Gitlab::ImportExport::Group::RelationFactory,
|
||||
reader: reader,
|
||||
importable: importable,
|
||||
importable_path: nil,
|
||||
importable_attributes: attributes
|
||||
)
|
||||
end
|
||||
|
||||
subject { relation_tree_restorer.restore }
|
||||
|
||||
shared_examples 'logging of relations creation' do
|
||||
context 'when log_import_export_relation_creation feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: group)
|
||||
end
|
||||
|
||||
it 'logs top-level relation creation' do
|
||||
expect(shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.at_least(:once)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when log_import_export_relation_creation feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: false)
|
||||
end
|
||||
|
||||
it 'does not log top-level relation creation' do
|
||||
expect(shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.never
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'restores group tree' do
|
||||
expect(subject).to eq(true)
|
||||
end
|
||||
|
||||
include_examples 'logging of relations creation'
|
||||
end
|
|
@ -0,0 +1,150 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This spec is a lightweight version of:
|
||||
# * project/tree_restorer_spec.rb
|
||||
#
|
||||
# In depth testing is being done in the above specs.
|
||||
# This spec tests that restore project works
|
||||
# but does not have 100% relation coverage.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::Project::RelationTreeRestorer do
|
||||
let_it_be(:importable, reload: true) do
|
||||
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
|
||||
end
|
||||
|
||||
include_context 'relation tree restorer shared context' do
|
||||
let(:importable_name) { 'project' }
|
||||
end
|
||||
|
||||
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
|
||||
let(:relation_tree_restorer) do
|
||||
described_class.new(
|
||||
user: user,
|
||||
shared: shared,
|
||||
relation_reader: relation_reader,
|
||||
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
|
||||
members_mapper: members_mapper,
|
||||
relation_factory: Gitlab::ImportExport::Project::RelationFactory,
|
||||
reader: reader,
|
||||
importable: importable,
|
||||
importable_path: 'project',
|
||||
importable_attributes: attributes
|
||||
)
|
||||
end
|
||||
|
||||
subject { relation_tree_restorer.restore }
|
||||
|
||||
shared_examples 'import project successfully' do
|
||||
describe 'imported project' do
|
||||
it 'has the project attributes and relations', :aggregate_failures do
|
||||
expect(subject).to eq(true)
|
||||
|
||||
project = Project.find_by_path('project')
|
||||
|
||||
expect(project.description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
|
||||
expect(project.labels.count).to eq(3)
|
||||
expect(project.boards.count).to eq(1)
|
||||
expect(project.project_feature).not_to be_nil
|
||||
expect(project.custom_attributes.count).to eq(2)
|
||||
expect(project.project_badges.count).to eq(2)
|
||||
expect(project.snippets.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'logging of relations creation' do
|
||||
context 'when log_import_export_relation_creation feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: group)
|
||||
end
|
||||
|
||||
it 'logs top-level relation creation' do
|
||||
expect(shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.at_least(:once)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when log_import_export_relation_creation feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: false)
|
||||
end
|
||||
|
||||
it 'does not log top-level relation creation' do
|
||||
expect(shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.never
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with legacy reader' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/project.json' }
|
||||
let(:relation_reader) do
|
||||
Gitlab::ImportExport::Json::LegacyReader::File.new(
|
||||
path,
|
||||
relation_names: reader.project_relation_names,
|
||||
allowed_path: 'project'
|
||||
)
|
||||
end
|
||||
|
||||
let(:attributes) { relation_reader.consume_attributes('project') }
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
|
||||
context 'with logging of relations creation' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:importable) do
|
||||
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project', group: group)
|
||||
end
|
||||
|
||||
include_examples 'logging of relations creation'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ndjson reader' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
|
||||
context 'when inside a group' do
|
||||
let_it_be(:group) do
|
||||
create(:group, :disabled_and_unoverridable)
|
||||
end
|
||||
|
||||
before do
|
||||
importable.update!(shared_runners_enabled: false, group: group)
|
||||
end
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid relations' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
|
||||
it 'logs the invalid relation and its errors' do
|
||||
expect(shared.logger)
|
||||
.to receive(:warn)
|
||||
.with(
|
||||
error_messages: "Title can't be blank. Title is invalid",
|
||||
message: '[Project/Group Import] Invalid object relation built',
|
||||
relation_class: 'ProjectLabel',
|
||||
relation_index: 0,
|
||||
relation_key: 'labels'
|
||||
).once
|
||||
|
||||
relation_tree_restorer.restore
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,19 +10,26 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::Project::Sample::RelationTreeRestorer do
|
||||
include_context 'relation tree restorer shared context'
|
||||
let_it_be(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
|
||||
|
||||
include_context 'relation tree restorer shared context' do
|
||||
let(:importable_name) { 'project' }
|
||||
end
|
||||
|
||||
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/sample_data/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
let(:sample_data_relation_tree_restorer) do
|
||||
described_class.new(
|
||||
user: user,
|
||||
shared: shared,
|
||||
relation_reader: relation_reader,
|
||||
object_builder: object_builder,
|
||||
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
|
||||
members_mapper: members_mapper,
|
||||
relation_factory: relation_factory,
|
||||
relation_factory: Gitlab::ImportExport::Project::Sample::RelationFactory,
|
||||
reader: reader,
|
||||
importable: importable,
|
||||
importable_path: importable_path,
|
||||
importable_path: 'project',
|
||||
importable_attributes: attributes
|
||||
)
|
||||
end
|
||||
|
@ -69,32 +76,21 @@ RSpec.describe Gitlab::ImportExport::Project::Sample::RelationTreeRestorer do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when restoring a project' do
|
||||
let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
|
||||
let(:importable_name) { 'project' }
|
||||
let(:importable_path) { 'project' }
|
||||
let(:object_builder) { Gitlab::ImportExport::Project::ObjectBuilder }
|
||||
let(:relation_factory) { Gitlab::ImportExport::Project::Sample::RelationFactory }
|
||||
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/sample_data/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
it 'initializes relation_factory with date_calculator as parameter' do
|
||||
expect(Gitlab::ImportExport::Project::Sample::RelationFactory).to receive(:create).with(hash_including(:date_calculator)).at_least(:once).times
|
||||
|
||||
it 'initializes relation_factory with date_calculator as parameter' do
|
||||
expect(Gitlab::ImportExport::Project::Sample::RelationFactory).to receive(:create).with(hash_including(:date_calculator)).at_least(:once).times
|
||||
subject
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
context 'when relation tree restorer is initialized' do
|
||||
it 'initializes date calculator with due dates' do
|
||||
expect(Gitlab::ImportExport::Project::Sample::DateCalculator).to receive(:new).with(Array)
|
||||
|
||||
context 'when relation tree restorer is initialized' do
|
||||
it 'initializes date calculator with due dates' do
|
||||
expect(Gitlab::ImportExport::Project::Sample::DateCalculator).to receive(:new).with(Array)
|
||||
|
||||
sample_data_relation_tree_restorer
|
||||
end
|
||||
end
|
||||
|
||||
context 'using ndjson reader' do
|
||||
it_behaves_like 'import project successfully'
|
||||
sample_data_relation_tree_restorer
|
||||
end
|
||||
end
|
||||
|
||||
context 'using ndjson reader' do
|
||||
it_behaves_like 'import project successfully'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This spec is a lightweight version of:
|
||||
# * project/tree_restorer_spec.rb
|
||||
#
|
||||
# In depth testing is being done in the above specs.
|
||||
# This spec tests that restore project works
|
||||
# but does not have 100% relation coverage.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
|
||||
include_context 'relation tree restorer shared context'
|
||||
|
||||
let(:relation_tree_restorer) do
|
||||
described_class.new(
|
||||
user: user,
|
||||
shared: shared,
|
||||
relation_reader: relation_reader,
|
||||
object_builder: object_builder,
|
||||
members_mapper: members_mapper,
|
||||
relation_factory: relation_factory,
|
||||
reader: reader,
|
||||
importable: importable,
|
||||
importable_path: importable_path,
|
||||
importable_attributes: attributes
|
||||
)
|
||||
end
|
||||
|
||||
subject { relation_tree_restorer.restore }
|
||||
|
||||
shared_examples 'import project successfully' do
|
||||
describe 'imported project' do
|
||||
it 'has the project attributes and relations', :aggregate_failures do
|
||||
expect(subject).to eq(true)
|
||||
|
||||
project = Project.find_by_path('project')
|
||||
|
||||
expect(project.description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
|
||||
expect(project.labels.count).to eq(3)
|
||||
expect(project.boards.count).to eq(1)
|
||||
expect(project.project_feature).not_to be_nil
|
||||
expect(project.custom_attributes.count).to eq(2)
|
||||
expect(project.project_badges.count).to eq(2)
|
||||
expect(project.snippets.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'logging of relations creation' do
|
||||
context 'when log_import_export_relation_creation feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: group)
|
||||
end
|
||||
|
||||
it 'logs top-level relation creation' do
|
||||
expect(relation_tree_restorer.shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.at_least(:once)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when log_import_export_relation_creation feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(log_import_export_relation_creation: false)
|
||||
end
|
||||
|
||||
it 'does not log top-level relation creation' do
|
||||
expect(relation_tree_restorer.shared.logger)
|
||||
.to receive(:info)
|
||||
.with(hash_including(message: '[Project/Group Import] Created new object relation'))
|
||||
.never
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when restoring a project' do
|
||||
let_it_be(:importable, reload: true) do
|
||||
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
|
||||
end
|
||||
|
||||
let(:importable_name) { 'project' }
|
||||
let(:importable_path) { 'project' }
|
||||
let(:object_builder) { Gitlab::ImportExport::Project::ObjectBuilder }
|
||||
let(:relation_factory) { Gitlab::ImportExport::Project::RelationFactory }
|
||||
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
|
||||
|
||||
context 'using legacy reader' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/project.json' }
|
||||
let(:relation_reader) do
|
||||
Gitlab::ImportExport::Json::LegacyReader::File.new(
|
||||
path,
|
||||
relation_names: reader.project_relation_names,
|
||||
allowed_path: 'project'
|
||||
)
|
||||
end
|
||||
|
||||
let(:attributes) { relation_reader.consume_attributes('project') }
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
|
||||
context 'logging of relations creation' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:importable) do
|
||||
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project', group: group)
|
||||
end
|
||||
|
||||
include_examples 'logging of relations creation'
|
||||
end
|
||||
end
|
||||
|
||||
context 'using ndjson reader' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
|
||||
context 'when inside a group' do
|
||||
let_it_be(:group) do
|
||||
create(:group, :disabled_and_unoverridable)
|
||||
end
|
||||
|
||||
before do
|
||||
importable.update!(shared_runners_enabled: false, group: group)
|
||||
end
|
||||
|
||||
it_behaves_like 'import project successfully'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid relations' do
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/project_with_invalid_relations/tree' }
|
||||
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
|
||||
|
||||
it 'logs the invalid relation and its errors' do
|
||||
expect(relation_tree_restorer.shared.logger)
|
||||
.to receive(:warn)
|
||||
.with(
|
||||
error_messages: "Title can't be blank. Title is invalid",
|
||||
message: '[Project/Group Import] Invalid object relation built',
|
||||
relation_class: 'ProjectLabel',
|
||||
relation_index: 0,
|
||||
relation_key: 'labels'
|
||||
).once
|
||||
|
||||
relation_tree_restorer.restore
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when restoring a group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:importable) { create(:group, parent: group) }
|
||||
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json' }
|
||||
let(:importable_name) { nil }
|
||||
let(:importable_path) { nil }
|
||||
let(:object_builder) { Gitlab::ImportExport::Group::ObjectBuilder }
|
||||
let(:relation_factory) { Gitlab::ImportExport::Group::RelationFactory }
|
||||
let(:relation_reader) do
|
||||
Gitlab::ImportExport::Json::LegacyReader::File.new(
|
||||
path,
|
||||
relation_names: reader.group_relation_names)
|
||||
end
|
||||
|
||||
let(:reader) do
|
||||
Gitlab::ImportExport::Reader.new(
|
||||
shared: shared,
|
||||
config: Gitlab::ImportExport::Config.new(config: Gitlab::ImportExport.legacy_group_config_file).to_h
|
||||
)
|
||||
end
|
||||
|
||||
it 'restores group tree' do
|
||||
expect(subject).to eq(true)
|
||||
end
|
||||
|
||||
include_examples 'logging of relations creation'
|
||||
end
|
||||
end
|
|
@ -7,9 +7,11 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { project.owner }
|
||||
|
||||
let(:ref) { 'refs/heads/master' }
|
||||
let(:source) { :push }
|
||||
let(:service) { described_class.new(project, user, { ref: ref }) }
|
||||
let(:ref) { 'refs/heads/master' }
|
||||
let(:variables_attributes) { [{ key: 'MYVAR', secret_value: 'hello' }] }
|
||||
let(:source) { :push }
|
||||
|
||||
let(:service) { described_class.new(project, user, { ref: ref, variables_attributes: variables_attributes }) }
|
||||
let(:pipeline) { service.execute(source).payload }
|
||||
|
||||
let(:file_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
|
||||
|
@ -24,6 +26,20 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
.and_return(File.read(Rails.root.join(file_location)))
|
||||
end
|
||||
|
||||
shared_examples 'not including the file' do
|
||||
it 'does not include the job in the file' do
|
||||
expect(pipeline).to be_created_successfully
|
||||
expect(pipeline.processables.pluck(:name)).to contain_exactly('job')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'including the file' do
|
||||
it 'includes the job in the file' do
|
||||
expect(pipeline).to be_created_successfully
|
||||
expect(pipeline.processables.pluck(:name)).to contain_exactly('job', 'rspec')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a local file' do
|
||||
let(:config) do
|
||||
<<~EOY
|
||||
|
@ -33,13 +49,10 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
EOY
|
||||
end
|
||||
|
||||
it 'includes the job in the file' do
|
||||
expect(pipeline).to be_created_successfully
|
||||
expect(pipeline.processables.pluck(:name)).to contain_exactly('job', 'rspec')
|
||||
end
|
||||
it_behaves_like 'including the file'
|
||||
end
|
||||
|
||||
context 'with a local file with rules' do
|
||||
context 'with a local file with rules with a project variable' do
|
||||
let(:config) do
|
||||
<<~EOY
|
||||
include:
|
||||
|
@ -54,19 +67,63 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
context 'when the rules matches' do
|
||||
let(:project_id) { project.id }
|
||||
|
||||
it 'includes the job in the file' do
|
||||
expect(pipeline).to be_created_successfully
|
||||
expect(pipeline.processables.pluck(:name)).to contain_exactly('job', 'rspec')
|
||||
end
|
||||
it_behaves_like 'including the file'
|
||||
end
|
||||
|
||||
context 'when the rules does not match' do
|
||||
let(:project_id) { non_existing_record_id }
|
||||
|
||||
it 'does not include the job in the file' do
|
||||
expect(pipeline).to be_created_successfully
|
||||
expect(pipeline.processables.pluck(:name)).to contain_exactly('job')
|
||||
end
|
||||
it_behaves_like 'not including the file'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a local file with rules with a predefined pipeline variable' do
|
||||
let(:config) do
|
||||
<<~EOY
|
||||
include:
|
||||
- local: #{file_location}
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "#{pipeline_source}"
|
||||
job:
|
||||
script: exit 0
|
||||
EOY
|
||||
end
|
||||
|
||||
context 'when the rules matches' do
|
||||
let(:pipeline_source) { 'push' }
|
||||
|
||||
it_behaves_like 'including the file'
|
||||
end
|
||||
|
||||
context 'when the rules does not match' do
|
||||
let(:pipeline_source) { 'web' }
|
||||
|
||||
it_behaves_like 'not including the file'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a local file with rules with a run pipeline variable' do
|
||||
let(:config) do
|
||||
<<~EOY
|
||||
include:
|
||||
- local: #{file_location}
|
||||
rules:
|
||||
- if: $MYVAR == "#{my_var}"
|
||||
job:
|
||||
script: exit 0
|
||||
EOY
|
||||
end
|
||||
|
||||
context 'when the rules matches' do
|
||||
let(:my_var) { 'hello' }
|
||||
|
||||
it_behaves_like 'including the file'
|
||||
end
|
||||
|
||||
context 'when the rules does not match' do
|
||||
let(:my_var) { 'mello' }
|
||||
|
||||
it_behaves_like 'not including the file'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples IntegrationsActions do
|
||||
RSpec.shared_examples Integrations::Actions do
|
||||
let(:integration) do
|
||||
create(:datadog_integration,
|
||||
integration_attributes.merge(
|
63
yarn.lock
63
yarn.lock
|
@ -2529,12 +2529,12 @@ axios-mock-adapter@^1.15.0:
|
|||
dependencies:
|
||||
deep-equal "^1.0.1"
|
||||
|
||||
axios@^0.20.0:
|
||||
version "0.20.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
|
||||
integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==
|
||||
axios@^0.24.0:
|
||||
version "0.24.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
|
||||
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
|
||||
dependencies:
|
||||
follow-redirects "^1.10.0"
|
||||
follow-redirects "^1.14.4"
|
||||
|
||||
babel-eslint@^10.0.3:
|
||||
version "10.0.3"
|
||||
|
@ -4982,10 +4982,10 @@ eslint-import-resolver-node@^0.3.4:
|
|||
debug "^2.6.9"
|
||||
resolve "^1.13.1"
|
||||
|
||||
eslint-import-resolver-webpack@0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.1.tgz#6d2fb928091daf2da46efa1e568055555b2de902"
|
||||
integrity sha512-O/8mG6AHmaKYSMb4lWxiXPpaARxOJ4rMQEHJ8vTgjS1MXooJA3KPgBPPAdOPoV17v5ML5120qod5FBLM+DtgEw==
|
||||
eslint-import-resolver-webpack@0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.2.tgz#fc813df0d08b9265cc7072d22393bda5198bdc1e"
|
||||
integrity sha512-XodIPyg1OgE2h5BDErz3WJoK7lawxKTJNhgPNafRST6csC/MZC+L5P6kKqsZGRInpbgc02s/WZMrb4uGJzcuRg==
|
||||
dependencies:
|
||||
array-find "^1.0.0"
|
||||
debug "^3.2.7"
|
||||
|
@ -4993,8 +4993,8 @@ eslint-import-resolver-webpack@0.13.1:
|
|||
find-root "^1.1.0"
|
||||
has "^1.0.3"
|
||||
interpret "^1.4.0"
|
||||
is-core-module "^2.4.0"
|
||||
is-regex "^1.1.3"
|
||||
is-core-module "^2.7.0"
|
||||
is-regex "^1.1.4"
|
||||
lodash "^4.17.21"
|
||||
resolve "^1.20.0"
|
||||
semver "^5.7.1"
|
||||
|
@ -5050,10 +5050,10 @@ eslint-plugin-jest@^23.8.2:
|
|||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^2.5.0"
|
||||
|
||||
eslint-plugin-no-jquery@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.6.0.tgz#7892cb7c086f7813156bca6bc48429825428e9eb"
|
||||
integrity sha512-xC7pbNHJMdyxqhzcNMRrmC5/tbt1T4KCKXjOqUpKm/CaRryGKS5iWztzWPrL0KwyI3R3ub6goHFmIQS19f+mZA==
|
||||
eslint-plugin-no-jquery@2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz#855f5631cf5b8e25b930cf6f06e02dd81f132e72"
|
||||
integrity sha512-Aeg7dA6GTH1AcWLlBtWNzOU9efK5KpNi7b0EhBO0o0M+awyzguUUo8gF6hXGjQ9n5h8/uRtYv9zOqQkeC5CG0w==
|
||||
|
||||
eslint-plugin-promise@^4.2.1:
|
||||
version "4.2.1"
|
||||
|
@ -5605,10 +5605,10 @@ flush-write-stream@^1.0.0:
|
|||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
|
||||
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.4:
|
||||
version "1.14.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
@ -5999,6 +5999,13 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
|
||||
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
|
||||
|
||||
has-tostringtag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
|
||||
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
has-value@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
|
||||
|
@ -6511,10 +6518,10 @@ is-ci@^2.0.0:
|
|||
dependencies:
|
||||
ci-info "^2.0.0"
|
||||
|
||||
is-core-module@^2.2.0, is-core-module@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
|
||||
integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
|
||||
is-core-module@^2.2.0, is-core-module@^2.7.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548"
|
||||
integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
|
@ -6690,13 +6697,13 @@ is-potential-custom-element-name@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
|
||||
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
|
||||
|
||||
is-regex@^1.1.1, is-regex@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||
integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
|
||||
is-regex@^1.1.1, is-regex@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-symbols "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-regexp@^2.0.0:
|
||||
version "2.1.0"
|
||||
|
|
Loading…
Reference in New Issue