Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-02 18:10:01 +00:00
parent 1c2ff01b69
commit e84a2fdfc8
91 changed files with 3820 additions and 2336 deletions

View File

@ -317,30 +317,3 @@ bundle-size-review:
expire_in: 31d
paths:
- bundle-size-review
.startup-css-check-base:
extends:
- .frontend-test-base
script:
- *yarn-install
- run_timed_command "yarn generate:startup_css"
- yarn check:startup_css
startup-css-check:
extends:
- .startup-css-check-base
- .frontend:rules:startup-css-check
needs:
- job: "compile-test-assets"
- job: "rspec frontend_fixture"
- job: "rspec-ee frontend_fixture"
optional: true
startup-css-check as-if-foss:
extends:
- .startup-css-check-base
- .as-if-foss
- .frontend:rules:startup-css-check-as-if-foss
needs:
- job: "compile-test-assets as-if-foss"
- job: "rspec frontend_fixture as-if-foss"

View File

@ -485,17 +485,6 @@
changes: *frontend-build-patterns
allow_failure: true
.frontend:rules:startup-css-check:
rules:
- changes: *code-backstage-qa-patterns
.frontend:rules:startup-css-check-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request
changes: *code-backstage-qa-patterns
################
# Memory rules #
################

View File

@ -3,7 +3,6 @@
"ignoreFiles": [
"app/assets/stylesheets/pages/emojis.scss",
"app/assets/stylesheets/startup/startup-*.scss",
"ee/app/assets/stylesheets/startup/startup-*.scss",
"app/assets/stylesheets/lazy_bundles/select2.scss",
"app/assets/stylesheets/highlight/themes/*.scss",
"app/assets/stylesheets/lazy_bundles/cropper.css"

View File

@ -32,7 +32,7 @@ export default (el) => {
canAwardEmoji: this.canAwardEmoji,
currentUserId: this.currentUserId,
defaultAwards: ['thumbsup', 'thumbsdown'],
selectedClass: 'gl-bg-blue-50! is-active',
selectedClass: 'selected',
},
on: {
award: this.toggleAward,

View File

@ -85,7 +85,7 @@ export default {
:boundary="getBoundaryElement()"
:class="dropdownClass"
menu-class="dropdown-extended-height"
category="tertiary"
category="secondary"
no-flip
right
lazy

View File

@ -304,7 +304,7 @@ export default {
v-else
v-gl-tooltip
:class="{ 'js-user-authored': isAuthoredByCurrentUser }"
class="note-action-button note-emoji-button add-reaction-button btn-icon js-add-award js-note-emoji"
class="note-action-button note-emoji-button add-reaction-button js-add-award js-note-emoji"
category="tertiary"
variant="default"
:title="$options.i18n.addReactionLabel"

View File

@ -173,7 +173,7 @@ export default {
v-for="awardList in groupedAwards"
:key="awardList.name"
v-gl-tooltip.viewport
class="gl-mr-3"
class="gl-mr-3 gl-my-2"
:class="awardList.classes"
:title="awardList.title"
data-testid="award-button"
@ -184,10 +184,10 @@ export default {
</template>
<span class="js-counter">{{ awardList.list.length }}</span>
</gl-button>
<div v-if="canAwardEmoji" class="award-menu-holder">
<div v-if="canAwardEmoji" class="award-menu-holder gl-my-2">
<emoji-picker
v-if="glFeatures.improvedEmojiPicker"
:toggle-class="['add-reaction-button gl-relative!', { 'is-active': isMenuOpen }]"
:toggle-class="['add-reaction-button btn-icon gl-relative!', { 'is-active': isMenuOpen }]"
@click="handleAward"
@shown="setIsMenuOpen(true)"
@hidden="setIsMenuOpen(false)"

View File

@ -255,27 +255,9 @@
// This forces the height and width of the inner content to match
// other gl-buttons despite all child elements being set to
// `position:absolute`
&::after {
content: '\a0';
display: block !important;
width: 1em;
color: transparent;
}
.reaction-control-icon {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
// center the icon vertically and horizontally within the button
display: flex;
align-items: center;
justify-content: center;
@include transition(opacity, transform);
.gl-icon {
height: $default-icon-size;
width: $default-icon-size;
@ -283,32 +265,26 @@
}
.reaction-control-icon-neutral {
opacity: 1;
display: flex;
}
.reaction-control-icon-positive,
.reaction-control-icon-super-positive {
opacity: 0;
display: none;
}
&:hover,
&.active,
&:active,
&.is-active {
// extra specificty added to override another selector
.reaction-control-icon .gl-icon {
color: $blue-500;
transform: scale(1.15);
}
.reaction-control-icon-neutral {
opacity: 0;
display: none;
}
}
&:hover {
.reaction-control-icon-positive {
opacity: 1;
display: flex;
}
}
@ -316,11 +292,11 @@
&:active,
&.is-active {
.reaction-control-icon-positive {
opacity: 0;
display: none;
}
.reaction-control-icon-super-positive {
opacity: 1;
display: flex;
}
}
@ -336,17 +312,13 @@
}
.reaction-control-icon-neutral {
opacity: 1;
display: flex;
}
.reaction-control-icon-positive,
.reaction-control-icon-super-positive {
opacity: 0;
display: none;
}
}
}
}
.awards .is-active {
box-shadow: inset 0 0 0 1px $blue-200;
}

View File

@ -72,7 +72,7 @@
}
&.content-component-block {
padding: 11px 0;
padding: 8px 0;
background-color: $body-bg;
}
@ -253,7 +253,7 @@
}
.content-block-small {
padding: 10px 0;
padding: 4px 0;
}
.landing {

View File

@ -67,10 +67,6 @@
.emoji-block {
padding: $gl-padding-4 0;
@include media-breakpoint-down(md) {
padding: $gl-padding-8 0;
}
}
}

View File

@ -130,10 +130,6 @@ ul.related-merge-requests > li {
&:not(:only-child) {
margin-right: $gl-padding-8;
}
@include media-breakpoint-down(md) {
margin-top: $gl-padding-8;
}
}
}
@ -154,10 +150,6 @@ ul.related-merge-requests > li {
.btn-group:not(.hidden) {
display: flex;
@include media-breakpoint-down(md) {
margin-top: $gl-padding-8;
}
}
.js-create-merge-request {

View File

@ -106,10 +106,6 @@
width: 100%;
}
}
.omniauth-btn {
width: 100%;
}
}
.new-session-tabs {

View File

@ -45,7 +45,6 @@ input[type='checkbox']:hover {
margin: 0 8px;
form {
display: block;
margin: 0;
padding: 4px;
width: $search-input-width;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
module Mutations
module Ci
module Runner
class Delete < BaseMutation
graphql_name 'RunnerDelete'
authorize :delete_runner
RunnerID = ::Types::GlobalIDType[::Ci::Runner]
argument :id, RunnerID,
required: true,
description: 'ID of the runner to delete.'
def resolve(id:, **runner_attrs)
runner = authorized_find!(id)
error = authenticate_delete_runner!(runner)
return { errors: [error] } if error
runner.destroy!
{ errors: runner.errors.full_messages }
end
def authenticate_delete_runner!(runner)
return if current_user.can_admin_all_resources?
"Runner #{runner.to_global_id} associated with more than one project" if runner.projects.count > 1
end
def find_object(id)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = RunnerID.coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end
end

View File

@ -100,6 +100,7 @@ module Types
mount_mutation Mutations::Ci::Job::Play
mount_mutation Mutations::Ci::Job::Retry
mount_mutation Mutations::Ci::Runner::Update, feature_flag: :runner_graphql_query
mount_mutation Mutations::Ci::Runner::Delete, feature_flag: :runner_graphql_query
mount_mutation Mutations::Namespace::PackageSettings::Update
mount_mutation Mutations::UserCallouts::Create
end

View File

@ -88,14 +88,6 @@ module GroupsHelper
.count
end
def group_open_merge_requests_count(group)
if Feature.enabled?(:cached_sidebar_merge_requests_count, group, default_enabled: :yaml)
cached_issuables_count(@group, type: :merge_requests)
else
number_with_delimiter(group_merge_requests_count(state: 'opened'))
end
end
def group_merge_requests_count(state:)
MergeRequestsFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)

View File

@ -19,13 +19,14 @@ module Ci
DuplicateDownstreamPipelineError.new,
bridge_id: @bridge.id, project_id: @bridge.project_id
)
return
return error('Already has a downstream pipeline')
end
pipeline_params = @bridge.downstream_pipeline_params
target_ref = pipeline_params.dig(:target_revision, :ref)
return unless ensure_preconditions!(target_ref)
return error('Pre-conditions not met') unless ensure_preconditions!(target_ref)
service = ::Ci::CreatePipelineService.new(
pipeline_params.fetch(:project),
@ -119,8 +120,11 @@ module Ci
return false if @bridge.triggers_child_pipeline?
if Feature.enabled?(:ci_drop_cyclical_triggered_pipelines, @bridge.project, default_enabled: :yaml)
checksums = @bridge.pipeline.base_and_ancestors.map { |pipeline| config_checksum(pipeline) }
checksums.uniq.length != checksums.length
pipeline_checksums = @bridge.pipeline.base_and_ancestors.filter_map do |pipeline|
config_checksum(pipeline) unless pipeline.child?
end
pipeline_checksums.uniq.length != pipeline_checksums.length
end
end

View File

@ -8,6 +8,5 @@
= f.number_field :max_artifacts_size, class: 'form-control'
%p.form-text.text-muted
= _("The maximum file size in megabytes for individual job artifacts.")
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size'), target: '_blank'
= link_to s_('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "btn gl-button btn-confirm"

View File

@ -36,7 +36,7 @@
= favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
= render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) }
= render 'layouts/startup_css'
- if user_application_theme == 'gl-dark'
= stylesheet_link_tag_defer "application_dark"
= yield :page_specific_styles

View File

@ -1,5 +1,4 @@
- startup_filename_default = user_application_theme == 'gl-dark' ? 'dark' : 'general'
- startup_filename = local_assigns.fetch(:startup_filename, nil) || startup_filename_default
- startup_filename = current_path?("sessions#new") ? 'signin' : user_application_theme == 'gl-dark' ? 'dark' : 'general'
%style
= Rails.application.assets_manifest.find_sources("themes/#{user_application_theme_css_filename}.css").first.to_s.html_safe if user_application_theme_css_filename

View File

@ -1,6 +1,6 @@
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head", { startup_filename: 'signin' }
= render "layouts/head"
%body.ui-indigo.login-page.application.navless{ class: "#{client_class_list}", data: { page: body_data_page, qa_selector: 'login_page' } }
= header_message
= render "layouts/init_client_detection_flags"

View File

@ -1,5 +1,5 @@
- issues_count = cached_issuables_count(@group, type: :issues)
- merge_requests_count = group_open_merge_requests_count(@group)
- merge_requests_count = cached_issuables_count(@group, type: :merge_requests)
- aside_title = @group.subgroup? ? _('Subgroup navigation') : _('Group navigation')
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(@group), 'aria-label': aside_title }

View File

@ -1,5 +1,5 @@
.content-block.content-block-small.emoji-list-container.js-noteable-awards
.content-block.emoji-block.emoji-list-container.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true, api_awards_path: award_emoji_merge_request_api_path(@merge_request) do
.ml-auto.mt-auto.mb-auto
.ml-auto.gl-my-2
#js-vue-sort-issue-discussions
= render "projects/merge_requests/discussion_filter"

View File

@ -4,7 +4,7 @@
.row.gl-m-0.gl-justify-content-space-between
.js-noteable-awards
= render 'award_emoji/awards_block', awardable: issuable, inline: true, api_awards_path: api_awards_path
.new-branch-col
.new-branch-col.gl-my-2
= render_if_exists "projects/issues/timeline_toggle", issuable: issuable
#js-vue-sort-issue-discussions
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(issuable), notes_filters: UserPreference.notes_filters.to_json } }

View File

@ -15,12 +15,11 @@
%ul.sidebar-sub-level-items{ class: ('is-fly-out-only' unless sidebar_menu.has_renderable_items?) }
= nav_link(**sidebar_menu.all_active_routes, html_options: { class: 'fly-out-top-item' } ) do
= link_to sidebar_menu.link, **sidebar_menu.collapsed_container_html_options do
%strong.fly-out-top-item-name
= sidebar_menu.title
- if sidebar_menu.has_pill?
%span.badge.badge-pill.count.fly-out-badge{ **sidebar_menu.pill_html_options }
= number_with_delimiter(sidebar_menu.pill_count)
- if sidebar_refactor_disabled?
= link_to sidebar_menu.link, **sidebar_menu.collapsed_container_html_options do
= render 'shared/nav/sidebar_menu_collapsed', sidebar_menu: sidebar_menu
- else
= render 'shared/nav/sidebar_menu_collapsed', sidebar_menu: sidebar_menu
- if sidebar_menu.has_items?
%li.divider.fly-out-top-item

View File

@ -0,0 +1,5 @@
%strong.fly-out-top-item-name
= sidebar_menu.title
- if sidebar_menu.has_pill?
%span.badge.badge-pill.count.fly-out-badge{ **sidebar_menu.pill_html_options }
= number_with_delimiter(sidebar_menu.pill_count)

View File

@ -43,7 +43,7 @@ module BulkImports
def run(pipeline_tracker)
if ndjson_pipeline?(pipeline_tracker)
status = ExportStatus.new(pipeline_tracker, pipeline_tracker.pipeline_class::RELATION)
status = ExportStatus.new(pipeline_tracker, pipeline_tracker.pipeline_class.relation)
raise(Pipeline::ExpiredError, 'Pipeline timeout') if job_timeout?(pipeline_tracker)
raise(Pipeline::FailedError, status.error) if status.failed?

View File

@ -1,8 +0,0 @@
---
name: cached_sidebar_merge_requests_count
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55971
rollout_issue_url:
milestone: '13.11'
type: development
group: group::product planning
default_enabled: true

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddLatestPipelineIdIntoVulnerabilityStatisticsTable < ActiveRecord::Migration[6.0]
def change
add_column :vulnerability_statistics, :latest_pipeline_id, :bigint
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddIndexToVulnerabilityStatisticsOnLatestPipelineId < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
INDEX_NAME = 'index_vulnerability_statistics_on_latest_pipeline_id'
disable_ddl_transaction!
def up
add_concurrent_index :vulnerability_statistics, :latest_pipeline_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :vulnerability_statistics, INDEX_NAME
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddForeignKeyForLatestPipelineIdToCiPipelines < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_concurrent_foreign_key :vulnerability_statistics, :ci_pipelines, column: :latest_pipeline_id, on_delete: :nullify
end
def down
with_lock_retries do
remove_foreign_key_if_exists :vulnerability_statistics, :ci_pipelines
end
end
end

View File

@ -0,0 +1 @@
ae91ea7481ea21ce29b4c0697f77fd83017c36d913739ed67e5c907a48c56f69

View File

@ -0,0 +1 @@
e72471e63dc108939473232437eda4c718382630c1173ae20023002d382e5ffa

View File

@ -0,0 +1 @@
3c53d85bec154ec68a23841d37317d10fa6c7c846bc5f54f5b7876081105ac7b

View File

@ -19249,7 +19249,8 @@ CREATE TABLE vulnerability_statistics (
low integer DEFAULT 0 NOT NULL,
unknown integer DEFAULT 0 NOT NULL,
info integer DEFAULT 0 NOT NULL,
letter_grade smallint NOT NULL
letter_grade smallint NOT NULL,
latest_pipeline_id bigint
);
CREATE SEQUENCE vulnerability_statistics_id_seq
@ -24869,6 +24870,8 @@ CREATE UNIQUE INDEX index_vulnerability_remediations_on_project_id_and_checksum
CREATE UNIQUE INDEX index_vulnerability_scanners_on_project_id_and_external_id ON vulnerability_scanners USING btree (project_id, external_id);
CREATE INDEX index_vulnerability_statistics_on_latest_pipeline_id ON vulnerability_statistics USING btree (latest_pipeline_id);
CREATE INDEX index_vulnerability_statistics_on_letter_grade ON vulnerability_statistics USING btree (letter_grade);
CREATE UNIQUE INDEX index_vulnerability_statistics_on_unique_project_id ON vulnerability_statistics USING btree (project_id);
@ -25973,6 +25976,9 @@ ALTER TABLE ONLY sprints
ALTER TABLE ONLY application_settings
ADD CONSTRAINT fk_e8a145f3a7 FOREIGN KEY (instance_administrators_group_id) REFERENCES namespaces(id) ON DELETE SET NULL;
ALTER TABLE ONLY vulnerability_statistics
ADD CONSTRAINT fk_e8b13c928f FOREIGN KEY (latest_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_triggers
ADD CONSTRAINT fk_e8e10d1964 FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -295,7 +295,6 @@ keytab
keytabs
Kibana
Kinesis
Klar
Knative
Kramdown
Kroki

View File

@ -24,11 +24,19 @@ developed and tested. We aim to be compatible with most external
sudo -i
```
1. Edit `/etc/gitlab/gitlab.rb` and add a **unique** ID for your node (arbitrary value):
1. Edit `/etc/gitlab/gitlab.rb` and add:
```ruby
# The unique identifier for the Geo node.
gitlab_rails['geo_node_name'] = '<node_name_here>'
##
## Geo Primary role
## - configure dependent flags automatically to enable Geo
##
roles ['geo_primary_role']
##
## The unique identifier for the Geo site.
##
gitlab_rails['geo_node_name'] = '<geo_site_name_here>'
```
1. Reconfigure the **primary** node for the change to take effect:

View File

@ -83,9 +83,9 @@ Fields related to design management.
Returns [`DesignManagement!`](#designmanagement).
### `Query.devopsAdoptionSegments`
### `Query.devopsAdoptionEnabledNamespaces`
Get configured DevOps adoption segments on the instance. **BETA** This endpoint is subject to change without notice.
Get configured DevOps adoption namespaces. **BETA** This endpoint is subject to change without notice.
Returns [`DevopsAdoptionSegmentConnection`](#devopsadoptionsegmentconnection).
@ -97,7 +97,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querydevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by display namespace. |
| <a id="querydevopsadoptionenablednamespacesdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by display namespace. |
### `Query.echo`
@ -758,27 +758,27 @@ Input type: `BoardListUpdateLimitMetricsInput`
| <a id="mutationboardlistupdatelimitmetricserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationboardlistupdatelimitmetricslist"></a>`list` | [`BoardList`](#boardlist) | The updated list. |
### `Mutation.bulkFindOrCreateDevopsAdoptionSegments`
### `Mutation.bulkEnableDevopsAdoptionNamespaces`
**BETA** This endpoint is subject to change without notice.
Input type: `BulkFindOrCreateDevopsAdoptionSegmentsInput`
Input type: `BulkEnableDevopsAdoptionNamespacesInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsnamespaceids"></a>`namespaceIds` | [`[NamespaceID!]!`](#namespaceid) | List of Namespace IDs for the segments. |
| <a id="mutationbulkenabledevopsadoptionnamespacesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationbulkenabledevopsadoptionnamespacesnamespaceids"></a>`namespaceIds` | [`[NamespaceID!]!`](#namespaceid) | List of Namespace IDs. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentssegments"></a>`segments` | [`[DevopsAdoptionSegment!]`](#devopsadoptionsegment) | Created segments after mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespacesenablednamespaces"></a>`enabledNamespaces` | [`[DevopsAdoptionSegment!]`](#devopsadoptionsegment) | Enabled namespaces after mutation. |
| <a id="mutationbulkenabledevopsadoptionnamespaceserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.ciCdSettingsUpdate`
@ -1094,28 +1094,6 @@ Input type: `CreateCustomEmojiInput`
| <a id="mutationcreatecustomemojicustomemoji"></a>`customEmoji` | [`CustomEmoji`](#customemoji) | The new custom emoji. |
| <a id="mutationcreatecustomemojierrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.createDevopsAdoptionSegment`
**BETA** This endpoint is subject to change without notice.
Input type: `CreateDevopsAdoptionSegmentInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatedevopsadoptionsegmentdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationcreatedevopsadoptionsegmentnamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | Namespace ID to set for the segment. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatedevopsadoptionsegmenterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationcreatedevopsadoptionsegmentsegment"></a>`segment` | [`DevopsAdoptionSegment`](#devopsadoptionsegment) | The segment after mutation. |
### `Mutation.createDiffNote`
Input type: `CreateDiffNoteInput`
@ -1678,26 +1656,6 @@ Input type: `DeleteAnnotationInput`
| <a id="mutationdeleteannotationclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeleteannotationerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.deleteDevopsAdoptionSegment`
**BETA** This endpoint is subject to change without notice.
Input type: `DeleteDevopsAdoptionSegmentInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdeletedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeletedevopsadoptionsegmentid"></a>`id` | [`[AnalyticsDevopsAdoptionSegmentID!]!`](#analyticsdevopsadoptionsegmentid) | One or many IDs of the segments to delete. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdeletedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdeletedevopsadoptionsegmenterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.designManagementDelete`
Input type: `DesignManagementDeleteInput`
@ -1914,6 +1872,26 @@ Input type: `DestroySnippetInput`
| <a id="mutationdestroysnippeterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdestroysnippetsnippet"></a>`snippet` | [`Snippet`](#snippet) | The snippet after mutation. |
### `Mutation.disableDevopsAdoptionNamespace`
**BETA** This endpoint is subject to change without notice.
Input type: `DisableDevopsAdoptionNamespaceInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdisabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdisabledevopsadoptionnamespaceid"></a>`id` | [`[AnalyticsDevopsAdoptionEnabledNamespaceID!]!`](#analyticsdevopsadoptionenablednamespaceid) | One or many IDs of the enabled namespaces to disable. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdisabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdisabledevopsadoptionnamespaceerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.discussionToggleResolve`
Toggles the resolved state of a discussion.
@ -1961,6 +1939,28 @@ Input type: `DismissVulnerabilityInput`
| <a id="mutationdismissvulnerabilityerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdismissvulnerabilityvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | The vulnerability after dismissal. |
### `Mutation.enableDevopsAdoptionNamespace`
**BETA** This endpoint is subject to change without notice.
Input type: `EnableDevopsAdoptionNamespaceInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationenabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationenabledevopsadoptionnamespacedisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationenabledevopsadoptionnamespacenamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | Namespace ID. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationenabledevopsadoptionnamespaceclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationenabledevopsadoptionnamespaceenablednamespace"></a>`enabledNamespace` | [`DevopsAdoptionSegment`](#devopsadoptionsegment) | Enabled namespace after mutation. |
| <a id="mutationenabledevopsadoptionnamespaceerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.environmentsCanaryIngressUpdate`
Input type: `EnvironmentsCanaryIngressUpdateInput`
@ -3569,6 +3569,26 @@ Input type: `RunDASTScanInput`
| <a id="mutationrundastscanerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationrundastscanpipelineurl"></a>`pipelineUrl` | [`String`](#string) | URL of the pipeline that was created. |
### `Mutation.runnerDelete`
Available only when feature flag `runner_graphql_query` is enabled.
Input type: `RunnerDeleteInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrunnerdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrunnerdeleteid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to delete. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrunnerdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrunnerdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.runnerUpdate`
Available only when feature flag `runner_graphql_query` is enabled.
@ -8271,15 +8291,15 @@ four standard [pagination arguments](#connection-pagination-arguments):
### `DevopsAdoptionSegment`
Segment.
Enabled namespace for DevopsAdoption.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="devopsadoptionsegmentdisplaynamespace"></a>`displayNamespace` | [`Namespace`](#namespace) | Namespace where data should be displayed. |
| <a id="devopsadoptionsegmentid"></a>`id` | [`ID!`](#id) | ID of the segment. |
| <a id="devopsadoptionsegmentlatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the segment. |
| <a id="devopsadoptionsegmentid"></a>`id` | [`ID!`](#id) | ID of the enabled namespace. |
| <a id="devopsadoptionsegmentlatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the enabled namespace. |
| <a id="devopsadoptionsegmentnamespace"></a>`namespace` | [`Namespace`](#namespace) | Namespace which should be calculated. |
### `DevopsAdoptionSnapshot`
@ -15010,11 +15030,11 @@ A `AlertManagementHttpIntegrationID` is a global ID. It is encoded as a string.
An example `AlertManagementHttpIntegrationID` is: `"gid://gitlab/AlertManagement::HttpIntegration/1"`.
### `AnalyticsDevopsAdoptionSegmentID`
### `AnalyticsDevopsAdoptionEnabledNamespaceID`
A `AnalyticsDevopsAdoptionSegmentID` is a global ID. It is encoded as a string.
A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a string.
An example `AnalyticsDevopsAdoptionSegmentID` is: `"gid://gitlab/Analytics::DevopsAdoption::Segment/1"`.
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
### `AwardableID`

View File

@ -199,13 +199,13 @@ Example response:
```csv
Group Name,Project Name,Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE,CWE,Other Identifiers
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520
Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869,CWE-1
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520
Gitlab.org,Defend,container_scanning,Trivy,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869,CWE-1
Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,CVE-2021-1234,CWE-2,"""yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a"""
Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,,,"""yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"""
Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,,,"""818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47"""

View File

@ -149,6 +149,8 @@ This decision is made on a case-by-case basis.
## More information
More information about the release procedures can be found in our
[release documentation](https://gitlab.com/gitlab-org/release/docs). You may also want to read our
[Responsible Disclosure Policy](https://about.gitlab.com/security/disclosure/).
You may also want to read our:
- [Release documentation](https://gitlab.com/gitlab-org/release/docs) describing release procedures
- [Deprecation guidelines](../development/deprecation_guidelines/index.md)
- [Responsible Disclosure Policy](https://about.gitlab.com/security/disclosure/)

View File

@ -10,7 +10,9 @@ Although GitLab has discontinued selling the Bronze and Starter tiers, GitLab
continues to honor the entitlements of existing Bronze and Starter tier GitLab
customers for the duration of their contracts at that level.
These features remain available to Bronze and Starter customers, even though
New paid features will not be released in Bronze and Starter tiers after GitLab 13.9.
The following features remain available to Bronze and Starter customers, even though
the tiers are no longer mentioned in GitLab documentation:
- [Activate GitLab EE with a license](../user/admin_area/license.md)

View File

@ -208,9 +208,9 @@ documentation.
## Auto Container Scanning **(ULTIMATE)**
Vulnerability Static Analysis for containers uses either [Clair](https://github.com/quay/clair)
or [Trivy](https://aquasecurity.github.io/trivy/latest/) to check for potential security issues in
Docker images. The Auto Container Scanning stage is skipped on licenses other than [Ultimate](https://about.gitlab.com/pricing/).
Vulnerability static analysis for containers uses [Trivy](https://aquasecurity.github.io/trivy/latest/)
to check for potential security issues in Docker images. The Auto Container Scanning stage is
skipped on licenses other than [Ultimate](https://about.gitlab.com/pricing/).
After creating the report, it's uploaded as an artifact which you can later download and
check out. The merge request displays any detected security issues.

View File

@ -45,11 +45,9 @@ The stages tracked by Value Stream Analytics by default represent the [GitLab fl
### Date ranges
To filter analytics results based on a date range, select one of these options:
- **Last 7 days**
- **Last 30 days** (default)
- **Last 90 days**
To filter analytics results based on a date range,
select different **From** and **To** days
from the date picker (default: last 30 days).
## How Time metrics are measured

View File

@ -46,12 +46,7 @@ To enable container scanning in your pipeline, you need the following:
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
- Docker `18.09.03` or higher installed on the same computer as the runner. If you're using the
shared runners on GitLab.com, then this is already the case.
- An image matching the following supported distributions (depending on the scanner being used):
| Scanning Engine | Supported distributions |
| --- | --- |
| [Trivy](https://github.com/aquasecurity/trivy) | Supported [operating systems](https://aquasecurity.github.io/trivy/latest/vuln-detection/os/) and [languages](https://aquasecurity.github.io/trivy/latest/vuln-detection/library/) |
- An image matching the [supported distributions](https://aquasecurity.github.io/trivy/latest/vuln-detection/os/)).
- [Build and push](../../packages/container_registry/index.md#build-and-push-by-using-gitlab-cicd)
your Docker image to your project's container registry. The name of the Docker image should use
the following [predefined CI/CD variables](../../../ci/variables/predefined_variables.md):
@ -98,14 +93,16 @@ How you enable container scanning depends on your GitLab version:
variable.
- GitLab 13.9 [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322656) integration with
[Trivy](https://github.com/aquasecurity/trivy) by upgrading `CS_MAJOR_VERSION` from `3` to `4`.
- GitLab 14.0 makes Trivy the default scanner.
- GitLab 14.0 [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61850)
integration with [Trivy](https://github.com/aquasecurity/trivy)
as the default for container scanning.
To include the `Container-Scanning.gitlab-ci.yml` template (GitLab 11.9 and later), add the
following to your `.gitlab-ci.yml` file:
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
```
The included template:
@ -144,7 +141,7 @@ build:
- docker push $IMAGE
include:
- template: Container-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
```
### Customizing the container scanning settings
@ -161,7 +158,7 @@ enables verbose output for the analyzer:
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
variables:
SECURE_LOG_LEVEL: 'debug'
@ -169,7 +166,7 @@ variables:
#### Available CI/CD variables
You can [configure](#customizing-the-container-scanning-settings) both analyzers by using the following CI/CD variables:
You can [configure](#customizing-the-container-scanning-settings) analyzers by using the following CI/CD variables:
| CI/CD Variable | Default | Description | Scanner |
| ------------------------------ | ------------- | ----------- | ------------ |
@ -195,7 +192,7 @@ This example sets `GIT_STRATEGY` to `fetch`:
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
container_scanning:
variables:
@ -224,7 +221,7 @@ your CI file:
offline environment, see
[Running container scanning in an offline environment](#running-container-scanning-in-an-offline-environment).
1. If present, remove the `.cs_common` configuration section.
1. If present, remove the `.cs_common` and `container_scanning_new` configuration sections.
1. If the `container_scanning` section is present, it's safer to create one from scratch based on
the new version of the [`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml).
@ -408,7 +405,7 @@ For details on saving and transporting Docker images as a file, see Docker's doc
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
container_scanning:
image: $CI_REGISTRY/namespace/gitlab-container-scanning
@ -432,7 +429,7 @@ variables:
image: docker:stable
update-vulnerabilities-db:
update-scanner-image:
services:
- docker:19-dind
script:
@ -574,8 +571,8 @@ the security vulnerabilities in your groups, projects and pipelines.
## Vulnerabilities database update
If you're using Klar and want more information about the vulnerabilities database update, see the
[maintenance table](../vulnerabilities/index.md#vulnerability-scanner-maintenance).
If you use container scanning and want more information about the vulnerabilities database update,
see the [maintenance table](../vulnerabilities/index.md#vulnerability-scanner-maintenance).
## Interacting with the vulnerabilities

View File

@ -101,7 +101,7 @@ of the finding's [first identifier](https://gitlab.com/gitlab-org/security-produ
combine to create the value.
Examples of primary identifiers include `PluginID` for OWASP Zed Attack Proxy (ZAP), or `CVE` for
Klar. Note that the identifier must be stable. Subsequent scans must return the same value for the
Trivy. Note that the identifier must be stable. Subsequent scans must return the same value for the
same finding, even if the location has slightly changed.
### Report finding
@ -122,7 +122,7 @@ The type of scan. This must be one of the following:
### Scanner
Software that can scan for vulnerabilities. The resulting scan report is typically not in the
[Secure report format](#secure-report-format). Examples include ESLint, Klar, and ZAP.
[Secure report format](#secure-report-format). Examples include ESLint, Trivy, and ZAP.
### Secure product

View File

@ -69,6 +69,8 @@ The following resources are migrated to the target instance:
- name
- link URL
- image URL
- Boards
- Board Lists
Any other items are **not** migrated.

View File

@ -104,7 +104,7 @@ Each stage of Value Stream Analytics is further described in the table below.
| **Stage** | **Description** |
| --------- | --------------- |
| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list](../../project/issue_board.md) created for it. |
| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label is tracked only if it already has an [Issue Board list](../../project/issue_board.md) created for it. |
| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../../project/issues/managing_issues.md#closing-issues-automatically) to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the closing pattern is not present, then the calculation takes the creation time of the first commit in the merge request as the start time. |
| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI/CD takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |

View File

@ -360,32 +360,7 @@ All the above can be done with the [`git-mr`](https://gitlab.com/glensc/git-mr)
## Cached merge request count
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299542) in GitLab 13.11.
> - It's [deployed behind a feature flag](../../../feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-cached-merge-request-count).
WARNING:
This feature might not be available to you. Refer to the previous **version history** note for details.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/327319) in GitLab 14.0.
In a group, the sidebar displays the total count of open merge requests. This value is cached if it's greater than
than 1000. The cached value is rounded to thousands (or millions) and updated every 24 hours.
### Enable or disable cached merge request count **(FREE SELF)**
Cached merge request count in the left sidebar is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../../administration/feature_flags.md)
can disable it.
To disable it:
```ruby
Feature.disable(:cached_sidebar_merge_requests_count)
```
To enable it:
```ruby
Feature.enable(:cached_sidebar_merge_requests_count)
```

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Pipelines
class BoardsPipeline
include NdjsonPipeline
relation_name 'boards'
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end
end
end
end

View File

@ -6,34 +6,9 @@ module BulkImports
class LabelsPipeline
include NdjsonPipeline
RELATION = 'labels'
relation_name 'labels'
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: RELATION
def transform(context, data)
relation_hash = data.first
relation_index = data.last
relation_definition = import_export_config.top_relation_tree(RELATION)
deep_transform_relation!(relation_hash, RELATION, relation_definition) do |key, hash|
Gitlab::ImportExport::Group::RelationFactory.create(
relation_index: relation_index,
relation_sym: key.to_sym,
relation_hash: hash,
importable: context.portable,
members_mapper: nil,
object_builder: object_builder,
user: context.current_user,
excluded_keys: import_export_config.relation_excluded_keys(key)
)
end
end
def load(_, label)
return unless label
label.save! unless label.persisted?
end
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end
end
end

View File

@ -4,26 +4,11 @@ module BulkImports
module Groups
module Pipelines
class MilestonesPipeline
include Pipeline
include NdjsonPipeline
extractor BulkImports::Common::Extractors::GraphqlExtractor,
query: BulkImports::Groups::Graphql::GetMilestonesQuery
relation_name 'milestones'
transformer Common::Transformers::ProhibitedAttributesTransformer
def load(context, data)
return unless data
raise ::BulkImports::Pipeline::NotAllowedError unless authorized?
context.group.milestones.create!(data)
end
private
def authorized?
context.current_user.can?(:admin_milestone, context.group)
end
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end
end
end

View File

@ -9,6 +9,30 @@ module BulkImports
included do
ndjson_pipeline!
def transform(context, data)
relation_hash, relation_index = data
relation_definition = import_export_config.top_relation_tree(relation)
deep_transform_relation!(relation_hash, relation, relation_definition) do |key, hash|
Gitlab::ImportExport::Group::RelationFactory.create(
relation_index: relation_index,
relation_sym: key.to_sym,
relation_hash: hash,
importable: context.portable,
members_mapper: members_mapper,
object_builder: object_builder,
user: context.current_user,
excluded_keys: import_export_config.relation_excluded_keys(key)
)
end
end
def load(_, object)
return unless object
object.save! unless object.persisted?
end
def deep_transform_relation!(relation_hash, relation_key, relation_definition, &block)
relation_key = relation_key_override(relation_key)
@ -58,6 +82,18 @@ module BulkImports
def object_builder
"Gitlab::ImportExport::#{portable.class}::ObjectBuilder".constantize
end
def relation
self.class.relation
end
def members_mapper
@members_mapper ||= Gitlab::ImportExport::MembersMapper.new(
exported_members: [],
user: current_user,
importable: portable
)
end
end
end
end

View File

@ -30,6 +30,10 @@ module BulkImports
@import_export_config ||= context.import_export_config
end
def current_user
@current_user ||= context.current_user
end
included do
private
@ -174,6 +178,14 @@ module BulkImports
class_attributes[:ndjson_pipeline]
end
def relation_name(name)
class_attributes[:relation_name] = name
end
def relation
class_attributes[:relation_name]
end
private
def add_attribute(sym, klass, options)

View File

@ -29,9 +29,13 @@ module BulkImports
pipeline: BulkImports::Groups::Pipelines::BadgesPipeline,
stage: 1
},
boards: {
pipeline: BulkImports::Groups::Pipelines::BoardsPipeline,
stage: 2
},
finisher: {
pipeline: BulkImports::Groups::Pipelines::EntityFinisher,
stage: 2
stage: 3
}
}.freeze

View File

@ -371,7 +371,9 @@ module Gitlab
end
def find_build_by_token(token)
::Ci::AuthJobFinder.new(token: token).execute
::Gitlab::Database::LoadBalancing::Session.current.use_primary do
::Ci::AuthJobFinder.new(token: token).execute
end
end
def user_auth_attempt!(user, success:)

View File

@ -70,6 +70,8 @@ module Gitlab
elsif data.key?('error')
status = ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR
system_output = data['error']
attachment = attachment_path(data['system_out'])
attachment = remove_project_prefix(attachment, job)
elsif data.key?('skipped')
status = ::Gitlab::Ci::Reports::TestCase::STATUS_SKIPPED
system_output = data['skipped']

View File

@ -3,7 +3,6 @@
"scripts": {
"check-dependencies": "scripts/frontend/check_dependencies.sh",
"block-dependencies": "node scripts/frontend/block_dependencies.js",
"check:startup_css": "scripts/frontend/startup_css/startup_css_changed.sh",
"clean": "rm -rf public/assets tmp/cache/*-loader",
"dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" node scripts/frontend/webpack_dev_server.js",
"file-coverage": "scripts/frontend/file_test_coverage.js",

View File

@ -27,6 +27,7 @@ const CSS_TO_REMOVE = [
/\.commit/,
/\.md/,
/\.with-performance-bar/,
/\.identicon/,
];
const APPLICATION_CSS_PREFIX = 'application';
const APPLICATION_DARK_CSS_PREFIX = 'application_dark';
@ -35,55 +36,49 @@ const UTILITIES_DARK_CSS_PREFIX = 'application_utilities_dark';
// paths -----------------------------------------------------------------------
const ROOT = path.resolve(__dirname, '../../..');
const ROOT_RAILS = IS_EE ? path.join(ROOT, 'ee') : ROOT;
const FIXTURES_FOLDER_NAME = IS_EE ? 'fixtures-ee' : 'fixtures';
const FIXTURES_ROOT = path.join(ROOT, 'tmp/tests/frontend', FIXTURES_FOLDER_NAME);
const PATH_SIGNIN_HTML = path.join(FIXTURES_ROOT, 'startup_css/sign-in.html');
const PATH_ASSETS = path.join(ROOT, 'tmp/startup_css_assets');
const PATH_STARTUP_SCSS = path.join(ROOT_RAILS, 'app/assets/stylesheets/startup');
// helpers ---------------------------------------------------------------------
const createMainOutput = ({ outFile, cssKeys, type }) => ({
outFile,
htmlPaths: [
path.join(FIXTURES_ROOT, `startup_css/project-${type}.html`),
path.join(FIXTURES_ROOT, `startup_css/project-${type}-legacy-menu.html`),
path.join(FIXTURES_ROOT, `startup_css/project-${type}-legacy-sidebar.html`),
path.join(FIXTURES_ROOT, `startup_css/project-${type}-signed-out.html`),
],
cssKeys,
purgeOptions: {
safelist: {
standard: [
'page-with-icon-sidebar',
'sidebar-collapsed-desktop',
// We want to include the root dropdown-menu style since it should be hidden by default
'dropdown-menu',
],
// We want to include the identicon backgrounds
greedy: [/^bg[0-9]$/],
const PATH_STARTUP_SCSS = path.join(ROOT, 'app/assets/stylesheets/startup');
const OUTPUTS = [
{
outFile: 'startup-general',
htmlPaths: [
path.join(FIXTURES_ROOT, 'startup_css/project-general.html'),
path.join(FIXTURES_ROOT, 'startup_css/project-general-legacy-menu.html'),
path.join(FIXTURES_ROOT, 'startup_css/project-general-signed-out.html'),
],
cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
// We want to include the root dropdown-menu style since it should be hidden by default
purgeOptions: {
safelist: {
standard: ['dropdown-menu'],
},
},
},
});
const OUTPUTS = [
createMainOutput({
type: 'general',
outFile: 'startup-general',
cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
}),
createMainOutput({
type: 'dark',
{
outFile: 'startup-dark',
htmlPaths: [
path.join(FIXTURES_ROOT, 'startup_css/project-dark.html'),
path.join(FIXTURES_ROOT, 'startup_css/project-dark-legacy-menu.html'),
path.join(FIXTURES_ROOT, 'startup_css/project-dark-signed-out.html'),
],
cssKeys: [APPLICATION_DARK_CSS_PREFIX, UTILITIES_DARK_CSS_PREFIX],
}),
// We want to include the root dropdown-menu styles since it should be hidden by default
purgeOptions: {
safelist: {
standard: ['dropdown-menu'],
},
},
},
{
outFile: 'startup-signin',
htmlPaths: [PATH_SIGNIN_HTML],
cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
purgeOptions: {
safelist: {
standard: ['fieldset', 'hidden'],
standard: ['fieldset'],
deep: [/login-page$/],
},
},

View File

@ -1,36 +0,0 @@
#!/bin/sh
echo "-----------------------------------------------------------"
echo "If you are run into any issues with Startup CSS generation,"
echo "please check out the feedback issue:"
echo ""
echo "https://gitlab.com/gitlab-org/gitlab/-/issues/331812"
echo "-----------------------------------------------------------"
startup_glob="*stylesheets/startup*"
echo "Staging changes to '${startup_glob}' so we can check for untracked files..."
git add ${startup_glob}
if [ -n "$(git diff HEAD --name-only -- ${startup_glob})" ]; then
diff=$(git diff HEAD -- ${startup_glob})
cat <<EOF
Startup CSS changes detected!
It looks like there have been recent changes which require
regenerating the Startup CSS files.
Consider one of the following options:
1. Regenerating locally with "yarn run generate:startup_css".
2. Copy and apply the following diff:
----- start diff -----
$diff
----- end diff -------
EOF
exit 1
fi

View File

@ -9,7 +9,7 @@ const buildFinalContent = (raw) => {
// https://gitlab.com/gitlab-org/gitlab/-/issues/331812
@charset "UTF-8";
${raw}
@import 'startup/cloaking';
@import 'cloaking';
@include cloak-startup-scss(none);
`;

View File

@ -62,11 +62,11 @@ RSpec.describe 'User interacts with awards' do
page.within('.awards') do
expect(page).to have_selector('[data-testid="award-button"]')
expect(page.find('[data-testid="award-button"].is-active .js-counter')).to have_content('1')
expect(page).to have_css('[data-testid="award-button"].is-active[title="You"]')
expect(page.find('[data-testid="award-button"].selected .js-counter')).to have_content('1')
expect(page).to have_css('[data-testid="award-button"].selected[title="You"]')
expect do
page.find('[data-testid="award-button"].is-active').click
page.find('[data-testid="award-button"].selected').click
wait_for_requests
end.to change { page.all('[data-testid="award-button"]').size }.from(3).to(2)
end
@ -205,7 +205,7 @@ RSpec.describe 'User interacts with awards' do
it 'adds award to issue' do
first('[data-testid="award-button"]').click
expect(page).to have_selector('[data-testid="award-button"].is-active')
expect(page).to have_selector('[data-testid="award-button"].selected')
expect(first('[data-testid="award-button"]')).to have_content '1'
visit project_issue_path(project, issue)
@ -215,7 +215,7 @@ RSpec.describe 'User interacts with awards' do
it 'removes award from issue' do
first('[data-testid="award-button"]').click
find('[data-testid="award-button"].is-active').click
find('[data-testid="award-button"].selected').click
expect(first('[data-testid="award-button"]')).to have_content '0'

View File

@ -18,7 +18,7 @@ RSpec.describe 'Merge request > User awards emoji', :js do
it 'adds award to merge request' do
first('[data-testid="award-button"]').click
expect(page).to have_selector('[data-testid="award-button"].is-active')
expect(page).to have_selector('[data-testid="award-button"].selected')
expect(first('[data-testid="award-button"]')).to have_content '1'
visit project_merge_request_path(project, merge_request)
@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User awards emoji', :js do
it 'removes award from merge request' do
first('[data-testid="award-button"]').click
find('[data-testid="award-button"].is-active').click
find('[data-testid="award-button"].selected').click
expect(first('[data-testid="award-button"]')).to have_content '0'
visit project_merge_request_path(project, merge_request)

View File

@ -0,0 +1 @@
{"id":173,"project_id":null,"created_at":"2020-02-11T14:35:51.561Z","updated_at":"2020-02-11T14:35:51.561Z","name":"first board","milestone_id":null,"group_id":4351,"weight":null,"lists":[{"id":189,"board_id":173,"label_id":271,"list_type":"label","position":0,"created_at":"2020-02-11T14:35:57.131Z","updated_at":"2020-02-11T14:35:57.131Z","user_id":null,"milestone_id":null,"max_issue_count":0,"max_issue_weight":0,"label":{"id":271,"title":"TSL","color":"#58796f","project_id":null,"created_at":"2019-11-20T17:02:20.541Z","updated_at":"2020-02-06T15:44:52.048Z","template":false,"description":null,"group_id":4351,"type":"GroupLabel","priorities":[]},"board":{"id":173,"project_id":null,"created_at":"2020-02-11T14:35:51.561Z","updated_at":"2020-02-11T14:35:51.561Z","name":"hi","milestone_id":null,"group_id":4351,"weight":null}},{"id":190,"board_id":173,"label_id":272,"list_type":"label","position":1,"created_at":"2020-02-11T14:35:57.868Z","updated_at":"2020-02-11T14:35:57.868Z","user_id":null,"milestone_id":null,"max_issue_count":0,"max_issue_weight":0,"label":{"id":272,"title":"Sosync","color":"#110320","project_id":null,"created_at":"2019-11-20T17:02:20.532Z","updated_at":"2020-02-06T15:44:52.057Z","template":false,"description":null,"group_id":4351,"type":"GroupLabel","priorities":[]},"board":{"id":173,"project_id":null,"created_at":"2020-02-11T14:35:51.561Z","updated_at":"2020-02-11T14:35:51.561Z","name":"hi","milestone_id":null,"group_id":4351,"weight":null}},{"id":188,"board_id":173,"label_id":null,"list_type":"closed","position":null,"created_at":"2020-02-11T14:35:51.593Z","updated_at":"2020-02-11T14:35:51.593Z","user_id":null,"milestone_id":null,"max_issue_count":0,"max_issue_weight":0}],"labels":[]}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
{"id":111,"title":"Label 1","color":"#6699cc","project_id":null,"created_at":"2021-04-15T07:15:08.063Z","updated_at":"2021-04-15T07:15:08.063Z","template":false,"description":"Label 1","group_id":107,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"}

View File

@ -0,0 +1,5 @@
{"id":7642,"title":"v4.0","project_id":null,"description":"Et laudantium enim omnis ea reprehenderit iure.","due_date":null,"created_at":"2019-11-20T17:02:14.336Z","updated_at":"2019-11-20T17:02:14.336Z","state":"closed","iid":5,"start_date":null,"group_id":4351}
{"id":7641,"title":"v3.0","project_id":null,"description":"Et repellat culpa nemo consequatur ut reprehenderit.","due_date":null,"created_at":"2019-11-20T17:02:14.323Z","updated_at":"2019-11-20T17:02:14.323Z","state":"active","iid":4,"start_date":null,"group_id":4351}
{"id":7640,"title":"v2.0","project_id":null,"description":"Velit cupiditate est neque voluptates iste rem sunt.","due_date":null,"created_at":"2019-11-20T17:02:14.309Z","updated_at":"2019-11-20T17:02:14.309Z","state":"active","iid":3,"start_date":null,"group_id":4351}
{"id":7639,"title":"v1.0","project_id":null,"description":"Amet velit repellat ut rerum aut cum.","due_date":null,"created_at":"2019-11-20T17:02:14.296Z","updated_at":"2019-11-20T17:02:14.296Z","state":"active","iid":2,"start_date":null,"group_id":4351}
{"id":7638,"title":"v0.0","project_id":null,"description":"Ea quia asperiores ut modi dolorem sunt non numquam.","due_date":null,"created_at":"2019-11-20T17:02:14.282Z","updated_at":"2019-11-20T17:02:14.282Z","state":"active","iid":1,"start_date":null,"group_id":4351}

View File

@ -11,13 +11,12 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
before(:all) do
stub_feature_flags(combined_menu: true)
stub_feature_flags(sidebar_refactor: true)
clean_frontend_fixtures('startup_css/')
end
shared_examples 'startup css project fixtures' do |type|
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :public, :repository, description: 'Code and stuff', creator: user) }
let(:project) { create(:project, :public, :repository, description: 'Code and stuff', avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png'), creator: user) }
before do
sign_in(user)
@ -43,17 +42,6 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
expect(response).to be_successful
end
it "startup_css/project-#{type}-legacy-sidebar.html" do
stub_feature_flags(sidebar_refactor: false)
get :show, params: {
namespace_id: project.namespace.to_param,
id: project
}
expect(response).to be_successful
end
it "startup_css/project-#{type}-signed-out.html" do
sign_out(user)

View File

@ -5,7 +5,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
class="awards js-awards-block"
>
<button
class="btn gl-mr-3 btn-default btn-md gl-button"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada, Leonardo, and Marie"
type="button"
@ -35,7 +35,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button selected"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, and Marie"
type="button"
@ -65,7 +65,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada and Jane"
type="button"
@ -95,7 +95,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button selected"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, Jane, and Leonardo"
type="button"
@ -125,7 +125,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button selected"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
@ -155,7 +155,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Marie"
type="button"
@ -185,7 +185,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</span>
</button>
<button
class="btn gl-mr-3 btn-default btn-md gl-button selected"
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
type="button"
@ -216,7 +216,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<div
class="award-menu-holder"
class="award-menu-holder gl-my-2"
>
<button
aria-label="Add reaction"

View File

@ -41,7 +41,14 @@ const TEST_AWARDS = [
];
const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class';
const REACTION_CONTROL_CLASSES = ['btn', 'gl-mr-3', 'btn-default', 'btn-md', 'gl-button'];
const REACTION_CONTROL_CLASSES = [
'btn',
'gl-mr-3',
'gl-my-2',
'btn-default',
'btn-md',
'gl-button',
];
describe('vue_shared/components/awards_list', () => {
let wrapper;

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Ci::Runner::Delete do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:runner) { create(:ci_runner) }
let(:current_ctx) { { current_user: user } }
let(:mutation_params) do
{
id: runner.to_global_id
}
end
specify { expect(described_class).to require_graphql_authorizations(:delete_runner) }
describe '#resolve' do
subject do
sync(resolve(described_class, args: mutation_params, ctx: current_ctx))
end
context 'when the user cannot admin the runner' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'with invalid params' do
it 'raises an error' do
mutation_params[:id] = "invalid-id"
expect { subject }.to raise_error(::GraphQL::CoercionError)
end
end
context 'when required arguments are missing' do
let(:mutation_params) { {} }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, "missing keyword: :id")
end
end
context 'when user can delete owned runner' do
let_it_be(:project) { create(:project, creator_id: user.id) }
let_it_be(:project_runner, reload: true) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) }
before_all do
project.add_maintainer(user)
end
context 'with one associated project' do
it 'deletes runner' do
mutation_params[:id] = project_runner.to_global_id
expect { subject }.to change { Ci::Runner.count }.by(-1)
expect(subject[:errors]).to be_empty
end
end
context 'with more than one associated project' do
let_it_be(:project2) { create(:project, creator_id: user.id) }
let_it_be(:two_projects_runner) { create(:ci_runner, :project, description: 'Two projects runner', projects: [project, project2]) }
before_all do
project2.add_maintainer(user)
end
it 'does not delete project runner' do
mutation_params[:id] = two_projects_runner.to_global_id
expect { subject }.not_to change { Ci::Runner.count }
expect(subject[:errors]).to contain_exactly("Runner #{two_projects_runner.to_global_id} associated with more than one project")
end
end
end
context 'when admin can delete runner', :enable_admin_mode do
let(:admin_user) { create(:user, :admin) }
let(:current_ctx) { { current_user: admin_user } }
it 'deletes runner' do
expect { subject }.to change { Ci::Runner.count }.by(-1)
expect(subject[:errors]).to be_empty
end
end
end
end

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe BulkImports::Common::Extractors::NdjsonExtractor do
let_it_be(:tmpdir) { Dir.mktmpdir }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/labels.ndjson.gz' }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/gz/labels.ndjson.gz' }
let_it_be(:import) { create(:bulk_import) }
let_it_be(:config) { create(:bulk_import_configuration, bulk_import: import) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: import) }

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::BoardsPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/gz/boards.ndjson.gz' }
let_it_be(:entity) do
create(
:bulk_import_entity,
group: group,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'My Destination Group',
destination_namespace: group.full_path
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:tmpdir) { Dir.mktmpdir }
before do
FileUtils.copy_file(filepath, File.join(tmpdir, 'boards.ndjson.gz'))
group.add_owner(user)
end
subject { described_class.new(context) }
describe '#run' do
it 'imports group boards into destination group and removes tmpdir' do
allow(Dir).to receive(:mktmpdir).and_return(tmpdir)
allow_next_instance_of(BulkImports::FileDownloadService) do |service|
allow(service).to receive(:execute)
end
expect { subject.run }.to change(Board, :count).by(1)
lists = group.boards.find_by(name: 'first board').lists
expect(lists.count).to eq(3)
expect(lists.first.label.title).to eq('TSL')
expect(lists.second.label.title).to eq('Sosync')
end
end
end

View File

@ -6,7 +6,7 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/labels.ndjson.gz' }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/gz/labels.ndjson.gz' }
let_it_be(:entity) do
create(
:bulk_import_entity,
@ -75,17 +75,4 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
end
end
end
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::NdjsonPipeline) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
it 'has extractor' do
expect(described_class.get_extractor)
.to eq(
klass: BulkImports::Common::Extractors::NdjsonExtractor,
options: { relation: described_class::RELATION }
)
end
end
end

View File

@ -5,119 +5,69 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:timestamp) { Time.new(2020, 01, 01).utc }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:filepath) { 'spec/fixtures/bulk_imports/gz/milestones.ndjson.gz' }
let_it_be(:entity) do
create(
:bulk_import_entity,
group: group,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'My Destination Group',
destination_namespace: group.full_path,
group: group
destination_namespace: group.full_path
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
let(:tmpdir) { Dir.mktmpdir }
before do
FileUtils.copy_file(filepath, File.join(tmpdir, 'milestones.ndjson.gz'))
group.add_owner(user)
end
describe '#run' do
it 'imports group milestones' do
first_page = extracted_data(title: 'milestone1', iid: 1, has_next_page: true)
last_page = extracted_data(title: 'milestone2', iid: 2)
subject { described_class.new(context) }
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor)
.to receive(:extract)
.and_return(first_page, last_page)
describe '#run' do
it 'imports group milestones into destination group and removes tmpdir' do
allow(Dir).to receive(:mktmpdir).and_return(tmpdir)
allow_next_instance_of(BulkImports::FileDownloadService) do |service|
allow(service).to receive(:execute)
end
expect { subject.run }.to change(Milestone, :count).by(2)
expect(group.milestones.pluck(:title)).to contain_exactly('milestone1', 'milestone2')
milestone = group.milestones.last
expect(milestone.description).to eq('desc')
expect(milestone.state).to eq('closed')
expect(milestone.start_date.to_s).to eq('2020-10-21')
expect(milestone.due_date.to_s).to eq('2020-10-22')
expect(milestone.created_at).to eq(timestamp)
expect(milestone.updated_at).to eq(timestamp)
expect { subject.run }.to change(Milestone, :count).by(5)
expect(group.milestones.pluck(:title)).to contain_exactly('v4.0', 'v3.0', 'v2.0', 'v1.0', 'v0.0')
expect(File.directory?(tmpdir)).to eq(false)
end
end
describe '#load' do
it 'creates the milestone' do
data = milestone_data('milestone')
context 'when milestone is not persisted' do
it 'saves the milestone' do
milestone = build(:milestone, group: group)
expect { subject.load(context, data) }.to change(Milestone, :count).by(1)
end
expect(milestone).to receive(:save!)
context 'when user is not authorized to create the milestone' do
before do
allow(user).to receive(:can?).with(:admin_milestone, group).and_return(false)
end
it 'raises NotAllowedError' do
data = extracted_data(title: 'milestone')
expect { subject.load(context, data) }.to raise_error(::BulkImports::Pipeline::NotAllowedError)
subject.load(context, milestone)
end
end
end
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::Pipeline) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
context 'when milestone is persisted' do
it 'does not save milestone' do
milestone = create(:milestone, group: group)
it 'has extractors' do
expect(described_class.get_extractor)
.to eq(
klass: BulkImports::Common::Extractors::GraphqlExtractor,
options: {
query: BulkImports::Groups::Graphql::GetMilestonesQuery
}
)
expect(milestone).not_to receive(:save!)
subject.load(context, milestone)
end
end
it 'has transformers' do
expect(described_class.transformers)
.to contain_exactly(
{ klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil }
)
context 'when milestone is missing' do
it 'returns' do
expect(subject.load(context, nil)).to be_nil
end
end
end
def milestone_data(title, iid: 1)
{
'title' => title,
'description' => 'desc',
'iid' => iid,
'state' => 'closed',
'start_date' => '2020-10-21',
'due_date' => '2020-10-22',
'created_at' => timestamp.to_s,
'updated_at' => timestamp.to_s
}
end
def extracted_data(title:, iid: 1, has_next_page: false)
page_info = {
'has_next_page' => has_next_page,
'next_page' => has_next_page ? 'cursor' : nil
}
BulkImports::Pipeline::ExtractedData.new(
data: milestone_data(title, iid: iid),
page_info: page_info
)
end
end

View File

@ -5,22 +5,31 @@ require 'spec_helper'
RSpec.describe BulkImports::NdjsonPipeline do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project) }
let_it_be(:klass) do
let_it_be(:user) { create(:user) }
let(:klass) do
Class.new do
include BulkImports::NdjsonPipeline
attr_reader :portable
relation_name 'test'
def initialize(portable)
attr_reader :portable, :current_user
def initialize(portable, user)
@portable = portable
@current_user = user
end
end
end
subject { klass.new(group) }
before do
stub_const('NdjsonPipelineClass', klass)
end
subject { NdjsonPipelineClass.new(group, user) }
it 'marks pipeline as ndjson' do
expect(klass.ndjson_pipeline?).to eq(true)
expect(NdjsonPipelineClass.ndjson_pipeline?).to eq(true)
end
describe '#deep_transform_relation!' do
@ -91,6 +100,60 @@ RSpec.describe BulkImports::NdjsonPipeline do
end
end
describe '#transform' do
it 'calls relation factory' do
hash = { key: :value }
data = [hash, 1]
user = double
config = double(relation_excluded_keys: nil, top_relation_tree: [])
context = double(portable: group, current_user: user, import_export_config: config)
allow(subject).to receive(:import_export_config).and_return(config)
expect(Gitlab::ImportExport::Group::RelationFactory)
.to receive(:create)
.with(
relation_index: 1,
relation_sym: :test,
relation_hash: hash,
importable: group,
members_mapper: instance_of(Gitlab::ImportExport::MembersMapper),
object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
user: user,
excluded_keys: nil
)
subject.transform(context, data)
end
end
describe '#load' do
context 'when object is not persisted' do
it 'saves the object' do
object = double(persisted?: false)
expect(object).to receive(:save!)
subject.load(nil, object)
end
end
context 'when object is persisted' do
it 'does not save the object' do
object = double(persisted?: true)
expect(object).not_to receive(:save!)
subject.load(nil, object)
end
end
context 'when object is missing' do
it 'returns' do
expect(subject.load(nil, nil)).to be_nil
end
end
end
describe '#relation_class' do
context 'when relation name is pluralized' do
it 'returns constantized class' do
@ -113,7 +176,7 @@ RSpec.describe BulkImports::NdjsonPipeline do
end
context 'when portable is project' do
subject { klass.new(project) }
subject { NdjsonPipelineClass.new(project, user) }
it 'returns group relation name override' do
expect(subject.relation_key_override('labels')).to eq('project_labels')

View File

@ -10,7 +10,8 @@ RSpec.describe BulkImports::Stage do
[1, BulkImports::Groups::Pipelines::MembersPipeline],
[1, BulkImports::Groups::Pipelines::LabelsPipeline],
[1, BulkImports::Groups::Pipelines::MilestonesPipeline],
[1, BulkImports::Groups::Pipelines::BadgesPipeline]
[1, BulkImports::Groups::Pipelines::BadgesPipeline],
[2, BulkImports::Groups::Pipelines::BoardsPipeline]
]
end

View File

@ -683,6 +683,28 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
end
describe '#build_access_token_check' do
subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: '1.2.3.4') }
let_it_be(:user) { create(:user) }
context 'for running build' do
let!(:build) { create(:ci_build, :running, user: user) }
it 'executes query using primary database' do
expect(Ci::Build).to receive(:find_by_token).with(build.token).and_wrap_original do |m, *args|
expect(::Gitlab::Database::LoadBalancing::Session.current.use_primary?).to eq(true)
m.call(*args)
end
expect(subject).to be_a(Gitlab::Auth::Result)
expect(subject.actor).to eq(user)
expect(subject.project).to eq(build.project)
expect(subject.type).to eq(:build)
end
end
end
describe 'find_with_user_password' do
let!(:user) do
create(:user,

View File

@ -444,6 +444,30 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do
end
end
context 'when attachment is specified in test case with error' do
let(:junit) do
<<~EOF
<testsuites>
<testsuite>
<testcase classname='Calculator' name='sumTest1' time='0.01'>
<error>Some error</error>
<system-out>[[ATTACHMENT|some/path.png]]</system-out>
</testcase>
</testsuite>
</testsuites>
EOF
end
it 'assigns correct attributes to the test case' do
expect { subject }.not_to raise_error
expect(test_cases[0].has_attachment?).to be_truthy
expect(test_cases[0].attachment).to eq("some/path.png")
expect(test_cases[0].job).to eq(job)
end
end
private
def flattened_test_cases(test_suite)

View File

@ -57,7 +57,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
it 'decompresses specified file' do
tmpdir = Dir.mktmpdir
filename = 'labels.ndjson.gz'
gz_filepath = "spec/fixtures/bulk_imports/#{filename}"
gz_filepath = "spec/fixtures/bulk_imports/gz/#{filename}"
FileUtils.copy_file(gz_filepath, File.join(tmpdir, filename))
subject.gunzip(dir: tmpdir, filename: filename)

View File

@ -13,7 +13,7 @@ RSpec.describe BulkImports::ExportUpload do
method = 'export_file'
filename = 'labels.ndjson.gz'
subject.public_send("#{method}=", fixture_file_upload("spec/fixtures/bulk_imports/#{filename}"))
subject.public_send("#{method}=", fixture_file_upload("spec/fixtures/bulk_imports/gz/#{filename}"))
subject.save!
url = "/uploads/-/system/bulk_imports/export_upload/export_file/#{subject.id}/#{filename}"

View File

@ -215,7 +215,7 @@ RSpec.describe API::GroupExport do
context 'when export file exists' do
it 'downloads exported group archive' do
upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/labels.ndjson.gz'))
upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/gz/labels.ndjson.gz'))
get api(download_path, user)

View File

@ -7,7 +7,7 @@ RSpec.describe BulkImports::FileDecompressionService do
let_it_be(:ndjson_filename) { 'labels.ndjson' }
let_it_be(:ndjson_filepath) { File.join(tmpdir, ndjson_filename) }
let_it_be(:gz_filename) { "#{ndjson_filename}.gz" }
let_it_be(:gz_filepath) { "spec/fixtures/bulk_imports/#{gz_filename}" }
let_it_be(:gz_filepath) { "spec/fixtures/bulk_imports/gz/#{gz_filename}" }
before do
FileUtils.copy_file(gz_filepath, File.join(tmpdir, gz_filename))

View File

@ -62,7 +62,7 @@ RSpec.describe BulkImports::RelationExportService do
let(:upload) { create(:bulk_import_export_upload, export: export) }
it 'removes existing export before exporting' do
upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/labels.ndjson.gz'))
upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/gz/labels.ndjson.gz'))
expect_any_instance_of(BulkImports::ExportUpload) do |upload|
expect(upload).to receive(:remove_export_file!)

View File

@ -136,7 +136,7 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
bridge_id: bridge.id, project_id: bridge.project.id)
.and_call_original
expect(Ci::CreatePipelineService).not_to receive(:new)
expect(service.execute(bridge)).to be_nil
expect(service.execute(bridge)).to eq({ message: "Already has a downstream pipeline", status: :error })
end
end
@ -393,6 +393,51 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
end
end
end
context 'when multi-project pipeline runs from child pipelines bridge job' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' }))
end
# instantiate new service, to clear memoized values from child pipeline run
subject(:execute_with_trigger_project_bridge) do
described_class.new(upstream_project, user).execute(trigger_project_bridge)
end
let!(:child_pipeline) do
service.execute(bridge)
bridge.downstream_pipeline
end
let!(:trigger_downstream_project) do
{
trigger: {
project: downstream_project.full_path,
branch: 'feature'
}
}
end
let!(:trigger_project_bridge) do
create(
:ci_bridge, status: :pending,
user: user,
options: trigger_downstream_project,
pipeline: child_pipeline
)
end
it 'creates a new pipeline' do
expect { execute_with_trigger_project_bridge }
.to change { Ci::Pipeline.count }.by(1)
new_pipeline = trigger_project_bridge.downstream_pipeline
expect(new_pipeline.child?).to eq(false)
expect(new_pipeline.triggered_by_pipeline).to eq child_pipeline
expect(trigger_project_bridge.reload).not_to be_failed
end
end
end
end

View File

@ -1338,4 +1338,22 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'projects_side_navigation', 'projects_side_navigation'
describe 'Collapsed menu items' do
it 'does not render the collapsed top menu as a link' do
render
expect(rendered).not_to have_selector('.sidebar-sub-level-items > li.fly-out-top-item > a')
end
context 'when feature flag :sidebar_refactor is disabled' do
it 'renders the collapsed top menu as a link' do
stub_feature_flags(sidebar_refactor: false)
render
expect(rendered).to have_selector('.sidebar-sub-level-items > li.fly-out-top-item > a')
end
end
end
end

View File

@ -140,6 +140,10 @@ RSpec.describe BulkImports::PipelineWorker do
def self.ndjson_pipeline?
true
end
def self.relation
'test'
end
end
end
@ -153,7 +157,6 @@ RSpec.describe BulkImports::PipelineWorker do
before do
stub_const('NdjsonPipeline', ndjson_pipeline)
stub_const('NdjsonPipeline::RELATION', 'test')
allow(BulkImports::Stage)
.to receive(:pipeline_exists?)
.with('NdjsonPipeline')