summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js2
-rw-r--r--app/assets/javascripts/import_projects/components/import_projects_table.vue37
-rw-r--r--app/assets/javascripts/import_projects/store/getters.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue2
-rw-r--r--app/graphql/mutations/issues/set_severity.rb25
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/models/ci/build.rb14
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/todo.rb6
-rw-r--r--app/services/issuable_base_service.rb27
-rw-r--r--app/services/merge_requests/update_service.rb9
-rw-r--r--app/services/todo_service.rb17
-rw-r--r--app/views/admin/appearances/_form.html.haml8
-rw-r--r--app/views/admin/appearances/_system_header_footer_form.html.haml2
-rw-r--r--app/views/admin/appearances/preview_sign_in.html.haml2
-rw-r--r--app/views/projects/pipelines/_pipeline_warnings.html.haml9
-rw-r--r--app/views/projects/pipelines/show.html.haml1
-rw-r--r--app/views/shared/blob/_markdown_buttons.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/form/_type_selector.html.haml11
-rw-r--r--changelogs/unreleased/230857-incident-type-selector-help.yml4
-rw-r--r--changelogs/unreleased/238564-graphql-mutation-to-update-incident-severity.yml5
-rw-r--r--changelogs/unreleased/app-logger-18.yml5
-rw-r--r--changelogs/unreleased/app-logger-2.yml5
-rw-r--r--changelogs/unreleased/app-logger-5.yml5
-rw-r--r--changelogs/unreleased/ph-225922-fixedImageDiscussionsNotShowingOnChangesTab.yml5
-rw-r--r--changelogs/unreleased/remove-pipeline-warnings-from-pipeline-view.yml5
-rw-r--r--changelogs/unreleased/vij-snippets-missing-param-requirement.yml5
-rw-r--r--changelogs/unreleased/xanf-import-projects-count.yml5
-rw-r--r--config/initializers/7_prometheus_metrics.rb2
-rw-r--r--config/initializers/active_record_lifecycle.rb4
-rw-r--r--config/initializers/deprecations.rb2
-rw-r--r--config/initializers/forbid_sidekiq_in_transactions.rb2
-rw-r--r--config/initializers/sidekiq.rb2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql96
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json289
-rw-r--r--doc/api/graphql/reference/index.md13
-rw-r--r--doc/ci/troubleshooting.md1
-rw-r--r--doc/development/import_project.md7
-rw-r--r--doc/operations/incident_management/img/new_incident_create_v13_4.pngbin0 -> 12106 bytes
-rw-r--r--doc/operations/incident_management/incidents.md18
-rw-r--r--doc/user/project/index.md6
-rw-r--r--lib/api/composer_packages.rb2
-rw-r--r--lib/api/conan_packages.rb2
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb4
-rw-r--r--lib/api/helpers/packages_helpers.rb4
-rw-r--r--lib/api/helpers/snippets_helpers.rb4
-rw-r--r--lib/api/maven_packages.rb8
-rw-r--r--lib/api/npm_packages.rb4
-rw-r--r--lib/api/nuget_packages.rb6
-rw-r--r--lib/api/project_snippets.rb3
-rw-r--r--lib/api/pypi_packages.rb6
-rw-r--r--lib/api/snippets.rb3
-rw-r--r--lib/gitlab/auth/ldap/person.rb2
-rw-r--r--lib/gitlab/ci/trace/metrics.rb30
-rw-r--r--lib/gitlab/reference_counter.rb6
-rw-r--r--locale/gitlab.pot31
-rw-r--r--package.json2
-rw-r--r--spec/features/import/manifest_import_spec.rb2
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb2
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb10
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js30
-rw-r--r--spec/frontend/import_projects/components/import_projects_table_spec.js42
-rw-r--r--spec/frontend/import_projects/store/getters_spec.js14
-rw-r--r--spec/graphql/mutations/issues/set_severity_spec.rb62
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb35
-rw-r--r--spec/models/ci/job_artifact_spec.rb4
-rw-r--r--spec/models/ci/pipeline_artifact_spec.rb4
-rw-r--r--spec/models/concerns/issuable_spec.rb54
-rw-r--r--spec/models/packages/package_file_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_severity_spec.rb57
-rw-r--r--spec/requests/api/project_snippets_spec.rb8
-rw-r--r--spec/requests/api/snippets_spec.rb1
-rw-r--r--spec/serializers/test_case_entity_spec.rb10
-rw-r--r--spec/services/issues/update_service_spec.rb21
-rw-r--r--spec/services/merge_requests/create_service_spec.rb74
-rw-r--r--spec/services/merge_requests/update_service_spec.rb26
-rw-r--r--spec/services/todo_service_spec.rb45
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb19
-rw-r--r--spec/views/projects/pipelines/show.html.haml_spec.rb26
-rw-r--r--yarn.lock8
83 files changed, 1170 insertions, 186 deletions
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 268326f9246..e016cdeed30 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -248,7 +248,7 @@ export default {
});
}
- if (!file.parallel_diff_lines || !file.highlighted_diff_lines) {
+ if (!file.parallel_diff_lines.length || !file.highlighted_diff_lines.length) {
const newDiscussions = (file.discussions || [])
.filter(d => d.id !== discussion.id)
.concat(discussion);
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue
index 5beb8d2edc5..407fbbb0871 100644
--- a/app/assets/javascripts/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue
@@ -1,8 +1,8 @@
<script>
import { throttle } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
-import { GlButton, GlLoadingIcon } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
+import { GlButton, GlLoadingIcon, GlModal } from '@gitlab/ui';
+import { n__, __, sprintf } from '~/locale';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import ProviderRepoTableRow from './provider_repo_table_row.vue';
import PageQueryParamSync from './page_query_param_sync.vue';
@@ -16,6 +16,7 @@ export default {
PageQueryParamSync,
GlLoadingIcon,
GlButton,
+ GlModal,
PaginationLinks,
},
props: {
@@ -42,6 +43,7 @@ export default {
'isImportingAnyRepo',
'hasImportableRepos',
'hasIncompatibleRepos',
+ 'importAllCount',
]),
availableNamespaces() {
@@ -61,8 +63,12 @@ export default {
importAllButtonText() {
return this.hasIncompatibleRepos
- ? __('Import all compatible repositories')
- : __('Import all repositories');
+ ? n__(
+ 'Import %d compatible repository',
+ 'Import %d compatible repositories',
+ this.importAllCount,
+ )
+ : n__('Import %d repository', 'Import %d repositories', this.importAllCount);
},
emptyStateText() {
@@ -111,9 +117,8 @@ export default {
<template>
<div>
<page-query-param-sync :page="pageInfo.page" @popstate="setPage" />
-
<p class="light text-nowrap mt-2">
- {{ s__('ImportProjects|Select the projects you want to import') }}
+ {{ s__('ImportProjects|Select the repositories you want to import') }}
</p>
<template v-if="hasIncompatibleRepos">
<slot name="incompatible-repos-warning"></slot>
@@ -130,9 +135,25 @@ export default {
:loading="isImportingAnyRepo"
:disabled="!hasImportableRepos"
type="button"
- @click="importAll"
+ @click="$refs.importAllModal.show()"
>{{ importAllButtonText }}</gl-button
>
+ <gl-modal
+ ref="importAllModal"
+ modal-id="import-all-modal"
+ :title="s__('ImportProjects|Import repositories')"
+ :ok-title="__('Import')"
+ @ok="importAll"
+ >
+ {{
+ n__(
+ 'Are you sure you want to import %d repository?',
+ 'Are you sure you want to import %d repositories?',
+ importAllCount,
+ )
+ }}
+ </gl-modal>
+
<slot name="actions"></slot>
<form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
<input
@@ -140,7 +161,7 @@ export default {
data-qa-selector="githubish_import_filter_field"
class="form-control"
name="filter"
- :placeholder="__('Filter your projects by name')"
+ :placeholder="__('Filter your repositories by name')"
autofocus
size="40"
@input="handleFilterInput($event)"
diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js
index fded478bae2..b76c52beea2 100644
--- a/app/assets/javascripts/import_projects/store/getters.js
+++ b/app/assets/javascripts/import_projects/store/getters.js
@@ -14,6 +14,8 @@ export const hasIncompatibleRepos = state => state.repositories.some(isIncompati
export const hasImportableRepos = state => state.repositories.some(isProjectImportable);
+export const importAllCount = state => state.repositories.filter(isProjectImportable).length;
+
export const getImportTarget = state => repoId => {
if (state.customImportTargets[repoId]) {
return state.customImportTargets[repoId];
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index fe975112b60..f79c66514d2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -129,7 +129,7 @@ export default {
<li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
<div class="d-inline-block">
<toolbar-button tag="**" :button-title="__('Add bold text')" icon="bold" />
- <toolbar-button tag="*" :button-title="__('Add italic text')" icon="italic" />
+ <toolbar-button tag="_" :button-title="__('Add italic text')" icon="italic" />
<toolbar-button
:prepend="true"
:tag="tag"
diff --git a/app/graphql/mutations/issues/set_severity.rb b/app/graphql/mutations/issues/set_severity.rb
new file mode 100644
index 00000000000..bc386e07178
--- /dev/null
+++ b/app/graphql/mutations/issues/set_severity.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class SetSeverity < Base
+ graphql_name 'IssueSetSeverity'
+
+ argument :severity, Types::IssuableSeverityEnum, required: true,
+ description: 'Set the incident severity level.'
+
+ def resolve(project_path:, iid:, severity:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ project = issue.project
+
+ ::Issues::UpdateService.new(project, current_user, severity: severity)
+ .execute(issue)
+
+ {
+ issue: issue,
+ errors: errors_on_object(issue)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index aea79f4f423..d970bef8959 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -24,6 +24,7 @@ module Types
mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetLocked
mount_mutation Mutations::Issues::SetDueDate
+ mount_mutation Mutations::Issues::SetSeverity
mount_mutation Mutations::Issues::SetSubscription
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::MergeRequests::Create
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 97b1dba1fc6..eccd1ea5490 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -871,13 +871,17 @@ module Ci
options.dig(:release)&.any?
end
- def hide_secrets(trace)
+ def hide_secrets(data, metrics = ::Gitlab::Ci::Trace::Metrics.new)
return unless trace
- trace = trace.dup
- Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project
- Gitlab::Ci::MaskSecret.mask!(trace, token) if token
- trace
+ data.dup.tap do |trace|
+ Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project
+ Gitlab::Ci::MaskSecret.mask!(trace, token) if token
+
+ if trace != data
+ metrics.increment_trace_operation(operation: :mutated)
+ end
+ end
end
def serializable_hash(options = {})
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 64bb22e886e..a4090a1d61d 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -195,6 +195,15 @@ module Issuable
issuable_severity&.severity || IssuableSeverity::DEFAULT
end
+ def update_severity(severity)
+ return unless incident?
+
+ severity = severity.to_s.downcase
+ severity = IssuableSeverity::DEFAULT unless IssuableSeverity.severities.key?(severity)
+
+ (issuable_severity || build_issuable_severity(issue_id: id)).update(severity: severity)
+ end
+
private
def description_max_length_for_new_records_is_valid
diff --git a/app/models/todo.rb b/app/models/todo.rb
index f973c1ff1d4..6c8e085762d 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -17,9 +17,11 @@ class Todo < ApplicationRecord
UNMERGEABLE = 6
DIRECTLY_ADDRESSED = 7
MERGE_TRAIN_REMOVED = 8 # This is an EE-only feature
+ REVIEW_REQUESTED = 9
ACTION_NAMES = {
ASSIGNED => :assigned,
+ REVIEW_REQUESTED => :review_requested,
MENTIONED => :mentioned,
BUILD_FAILED => :build_failed,
MARKED => :marked,
@@ -167,6 +169,10 @@ class Todo < ApplicationRecord
action == ASSIGNED
end
+ def review_requested?
+ action == REVIEW_REQUESTED
+ end
+
def merge_train_removed?
action == MERGE_TRAIN_REMOVED
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index dea77f11baa..56bcef0c562 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -184,10 +184,7 @@ class IssuableBaseService < BaseService
handle_quick_actions(issuable)
filter_params(issuable)
- change_state(issuable)
- change_subscription(issuable)
- change_todo(issuable)
- toggle_award(issuable)
+ change_additional_attributes(issuable)
old_associations = associations_before_update(issuable)
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
@@ -305,6 +302,14 @@ class IssuableBaseService < BaseService
issuable.title_changed? || issuable.description_changed?
end
+ def change_additional_attributes(issuable)
+ change_state(issuable)
+ change_severity(issuable)
+ change_subscription(issuable)
+ change_todo(issuable)
+ toggle_award(issuable)
+ end
+
def change_state(issuable)
case params.delete(:state_event)
when 'reopen'
@@ -314,6 +319,12 @@ class IssuableBaseService < BaseService
end
end
+ def change_severity(issuable)
+ if severity = params.delete(:severity)
+ issuable.update_severity(severity)
+ end
+ end
+
def change_subscription(issuable)
case params.delete(:subscription_event)
when 'subscribe'
@@ -358,8 +369,8 @@ class IssuableBaseService < BaseService
associations
end
- def has_changes?(issuable, old_labels: [], old_assignees: [])
- valid_attrs = [:title, :description, :assignee_ids, :milestone_id, :target_branch]
+ def has_changes?(issuable, old_labels: [], old_assignees: [], old_reviewers: [])
+ valid_attrs = [:title, :description, :assignee_ids, :reviewer_ids, :milestone_id, :target_branch]
attrs_changed = valid_attrs.any? do |attr|
issuable.previous_changes.include?(attr.to_s)
@@ -369,7 +380,9 @@ class IssuableBaseService < BaseService
assignees_changed = issuable.assignees != old_assignees
- attrs_changed || labels_changed || assignees_changed
+ reviewers_changed = issuable.reviewers != old_reviewers if issuable.allows_reviewers?
+
+ attrs_changed || labels_changed || assignees_changed || reviewers_changed
end
def invalidate_cache_counts(issuable, users: [])
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index cf02158b629..1468bfd6bb6 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -24,8 +24,9 @@ module MergeRequests
old_labels = old_associations.fetch(:labels, [])
old_mentioned_users = old_associations.fetch(:mentioned_users, [])
old_assignees = old_associations.fetch(:assignees, [])
+ old_reviewers = old_associations.fetch(:reviewers, [])
- if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees)
+ if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees, old_reviewers: old_reviewers)
todo_service.resolve_todos_for_target(merge_request, current_user)
end
@@ -44,6 +45,8 @@ module MergeRequests
handle_assignees_change(merge_request, old_assignees) if merge_request.assignees != old_assignees
+ handle_reviewers_change(merge_request, old_reviewers) if merge_request.reviewers != old_reviewers
+
if merge_request.previous_changes.include?('target_branch') ||
merge_request.previous_changes.include?('source_branch')
merge_request.mark_as_unchecked
@@ -108,6 +111,10 @@ module MergeRequests
todo_service.reassigned_assignable(merge_request, current_user, old_assignees)
end
+ def handle_reviewers_change(merge_request, old_reviewers)
+ todo_service.reassigned_reviewable(merge_request, current_user, old_reviewers)
+ end
+
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
SystemNoteService.change_branch(
issuable, issuable.project, current_user, branch_type,
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index a3db2ae7947..1220b4cbe29 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -57,6 +57,14 @@ class TodoService
create_assignment_todo(issuable, current_user, old_assignees)
end
+ # When we reassign an reviewable object (merge request) we should:
+ #
+ # * create a pending todo for new reviewer if object is assigned
+ #
+ def reassigned_reviewable(issuable, current_user, old_reviewers = [])
+ create_reviewer_todo(issuable, current_user, old_reviewers)
+ end
+
# When create a merge request we should:
#
# * creates a pending todo for assignee if merge request is assigned
@@ -217,6 +225,7 @@ class TodoService
def new_issuable(issuable, author)
create_assignment_todo(issuable, author)
+ create_reviewer_todo(issuable, author) if issuable.allows_reviewers?
create_mention_todos(issuable.project, issuable, author)
end
@@ -250,6 +259,14 @@ class TodoService
end
end
+ def create_reviewer_todo(target, author, old_reviewers = [])
+ if target.reviewers.any?
+ reviewers = target.reviewers - old_reviewers
+ attributes = attributes_for_todo(target.project, target, author, Todo::REVIEW_REQUESTED)
+ create_todos(reviewers, attributes)
+ end
+ end
+
def create_mention_todos(parent, target, author, note = nil, skip_users = [])
# Create Todos for directly addressed users
directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users)
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index fcb1c1a6f3e..ad3795445d1 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -16,7 +16,7 @@
= image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
- = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
+ = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: ""
@@ -35,7 +35,7 @@
= image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
- = link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
+ = link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: ''
@@ -67,7 +67,7 @@
= image_tag @appearance.logo_path, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
- = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
+ = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
%hr
= f.hidden_field :logo_cache
= f.file_field :logo, class: ""
@@ -101,7 +101,7 @@
= parsed_with_gfm
.gl-mt-3.gl-mb-3
- = f.submit 'Update appearance settings', class: 'btn btn-success'
+ = f.submit 'Update appearance settings', class: 'btn gl-button btn-success'
- if @appearance.persisted? || @appearance.updated_at
.mt-4
- if @appearance.persisted?
diff --git a/app/views/admin/appearances/_system_header_footer_form.html.haml b/app/views/admin/appearances/_system_header_footer_form.html.haml
index 7f53b2baa32..b50778a1076 100644
--- a/app/views/admin/appearances/_system_header_footer_form.html.haml
+++ b/app/views/admin/appearances/_system_header_footer_form.html.haml
@@ -23,7 +23,7 @@
= _('Add header and footer to emails. Please note that color settings will only be applied within the application interface')
.form-group.js-toggle-colors-container
- %button.btn.btn-link.js-toggle-colors-link{ type: 'button' }
+ %button.btn.gl-button.btn-link.js-toggle-colors-link{ type: 'button' }
= _('Customize colors')
.form-group.js-toggle-colors-container.hide
= form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold'
diff --git a/app/views/admin/appearances/preview_sign_in.html.haml b/app/views/admin/appearances/preview_sign_in.html.haml
index 2cd95071c73..eec4719c13c 100644
--- a/app/views/admin/appearances/preview_sign_in.html.haml
+++ b/app/views/admin/appearances/preview_sign_in.html.haml
@@ -8,5 +8,5 @@
= label_tag :password
= password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.'
.form-group
- = button_tag "Sign in", class: "btn-success btn"
+ = button_tag "Sign in", class: "btn gl-button btn-success"
diff --git a/app/views/projects/pipelines/_pipeline_warnings.html.haml b/app/views/projects/pipelines/_pipeline_warnings.html.haml
deleted file mode 100644
index 0da678401f5..00000000000
--- a/app/views/projects/pipelines/_pipeline_warnings.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- total_warnings = warnings.length
-- message = warning_header(total_warnings)
-
-- if warnings.any?
- .bs-callout.bs-callout-warning
- %details
- %summary.gl-mb-2= message
- - warnings.map(&:content).each do |warning|
- = markdown(warning)
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index c3f14fc82d2..a9c140aee5f 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -19,7 +19,6 @@
- lint_link_start = '<a href="%{url}">'.html_safe % { url: lint_link_url }
= s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
- = render "projects/pipelines/pipeline_warnings", warnings: @pipeline.warning_messages(limit: Gitlab::Ci::Warnings::MAX_LIMIT)
= render "projects/pipelines/with_tabs", pipeline: @pipeline, pipeline_has_errors: pipeline_has_errors
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/views/shared/blob/_markdown_buttons.html.haml b/app/views/shared/blob/_markdown_buttons.html.haml
index 32fd732cda9..d0719bf21fe 100644
--- a/app/views/shared/blob/_markdown_buttons.html.haml
+++ b/app/views/shared/blob/_markdown_buttons.html.haml
@@ -1,6 +1,6 @@
.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: _("Add bold text") })
- = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: _("Add italic text") })
+ = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "_" }, title: _("Add italic text") })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
= markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: _("Add a link") })
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index cce0e9fd2f7..dc985f96e3f 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -167,7 +167,7 @@
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
.sidebar-mr-source-branch.hide-collapsed
%span
- = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<cite class='ref-name' title='#{source_branch}'>".html_safe, source_branch_close: "</cite>".html_safe, source_branch: source_branch }
+ = _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<cite title='#{source_branch}'>".html_safe, source_branch_close: "</cite>".html_safe, source_branch: source_branch }
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
- if issuable_sidebar.dig(:current_user, :can_move)
diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml
index 1c3a720ebcc..668b4e90bef 100644
--- a/app/views/shared/issuable/form/_type_selector.html.haml
+++ b/app/views/shared/issuable/form/_type_selector.html.haml
@@ -1,10 +1,10 @@
.form-group.row.gl-mb-0
= form.label :type, 'Type', class: 'col-form-label col-sm-2'
.col-sm-10
- .issuable-form-select-holder.selectbox.form-group
+ .issuable-form-select-holder.selectbox.form-group.gl-mb-0
.dropdown.js-issuable-type-filter-dropdown-wrap
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.dropdown-label
+ %span.dropdown-toggle-text.is-default
= type.capitalize || _("Select type")
= icon('chevron-down')
.dropdown-menu.dropdown-menu-selectable.dropdown-select
@@ -18,5 +18,10 @@
= link_to new_project_issue_path(@project), class: ("is-active" if type === 'issue') do
= _("Issue")
%li.js-filter-issuable-type
- = link_to new_project_issue_path(@project, { 'issue[issue_type]': 'incident', issuable_template: 'incident' }), class: ("is-active" if type === 'incident') do
+ = link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if type === 'incident') do
= _("Incident")
+ - if type === 'incident'
+ %p.form-text.text-muted
+ - incident_docs_url = help_page_path('operations/incident_management/incidents.md', anchor: 'create-and-manage-incidents-in-gitlab')
+ - incident_docs_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: incident_docs_url }
+ = _('A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents.').html_safe % { incident_docs_start: incident_docs_start, incident_docs_end: '</a>'.html_safe }
diff --git a/changelogs/unreleased/230857-incident-type-selector-help.yml b/changelogs/unreleased/230857-incident-type-selector-help.yml
new file mode 100644
index 00000000000..34541958b2f
--- /dev/null
+++ b/changelogs/unreleased/230857-incident-type-selector-help.yml
@@ -0,0 +1,4 @@
+title: Add help text to incident type select on new issue form
+merge_request: 41567
+author:
+type: changed
diff --git a/changelogs/unreleased/238564-graphql-mutation-to-update-incident-severity.yml b/changelogs/unreleased/238564-graphql-mutation-to-update-incident-severity.yml
new file mode 100644
index 00000000000..71cbd317eb6
--- /dev/null
+++ b/changelogs/unreleased/238564-graphql-mutation-to-update-incident-severity.yml
@@ -0,0 +1,5 @@
+---
+title: Allows to update incident severity via GraphQL
+merge_request: 40869
+author:
+type: added
diff --git a/changelogs/unreleased/app-logger-18.yml b/changelogs/unreleased/app-logger-18.yml
new file mode 100644
index 00000000000..0c0f2288e93
--- /dev/null
+++ b/changelogs/unreleased/app-logger-18.yml
@@ -0,0 +1,5 @@
+---
+title: Use GitLab AppLogger
+merge_request: 41290
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/app-logger-2.yml b/changelogs/unreleased/app-logger-2.yml
new file mode 100644
index 00000000000..bdc69b2714c
--- /dev/null
+++ b/changelogs/unreleased/app-logger-2.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in config/initializers/*
+merge_request: 41047
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/app-logger-5.yml b/changelogs/unreleased/app-logger-5.yml
new file mode 100644
index 00000000000..f0ffd786352
--- /dev/null
+++ b/changelogs/unreleased/app-logger-5.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in some files of lib/gitlab/ldap/sync/*
+merge_request: 41051
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/ph-225922-fixedImageDiscussionsNotShowingOnChangesTab.yml b/changelogs/unreleased/ph-225922-fixedImageDiscussionsNotShowingOnChangesTab.yml
new file mode 100644
index 00000000000..fa55a99106f
--- /dev/null
+++ b/changelogs/unreleased/ph-225922-fixedImageDiscussionsNotShowingOnChangesTab.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed image comments not showing on the changes tab
+merge_request: 41683
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-pipeline-warnings-from-pipeline-view.yml b/changelogs/unreleased/remove-pipeline-warnings-from-pipeline-view.yml
new file mode 100644
index 00000000000..e1387518c57
--- /dev/null
+++ b/changelogs/unreleased/remove-pipeline-warnings-from-pipeline-view.yml
@@ -0,0 +1,5 @@
+---
+title: Remove pipeline warnings from pipeline view
+merge_request: 41419
+author:
+type: changed
diff --git a/changelogs/unreleased/vij-snippets-missing-param-requirement.yml b/changelogs/unreleased/vij-snippets-missing-param-requirement.yml
new file mode 100644
index 00000000000..85ae377b07b
--- /dev/null
+++ b/changelogs/unreleased/vij-snippets-missing-param-requirement.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to update only Snippet descriptions via REST endpoint
+merge_request: 41581
+author:
+type: changed
diff --git a/changelogs/unreleased/xanf-import-projects-count.yml b/changelogs/unreleased/xanf-import-projects-count.yml
new file mode 100644
index 00000000000..8e506a859b5
--- /dev/null
+++ b/changelogs/unreleased/xanf-import-projects-count.yml
@@ -0,0 +1,5 @@
+---
+title: Add confirmation dialog when importing multiple projects
+merge_request: 41306
+author:
+type: changed
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 082f5568391..d5d8587f1c8 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -16,7 +16,7 @@ def prometheus_default_multiproc_dir
end
Prometheus::Client.configure do |config|
- config.logger = Rails.logger # rubocop:disable Gitlab/RailsLogger
+ config.logger = Gitlab::AppLogger
config.initial_mmap_file_size = 4 * 1024
diff --git a/config/initializers/active_record_lifecycle.rb b/config/initializers/active_record_lifecycle.rb
index 493d328b93e..4d63ffaf711 100644
--- a/config/initializers/active_record_lifecycle.rb
+++ b/config/initializers/active_record_lifecycle.rb
@@ -7,7 +7,7 @@ if defined?(ActiveRecord::Base) && !Gitlab::Runtime.sidekiq?
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
- Rails.logger.debug("ActiveRecord connection established") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.debug("ActiveRecord connection established")
end
end
end
@@ -20,6 +20,6 @@ if defined?(ActiveRecord::Base)
# as there's no need for the master process to hold a connection
ActiveRecord::Base.connection.disconnect!
- Rails.logger.debug("ActiveRecord connection disconnected") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.debug("ActiveRecord connection disconnected")
end
end
diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb
index 0d096e34eb7..2b07ca665e2 100644
--- a/config/initializers/deprecations.rb
+++ b/config/initializers/deprecations.rb
@@ -2,7 +2,7 @@ if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE']
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
deprecator.behavior = -> (message, callstack) {
- Rails.logger.warn("#{message}: #{callstack[1..20].join}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn("#{message}: #{callstack[1..20].join}")
}
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb
index 9bade443aae..6bcd4dbd52f 100644
--- a/config/initializers/forbid_sidekiq_in_transactions.rb
+++ b/config/initializers/forbid_sidekiq_in_transactions.rb
@@ -28,7 +28,7 @@ module Sidekiq
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
MSG
rescue Sidekiq::Worker::EnqueueFromTransactionError => e
- ::Rails.logger.error(e.message) if ::Rails.env.production?
+ Gitlab::AppLogger.error(e.message) if ::Rails.env.production?
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index febcedfee82..cfb34fa9784 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -70,7 +70,7 @@ Sidekiq.configure_server do |config|
cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
else
cron_jobs.delete(k)
- Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
end
end
Sidekiq::Cron::Job.load_from_hash! cron_jobs
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index e23cb4b1b38..c54b04c0d42 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2193,12 +2193,12 @@ input ConfigureSastInput {
clientMutationId: String
"""
- Payload containing SAST variable values (https://docs.gitlab.com/ee/user/application_security/sast/#available-variables).
+ SAST CI configuration for the project
"""
- configuration: JSON!
+ configuration: SastCiConfigurationInput!
"""
- Full path of the project.
+ Full path of the project
"""
projectPath: ID!
}
@@ -2218,9 +2218,14 @@ type ConfigureSastPayload {
errors: [String!]!
"""
- JSON containing the status of MR creation.
+ Status of creating the commit for the supplied SAST CI configuration
+ """
+ status: String!
+
+ """
+ Redirect path to use when the response is successful
"""
- result: JSON
+ successPath: String
}
"""
@@ -8076,6 +8081,51 @@ type IssueSetLockedPayload {
}
"""
+Autogenerated input type of IssueSetSeverity
+"""
+input IssueSetSeverityInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The IID of the issue to mutate
+ """
+ iid: String!
+
+ """
+ The project the issue to mutate is in
+ """
+ projectPath: ID!
+
+ """
+ Set the incident severity level.
+ """
+ severity: IssuableSeverity!
+}
+
+"""
+Autogenerated return type of IssueSetSeverity
+"""
+type IssueSetSeverityPayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The issue after mutation
+ """
+ issue: Issue
+}
+
+"""
Autogenerated input type of IssueSetSubscription
"""
input IssueSetSubscriptionInput {
@@ -10279,6 +10329,7 @@ type Mutation {
issueSetEpic(input: IssueSetEpicInput!): IssueSetEpicPayload
issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload
issueSetLocked(input: IssueSetLockedInput!): IssueSetLockedPayload
+ issueSetSeverity(input: IssueSetSeverityInput!): IssueSetSeverityPayload
issueSetSubscription(input: IssueSetSubscriptionInput!): IssueSetSubscriptionPayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
@@ -14697,6 +14748,41 @@ type SastCiConfigurationEntityEdge {
}
"""
+Represents an entity in SAST CI configuration
+"""
+input SastCiConfigurationEntityInput {
+ """
+ Default value that is used if value is empty
+ """
+ defaultValue: String!
+
+ """
+ CI keyword of entity
+ """
+ field: String!
+
+ """
+ Current value of the entity
+ """
+ value: String!
+}
+
+"""
+Represents a CI configuration of SAST
+"""
+input SastCiConfigurationInput {
+ """
+ List of global entities related to SAST configuration
+ """
+ global: [SastCiConfigurationEntityInput!]
+
+ """
+ List of pipeline entities related to SAST configuration
+ """
+ pipeline: [SastCiConfigurationEntityInput!]
+}
+
+"""
Represents an entity for options in SAST CI configuration
"""
type SastCiConfigurationOptionsEntity {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 154085e6264..de62bf43923 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -5971,7 +5971,7 @@
"inputFields": [
{
"name": "projectPath",
- "description": "Full path of the project.",
+ "description": "Full path of the project",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -5985,13 +5985,13 @@
},
{
"name": "configuration",
- "description": "Payload containing SAST variable values (https://docs.gitlab.com/ee/user/application_security/sast/#available-variables).",
+ "description": "SAST CI configuration for the project",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
- "kind": "SCALAR",
- "name": "JSON",
+ "kind": "INPUT_OBJECT",
+ "name": "SastCiConfigurationInput",
"ofType": null
}
},
@@ -6058,14 +6058,32 @@
"deprecationReason": null
},
{
- "name": "result",
- "description": "JSON containing the status of MR creation.",
+ "name": "status",
+ "description": "Status of creating the commit for the supplied SAST CI configuration",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "successPath",
+ "description": "Redirect path to use when the response is successful",
"args": [
],
"type": {
"kind": "SCALAR",
- "name": "JSON",
+ "name": "String",
"ofType": null
},
"isDeprecated": false,
@@ -22369,6 +22387,136 @@
},
{
"kind": "INPUT_OBJECT",
+ "name": "IssueSetSeverityInput",
+ "description": "Autogenerated input type of IssueSetSeverity",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "The project the issue to mutate is in",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The IID of the issue to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "severity",
+ "description": "Set the incident severity level.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "IssuableSeverity",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "IssueSetSeverityPayload",
+ "description": "Autogenerated return type of IssueSetSeverity",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "issue",
+ "description": "The issue after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Issue",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
"name": "IssueSetSubscriptionInput",
"description": "Autogenerated input type of IssueSetSubscription",
"fields": null,
@@ -29905,6 +30053,33 @@
"deprecationReason": null
},
{
+ "name": "issueSetSeverity",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "IssueSetSeverityInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "IssueSetSeverityPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "issueSetSubscription",
"description": null,
"args": [
@@ -42873,6 +43048,106 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "SastCiConfigurationEntityInput",
+ "description": "Represents an entity in SAST CI configuration",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "field",
+ "description": "CI keyword of entity",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "defaultValue",
+ "description": "Default value that is used if value is empty",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "value",
+ "description": "Current value of the entity",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "SastCiConfigurationInput",
+ "description": "Represents a CI configuration of SAST",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "global",
+ "description": "List of global entities related to SAST configuration",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "SastCiConfigurationEntityInput",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "pipeline",
+ "description": "List of pipeline entities related to SAST configuration",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "SastCiConfigurationEntityInput",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "SastCiConfigurationOptionsEntity",
"description": "Represents an entity for options in SAST CI configuration",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3b139efd4ea..bc61283cc33 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -369,7 +369,8 @@ Autogenerated return type of ConfigureSast
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
-| `result` | JSON | JSON containing the status of MR creation. |
+| `status` | String! | Status of creating the commit for the supplied SAST CI configuration |
+| `successPath` | String | Redirect path to use when the response is successful |
## ContainerExpirationPolicy
@@ -1227,6 +1228,16 @@ Autogenerated return type of IssueSetLocked
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
+## IssueSetSeverityPayload
+
+Autogenerated return type of IssueSetSeverity
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `issue` | Issue | The issue after mutation |
+
## IssueSetSubscriptionPayload
Autogenerated return type of IssueSetSubscription
diff --git a/doc/ci/troubleshooting.md b/doc/ci/troubleshooting.md
index 96d94a6c165..4f4311570e0 100644
--- a/doc/ci/troubleshooting.md
+++ b/doc/ci/troubleshooting.md
@@ -11,7 +11,6 @@ type: reference
Pipeline configuration warnings are shown when you:
-- [View pipeline details](pipelines/index.md#view-pipelines).
- [Validate configuration with the CI Lint tool](yaml/README.md#validate-the-gitlab-ciyml).
- [Manually run a pipeline](pipelines/index.md#run-a-pipeline-manually).
diff --git a/doc/development/import_project.md b/doc/development/import_project.md
index 1fa6ea5d405..9e2f5af6738 100644
--- a/doc/development/import_project.md
+++ b/doc/development/import_project.md
@@ -96,6 +96,13 @@ If you want to import it to a new group or subgroup then create it first.
The specified project export file in `archive_path` is missing.
+##### `Exception: Permission denied @ rb_sysopen - (filename)`
+
+The specified project export file can not be accessed by the `git` user.
+
+Setting the file owner to `git:git`, changing the file permissions to `0400`, and moving it to a
+public folder (for example `/tmp/`) fixes the issue.
+
##### `Name can contain only letters, digits, emojis ...`
```plaintext
diff --git a/doc/operations/incident_management/img/new_incident_create_v13_4.png b/doc/operations/incident_management/img/new_incident_create_v13_4.png
new file mode 100644
index 00000000000..458532736bd
--- /dev/null
+++ b/doc/operations/incident_management/img/new_incident_create_v13_4.png
Binary files differ
diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md
index 528d29f9343..2a0910a1d9f 100644
--- a/doc/operations/incident_management/incidents.md
+++ b/doc/operations/incident_management/incidents.md
@@ -73,15 +73,27 @@ when you receive notification that the alert is resolved.
## Create an incident manually
-> [Moved](https://gitlab.com/gitlab-org/monitor/health/-/issues/24) to GitLab core in 13.3.
+If you have at least Developer [permissions](../../user/permissions.md), to create an Incident, you have two options.
+
+### From the Incidents List
-For users with at least Developer [permissions](../../user/permissions.md), to create a Incident you can take any of the following actions:
+> [Moved](https://gitlab.com/gitlab-org/monitor/health/-/issues/24) to GitLab core in 13.3.
- Navigate to **Operations > Incidents** and click **Create Incident**.
- Create a new issue using the `incident` template available when creating it.
- Create a new issue and assign the `incident` label to it.
-![Incident List Create](img/incident_list_create_v13_3.png)
+![Incident List Create](./img/incident_list_create_v13_3.png)
+
+### From the Issues List
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230857) in GitLab 13.4.
+
+- Navigate to **Issues > List** and click **Create Issue**.
+- Create a new issue using the `type` drop-down and select `Incident`.
+- The page will refresh and you will notice there are now only fields relevant to Incidents.
+
+![Incident List Create](./img/new_incident_create_v13_4.png)
## Configure PagerDuty integration
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index a49c5fe054a..da09b9d598e 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -192,12 +192,16 @@ To delete a project, first navigate to the home page for that project.
### Delayed deletion **(PREMIUM)**
-By default, clicking to delete a project is followed by a seven day delay. Admins can restore the project during this period of time.
+By default, projects in a personal namespace are deleted after a seven day delay.
+
+Admins can restore the project during this period of time.
This delay [may be changed by an admin](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
Admins can view all projects pending deletion. If you're an administrator, go to the top navigation bar, click **Projects > Your projects**, and then select the **Deleted projects** tab.
From this tab an admin can restore any project.
+For information on delay deletion of projects within a group, please see [Enabling delayed Project removal](../group/index.md#enabling-delayed-project-removal)
+
## CI/CD for external repositories **(PREMIUM)**
Instead of importing a repository directly to GitLab, you can connect your repository
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index 05887e58425..31d097c4bea 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -123,7 +123,7 @@ module API
bad_request!
end
- track_event('push_package')
+ package_event('push_package')
::Packages::Composer::CreatePackageService
.new(authorized_user_project, current_user, declared_params)
diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb
index cf91d8ac74a..899683de184 100644
--- a/lib/api/conan_packages.rb
+++ b/lib/api/conan_packages.rb
@@ -242,7 +242,7 @@ module API
delete do
authorize!(:destroy_package, project)
- track_event('delete_package')
+ package_event('delete_package')
package.destroy
end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index b849907bcde..5b513ce1aab 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -124,7 +124,7 @@ module API
conan_package_reference: params[:conan_package_reference]
).execute!
- track_event('pull_package') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
+ package_event('pull_package') if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY
present_carrierwave_file!(package_file.file)
end
@@ -135,7 +135,7 @@ module API
def track_push_package_event
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
- track_event('push_package')
+ package_event('push_package')
end
end
diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb
index c6037d52de9..403f5ea3851 100644
--- a/lib/api/helpers/packages_helpers.rb
+++ b/lib/api/helpers/packages_helpers.rb
@@ -47,6 +47,10 @@ module API
authorize_create_package!(subject)
require_gitlab_workhorse!
end
+
+ def package_event(event_name, **args)
+ track_event(event_name, **args)
+ end
end
end
end
diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb
index 560c61c7e53..9224381735f 100644
--- a/lib/api/helpers/snippets_helpers.rb
+++ b/lib/api/helpers/snippets_helpers.rb
@@ -41,6 +41,10 @@ module API
mutually_exclusive :files, :file_name
end
+ params :minimum_update_params do
+ at_least_one_of :content, :description, :files, :file_name, :title, :visibility
+ end
+
def content_for(snippet)
if snippet.empty_repo?
env['api.format'] = :txt
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index caaeabf7061..e6d9a9a7c20 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -107,7 +107,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
end
@@ -145,7 +145,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
@@ -181,7 +181,7 @@ module API
when 'sha1'
package_file.file_sha1
else
- track_event('pull_package') if jar_file?(format)
+ package_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
end
@@ -233,7 +233,7 @@ module API
when 'md5'
nil
else
- track_event('push_package') if jar_file?(format)
+ package_event('push_package') if jar_file?(format)
file_params = {
file: params[:file],
diff --git a/lib/api/npm_packages.rb b/lib/api/npm_packages.rb
index 21ca57b7985..fca405b76b7 100644
--- a/lib/api/npm_packages.rb
+++ b/lib/api/npm_packages.rb
@@ -141,7 +141,7 @@ module API
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
- track_event('pull_package')
+ package_event('pull_package')
present_carrierwave_file!(package_file.file)
end
@@ -157,7 +157,7 @@ module API
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(user_project)
- track_event('push_package')
+ package_event('push_package')
created_package = ::Packages::Npm::CreatePackageService
.new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb
index 87290cd07d9..f84a3acbe6d 100644
--- a/lib/api/nuget_packages.rb
+++ b/lib/api/nuget_packages.rb
@@ -105,7 +105,7 @@ module API
package_file = ::Packages::CreatePackageFileService.new(package, file_params)
.execute
- track_event('push_package')
+ package_event('push_package')
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
@@ -198,7 +198,7 @@ module API
not_found!('Package') unless package_file
- track_event('pull_package')
+ package_event('pull_package')
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
present_carrierwave_file!(package_file.file, supports_direct_download: false)
@@ -233,7 +233,7 @@ module API
.new(authorized_user_project, params[:q], search_options)
.execute
- track_event('search_package')
+ package_event('search_package')
present ::Packages::Nuget::SearchResultsPresenter.new(search),
with: ::API::Entities::Nuget::SearchResults
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 05765d1e6ac..f6e87fece89 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -93,8 +93,7 @@ module API
desc: 'The visibility of the snippet'
use :update_file_params
-
- at_least_one_of :title, :file_name, :content, :files, :visibility
+ use :minimum_update_params
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index b2528ceae94..c07db68f8a8 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -72,7 +72,7 @@ module API
package = packages_finder(project).by_file_name_and_sha256(filename, params[:sha256])
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
- track_event('pull_package')
+ package_event('pull_package')
present_carrierwave_file!(package_file.file, supports_direct_download: true)
end
@@ -91,7 +91,7 @@ module API
get 'simple/*package_name', format: :txt do
authorize_read_package!(authorized_user_project)
- track_event('list_package')
+ package_event('list_package')
packages = find_package_versions
presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project)
@@ -122,7 +122,7 @@ module API
authorize_upload!(authorized_user_project)
bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size)
- track_event('push_package')
+ package_event('push_package')
::Packages::Pypi::CreatePackageService
.new(authorized_user_project, current_user, declared_params)
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index b6914d571dc..c6ef35875fc 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -106,8 +106,7 @@ module API
desc: 'The visibility of the snippet'
use :update_file_params
-
- at_least_one_of :title, :file_name, :content, :files, :visibility
+ use :minimum_update_params
end
put ':id' do
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index 88b93776bec..0042a747ff4 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -45,7 +45,7 @@ module Gitlab
def self.normalize_dn(dn)
::Gitlab::Auth::Ldap::DN.new(dn).to_normalized_s
rescue ::Gitlab::Auth::Ldap::DN::FormatError => e
- Gitlab::AppLogger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
dn
end
diff --git a/lib/gitlab/ci/trace/metrics.rb b/lib/gitlab/ci/trace/metrics.rb
new file mode 100644
index 00000000000..7351baf5fcc
--- /dev/null
+++ b/lib/gitlab/ci/trace/metrics.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Trace
+ class Metrics
+ extend Gitlab::Utils::StrongMemoize
+
+ OPERATIONS = [:mutated].freeze
+
+ def increment_trace_operation(operation: :unknown)
+ unless OPERATIONS.include?(operation)
+ raise ArgumentError, 'unknown trace operation'
+ end
+
+ self.class.trace_operations.increment(operation: operation)
+ end
+
+ def self.trace_operations
+ strong_memoize(:trace_operations) do
+ name = :gitlab_ci_trace_operations_total
+ comment = 'Total amount of different operations on a build trace'
+
+ Gitlab::Metrics.counter(name, comment)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb
index 5fdfa5e75ed..c2fa2e1330a 100644
--- a/lib/gitlab/reference_counter.rb
+++ b/lib/gitlab/reference_counter.rb
@@ -51,10 +51,8 @@ module Gitlab
redis_cmd do |redis|
current_value = redis.decr(key)
if current_value < 0
- # rubocop:disable Gitlab/RailsLogger
- Rails.logger.warn("Reference counter for #{gl_repository} decreased" \
+ Gitlab::AppLogger.warn("Reference counter for #{gl_repository} decreased" \
" when its value was less than 1. Resetting the counter.")
- # rubocop:enable Gitlab/RailsLogger
redis.del(key)
end
end
@@ -87,7 +85,7 @@ module Gitlab
true
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
false
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index dc0337c7ecd..6a9ec5a011b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1105,6 +1105,9 @@ msgstr ""
msgid ":%{startLine} to %{endLine}"
msgstr ""
+msgid "A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents."
+msgstr ""
+
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
@@ -3268,6 +3271,11 @@ msgstr ""
msgid "Are you sure you want to erase this build?"
msgstr ""
+msgid "Are you sure you want to import %d repository?"
+msgid_plural "Are you sure you want to import %d repositories?"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Are you sure you want to lose unsaved changes?"
msgstr ""
@@ -10971,7 +10979,7 @@ msgstr ""
msgid "Filter results..."
msgstr ""
-msgid "Filter your projects by name"
+msgid "Filter your repositories by name"
msgstr ""
msgid "Filter..."
@@ -13018,6 +13026,16 @@ msgstr ""
msgid "Import"
msgstr ""
+msgid "Import %d compatible repository"
+msgid_plural "Import %d compatible repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Import %d repository"
+msgid_plural "Import %d repositories"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Import CSV"
msgstr ""
@@ -13027,15 +13045,9 @@ msgstr ""
msgid "Import all compatible projects"
msgstr ""
-msgid "Import all compatible repositories"
-msgstr ""
-
msgid "Import all projects"
msgstr ""
-msgid "Import all repositories"
-msgstr ""
-
msgid "Import an exported GitLab project"
msgstr ""
@@ -13123,6 +13135,9 @@ msgstr ""
msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
msgstr ""
+msgid "ImportProjects|Import repositories"
+msgstr ""
+
msgid "ImportProjects|Importing the project failed"
msgstr ""
@@ -13135,7 +13150,7 @@ msgstr ""
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
-msgid "ImportProjects|Select the projects you want to import"
+msgid "ImportProjects|Select the repositories you want to import"
msgstr ""
msgid "ImportProjects|The remote data could not be imported."
diff --git a/package.json b/package.json
index 508c6732eed..0f0b08e8d5b 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.164.0",
- "@gitlab/ui": "20.18.3",
+ "@gitlab/ui": "20.19.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.22.3",
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
index 9c359e932d5..cfd0c7e210f 100644
--- a/spec/features/import/manifest_import_spec.rb
+++ b/spec/features/import/manifest_import_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'Import multiple repositories by uploading a manifest file', :js
attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
click_on 'List available repositories'
- expect(page).to have_button('Import all repositories')
+ expect(page).to have_button('Import 660 repositories')
expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint')
end
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index aab9d1026f9..6dc1cbfb2d7 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -36,6 +36,6 @@ RSpec.describe 'Issue markdown toolbar', :js do
all('.toolbar-btn')[1].click
- expect(find('#note-body')[:value]).to eq("test\n*underline*\n")
+ expect(find('#note-body')[:value]).to eq("test\n_underline_\n")
end
end
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index fe773685373..c00ba558a7b 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -196,7 +196,7 @@ RSpec.describe "User creates issue" do
end
it 'pre-fills the issue type dropdown with issue type' do
- expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-label')).to have_content('Issue')
+ expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Issue')
end
it 'does not hide the milestone select' do
@@ -208,11 +208,11 @@ RSpec.describe "User creates issue" do
let(:project) { create(:project) }
before do
- visit new_project_issue_path(project, { 'issue[issue_type]': 'incident', issuable_template: 'incident' })
+ visit new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' } })
end
it 'pre-fills the issue type dropdown with incident type' do
- expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-label')).to have_content('Incident')
+ expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Incident')
end
it 'hides the epic select' do
@@ -226,6 +226,10 @@ RSpec.describe "User creates issue" do
it 'hides the weight input' do
expect(page).not_to have_selector('.qa-issuable-weight-input')
end
+
+ it 'shows the incident help text' do
+ expect(page).to have_text('A modified issue to guide the resolution of incidents.')
+ end
end
context 'suggestions', :js do
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index 393f042a9f9..e1d855ae0cf 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -645,6 +645,36 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions).toHaveLength(1);
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toBe(1);
});
+
+ it('should add discussion to file', () => {
+ const state = {
+ latestDiff: true,
+ diffFiles: [
+ {
+ file_hash: 'ABC',
+ discussions: [],
+ parallel_diff_lines: [],
+ highlighted_diff_lines: [],
+ },
+ ],
+ };
+ const discussion = {
+ id: 1,
+ line_code: 'ABC_1',
+ diff_discussion: true,
+ resolvable: true,
+ diff_file: {
+ file_hash: state.diffFiles[0].file_hash,
+ },
+ };
+
+ mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
+ discussion,
+ diffPositionByLineCode: null,
+ });
+
+ expect(state.diffFiles[0].discussions.length).toEqual(1);
+ });
});
describe('REMOVE_LINE_DISCUSSIONS', () => {
diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js
index 83d27635af9..bcb5a3a2231 100644
--- a/spec/frontend/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_projects/components/import_projects_table_spec.js
@@ -16,15 +16,24 @@ describe('ImportProjectsTable', () => {
wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
const providerTitle = 'THE PROVIDER';
- const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
+ const providerRepo = {
+ importSource: {
+ id: 10,
+ sanitizedName: 'sanitizedName',
+ fullName: 'fullName',
+ },
+ importedProject: null,
+ };
const findImportAllButton = () =>
wrapper
.findAll(GlButton)
.filter(w => w.props().variant === 'success')
.at(0);
+ const findImportAllModal = () => wrapper.find({ ref: 'importAllModal' });
const importAllFn = jest.fn();
+ const importAllModalShowFn = jest.fn();
const setPageFn = jest.fn();
function createComponent({
@@ -64,6 +73,9 @@ describe('ImportProjectsTable', () => {
paginatable,
},
slots,
+ stubs: {
+ GlModal: { template: '<div>Modal!</div>', methods: { show: importAllModalShowFn } },
+ },
});
}
@@ -110,18 +122,21 @@ describe('ImportProjectsTable', () => {
});
it.each`
- hasIncompatibleRepos | buttonText
- ${false} | ${'Import all repositories'}
- ${true} | ${'Import all compatible repositories'}
+ hasIncompatibleRepos | count | buttonText
+ ${false} | ${1} | ${'Import 1 repository'}
+ ${true} | ${1} | ${'Import 1 compatible repository'}
+ ${false} | ${5} | ${'Import 5 repositories'}
+ ${true} | ${5} | ${'Import 5 compatible repositories'}
`(
- 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos',
- ({ hasIncompatibleRepos, buttonText }) => {
+ 'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos and repos count is $count',
+ ({ hasIncompatibleRepos, buttonText, count }) => {
createComponent({
state: {
providerRepos: [providerRepo],
},
getters: {
hasIncompatibleRepos: () => hasIncompatibleRepos,
+ importAllCount: () => count,
},
});
@@ -129,19 +144,28 @@ describe('ImportProjectsTable', () => {
},
);
- it('renders an empty state if there are no projects available', () => {
+ it('renders an empty state if there are no repositories available', () => {
createComponent({ state: { repositories: [] } });
expect(wrapper.find(ProviderRepoTableRow).exists()).toBe(false);
expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`);
});
- it('sends importAll event when import button is clicked', async () => {
- createComponent({ state: { providerRepos: [providerRepo] } });
+ it('opens confirmation modal when import all button is clicked', async () => {
+ createComponent({ state: { repositories: [providerRepo] } });
findImportAllButton().vm.$emit('click');
await nextTick();
+ expect(importAllModalShowFn).toHaveBeenCalled();
+ });
+
+ it('triggers importAll action when modal is confirmed', async () => {
+ createComponent({ state: { providerRepos: [providerRepo] } });
+
+ findImportAllModal().vm.$emit('ok');
+ await nextTick();
+
expect(importAllFn).toHaveBeenCalled();
});
diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js
index 5b2a8ea06f0..1ce42e534ea 100644
--- a/spec/frontend/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_projects/store/getters_spec.js
@@ -3,6 +3,7 @@ import {
isImportingAnyRepo,
hasIncompatibleRepos,
hasImportableRepos,
+ importAllCount,
getImportTarget,
} from '~/import_projects/store/getters';
import { STATUSES } from '~/import_projects/constants';
@@ -97,6 +98,19 @@ describe('import_projects store getters', () => {
});
});
+ describe('importAllCount', () => {
+ it('returns count of available importable projects ', () => {
+ localState.repositories = [
+ IMPORTABLE_REPO,
+ IMPORTABLE_REPO,
+ IMPORTED_REPO,
+ INCOMPATIBLE_REPO,
+ ];
+
+ expect(importAllCount(localState)).toBe(2);
+ });
+ });
+
describe('getImportTarget', () => {
it('returns default value if no custom target available', () => {
localState.defaultTargetNamespace = 'default';
diff --git a/spec/graphql/mutations/issues/set_severity_spec.rb b/spec/graphql/mutations/issues/set_severity_spec.rb
new file mode 100644
index 00000000000..ed73d3b777e
--- /dev/null
+++ b/spec/graphql/mutations/issues/set_severity_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Issues::SetSeverity do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:incident) }
+ let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
+
+ describe '#resolve' do
+ let(:severity) { 'CRITICAL' }
+ let(:mutated_incident) { subject[:issue] }
+
+ subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, severity: severity) }
+
+ context 'when the user cannot update the issue' do
+ it 'raises an error' do
+ expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user can update the issue' do
+ before do
+ issue.project.add_developer(user)
+ end
+
+ context 'when issue type is incident' do
+ context 'when severity has a correct value' do
+ it 'updates severity' do
+ expect(resolve[:issue].severity).to eq('critical')
+ end
+
+ it 'returns no errors' do
+ expect(resolve[:errors]).to be_empty
+ end
+ end
+
+ context 'when severity has an unsuported value' do
+ let(:severity) { 'unsupported-severity' }
+
+ it 'sets severity to default' do
+ expect(resolve[:issue].severity).to eq(IssuableSeverity::DEFAULT)
+ end
+
+ it 'returns no errorsr' do
+ expect(resolve[:errors]).to be_empty
+ end
+ end
+ end
+
+ context 'when issue type is not incident' do
+ let!(:issue) { create(:issue) }
+
+ it 'does not updates the issue' do
+ expect { resolve }.not_to change { issue.updated_at }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index de538c6c263..3ad3f610128 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -29,6 +29,7 @@ issues:
- merge_requests_closing_issues
- metrics
- timelogs
+- issuable_severity
- issue_assignees
- closed_by
- epic_issue
@@ -542,6 +543,8 @@ timelogs:
- note
push_event_payload:
- event
+issuable_severity:
+- issue
issue_assignees:
- issue
- assignee
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index 0d0ac75ee22..83e4006c69b 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
end
it 'warns if attempting to decrease a counter with a value of zero or less, and resets the counter' do
- expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
+ expect(Gitlab::AppLogger).to receive(:warn).with("Reference counter for project-1" \
" decreased when its value was less than 1. Resetting the counter.")
expect { reference_counter.decrease }.not_to change { reference_counter.value }
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6d775e805d9..2a422ddc9c0 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1052,18 +1052,53 @@ RSpec.describe Ci::Build do
end
describe '#hide_secrets' do
+ let(:metrics) { spy('metrics') }
let(:subject) { build.hide_secrets(data) }
context 'hide runners token' do
let(:data) { "new #{project.runners_token} data"}
it { is_expected.to match(/^new x+ data$/) }
+
+ it 'increments trace mutation metric' do
+ build.hide_secrets(data, metrics)
+
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :mutated)
+ end
end
context 'hide build token' do
let(:data) { "new #{build.token} data"}
it { is_expected.to match(/^new x+ data$/) }
+
+ it 'increments trace mutation metric' do
+ build.hide_secrets(data, metrics)
+
+ expect(metrics)
+ .to have_received(:increment_trace_operation)
+ .with(operation: :mutated)
+ end
+ end
+
+ context 'when build does not include secrets' do
+ let(:data) { 'my build log' }
+
+ it 'does not mutate trace' do
+ trace = build.hide_secrets(data)
+
+ expect(trace).to eq data
+ end
+
+ it 'does not increment trace mutation metric' do
+ build.hide_secrets(data, metrics)
+
+ expect(metrics)
+ .not_to have_received(:increment_trace_operation)
+ .with(operation: :mutated)
+ end
end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 0518b2c7540..779839df670 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -20,7 +20,9 @@ RSpec.describe Ci::JobArtifact do
it_behaves_like 'having unique enum values'
it_behaves_like 'UpdateProjectStatistics' do
- subject { build(:ci_job_artifact, :archive, size: 107464) }
+ let_it_be(:job, reload: true) { create(:ci_build) }
+
+ subject { build(:ci_job_artifact, :archive, job: job, size: 107464) }
end
describe '.not_expired' do
diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb
index 716ab4d8522..8cbace845a9 100644
--- a/spec/models/ci/pipeline_artifact_spec.rb
+++ b/spec/models/ci/pipeline_artifact_spec.rb
@@ -13,7 +13,9 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
it_behaves_like 'having unique enum values'
it_behaves_like 'UpdateProjectStatistics' do
- subject { build(:ci_pipeline_artifact) }
+ let_it_be(:pipeline, reload: true) { create(:ci_pipeline) }
+
+ subject { build(:ci_pipeline_artifact, pipeline: pipeline) }
end
describe 'validations' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index b00395cd926..5ed9eb6252a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -891,4 +891,58 @@ RSpec.describe Issuable do
end
end
end
+
+ describe '#update_severity' do
+ let(:severity) { 'low' }
+
+ subject(:update_severity) { issuable.update_severity(severity) }
+
+ context 'when issuable not an incident' do
+ %i(issue merge_request).each do |issuable_type|
+ let(:issuable) { build_stubbed(issuable_type) }
+
+ it { is_expected.to be_nil }
+
+ it 'does not set severity' do
+ expect { subject }.not_to change(IssuableSeverity, :count)
+ end
+ end
+ end
+
+ context 'when issuable is an incident' do
+ let!(:issuable) { create(:incident) }
+
+ context 'when issuable does not have issuable severity yet' do
+ it 'creates new record' do
+ expect { update_severity }.to change { IssuableSeverity.where(issue: issuable).count }.to(1)
+ end
+
+ it 'sets severity to specified value' do
+ expect { update_severity }.to change { issuable.severity }.to('low')
+ end
+ end
+
+ context 'when issuable has an issuable severity' do
+ let!(:issuable_severity) { create(:issuable_severity, issue: issuable, severity: 'medium') }
+
+ it 'does not create new record' do
+ expect { update_severity }.not_to change(IssuableSeverity, :count)
+ end
+
+ it 'updates existing issuable severity' do
+ expect { update_severity }.to change { issuable_severity.severity }.to(severity)
+ end
+ end
+
+ context 'when severity value is unsupported' do
+ let(:severity) { 'unsupported-severity' }
+
+ it 'sets the severity to default value' do
+ update_severity
+
+ expect(issuable.issuable_severity.severity).to eq(IssuableSeverity::DEFAULT)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
index 7cc8e13d449..6b992dbc2a5 100644
--- a/spec/models/packages/package_file_spec.rb
+++ b/spec/models/packages/package_file_spec.rb
@@ -33,15 +33,17 @@ RSpec.describe Packages::PackageFile, type: :model do
end
context 'updating project statistics' do
+ let_it_be(:package, reload: true) { create(:package) }
+
context 'when the package file has an explicit size' do
it_behaves_like 'UpdateProjectStatistics' do
- subject { build(:package_file, :jar, size: 42) }
+ subject { build(:package_file, :jar, package: package, size: 42) }
end
end
context 'when the package file does not have a size' do
it_behaves_like 'UpdateProjectStatistics' do
- subject { build(:package_file, size: nil) }
+ subject { build(:package_file, package: package, size: nil) }
end
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
new file mode 100644
index 00000000000..96fd2368765
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting severity level of an incident' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let(:incident) { create(:incident) }
+ let(:project) { incident.project }
+ let(:input) { { severity: 'CRITICAL' } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: incident.iid.to_s
+ }
+
+ graphql_mutation(:issue_set_severity, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ severity
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_severity)
+ end
+
+ context 'when the user is not allowed to update the incident' do
+ it 'returns an error' do
+ error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when the user is allowed to update the incident' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'updates the issue' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response.dig('issue', 'severity')).to eq('CRITICAL')
+ end
+ end
+end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 0746dee3e51..08c88873078 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -306,15 +306,9 @@ RSpec.describe API::ProjectSnippets do
it_behaves_like 'snippet file updates'
it_behaves_like 'snippet non-file updates'
+ it_behaves_like 'snippet individual non-file updates'
it_behaves_like 'invalid snippet updates'
- it 'updates snippet with visibility parameter' do
- expect { update_snippet(params: { visibility: 'private' }) }
- .to change { snippet.reload.visibility }
-
- expect(snippet.visibility).to eq('private')
- end
-
it_behaves_like 'update with repository actions' do
let(:snippet_without_repo) { create(:project_snippet, author: admin, project: project, visibility_level: visibility_level) }
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 5bba308a2d3..8d77026d26c 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -393,6 +393,7 @@ RSpec.describe API::Snippets do
it_behaves_like 'snippet file updates'
it_behaves_like 'snippet non-file updates'
+ it_behaves_like 'snippet individual non-file updates'
it_behaves_like 'invalid snippet updates'
it "returns 404 for another user's snippet" do
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index bd2a1b0fb98..32e9562f4c1 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe TestCaseEntity do
include TestReportsHelper
+ let_it_be(:job) { create(:ci_build) }
+
let(:entity) { described_class.new(test_case) }
describe '#as_json' do
@@ -38,7 +40,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is present' do
- let(:test_case) { build(:test_case, :failed_with_attachment) }
+ let(:test_case) { build(:test_case, :failed_with_attachment, job: job) }
it 'returns the attachment_url' do
expect(subject).to include(:attachment_url)
@@ -46,7 +48,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is not present' do
- let(:test_case) { build(:test_case) }
+ let(:test_case) { build(:test_case, job: job) }
it 'returns a nil attachment_url' do
expect(subject[:attachment_url]).to be_nil
@@ -60,7 +62,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is present' do
- let(:test_case) { build(:test_case, :failed_with_attachment) }
+ let(:test_case) { build(:test_case, :failed_with_attachment, job: job) }
it 'returns no attachment_url' do
expect(subject).not_to include(:attachment_url)
@@ -68,7 +70,7 @@ RSpec.describe TestCaseEntity do
end
context 'when attachment is not present' do
- let(:test_case) { build(:test_case) }
+ let(:test_case) { build(:test_case, job: job) }
it 'returns no attachment_url' do
expect(subject).not_to include(:attachment_url)
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index b5811077acf..0c28ceb7466 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -52,7 +52,8 @@ RSpec.describe Issues::UpdateService, :mailer do
state_event: 'close',
label_ids: [label.id],
due_date: Date.tomorrow,
- discussion_locked: true
+ discussion_locked: true,
+ severity: 'low'
}
end
@@ -71,6 +72,24 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.discussion_locked).to be_truthy
end
+ context 'when issue type is not incident' do
+ it 'returns default severity' do
+ update_issue(opts)
+
+ expect(issue.severity).to eq(IssuableSeverity::DEFAULT)
+ end
+ end
+
+ context 'when issue type is incident' do
+ let(:issue) { create(:incident, project: project) }
+
+ it 'changes updates the severity' do
+ update_issue(opts)
+
+ expect(issue.severity).to eq('low')
+ end
+ end
+
it 'refreshes the number of open issues when the issue is made confidential', :use_clean_rails_memory_store_caching do
issue # make sure the issue is created first so our counts are correct.
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 05be3c1946b..d042b318d02 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:assignee) { create(:user) }
+ let(:user2) { create(:user) }
describe '#execute' do
context 'valid params' do
@@ -26,7 +26,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- project.add_developer(assignee)
+ project.add_developer(user2)
allow(service).to receive(:execute_hooks)
end
@@ -75,7 +75,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
description: "well this is not done yet\n/wip",
source_branch: 'feature',
target_branch: 'master',
- assignees: [assignee]
+ assignees: [user2]
}
end
@@ -91,7 +91,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
description: "well this is not done yet\n/wip",
source_branch: 'feature',
target_branch: 'master',
- assignees: [assignee]
+ assignees: [user2]
}
end
@@ -108,17 +108,17 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
description: 'please fix',
source_branch: 'feature',
target_branch: 'master',
- assignees: [assignee]
+ assignees: [user2]
}
end
- it { expect(merge_request.assignees).to eq([assignee]) }
+ it { expect(merge_request.assignees).to eq([user2]) }
it 'creates a todo for new assignee' do
attributes = {
project: project,
author: user,
- user: assignee,
+ user: user2,
target_id: merge_request.id,
target_type: merge_request.class.name,
action: Todo::ASSIGNED,
@@ -129,6 +129,34 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
end
+ context 'when reviewer is assigned' do
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ description: 'please fix',
+ source_branch: 'feature',
+ target_branch: 'master',
+ reviewers: [user2]
+ }
+ end
+
+ it { expect(merge_request.reviewers).to eq([user2]) }
+
+ it 'creates a todo for new reviewer' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: merge_request.id,
+ target_type: merge_request.class.name,
+ action: Todo::REVIEW_REQUESTED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
context 'when head pipelines already exist for merge request source branch', :sidekiq_inline do
let(:shas) { project.repository.commits(opts[:source_branch], limit: 2).map(&:id) }
let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
@@ -213,7 +241,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
before do
stub_feature_flags(ci_disallow_to_create_merge_request_pipelines_in_target_project: false)
- target_project.add_developer(assignee)
+ target_project.add_developer(user2)
target_project.add_maintainer(user)
end
@@ -366,7 +394,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
assignee_ids: create(:user).id,
milestone_id: 1,
title: 'Title',
- description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}"),
+ description: %(/assign @#{user2.username}\n/milestone %"#{milestone.name}"),
source_branch: 'feature',
target_branch: 'master'
}
@@ -374,12 +402,12 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- project.add_maintainer(assignee)
+ project.add_maintainer(user2)
end
it 'assigns and sets milestone to issuable from command' do
expect(merge_request).to be_persisted
- expect(merge_request.assignees).to eq([assignee])
+ expect(merge_request.assignees).to eq([user2])
expect(merge_request.milestone).to eq(milestone)
end
end
@@ -387,7 +415,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
context 'merge request create service' do
context 'asssignee_id' do
- let(:assignee) { create(:user) }
+ let(:user2) { create(:user) }
before do
project.add_maintainer(user)
@@ -410,12 +438,12 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'saves assignee when user id is valid' do
- project.add_maintainer(assignee)
- opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
+ project.add_maintainer(user2)
+ opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }
merge_request = described_class.new(project, user, opts).execute
- expect(merge_request.assignees).to eq([assignee])
+ expect(merge_request.assignees).to eq([user2])
end
context 'when assignee is set' do
@@ -423,18 +451,18 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
{
title: 'Title',
description: 'Description',
- assignee_ids: [assignee.id],
+ assignee_ids: [user2.id],
source_branch: 'feature',
target_branch: 'master'
}
end
it 'invalidates open merge request counter for assignees when merge request is assigned' do
- project.add_maintainer(assignee)
+ project.add_maintainer(user2)
described_class.new(project, user, opts).execute
- expect(assignee.assigned_open_merge_requests_count).to eq 1
+ expect(user2.assigned_open_merge_requests_count).to eq 1
end
end
@@ -449,7 +477,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
levels.each do |level|
it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
project.update!(visibility_level: level)
- opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }
merge_request = described_class.new(project, user, opts).execute
@@ -475,7 +503,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- project.add_developer(assignee)
+ project.add_developer(user2)
end
it 'creates a `MergeRequestsClosingIssues` record for each issue' do
@@ -503,7 +531,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
context 'when user can not access source project' do
before do
- target_project.add_developer(assignee)
+ target_project.add_developer(user2)
target_project.add_maintainer(user)
end
@@ -515,7 +543,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
context 'when user can not access target project' do
before do
- target_project.add_developer(assignee)
+ target_project.add_developer(user2)
target_project.add_maintainer(user)
end
@@ -567,7 +595,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
before do
- project.add_developer(assignee)
+ project.add_developer(user2)
project.add_maintainer(user)
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 5de1aaa65f6..6b7463d4996 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -52,6 +52,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
title: 'New title',
description: 'Also please fix',
assignee_ids: [user.id],
+ reviewer_ids: [user.id],
state_event: 'close',
label_ids: [label.id],
target_branch: 'target',
@@ -75,6 +76,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
expect(@merge_request).to be_valid
expect(@merge_request.title).to eq('New title')
expect(@merge_request.assignees).to match_array([user])
+ expect(@merge_request.reviewers).to match_array([user])
expect(@merge_request).to be_closed
expect(@merge_request.labels.count).to eq(1)
expect(@merge_request.labels.first.title).to eq(label.name)
@@ -402,6 +404,30 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
end
+ context 'when reviewers gets changed' do
+ before do
+ update_merge_request({ reviewer_ids: [user2.id] })
+ end
+
+ it 'marks pending todo as done' do
+ expect(pending_todo.reload).to be_done
+ end
+
+ it 'creates a pending todo for new review request' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: merge_request.id,
+ target_type: merge_request.class.name,
+ action: Todo::REVIEW_REQUESTED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
context 'when the milestone is removed' do
let!(:non_subscriber) { create(:user) }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 94d4b61933d..86a428bca92 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -65,6 +65,40 @@ RSpec.describe TodoService do
end
end
+ shared_examples 'reassigned reviewable target' do
+ context 'with no existing reviewers' do
+ let(:assigned_reviewers) { [] }
+
+ it 'creates a pending todo for new reviewer' do
+ target.reviewers = [john_doe]
+ service.send(described_method, target, author)
+
+ should_create_todo(user: john_doe, target: target, action: Todo::REVIEW_REQUESTED)
+ end
+ end
+
+ context 'with an existing reviewer' do
+ let(:assigned_reviewers) { [john_doe] }
+
+ it 'does not create a todo if unassigned' do
+ target.reviewers = []
+
+ should_not_create_any_todo { service.send(described_method, target, author) }
+ end
+
+ it 'creates a todo if new reviewer is the current user' do
+ target.reviewers = [john_doe]
+ service.send(described_method, target, john_doe)
+
+ should_create_todo(user: john_doe, target: target, author: john_doe, action: Todo::REVIEW_REQUESTED)
+ end
+
+ it 'does not create a todo if already assigned' do
+ should_not_create_any_todo { service.send(described_method, target, author, [john_doe]) }
+ end
+ end
+ end
+
describe 'Issues' do
let(:issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:addressed_issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
@@ -605,6 +639,17 @@ RSpec.describe TodoService do
end
end
+ describe '#reassigned_reviewable' do
+ let(:described_method) { :reassigned_reviewable }
+
+ context 'reviewable is a merge request' do
+ it_behaves_like 'reassigned reviewable target' do
+ let(:assigned_reviewers) { [] }
+ let(:target) { create(:merge_request, source_project: project, author: author, reviewers: assigned_reviewers) }
+ end
+ end
+ end
+
describe 'Merge Requests' do
let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
index 97f85977a20..051367fbe96 100644
--- a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -170,6 +170,25 @@ RSpec.shared_examples 'snippet non-file updates' do
end
end
+RSpec.shared_examples 'snippet individual non-file updates' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:attribute, :updated_value) do
+ :description | 'new description'
+ :title | 'new title'
+ :visibility | 'private'
+ end
+
+ with_them do
+ it 'updates the attribute' do
+ params = { attribute => updated_value }
+
+ expect { update_snippet(params: params) }
+ .to change { snippet.reload.send(attribute) }.to(updated_value)
+ end
+ end
+end
+
RSpec.shared_examples 'invalid snippet updates' do
it 'returns 404 for invalid snippet id' do
update_snippet(snippet_id: non_existing_record_id, params: { title: 'foo' })
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
index 49add434ab5..b998023b40e 100644
--- a/spec/views/projects/pipelines/show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -16,24 +16,6 @@ RSpec.describe 'projects/pipelines/show' do
stub_feature_flags(new_pipeline_form: false)
end
- shared_examples 'pipeline with warning messages' do
- let(:warning_messages) do
- [double(content: 'warning 1'), double(content: 'warning 2')]
- end
-
- before do
- allow(pipeline).to receive(:warning_messages).and_return(warning_messages)
- end
-
- it 'displays the warnings' do
- render
-
- expect(rendered).to have_css('.bs-callout-warning')
- expect(rendered).to have_content('warning 1')
- expect(rendered).to have_content('warning 2')
- end
- end
-
context 'when pipeline has errors' do
before do
allow(pipeline).to receive(:yaml_errors).and_return('some errors')
@@ -51,10 +33,6 @@ RSpec.describe 'projects/pipelines/show' do
expect(rendered).not_to have_css('ul.pipelines-tabs')
end
-
- context 'when pipeline has also warnings' do
- it_behaves_like 'pipeline with warning messages'
- end
end
context 'when pipeline is valid' do
@@ -69,9 +47,5 @@ RSpec.describe 'projects/pipelines/show' do
expect(rendered).to have_css('ul.pipelines-tabs')
end
-
- context 'when pipeline has warnings' do
- it_behaves_like 'pipeline with warning messages'
- end
end
end
diff --git a/yarn.lock b/yarn.lock
index 916f597f40e..5e01d222b15 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.164.0.tgz#6cefad871c45f945ef92b99015d0f510b1d2de4a"
integrity sha512-a9e/cYUc1QQk7azjH4x/m6/p3icavwGEi5F9ipNlDqiJtUor5tqojxvMxPOhuVbN/mTwnC6lGsSZg4tqTsdJAQ==
-"@gitlab/ui@20.18.3":
- version "20.18.3"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.18.3.tgz#14b1d6571756fbef7966d6658f56ca213ded15e8"
- integrity sha512-yeXc40J8ifXm1aApuNNjEOZraDexYAyKZNRa05rLPXPppLnwZtx4Io5z9QiskN6fMiL648EgzC8JW576qtByCw==
+"@gitlab/ui@20.19.0":
+ version "20.19.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.19.0.tgz#4f7f3ff5ffa59baf6390f7ab25f199ba27b9b84b"
+ integrity sha512-QXccwQNWfyCKqhRNIKZRnaE1JJR3g29hcHZoTQKKSlPVolHbqssszBOL8A4/H7TWuCFWRjswJPHFHfHeBHWccQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"