Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1c2ff01b69
commit
e84a2fdfc8
|
@ -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"
|
||||
|
|
|
@ -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 #
|
||||
################
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -85,7 +85,7 @@ export default {
|
|||
:boundary="getBoundaryElement()"
|
||||
:class="dropdownClass"
|
||||
menu-class="dropdown-extended-height"
|
||||
category="tertiary"
|
||||
category="secondary"
|
||||
no-flip
|
||||
right
|
||||
lazy
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -67,10 +67,6 @@
|
|||
|
||||
.emoji-block {
|
||||
padding: $gl-padding-4 0;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
padding: $gl-padding-8 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -106,10 +106,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.omniauth-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.new-session-tabs {
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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?
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
ae91ea7481ea21ce29b4c0697f77fd83017c36d913739ed67e5c907a48c56f69
|
|
@ -0,0 +1 @@
|
|||
e72471e63dc108939473232437eda4c718382630c1173ae20023002d382e5ffa
|
|
@ -0,0 +1 @@
|
|||
3c53d85bec154ec68a23841d37317d10fa6c7c846bc5f54f5b7876081105ac7b
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -295,7 +295,6 @@ keytab
|
|||
keytabs
|
||||
Kibana
|
||||
Kinesis
|
||||
Klar
|
||||
Knative
|
||||
Kramdown
|
||||
Kroki
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:)
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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$/],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
`;
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
@ -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"}
|
|
@ -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}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue