summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-17 12:09:20 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-17 12:09:20 +0000
commitb84eeb256c4a780d902faee1f99ca9a711b3214a (patch)
tree32918aadbea9210eace50efbce9afbfb8cd3ba84
parent53ae6b7e3f83591ad251a3f771f5bf3b8cf087ba (diff)
downloadgitlab-ce-b84eeb256c4a780d902faee1f99ca9a711b3214a.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml8
-rw-r--r--app/assets/javascripts/issuables_list/components/issuable.vue9
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue20
-rw-r--r--app/assets/javascripts/reports/store/mutations.js2
-rw-r--r--app/assets/javascripts/reports/store/state.js5
-rw-r--r--app/assets/javascripts/reports/store/utils.js9
-rw-r--r--app/controllers/boards/issues_controller.rb12
-rw-r--r--app/graphql/mutations/notes/update.rb38
-rw-r--r--app/graphql/mutations/notes/update/base.rb48
-rw-r--r--app/graphql/mutations/notes/update/image_diff_note.rb60
-rw-r--r--app/graphql/mutations/notes/update/note.rb22
-rw-r--r--app/graphql/types/mutation_type.rb11
-rw-r--r--app/graphql/types/notes/diff_position_type.rb4
-rw-r--r--app/graphql/types/notes/update_diff_image_position_input_type.rb29
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/mailers/emails/pipelines.rb8
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb8
-rw-r--r--app/serializers/test_reports_comparer_entity.rb1
-rw-r--r--app/serializers/test_suite_comparer_entity.rb15
-rw-r--r--app/services/snippets/create_service.rb10
-rw-r--r--app/views/projects/wikis/_form.html.haml4
-rw-r--r--changelogs/unreleased/196843-remove-feature-flag.yml5
-rw-r--r--changelogs/unreleased/34353-graphql-mutation-update-image-diff-note.yml5
-rw-r--r--changelogs/unreleased/36505-nuget-the-search-service.yml5
-rw-r--r--changelogs/unreleased/Updated-new-edit-wiki-page-to-equal-ui-elements.yml5
-rw-r--r--changelogs/unreleased/bvl-correct-thread-count.yml5
-rw-r--r--changelogs/unreleased/fix-blocked-status.yml5
-rw-r--r--changelogs/unreleased/fix-wrong-mr-id-in-pipeline-failure-email.yml5
-rw-r--r--changelogs/unreleased/fj-39199-create-snippet-repository.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-final.yml5
-rw-r--r--db/migrate/20200207090921_add_nuget_index_to_packages_packages.rb18
-rw-r--r--db/schema.rb1
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql87
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json210
-rw-r--r--doc/api/graphql/reference/index.md14
-rw-r--r--doc/user/analytics/productivity_analytics.md5
-rw-r--r--doc/user/application_security/container_scanning/index.md38
-rw-r--r--doc/user/application_security/index.md1
-rw-r--r--doc/user/project/issues/design_management.md6
-rw-r--r--lib/api/entities/application_setting.rb2
-rw-r--r--lib/api/entities/board.rb2
-rw-r--r--lib/api/entities/group.rb2
-rw-r--r--lib/api/entities/group_detail.rb2
-rw-r--r--lib/api/entities/identity.rb2
-rw-r--r--lib/api/entities/issue.rb2
-rw-r--r--lib/api/entities/issue_basic.rb2
-rw-r--r--lib/api/entities/list.rb2
-rw-r--r--lib/api/entities/member.rb2
-rw-r--r--lib/api/entities/merge_request_basic.rb2
-rw-r--r--lib/api/entities/namespace.rb2
-rw-r--r--lib/api/entities/project.rb (renamed from lib/api/entities.rb)19
-rw-r--r--lib/api/entities/protected_branch.rb2
-rw-r--r--lib/api/entities/protected_ref_access.rb2
-rw-r--r--lib/api/entities/todo.rb2
-rw-r--r--lib/api/entities/user_public.rb2
-rw-r--r--lib/api/entities/user_with_admin.rb2
-rw-r--r--lib/gitlab/ci/parsers/test/junit.rb5
-rw-r--r--lib/gitlab/ci/reports/test_reports_comparer.rb2
-rw-r--r--lib/gitlab/ci/reports/test_suite_comparer.rb30
-rw-r--r--lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/runtime.rb10
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/factories/container_expiration_policies.rb13
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb131
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb12
-rw-r--r--spec/fixtures/api/schemas/entities/test_reports_comparer.json2
-rw-r--r--spec/fixtures/api/schemas/entities/test_suite_comparer.json12
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js4
-rw-r--r--spec/frontend/issuables_list/issuable_list_test_data.js3
-rw-r--r--spec/frontend/reports/store/utils_spec.js60
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js48
-rw-r--r--spec/javascripts/reports/mock_data/new_and_fixed_failures_report.json56
-rw-r--r--spec/javascripts/reports/mock_data/new_errors_report.json38
-rw-r--r--spec/javascripts/reports/mock_data/new_failures_report.json39
-rw-r--r--spec/javascripts/reports/mock_data/no_failures_report.json29
-rw-r--r--spec/javascripts/reports/mock_data/resolved_failures.json31
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb140
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb9
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/runtime_spec.rb4
-rw-r--r--spec/mailers/emails/pipelines_spec.rb42
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb244
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb (renamed from spec/requests/api/graphql/mutations/notes/update_spec.rb)15
-rw-r--r--spec/serializers/test_reports_comparer_entity_spec.rb6
-rw-r--r--spec/serializers/test_suite_comparer_entity_spec.rb78
-rw-r--r--spec/services/snippets/create_service_spec.rb30
-rw-r--r--spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb20
91 files changed, 1801 insertions, 200 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 7171f94decc..babdfb2ed75 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -200,7 +200,7 @@
.use-pg9:
services:
- - name: postgres:9.6
+ - name: postgres:9.6.17
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
variables:
@@ -209,7 +209,7 @@
.use-pg10:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- - name: postgres:10.9
+ - name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
variables:
@@ -217,7 +217,7 @@
.use-pg9-ee:
services:
- - name: postgres:9.6
+ - name: postgres:9.6.17
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- name: elasticsearch:6.4.2
@@ -227,7 +227,7 @@
.use-pg10-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- - name: postgres:10.9
+ - name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- name: elasticsearch:6.4.2
diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue
index eb924609a8a..2fd92e009eb 100644
--- a/app/assets/javascripts/issuables_list/components/issuable.vue
+++ b/app/assets/javascripts/issuables_list/components/issuable.vue
@@ -3,7 +3,7 @@
* This is tightly coupled to projects/issues/_issue.html.haml,
* any changes done to the haml need to be reflected here.
*/
-import { escape, isNumber } from 'underscore';
+import { escape, isNumber } from 'lodash';
import { GlLink, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import {
dateInWords,
@@ -19,8 +19,6 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
-const ISSUE_TOKEN = '#';
-
export default {
components: {
Icon,
@@ -119,8 +117,7 @@ export default {
);
},
referencePath() {
- // TODO: The API should return the reference path (it doesn't now) https://gitlab.com/gitlab-org/gitlab/issues/31301
- return `${ISSUE_TOKEN}${this.issuable.iid}`;
+ return this.issuable.references.relative;
},
updatedDateString() {
return formatDate(new Date(this.issuable.updated_at), 'mmm d, yyyy h:MMtt');
@@ -230,7 +227,7 @@ export default {
</div>
<div class="issuable-info">
- <span>{{ referencePath }}</span>
+ <span class="js-ref-path">{{ referencePath }}</span>
<span class="d-none d-sm-inline-block mr-1">
&middot;
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index 82601363aa4..88d174f96ed 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -62,9 +62,21 @@ export default {
return (
report.existing_failures.length > 0 ||
report.new_failures.length > 0 ||
- report.resolved_failures.length > 0
+ report.resolved_failures.length > 0 ||
+ report.existing_errors.length > 0 ||
+ report.new_errors.length > 0 ||
+ report.resolved_errors.length > 0
);
},
+ unresolvedIssues(report) {
+ return report.existing_failures.concat(report.existing_errors);
+ },
+ newIssues(report) {
+ return report.new_failures.concat(report.new_errors);
+ },
+ resolvedIssues(report) {
+ return report.resolved_failures.concat(report.resolved_errors);
+ },
},
};
</script>
@@ -87,9 +99,9 @@ export default {
<issues-list
v-if="shouldRenderIssuesList(report)"
:key="`issues-list-${i}`"
- :unresolved-issues="report.existing_failures"
- :new-issues="report.new_failures"
- :resolved-issues="report.resolved_failures"
+ :unresolved-issues="unresolvedIssues(report)"
+ :new-issues="newIssues(report)"
+ :resolved-issues="resolvedIssues(report)"
:component="$options.componentNames.TestIssueBody"
class="report-block-group-list"
/>
diff --git a/app/assets/javascripts/reports/store/mutations.js b/app/assets/javascripts/reports/store/mutations.js
index 2a37f5b74fa..68f6de3a7ee 100644
--- a/app/assets/javascripts/reports/store/mutations.js
+++ b/app/assets/javascripts/reports/store/mutations.js
@@ -16,6 +16,7 @@ export default {
state.summary.total = response.summary.total;
state.summary.resolved = response.summary.resolved;
state.summary.failed = response.summary.failed;
+ state.summary.errored = response.summary.errored;
state.status = response.status;
state.reports = response.suites;
@@ -29,6 +30,7 @@ export default {
total: 0,
resolved: 0,
failed: 0,
+ errored: 0,
};
state.status = null;
},
diff --git a/app/assets/javascripts/reports/store/state.js b/app/assets/javascripts/reports/store/state.js
index d0b2d0a37f5..4f9eb53e787 100644
--- a/app/assets/javascripts/reports/store/state.js
+++ b/app/assets/javascripts/reports/store/state.js
@@ -13,6 +13,7 @@ export default () => ({
total: 0,
resolved: 0,
failed: 0,
+ errored: 0,
},
/**
@@ -23,10 +24,14 @@ export default () => ({
* total: {Number},
* resolved: {Number},
* failed: {Number},
+ * errored: {Number},
* },
* new_failures: {Array.<Object>},
* resolved_failures: {Array.<Object>},
* existing_failures: {Array.<Object>},
+ * new_errors: {Array.<Object>},
+ * resolved_errors: {Array.<Object>},
+ * existing_errors: {Array.<Object>},
* }
*/
reports: [],
diff --git a/app/assets/javascripts/reports/store/utils.js b/app/assets/javascripts/reports/store/utils.js
index 7381f038eaf..ce3ffaae703 100644
--- a/app/assets/javascripts/reports/store/utils.js
+++ b/app/assets/javascripts/reports/store/utils.js
@@ -8,10 +8,11 @@ import {
} from '../constants';
const textBuilder = results => {
- const { failed, resolved, total } = results;
+ const { failed, errored, resolved, total } = results;
- const failedString = failed
- ? n__('%d failed/error test result', '%d failed/error test results', failed)
+ const failedOrErrored = (failed || 0) + (errored || 0);
+ const failedString = failedOrErrored
+ ? n__('%d failed/error test result', '%d failed/error test results', failedOrErrored)
: null;
const resolvedString = resolved
? n__('%d fixed test result', '%d fixed test results', resolved)
@@ -20,7 +21,7 @@ const textBuilder = results => {
let resultsString = s__('Reports|no changed test results');
- if (failed) {
+ if (failedOrErrored) {
if (resolved) {
resultsString = sprintf(s__('Reports|%{failedString} and %{resolvedString}'), {
failedString,
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index ed2c39e3fd3..5e14339bb07 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -86,8 +86,12 @@ module Boards
head(:forbidden) unless can?(current_user, :admin_issue, board)
end
+ def serializer_options(issues)
+ {}
+ end
+
def render_issues(issues, metadata)
- data = { issues: serialize_as_json(issues) }
+ data = { issues: serialize_as_json(issues, opts: serializer_options(issues)) }
data.merge!(metadata)
render json: data
@@ -133,8 +137,10 @@ module Boards
IssueSerializer.new(current_user: current_user)
end
- def serialize_as_json(resource)
- serializer.represent(resource, serializer: 'board', include_full_project_path: board.group_board?)
+ def serialize_as_json(resource, opts: {})
+ opts.merge!(include_full_project_path: board.group_board?, serializer: 'board')
+
+ serializer.represent(resource, opts)
end
def whitelist_query_limiting
diff --git a/app/graphql/mutations/notes/update.rb b/app/graphql/mutations/notes/update.rb
deleted file mode 100644
index ebf57b800c0..00000000000
--- a/app/graphql/mutations/notes/update.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Mutations
- module Notes
- class Update < Base
- graphql_name 'UpdateNote'
-
- authorize :admin_note
-
- argument :id,
- GraphQL::ID_TYPE,
- required: true,
- description: 'The global id of the note to update'
-
- argument :body,
- GraphQL::STRING_TYPE,
- required: true,
- description: copy_field_description(Types::Notes::NoteType, :body)
-
- def resolve(args)
- note = authorized_find!(id: args[:id])
-
- check_object_is_note!(note)
-
- note = ::Notes::UpdateService.new(
- note.project,
- current_user,
- { note: args[:body] }
- ).execute(note)
-
- {
- note: note.reset,
- errors: errors_on_object(note)
- }
- end
- end
- end
-end
diff --git a/app/graphql/mutations/notes/update/base.rb b/app/graphql/mutations/notes/update/base.rb
new file mode 100644
index 00000000000..9a53337f253
--- /dev/null
+++ b/app/graphql/mutations/notes/update/base.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Update
+ # This is a Base class for the Note update mutations and is not
+ # mounted as a GraphQL mutation itself.
+ class Base < Mutations::Notes::Base
+ authorize :admin_note
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the note to update'
+
+ def resolve(args)
+ note = authorized_find!(id: args[:id])
+
+ pre_update_checks!(note, args)
+
+ updated_note = ::Notes::UpdateService.new(
+ note.project,
+ current_user,
+ note_params(note, args)
+ ).execute(note)
+
+ # It's possible for updated_note to be `nil`, in the situation
+ # where the note is deleted within `Notes::UpdateService` due to
+ # the body of the note only containing Quick Actions.
+ {
+ note: updated_note&.reset,
+ errors: updated_note ? errors_on_object(updated_note) : []
+ }
+ end
+
+ private
+
+ def pre_update_checks!(_note, _args)
+ raise NotImplementedError
+ end
+
+ def note_params(_note, args)
+ { note: args[:body] }.compact
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/update/image_diff_note.rb b/app/graphql/mutations/notes/update/image_diff_note.rb
new file mode 100644
index 00000000000..7aad3af1e04
--- /dev/null
+++ b/app/graphql/mutations/notes/update/image_diff_note.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Update
+ class ImageDiffNote < Mutations::Notes::Update::Base
+ graphql_name 'UpdateImageDiffNote'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ argument :position,
+ Types::Notes::UpdateDiffImagePositionInputType,
+ required: false,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ def ready?(**args)
+ # As both arguments are optional, validate here that one of the
+ # arguments are present.
+ #
+ # This may be able to be done using InputUnions in the future
+ # if this RFC is merged:
+ # https://github.com/graphql/graphql-spec/blob/master/rfcs/InputUnion.md
+ if args.values_at(:body, :position).compact.blank?
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'body or position arguments are required'
+ end
+
+ super(args)
+ end
+
+ private
+
+ def pre_update_checks!(note, args)
+ unless note.is_a?(DiffNote) && note.position.on_image?
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Resource is not an ImageDiffNote'
+ end
+ end
+
+ def note_params(note, args)
+ super(note, args).merge(
+ position: position_params(note, args)
+ ).compact
+ end
+
+ def position_params(note, args)
+ new_position = args[:position]&.to_h&.compact
+ return unless new_position
+
+ original_position = note.position.to_h
+
+ Gitlab::Diff::Position.new(original_position.merge(new_position))
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/update/note.rb b/app/graphql/mutations/notes/update/note.rb
new file mode 100644
index 00000000000..03a174fc8d9
--- /dev/null
+++ b/app/graphql/mutations/notes/update/note.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Update
+ class Note < Mutations::Notes::Update::Base
+ graphql_name 'UpdateNote'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ private
+
+ def pre_update_checks!(note, _args)
+ check_object_is_note!(note)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index fc0a2a099df..ee0f4dbb05f 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -4,7 +4,7 @@ module Types
class MutationType < BaseObject
include Gitlab::Graphql::MountMutation
- graphql_name "Mutation"
+ graphql_name 'Mutation'
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
@@ -20,7 +20,14 @@ module Types
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
- mount_mutation Mutations::Notes::Update
+ mount_mutation Mutations::Notes::Update::Note,
+ description: 'Updates a Note. If the body of the Note contains only quick actions, ' \
+ 'the Note will be destroyed during the update, and no Note will be ' \
+ 'returned'
+ mount_mutation Mutations::Notes::Update::ImageDiffNote,
+ description: 'Updates a DiffNote on an image (a `Note` where the `position.positionType` is `"image"`). ' \
+ 'If the body of the Note contains only quick actions, the Note will be ' \
+ 'destroyed during the update, and no Note will be returned'
mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore
diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb
index 654562da0a7..cc00feba2e6 100644
--- a/app/graphql/types/notes/diff_position_type.rb
+++ b/app/graphql/types/notes/diff_position_type.rb
@@ -29,10 +29,10 @@ module Types
# Fields for image positions
field :x, GraphQL::INT_TYPE, null: true,
- description: 'X position on which the comment was made',
+ description: 'X position of the note',
resolve: -> (position, _args, _ctx) { position.x if position.on_image? }
field :y, GraphQL::INT_TYPE, null: true,
- description: 'Y position on which the comment was made',
+ description: 'Y position of the note',
resolve: -> (position, _args, _ctx) { position.y if position.on_image? }
field :width, GraphQL::INT_TYPE, null: true,
description: 'Total width of the image',
diff --git a/app/graphql/types/notes/update_diff_image_position_input_type.rb b/app/graphql/types/notes/update_diff_image_position_input_type.rb
new file mode 100644
index 00000000000..af99764f9f2
--- /dev/null
+++ b/app/graphql/types/notes/update_diff_image_position_input_type.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # InputType used for updateImageDiffNote mutation.
+ #
+ # rubocop: disable Graphql/AuthorizeTypes
+ class UpdateDiffImagePositionInputType < BaseInputObject
+ graphql_name 'UpdateDiffImagePositionInput'
+
+ argument :x, GraphQL::INT_TYPE,
+ required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :x)
+
+ argument :y, GraphQL::INT_TYPE,
+ required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :y)
+
+ argument :width, GraphQL::INT_TYPE,
+ required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :width)
+
+ argument :height, GraphQL::INT_TYPE,
+ required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :height)
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4ed99b229b5..023790f7d87 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -720,8 +720,7 @@ module ProjectsHelper
end
def settings_container_registry_expiration_policy_available?(project)
- Feature.enabled?(:registry_retention_policies_settings, project) &&
- Gitlab.config.registry.enabled &&
+ Gitlab.config.registry.enabled &&
can?(current_user, :destroy_container_image, project)
end
end
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 95bb52d8f97..773b9fead3a 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -15,7 +15,13 @@ module Emails
def pipeline_mail(pipeline, recipients, status)
@project = pipeline.project
@pipeline = pipeline
- @merge_request = pipeline.all_merge_requests.first
+
+ @merge_request = if pipeline.merge_request?
+ pipeline.merge_request
+ else
+ pipeline.merge_requests_as_head_pipeline.first
+ end
+
add_headers
# We use bcc here because we don't want to generate these emails for a
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index dde73b567db..39e8408f794 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -15,8 +15,8 @@ module Analytics
validates :name, exclusion: { in: Gitlab::Analytics::CycleAnalytics::DefaultStages.names }, if: :custom?
validates :start_event_identifier, presence: true
validates :end_event_identifier, presence: true
- validates :start_event_label, presence: true, if: :start_event_label_based?
- validates :end_event_label, presence: true, if: :end_event_label_based?
+ validates :start_event_label_id, presence: true, if: :start_event_label_based?
+ validates :end_event_label_id, presence: true, if: :end_event_label_based?
validate :validate_stage_event_pairs
validate :validate_labels
@@ -109,8 +109,8 @@ module Analytics
end
def validate_labels
- validate_label_within_group(:start_event_label, start_event_label_id) if start_event_label_id_changed?
- validate_label_within_group(:end_event_label, end_event_label_id) if end_event_label_id_changed?
+ validate_label_within_group(:start_event_label_id, start_event_label_id) if start_event_label_id_changed?
+ validate_label_within_group(:end_event_label_id, end_event_label_id) if end_event_label_id_changed?
end
def validate_label_within_group(association_name, label_id)
diff --git a/app/serializers/test_reports_comparer_entity.rb b/app/serializers/test_reports_comparer_entity.rb
index d7a3dd34fdc..5f8a68338cc 100644
--- a/app/serializers/test_reports_comparer_entity.rb
+++ b/app/serializers/test_reports_comparer_entity.rb
@@ -7,6 +7,7 @@ class TestReportsComparerEntity < Grape::Entity
expose :total_count, as: :total
expose :resolved_count, as: :resolved
expose :failed_count, as: :failed
+ expose :error_count, as: :errored
end
expose :suite_comparers, as: :suites, using: TestSuiteComparerEntity
diff --git a/app/serializers/test_suite_comparer_entity.rb b/app/serializers/test_suite_comparer_entity.rb
index d402a4d5718..78c243f75b8 100644
--- a/app/serializers/test_suite_comparer_entity.rb
+++ b/app/serializers/test_suite_comparer_entity.rb
@@ -11,6 +11,7 @@ class TestSuiteComparerEntity < Grape::Entity
expose :total_count, as: :total
expose :resolved_count, as: :resolved
expose :failed_count, as: :failed
+ expose :error_count, as: :errored
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -28,6 +29,20 @@ class TestSuiteComparerEntity < Grape::Entity
max_tests(suite.new_failures, suite.existing_failures))
end
+ expose :new_errors, using: TestCaseEntity do |suite|
+ suite.new_errors.take(max_tests)
+ end
+
+ expose :existing_errors, using: TestCaseEntity do |suite|
+ suite.existing_errors.take(
+ max_tests(suite.new_errors))
+ end
+
+ expose :resolved_errors, using: TestCaseEntity do |suite|
+ suite.resolved_errors.take(
+ max_tests(suite.new_errors, suite.existing_errors))
+ end
+
private
def max_tests(*used)
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index 51860adca77..9e87bebbe4e 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -24,7 +24,9 @@ module Snippets
spam_check(snippet, current_user)
snippet_saved = snippet.with_transaction_returning_status do
- snippet.save
+ if snippet.save && snippet.store_mentions!
+ create_repository_for(snippet, current_user)
+ end
end
if snippet_saved
@@ -36,5 +38,11 @@ module Snippets
snippet_error_response(snippet, 400)
end
end
+
+ private
+
+ def create_repository_for(snippet, user)
+ snippet.create_repository if Feature.enabled?(:version_snippets, user)
+ end
end
end
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 0affd9f0e6f..438d390389c 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -27,7 +27,9 @@
.form-group.row
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
- = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control'
+ .select-wrapper
+ = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control select-control'
+ = icon('chevron-down')
.form-group.row
.col-sm-12= f.label :content, class: 'control-label-full-width'
diff --git a/changelogs/unreleased/196843-remove-feature-flag.yml b/changelogs/unreleased/196843-remove-feature-flag.yml
new file mode 100644
index 00000000000..b544afb4344
--- /dev/null
+++ b/changelogs/unreleased/196843-remove-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Container Registry tag expiration policy settings
+merge_request: 25123
+author:
+type: added
diff --git a/changelogs/unreleased/34353-graphql-mutation-update-image-diff-note.yml b/changelogs/unreleased/34353-graphql-mutation-update-image-diff-note.yml
new file mode 100644
index 00000000000..d4e18d0cdb6
--- /dev/null
+++ b/changelogs/unreleased/34353-graphql-mutation-update-image-diff-note.yml
@@ -0,0 +1,5 @@
+---
+title: Add updateImageDiffNote mutation
+merge_request: 24027
+author:
+type: added
diff --git a/changelogs/unreleased/36505-nuget-the-search-service.yml b/changelogs/unreleased/36505-nuget-the-search-service.yml
new file mode 100644
index 00000000000..5dde4d401a0
--- /dev/null
+++ b/changelogs/unreleased/36505-nuget-the-search-service.yml
@@ -0,0 +1,5 @@
+---
+title: Add specialized index to packages_packages database table
+merge_request: 24182
+author:
+type: other
diff --git a/changelogs/unreleased/Updated-new-edit-wiki-page-to-equal-ui-elements.yml b/changelogs/unreleased/Updated-new-edit-wiki-page-to-equal-ui-elements.yml
new file mode 100644
index 00000000000..d1974b15ecc
--- /dev/null
+++ b/changelogs/unreleased/Updated-new-edit-wiki-page-to-equal-ui-elements.yml
@@ -0,0 +1,5 @@
+---
+title: Updated ui elements in wiki page creation
+merge_request: 25197
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/bvl-correct-thread-count.yml b/changelogs/unreleased/bvl-correct-thread-count.yml
new file mode 100644
index 00000000000..7cee02d849c
--- /dev/null
+++ b/changelogs/unreleased/bvl-correct-thread-count.yml
@@ -0,0 +1,5 @@
+---
+title: Fix sidekiq jobs not always getting a database connection when running with low concurrency
+merge_request: 25261
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-blocked-status.yml b/changelogs/unreleased/fix-blocked-status.yml
new file mode 100644
index 00000000000..5470fc06443
--- /dev/null
+++ b/changelogs/unreleased/fix-blocked-status.yml
@@ -0,0 +1,5 @@
+---
+title: Show blocked status for all blocked issues on issue boards
+merge_request: 24631
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-wrong-mr-id-in-pipeline-failure-email.yml b/changelogs/unreleased/fix-wrong-mr-id-in-pipeline-failure-email.yml
new file mode 100644
index 00000000000..f73ffe39eb5
--- /dev/null
+++ b/changelogs/unreleased/fix-wrong-mr-id-in-pipeline-failure-email.yml
@@ -0,0 +1,5 @@
+---
+title: Fix wrong MR link is shown on pipeline failure email
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-39199-create-snippet-repository.yml b/changelogs/unreleased/fj-39199-create-snippet-repository.yml
new file mode 100644
index 00000000000..d5ab2d7f1a4
--- /dev/null
+++ b/changelogs/unreleased/fj-39199-create-snippet-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Create snippet repository when it's created
+merge_request: 22269
+author:
+type: added
diff --git a/changelogs/unreleased/refactoring-entities-file-final.yml b/changelogs/unreleased/refactoring-entities-file-final.yml
new file mode 100644
index 00000000000..80f5d5aca7a
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-final.yml
@@ -0,0 +1,5 @@
+---
+title: Separate project entity into own class file
+merge_request: 25297
+author: Rajendra Kadam
+type: added
diff --git a/db/migrate/20200207090921_add_nuget_index_to_packages_packages.rb b/db/migrate/20200207090921_add_nuget_index_to_packages_packages.rb
new file mode 100644
index 00000000000..1bd9c4dc0d0
--- /dev/null
+++ b/db/migrate/20200207090921_add_nuget_index_to_packages_packages.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddNugetIndexToPackagesPackages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_packages_project_id_name_partial_for_nuget'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :packages_packages, [:project_id, :name], name: INDEX_NAME, where: "name <> 'NuGet.Temporary.Package' AND version is not null AND package_type = 4"
+ end
+
+ def down
+ remove_concurrent_index_by_name :packages_packages, INDEX_NAME
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7b27b2524ce..8836ace2312 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3012,6 +3012,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_204737) do
t.index ["name"], name: "index_packages_packages_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["project_id", "created_at"], name: "index_packages_packages_on_project_id_and_created_at"
t.index ["project_id", "name", "version", "package_type"], name: "idx_packages_packages_on_project_id_name_version_package_type"
+ t.index ["project_id", "name"], name: "index_packages_project_id_name_partial_for_nuget", where: "(((name)::text <> 'NuGet.Temporary.Package'::text) AND (version IS NOT NULL) AND (package_type = 4))"
t.index ["project_id", "package_type"], name: "index_packages_packages_on_project_id_and_package_type"
t.index ["project_id", "version"], name: "index_packages_packages_on_project_id_and_version"
end
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index ecf34b14aa0..f698db84982 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1406,12 +1406,12 @@ input DiffImagePositionInput {
width: Int!
"""
- X position on which the comment was made
+ X position of the note
"""
x: Int!
"""
- Y position on which the comment was made
+ Y position of the note
"""
y: Int!
}
@@ -1475,12 +1475,12 @@ type DiffPosition {
width: Int
"""
- X position on which the comment was made
+ X position of the note
"""
x: Int
"""
- Y position on which the comment was made
+ Y position of the note
"""
y: Int
}
@@ -4660,6 +4660,18 @@ type Mutation {
todosMarkAllDone(input: TodosMarkAllDoneInput!): TodosMarkAllDonePayload
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
+
+ """
+ Updates a DiffNote on an image (a `Note` where the `position.positionType` is
+ `"image"`). If the body of the Note contains only quick actions, the Note will
+ be destroyed during the update, and no Note will be returned
+ """
+ updateImageDiffNote(input: UpdateImageDiffNoteInput!): UpdateImageDiffNotePayload
+
+ """
+ Updates a Note. If the body of the Note contains only quick actions, the Note
+ will be destroyed during the update, and no Note will be returned
+ """
updateNote(input: UpdateNoteInput!): UpdateNotePayload
updateSnippet(input: UpdateSnippetInput!): UpdateSnippetPayload
}
@@ -7533,6 +7545,28 @@ enum TypeEnum {
project
}
+input UpdateDiffImagePositionInput {
+ """
+ Total height of the image
+ """
+ height: Int
+
+ """
+ Total width of the image
+ """
+ width: Int
+
+ """
+ X position of the note
+ """
+ x: Int
+
+ """
+ Y position of the note
+ """
+ y: Int
+}
+
"""
Autogenerated input type of UpdateEpic
"""
@@ -7619,6 +7653,51 @@ type UpdateEpicPayload {
}
"""
+Autogenerated input type of UpdateImageDiffNote
+"""
+input UpdateImageDiffNoteInput {
+ """
+ Content of the note
+ """
+ body: String
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The global id of the note to update
+ """
+ id: ID!
+
+ """
+ The position of this note on a diff
+ """
+ position: UpdateDiffImagePositionInput
+}
+
+"""
+Autogenerated return type of UpdateImageDiffNote
+"""
+type UpdateImageDiffNotePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Reasons why the mutation failed.
+ """
+ errors: [String!]!
+
+ """
+ The note after mutation
+ """
+ note: Note
+}
+
+"""
Autogenerated input type of UpdateNote
"""
input UpdateNoteInput {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 33252993682..38a836acb6b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -8063,7 +8063,7 @@
},
{
"name": "x",
- "description": "X position on which the comment was made",
+ "description": "X position of the note",
"args": [
],
@@ -8077,7 +8077,7 @@
},
{
"name": "y",
- "description": "Y position on which the comment was made",
+ "description": "Y position of the note",
"args": [
],
@@ -19427,8 +19427,35 @@
"deprecationReason": null
},
{
+ "name": "updateImageDiffNote",
+ "description": "Updates a DiffNote on an image (a `Note` where the `position.positionType` is `\"image\"`). If the body of the Note contains only quick actions, the Note will be destroyed during the update, and no Note will be returned",
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateImageDiffNoteInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "UpdateImageDiffNotePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updateNote",
- "description": null,
+ "description": "Updates a Note. If the body of the Note contains only quick actions, the Note will be destroyed during the update, and no Note will be returned",
"args": [
{
"name": "input",
@@ -21640,7 +21667,7 @@
},
{
"name": "x",
- "description": "X position on which the comment was made",
+ "description": "X position of the note",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -21654,7 +21681,7 @@
},
{
"name": "y",
- "description": "Y position on which the comment was made",
+ "description": "Y position of the note",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -21817,6 +21844,179 @@
},
{
"kind": "OBJECT",
+ "name": "UpdateImageDiffNotePayload",
+ "description": "Autogenerated return type of UpdateImageDiffNote",
+ "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": "Reasons why the mutation failed.",
+ "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": "note",
+ "description": "The note after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Note",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateImageDiffNoteInput",
+ "description": "Autogenerated input type of UpdateImageDiffNote",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": "The global id of the note to update",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "body",
+ "description": "Content of the note",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "position",
+ "description": "The position of this note on a diff",
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateDiffImagePositionInput",
+ "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": "INPUT_OBJECT",
+ "name": "UpdateDiffImagePositionInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "x",
+ "description": "X position of the note",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "y",
+ "description": "Y position of the note",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "width",
+ "description": "Total width of the image",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "height",
+ "description": "Total height of the image",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "DestroyNotePayload",
"description": "Autogenerated return type of DestroyNote",
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 43e7aef59e8..c1a5882eb25 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -246,8 +246,8 @@ Autogenerated return type of DestroySnippet
| `oldPath` | String | Path of the file on the start SHA |
| `positionType` | DiffPositionType! | Type of file the position refers to |
| `width` | Int | Total width of the image |
-| `x` | Int | X position on which the comment was made |
-| `y` | Int | Y position on which the comment was made |
+| `x` | Int | X position of the note |
+| `y` | Int | Y position of the note |
## DiffRefs
@@ -1230,6 +1230,16 @@ Autogenerated return type of UpdateEpic
| `epic` | Epic | The epic after mutation |
| `errors` | String! => Array | Reasons why the mutation failed. |
+## UpdateImageDiffNotePayload
+
+Autogenerated return type of UpdateImageDiffNote
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `note` | Note | The note after mutation |
+
## UpdateNotePayload
Autogenerated return type of UpdateNote
diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md
index 572265b5b09..36dd9b5d6bf 100644
--- a/doc/user/analytics/productivity_analytics.md
+++ b/doc/user/analytics/productivity_analytics.md
@@ -25,10 +25,7 @@ Productivity Analytics allows GitLab users to:
## Accessing metrics and visualizations
-To access the **Productivity Analytics** page:
-
-1. Go to **Analytics** from the top navigation bar.
-1. Select **Productivity Analytics** from the menu.
+To access the chart, navigate to a group's sidebar and select **Analytics > Productivity Analytics**.
The following metrics and visualizations are available on a project or group level - currently only covering **merged** merge requests:
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 3bdda338b76..ff15b299cea 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -137,7 +137,7 @@ build:
If you want to whitelist specific vulnerabilities, you'll need to:
- 1. Set `GIT_STRATEGY: fetch` in your `.gitlab-ci.yml` file by following the instructions described in the
+ 1. Set [`GIT_STRATEGY: fetch`](../../../ci/yaml/README.md#git-strategy) in your `.gitlab-ci.yml` file by following the instructions described in the
[overriding the Container Scanning template](#overriding-the-container-scanning-template) section of this document.
1. Define the whitelisted vulnerabilities in a YAML file named `clair-whitelist.yml` which must use the format described
in the [following whitelist example file](https://github.com/arminc/clair-scanner/blob/v12/example-whitelist.yaml).
@@ -163,18 +163,19 @@ container_scanning:
Container Scanning can be [configured](#overriding-the-container-scanning-template)
using environment variables.
-| Environment Variable | Description | Default |
-| ------ | ------ | ------ |
-| `KLAR_TRACE` | Set to true to enable more verbose output from klar. | `"false"` |
-| `DOCKER_USER` | Username for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_USER` |
-| `DOCKER_PASSWORD` | Password for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_PASSWORD` |
-| `CLAIR_OUTPUT` | Severity level threshold. Vulnerabilities with severity level higher than or equal to this threshold will be outputted. Supported levels are `Unknown`, `Negligible`, `Low`, `Medium`, `High`, `Critical` and `Defcon1`. | `Unknown` |
-| `REGISTRY_INSECURE` | Allow [Klar](https://github.com/optiopay/klar) to access insecure registries (HTTP only). Should only be set to `true` when testing the image locally. | `"false"` |
+| Environment Variable | Description | Default |
+| ------ | ------ | ------ |
+| `KLAR_TRACE` | Set to true to enable more verbose output from klar. | `"false"` |
+| `DOCKER_USER` | Username for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_USER` |
+| `DOCKER_PASSWORD` | Password for accessing a Docker registry requiring authentication. | `$CI_REGISTRY_PASSWORD` |
+| `CLAIR_OUTPUT` | Severity level threshold. Vulnerabilities with severity level higher than or equal to this threshold will be outputted. Supported levels are `Unknown`, `Negligible`, `Low`, `Medium`, `High`, `Critical` and `Defcon1`. | `Unknown` |
+| `REGISTRY_INSECURE` | Allow [Klar](https://github.com/optiopay/klar) to access insecure registries (HTTP only). Should only be set to `true` when testing the image locally. | `"false"` |
| `CLAIR_VULNERABILITIES_DB_URL` | This variable is explicitly set in the [services section](https://gitlab.com/gitlab-org/gitlab/blob/30522ca8b901223ac8c32b633d8d67f340b159c1/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml#L17-19) of the `Container-Scanning.gitlab-ci.yml` file and defaults to `clair-vulnerabilities-db`. This value represents the address that the [postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db) is running on and **shouldn't be changed** unless you're running the image locally as described in the [Running the scanning tool](https://gitlab.com/gitlab-org/security-products/analyzers/klar/#running-the-scanning-tool) section of the [GitLab klar analyzer readme](https://gitlab.com/gitlab-org/security-products/analyzers/klar). | `clair-vulnerabilities-db` |
-| `CI_APPLICATION_REPOSITORY` | Docker repository URL for the image to be scanned. | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` |
-| `CI_APPLICATION_TAG` | Docker respository tag for the image to be scanned. | `$CI_COMMIT_SHA` |
-| `CLAIR_DB_IMAGE` | The Docker image name and tag for the [postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes, or to refer to a locally hosted vulnerabilities database for an on-premise air-gapped installation. | `arminc/clair-db:latest` |
-| `CLAIR_DB_IMAGE_TAG` | (**DEPRECATED - use `CLAIR_DB_IMAGE` instead**) The Docker image tag for the [postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes. | `latest` |
+| `CI_APPLICATION_REPOSITORY` | Docker repository URL for the image to be scanned. | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` |
+| `CI_APPLICATION_TAG` | Docker respository tag for the image to be scanned. | `$CI_COMMIT_SHA` |
+| `CLAIR_DB_IMAGE` | The Docker image name and tag for the [Postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes, or to refer to a locally hosted vulnerabilities database for an on-premise air-gapped installation. | `arminc/clair-db:latest` |
+| `CLAIR_DB_IMAGE_TAG` | (**DEPRECATED - use `CLAIR_DB_IMAGE` instead**) The Docker image tag for the [Postgres server hosting the vulnerabilities definitions](https://hub.docker.com/r/arminc/clair-db). It can be useful to override this value with a specific version, for example, to provide a consistent set of vulnerabilities for integration testing purposes. | `latest` |
+| `DOCKERFILE_PATH` | The path to the `Dockerfile` to be used for generating remediations. By default, the scanner will look for a file named `Dockerfile` in the root directory of the project, so this variable should only be configured if your `Dockerfile` is in a non-standard location, such as a subdirectory. See [Solutions for vulnerabilities](#solutions-for-vulnerabilities-auto-remediation) for more details. | `Dockerfile` |
## Security Dashboard
@@ -187,6 +188,19 @@ vulnerabilities in your groups, projects and pipelines. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+## Solutions for vulnerabilities (auto-remediation)
+
+Some vulnerabilities can be fixed by applying the solution that GitLab
+automatically generates.
+
+To enable remediation support, the scanning tool _must_ have access to the `Dockerfile` specified by
+the `DOCKERFILE_PATH` environment variable. To ensure that the scanning tool has access to this
+file, it's necessary to set [`GIT_STRATEGY: fetch`](../../../ci/yaml/README.md#git-strategy) in
+your `.gitlab-ci.yml` file by following the instructions described in this document's
+[overriding the Container Scanning template](#overriding-the-container-scanning-template) section.
+
+Read more about the [solutions for vulnerabilities](../index.md#solutions-for-vulnerabilities-auto-remediation).
+
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index a48152c2aab..13ea45816b8 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -112,6 +112,7 @@ automatically generates. The following scanners are supported:
- [Dependency Scanning](dependency_scanning/index.md):
Automatic Patch creation is only available for Node.js projects managed with
`yarn`.
+- [Container Scanning](container_scanning/index.md)
#### Manually applying the suggested patch
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 627aeac54d6..58da77697d8 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -144,8 +144,10 @@ which you can start a new discussion:
![Starting a new discussion on design](img/adding_note_to_design_1.png)
-From GitLab 12.8 on, when you are starting a new discussion, you can adjust the badge's position by
-dragging it around the image.
+[Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34353) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8,
+you can adjust the badge's position by dragging it around the image. This is useful
+for when your design layout has changed between revisions, or if you need to move an
+existing badge to add a new one in its place.
Different discussions have different badge numbers:
diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb
index 6ca2d1e6da4..e9572a8d430 100644
--- a/lib/api/entities/application_setting.rb
+++ b/lib/api/entities/application_setting.rb
@@ -34,3 +34,5 @@ module API
end
end
end
+
+API::Entities::ApplicationSetting.prepend_if_ee('EE::API::Entities::ApplicationSetting')
diff --git a/lib/api/entities/board.rb b/lib/api/entities/board.rb
index afbf5b4b65b..5bb1cde0fa9 100644
--- a/lib/api/entities/board.rb
+++ b/lib/api/entities/board.rb
@@ -12,3 +12,5 @@ module API
end
end
end
+
+API::Entities::Board.prepend_if_ee('EE::API::Entities::Board')
diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb
index 8bcad5bca35..ae5ee4784ed 100644
--- a/lib/api/entities/group.rb
+++ b/lib/api/entities/group.rb
@@ -34,3 +34,5 @@ module API
end
end
end
+
+API::Entities::Group.prepend_if_ee('EE::API::Entities::Group', with_descendants: true)
diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb
index 97b98aac585..e03047a6e75 100644
--- a/lib/api/entities/group_detail.rb
+++ b/lib/api/entities/group_detail.rb
@@ -34,3 +34,5 @@ module API
end
end
end
+
+API::Entities::GroupDetail.prepend_if_ee('EE::API::Entities::GroupDetail')
diff --git a/lib/api/entities/identity.rb b/lib/api/entities/identity.rb
index 8969a0256c7..52045b6250a 100644
--- a/lib/api/entities/identity.rb
+++ b/lib/api/entities/identity.rb
@@ -7,3 +7,5 @@ module API
end
end
end
+
+API::Entities::Identity.prepend_if_ee('EE::API::Entities::Identity')
diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb
index b7eb22b2aba..5f2609cf68b 100644
--- a/lib/api/entities/issue.rb
+++ b/lib/api/entities/issue.rb
@@ -46,3 +46,5 @@ module API
end
end
end
+
+API::Entities::Issue.prepend_if_ee('EE::API::Entities::Issue')
diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb
index 7e4be35d20b..af92f4124f1 100644
--- a/lib/api/entities/issue_basic.rb
+++ b/lib/api/entities/issue_basic.rb
@@ -41,3 +41,5 @@ module API
end
end
end
+
+API::Entities::IssueBasic.prepend_if_ee('EE::API::Entities::IssueBasic', with_descendants: true)
diff --git a/lib/api/entities/list.rb b/lib/api/entities/list.rb
index e856359efc1..480e722c22c 100644
--- a/lib/api/entities/list.rb
+++ b/lib/api/entities/list.rb
@@ -9,3 +9,5 @@ module API
end
end
end
+
+API::Entities::List.prepend_if_ee('EE::API::Entities::List')
diff --git a/lib/api/entities/member.rb b/lib/api/entities/member.rb
index 558f37d1a56..14e97f41e77 100644
--- a/lib/api/entities/member.rb
+++ b/lib/api/entities/member.rb
@@ -9,3 +9,5 @@ module API
end
end
end
+
+API::Entities::Member.prepend_if_ee('EE::API::Entities::Member', with_descendants: true)
diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb
index ce8bfa9e670..8cec2c1a97e 100644
--- a/lib/api/entities/merge_request_basic.rb
+++ b/lib/api/entities/merge_request_basic.rb
@@ -92,3 +92,5 @@ module API
end
end
end
+
+API::Entities::MergeRequestBasic.prepend_if_ee('EE::API::Entities::MergeRequestBasic', with_descendants: true)
diff --git a/lib/api/entities/namespace.rb b/lib/api/entities/namespace.rb
index b21dd29c7b4..a7e06cc3e02 100644
--- a/lib/api/entities/namespace.rb
+++ b/lib/api/entities/namespace.rb
@@ -13,3 +13,5 @@ module API
end
end
end
+
+API::Entities::Namespace.prepend_if_ee('EE::API::Entities::Namespace')
diff --git a/lib/api/entities.rb b/lib/api/entities/project.rb
index 479d662f3f5..6ed2ed34360 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities/project.rb
@@ -131,21 +131,4 @@ module API
end
end
-# rubocop: disable Cop/InjectEnterpriseEditionModule
-::API::Entities::ApplicationSetting.prepend_if_ee('EE::API::Entities::ApplicationSetting')
-::API::Entities::Board.prepend_if_ee('EE::API::Entities::Board')
-::API::Entities::Group.prepend_if_ee('EE::API::Entities::Group', with_descendants: true)
-::API::Entities::GroupDetail.prepend_if_ee('EE::API::Entities::GroupDetail')
-::API::Entities::IssueBasic.prepend_if_ee('EE::API::Entities::IssueBasic', with_descendants: true)
-::API::Entities::Issue.prepend_if_ee('EE::API::Entities::Issue')
-::API::Entities::List.prepend_if_ee('EE::API::Entities::List')
-::API::Entities::MergeRequestBasic.prepend_if_ee('EE::API::Entities::MergeRequestBasic', with_descendants: true)
-::API::Entities::Member.prepend_if_ee('EE::API::Entities::Member', with_descendants: true)
-::API::Entities::Namespace.prepend_if_ee('EE::API::Entities::Namespace')
-::API::Entities::Project.prepend_if_ee('EE::API::Entities::Project', with_descendants: true)
-::API::Entities::ProtectedRefAccess.prepend_if_ee('EE::API::Entities::ProtectedRefAccess')
-::API::Entities::UserPublic.prepend_if_ee('EE::API::Entities::UserPublic', with_descendants: true)
-::API::Entities::Todo.prepend_if_ee('EE::API::Entities::Todo')
-::API::Entities::ProtectedBranch.prepend_if_ee('EE::API::Entities::ProtectedBranch')
-::API::Entities::Identity.prepend_if_ee('EE::API::Entities::Identity')
-::API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin', with_descendants: true)
+API::Entities::Project.prepend_if_ee('EE::API::Entities::Project', with_descendants: true)
diff --git a/lib/api/entities/protected_branch.rb b/lib/api/entities/protected_branch.rb
index e41d497c836..80c8a791053 100644
--- a/lib/api/entities/protected_branch.rb
+++ b/lib/api/entities/protected_branch.rb
@@ -10,3 +10,5 @@ module API
end
end
end
+
+API::Entities::ProtectedBranch.prepend_if_ee('EE::API::Entities::ProtectedBranch')
diff --git a/lib/api/entities/protected_ref_access.rb b/lib/api/entities/protected_ref_access.rb
index ab878be45d2..f0185705b06 100644
--- a/lib/api/entities/protected_ref_access.rb
+++ b/lib/api/entities/protected_ref_access.rb
@@ -10,3 +10,5 @@ module API
end
end
end
+
+API::Entities::ProtectedRefAccess.prepend_if_ee('EE::API::Entities::ProtectedRefAccess')
diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb
index 820d1ceaadd..abfdde89bf1 100644
--- a/lib/api/entities/todo.rb
+++ b/lib/api/entities/todo.rb
@@ -44,3 +44,5 @@ module API
end
end
end
+
+API::Entities::Todo.prepend_if_ee('EE::API::Entities::Todo')
diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb
index 100f73760ca..15e9b905bef 100644
--- a/lib/api/entities/user_public.rb
+++ b/lib/api/entities/user_public.rb
@@ -17,3 +17,5 @@ module API
end
end
end
+
+API::Entities::UserPublic.prepend_if_ee('EE::API::Entities::UserPublic', with_descendants: true)
diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb
index c2f873ae802..d3df12200ff 100644
--- a/lib/api/entities/user_with_admin.rb
+++ b/lib/api/entities/user_with_admin.rb
@@ -7,3 +7,5 @@ module API
end
end
end
+
+API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin', with_descendants: true)
diff --git a/lib/gitlab/ci/parsers/test/junit.rb b/lib/gitlab/ci/parsers/test/junit.rb
index 8f8cae0b5f2..133eb16a83e 100644
--- a/lib/gitlab/ci/parsers/test/junit.rb
+++ b/lib/gitlab/ci/parsers/test/junit.rb
@@ -50,10 +50,7 @@ module Gitlab
status = ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED
system_output = data['failure']
elsif data['error']
- # For now, as an MVC, we are grouping error test cases together
- # with failed ones. But we will improve this further on
- # https://gitlab.com/gitlab-org/gitlab/issues/32046.
- status = ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED
+ status = ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR
system_output = data['error']
else
status = ::Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS
diff --git a/lib/gitlab/ci/reports/test_reports_comparer.rb b/lib/gitlab/ci/reports/test_reports_comparer.rb
index 11810bdc0a8..c6f17f0764f 100644
--- a/lib/gitlab/ci/reports/test_reports_comparer.rb
+++ b/lib/gitlab/ci/reports/test_reports_comparer.rb
@@ -29,7 +29,7 @@ module Gitlab
end
end
- %w(total_count resolved_count failed_count).each do |method|
+ %w(total_count resolved_count failed_count error_count).each do |method|
define_method(method) do
# rubocop: disable CodeReuse/ActiveRecord
suite_comparers.sum { |suite| suite.public_send(method) } # rubocop:disable GitlabSecurity/PublicSend
diff --git a/lib/gitlab/ci/reports/test_suite_comparer.rb b/lib/gitlab/ci/reports/test_suite_comparer.rb
index 9cb7db5934c..a58de43e55e 100644
--- a/lib/gitlab/ci/reports/test_suite_comparer.rb
+++ b/lib/gitlab/ci/reports/test_suite_comparer.rb
@@ -38,6 +38,30 @@ module Gitlab
end
end
+ def new_errors
+ strong_memoize(:new_errors) do
+ head_suite.error.reject do |key, _|
+ base_suite.error.include?(key)
+ end.values
+ end
+ end
+
+ def existing_errors
+ strong_memoize(:existing_errors) do
+ head_suite.error.select do |key, _|
+ base_suite.error.include?(key)
+ end.values
+ end
+ end
+
+ def resolved_errors
+ strong_memoize(:resolved_errors) do
+ head_suite.success.select do |key, _|
+ base_suite.error.include?(key)
+ end.values
+ end
+ end
+
def total_count
head_suite.total_count
end
@@ -47,12 +71,16 @@ module Gitlab
end
def resolved_count
- resolved_failures.count
+ resolved_failures.count + resolved_errors.count
end
def failed_count
new_failures.count + existing_failures.count
end
+
+ def error_count
+ new_errors.count + existing_errors.count
+ end
end
end
end
diff --git a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
index 5c790f3e0ab..2333fb4e947 100644
--- a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
@@ -27,7 +27,7 @@ license_scanning:
refs:
- branches
variables:
- - $GITLAB_FEATURES =~ /\blicense_management\b/
+ - $GITLAB_FEATURES =~ /\blicense_scanning\b/
except:
variables:
- $LICENSE_MANAGEMENT_DISABLED
diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb
index 3f6361c7276..bf579dd3b77 100644
--- a/lib/gitlab/runtime.rb
+++ b/lib/gitlab/runtime.rb
@@ -78,12 +78,16 @@ module Gitlab
end
def max_threads
+ main_thread = 1
+
if puma?
- Puma.cli_config.options[:max_threads]
+ Puma.cli_config.options[:max_threads] + main_thread
elsif sidekiq?
- Sidekiq.options[:concurrency]
+ # An extra thread for the poller in Sidekiq Cron:
+ # https://github.com/ondrejbartas/sidekiq-cron#under-the-hood
+ Sidekiq.options[:concurrency] + main_thread + 1
else
- 1
+ main_thread
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b8ed2c62e0c..ecf73002112 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -502,6 +502,9 @@ msgstr ""
msgid "'%{level}' is not a valid visibility level"
msgstr ""
+msgid "'%{name}' stage already exists"
+msgstr ""
+
msgid "'%{source}' is not a import source"
msgstr ""
@@ -6537,6 +6540,9 @@ msgstr ""
msgid "DesignManagement|Could not create new discussion. Please try again."
msgstr ""
+msgid "DesignManagement|Could not update discussion. Please try again."
+msgstr ""
+
msgid "DesignManagement|Delete"
msgstr ""
@@ -19338,6 +19344,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
+msgid "There was a problem refreshing the data, please try again"
+msgstr ""
+
msgid "There was a problem saving your custom stage, please try again"
msgstr ""
@@ -22394,6 +22403,9 @@ msgstr ""
msgid "Your comment could not be updated! Please check your network connection and try again."
msgstr ""
+msgid "Your custom stage '%{title}' was created"
+msgstr ""
+
msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
msgstr ""
diff --git a/spec/factories/container_expiration_policies.rb b/spec/factories/container_expiration_policies.rb
index 951127a4aa7..41c3a7f8cb9 100644
--- a/spec/factories/container_expiration_policies.rb
+++ b/spec/factories/container_expiration_policies.rb
@@ -2,7 +2,18 @@
FactoryBot.define do
factory :container_expiration_policy, class: 'ContainerExpirationPolicy' do
- association :project, factory: [:project, :without_container_expiration_policy]
+ # Note: because of the project_id primary_key on
+ # container_expiration_policies, and the create_container_expiration_policy
+ # callback on Project, we need to build the project first before assigning
+ # it to a container_expiration_policy.
+ #
+ # Also, if you wish to assign an existing project to a
+ # container_expiration_policy, you will then have to destroy the project's
+ # container_expiration_policy first.
+ before(:create) do |container_expiration_policy|
+ container_expiration_policy.project = build(:project) unless container_expiration_policy.project
+ end
+
cadence { '1d' }
enabled { true }
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 9be0b308680..ba38e1bb312 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -140,12 +140,6 @@ FactoryBot.define do
end
end
- trait :without_container_expiration_policy do
- after(:build) do |project|
- project.class.skip_callback(:create, :after, :create_container_expiration_policy, raise: false)
- end
- end
-
# Build a custom repository by specifying a hash of `filename => content` in
# the transient `files` attribute. Each file will be created in its own
# commit, operating against the master branch. So, the following call:
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index d12843d7150..94f57cdda74 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -699,6 +699,137 @@ describe 'Merge request > User sees merge widget', :js do
end
end
+ context 'when a new error exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_error)
+ end
+ end
+
+ it 'shows test reports summary which includes the new error' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 failed/error test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found no changed test results out of 1 total test')
+ expect(page).to have_content('junit found 1 failed/error test result out of 1 total test')
+ expect(page).to have_content('New')
+ expect(page).to have_content('addTest')
+ end
+ end
+ end
+
+ context 'when user clicks the new error' do
+ it 'shows the test report detail' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'addTest'
+
+ expect(page).to have_content('8.88')
+ end
+ end
+ end
+ end
+ end
+
+ context 'when an existing error exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_error)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_error)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ it 'shows test reports summary which includes the existing error' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 failed/error test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found 1 failed/error test result out of 1 total test')
+ expect(page).to have_content('junit found no changed test results out of 1 total test')
+ expect(page).not_to have_content('New')
+ expect(page).to have_content('Test#sum when a is 4 and b is 4 returns summary')
+ end
+ end
+ end
+
+ context 'when user clicks the existing error' do
+ it 'shows test report detail of it' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'Test#sum when a is 4 and b is 4 returns summary'
+
+ expect(page).to have_content('4.44')
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a resolved error exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_error)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ it 'shows test reports summary which includes the resolved error' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 fixed test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found no changed test results out of 1 total test')
+ expect(page).to have_content('junit found 1 fixed test result out of 1 total test')
+ expect(page).to have_content('addTest')
+ end
+ end
+ end
+
+ context 'when user clicks the resolved error' do
+ it 'shows test report detail of it' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'addTest'
+
+ expect(page).to have_content('5.55')
+ end
+ end
+ end
+ end
+ end
+
context 'properly truncates the report' do
let(:base_reports) do
Gitlab::Ci::Reports::TestReports.new.tap do |reports|
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index 4c5bc290402..fc1a85c3efe 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -10,7 +10,6 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy'
before do
sign_in(user)
stub_container_registry_config(enabled: true)
- stub_feature_flags(registry_retention_policies_settings: true)
end
context 'as owner' do
@@ -63,15 +62,4 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy'
expect(page).not_to have_selector('#js-registry-policies')
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(registry_retention_policies_settings: false)
- visit project_settings_ci_cd_path(project)
- end
-
- it 'does not exists' do
- expect(page).not_to have_selector('#js-registry-policies')
- end
- end
end
diff --git a/spec/fixtures/api/schemas/entities/test_reports_comparer.json b/spec/fixtures/api/schemas/entities/test_reports_comparer.json
index d7880801c01..03812527f71 100644
--- a/spec/fixtures/api/schemas/entities/test_reports_comparer.json
+++ b/spec/fixtures/api/schemas/entities/test_reports_comparer.json
@@ -12,11 +12,13 @@
"properties": {
"total": { "type": "integer" },
"resolved": { "type": "integer" },
+ "errored": { "type": "integer" },
"failed": { "type": "integer" }
},
"required": [
"total",
"resolved",
+ "errored",
"failed"
]
},
diff --git a/spec/fixtures/api/schemas/entities/test_suite_comparer.json b/spec/fixtures/api/schemas/entities/test_suite_comparer.json
index d63fea1f0db..ecb331ae013 100644
--- a/spec/fixtures/api/schemas/entities/test_suite_comparer.json
+++ b/spec/fixtures/api/schemas/entities/test_suite_comparer.json
@@ -16,17 +16,17 @@
"properties": {
"total": { "type": "integer" },
"resolved": { "type": "integer" },
+ "errored": { "type": "integer" },
"failed": { "type": "integer" }
},
- "required": [
- "total",
- "resolved",
- "failed"
- ]
+ "required": ["total", "resolved", "errored", "failed"]
},
"new_failures": { "type": "array", "items": { "$ref": "test_case.json" } },
"resolved_failures": { "type": "array", "items": { "$ref": "test_case.json" } },
- "existing_failures": { "type": "array", "items": { "$ref": "test_case.json" } }
+ "existing_failures": { "type": "array", "items": { "$ref": "test_case.json" } },
+ "new_errors": { "type": "array", "items": { "$ref": "test_case.json" } },
+ "resolved_errors": { "type": "array", "items": { "$ref": "test_case.json" } },
+ "existing_errors": { "type": "array", "items": { "$ref": "test_case.json" } }
},
"additionalProperties": false
}
diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js
index 81f6b60ae25..980def06078 100644
--- a/spec/frontend/issuables_list/components/issuable_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_spec.js
@@ -122,6 +122,10 @@ describe('Issuable component', () => {
expect(finder().exists()).toBe(false);
});
+ it('show relative reference path', () => {
+ expect(wrapper.find('.js-ref-path').text()).toBe(issuable.references.relative);
+ });
+
it('does not have closed text', () => {
expect(wrapper.text()).not.toContain(TEXT_CLOSED);
});
diff --git a/spec/frontend/issuables_list/issuable_list_test_data.js b/spec/frontend/issuables_list/issuable_list_test_data.js
index 617780fd736..19d8ee7f71a 100644
--- a/spec/frontend/issuables_list/issuable_list_test_data.js
+++ b/spec/frontend/issuables_list/issuable_list_test_data.js
@@ -26,6 +26,9 @@ export const simpleIssue = {
web_url: 'http://localhost:3001/h5bp/html5-boilerplate/issues/31',
has_tasks: false,
weight: null,
+ references: {
+ relative: 'html-boilerplate#45',
+ },
};
export const testLabels = [
diff --git a/spec/frontend/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js
index f0141b9e162..0d9a8dd4585 100644
--- a/spec/frontend/reports/store/utils_spec.js
+++ b/spec/frontend/reports/store/utils_spec.js
@@ -35,6 +35,16 @@ describe('Reports store utils', () => {
);
});
+ it('should render text for multiple errored results', () => {
+ const name = 'Test summary';
+ const data = { errored: 7, total: 10 };
+ const result = utils.summaryTextBuilder(name, data);
+
+ expect(result).toBe(
+ 'Test summary contained 7 failed/error test results out of 10 total tests',
+ );
+ });
+
it('should render text for multiple fixed results', () => {
const name = 'Test summary';
const data = { resolved: 4, total: 10 };
@@ -62,6 +72,27 @@ describe('Reports store utils', () => {
'Test summary contained 1 failed/error test result and 1 fixed test result out of 10 total tests',
);
});
+
+ it('should render text for singular failed, errored, and fixed results', () => {
+ // these will be singular when the copy is updated
+ const name = 'Test summary';
+ const data = { failed: 1, errored: 1, resolved: 1, total: 10 };
+ const result = utils.summaryTextBuilder(name, data);
+
+ expect(result).toBe(
+ 'Test summary contained 2 failed/error test results and 1 fixed test result out of 10 total tests',
+ );
+ });
+
+ it('should render text for multiple failed, errored, and fixed results', () => {
+ const name = 'Test summary';
+ const data = { failed: 2, errored: 3, resolved: 4, total: 10 };
+ const result = utils.summaryTextBuilder(name, data);
+
+ expect(result).toBe(
+ 'Test summary contained 5 failed/error test results and 4 fixed test results out of 10 total tests',
+ );
+ });
});
describe('reportTextBuilder', () => {
@@ -89,6 +120,14 @@ describe('Reports store utils', () => {
expect(result).toBe('Rspec found 3 failed/error test results out of 10 total tests');
});
+ it('should render text for multiple errored results', () => {
+ const name = 'Rspec';
+ const data = { errored: 7, total: 10 };
+ const result = utils.reportTextBuilder(name, data);
+
+ expect(result).toBe('Rspec found 7 failed/error test results out of 10 total tests');
+ });
+
it('should render text for multiple fixed results', () => {
const name = 'Rspec';
const data = { resolved: 4, total: 10 };
@@ -116,6 +155,27 @@ describe('Reports store utils', () => {
'Rspec found 1 failed/error test result and 1 fixed test result out of 10 total tests',
);
});
+
+ it('should render text for singular failed, errored, and fixed results', () => {
+ // these will be singular when the copy is updated
+ const name = 'Rspec';
+ const data = { failed: 1, errored: 1, resolved: 1, total: 10 };
+ const result = utils.reportTextBuilder(name, data);
+
+ expect(result).toBe(
+ 'Rspec found 2 failed/error test results and 1 fixed test result out of 10 total tests',
+ );
+ });
+
+ it('should render text for multiple failed, errored, and fixed results', () => {
+ const name = 'Rspec';
+ const data = { failed: 2, errored: 3, resolved: 4, total: 10 };
+ const result = utils.reportTextBuilder(name, data);
+
+ expect(result).toBe(
+ 'Rspec found 5 failed/error test results and 4 fixed test results out of 10 total tests',
+ );
+ });
});
describe('statusIcon', () => {
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index 1b006cdbd4e..154aa881d2d 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -5,6 +5,7 @@ import state from '~/reports/store/state';
import component from '~/reports/components/grouped_test_reports_app.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import newFailedTestReports from '../mock_data/new_failures_report.json';
+import newErrorsTestReports from '../mock_data/new_errors_report.json';
import successTestReports from '../mock_data/no_failures_report.json';
import mixedResultsTestReports from '../mock_data/new_and_fixed_failures_report.json';
import resolvedFailures from '../mock_data/resolved_failures.json';
@@ -99,6 +100,34 @@ describe('Grouped Test Reports App', () => {
});
});
+ describe('with new error result', () => {
+ beforeEach(() => {
+ mock.onGet('test_results.json').reply(200, newErrorsTestReports, {});
+ vm = mountComponent(Component, {
+ endpoint: 'test_results.json',
+ });
+ });
+
+ it('renders error summary text + new badge', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
+ 'Test summary contained 2 failed/error test results out of 11 total tests',
+ );
+
+ expect(vm.$el.textContent).toContain(
+ 'karma found 2 failed/error test results out of 3 total tests',
+ );
+
+ expect(vm.$el.textContent).toContain('New');
+ expect(vm.$el.textContent).toContain(
+ 'rspec:pg found no changed test results out of 8 total tests',
+ );
+ done();
+ }, 0);
+ });
+ });
+
describe('with mixed results', () => {
beforeEach(() => {
mock.onGet('test_results.json').reply(200, mixedResultsTestReports, {});
@@ -127,7 +156,7 @@ describe('Grouped Test Reports App', () => {
});
});
- describe('with resolved failures', () => {
+ describe('with resolved failures and resolved errors', () => {
beforeEach(() => {
mock.onGet('test_results.json').reply(200, resolvedFailures, {});
vm = mountComponent(Component, {
@@ -139,11 +168,11 @@ describe('Grouped Test Reports App', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
- 'Test summary contained 2 fixed test results out of 11 total tests',
+ 'Test summary contained 4 fixed test results out of 11 total tests',
);
expect(vm.$el.textContent).toContain(
- 'rspec:pg found 2 fixed test results out of 8 total tests',
+ 'rspec:pg found 4 fixed test results out of 8 total tests',
);
done();
}, 0);
@@ -161,6 +190,19 @@ describe('Grouped Test Reports App', () => {
done();
}, 0);
});
+
+ it('renders resolved errors', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.report-block-container').textContent).toContain(
+ resolvedFailures.suites[0].resolved_errors[0].name,
+ );
+
+ expect(vm.$el.querySelector('.report-block-container').textContent).toContain(
+ resolvedFailures.suites[0].resolved_errors[1].name,
+ );
+ done();
+ }, 0);
+ });
});
describe('with error', () => {
diff --git a/spec/javascripts/reports/mock_data/new_and_fixed_failures_report.json b/spec/javascripts/reports/mock_data/new_and_fixed_failures_report.json
index ceaf894375a..6141e5433a6 100644
--- a/spec/javascripts/reports/mock_data/new_and_fixed_failures_report.json
+++ b/spec/javascripts/reports/mock_data/new_and_fixed_failures_report.json
@@ -1 +1,55 @@
-{"status":"failed","summary":{"total":11,"resolved":2,"failed":2},"suites":[{"name":"rspec:pg","status":"failed","summary":{"total":8,"resolved":2,"failed":1},"new_failures":[{"status":"failed","name":"Test#subtract when a is 2 and b is 1 returns correct result","execution_time":0.00908,"system_output":"Failure/Error: is_expected.to eq(1)\n\n expected: 1\n got: 3\n\n (compared using ==)\n./spec/test_spec.rb:43:in `block (4 levels) in <top (required)>'"}],"resolved_failures":[{"status":"success","name":"Test#sum when a is 1 and b is 2 returns summary","execution_time":0.000318,"system_output":null},{"status":"success","name":"Test#sum when a is 100 and b is 200 returns summary","execution_time":0.000074,"system_output":null}],"existing_failures":[]},{"name":"java ant","status":"failed","summary":{"total":3,"resolved":0,"failed":1},"new_failures":[],"resolved_failures":[],"existing_failures":[{"status":"failed","name":"sumTest","execution_time":0.004,"system_output":"junit.framework.AssertionFailedError: expected:<3> but was:<-1>\n\tat CalculatorTest.sumTest(Unknown Source)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n"}]}]} \ No newline at end of file
+{
+ "status": "failed",
+ "summary": { "total": 11, "resolved": 2, "errored": 0, "failed": 2 },
+ "suites": [
+ {
+ "name": "rspec:pg",
+ "status": "failed",
+ "summary": { "total": 8, "resolved": 2, "errored": 0, "failed": 1 },
+ "new_failures": [
+ {
+ "status": "failed",
+ "name": "Test#subtract when a is 2 and b is 1 returns correct result",
+ "execution_time": 0.00908,
+ "system_output": "Failure/Error: is_expected.to eq(1)\n\n expected: 1\n got: 3\n\n (compared using ==)\n./spec/test_spec.rb:43:in `block (4 levels) in <top (required)>'"
+ }
+ ],
+ "resolved_failures": [
+ {
+ "status": "success",
+ "name": "Test#sum when a is 1 and b is 2 returns summary",
+ "execution_time": 0.000318,
+ "system_output": null
+ },
+ {
+ "status": "success",
+ "name": "Test#sum when a is 100 and b is 200 returns summary",
+ "execution_time": 0.000074,
+ "system_output": null
+ }
+ ],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ },
+ {
+ "name": "java ant",
+ "status": "failed",
+ "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 1 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [
+ {
+ "status": "failed",
+ "name": "sumTest",
+ "execution_time": 0.004,
+ "system_output": "junit.framework.AssertionFailedError: expected:<3> but was:<-1>\n\tat CalculatorTest.sumTest(Unknown Source)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n"
+ }
+ ],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ }
+ ]
+}
diff --git a/spec/javascripts/reports/mock_data/new_errors_report.json b/spec/javascripts/reports/mock_data/new_errors_report.json
new file mode 100644
index 00000000000..cebf98fdb63
--- /dev/null
+++ b/spec/javascripts/reports/mock_data/new_errors_report.json
@@ -0,0 +1,38 @@
+{
+ "summary": { "total": 11, "resolved": 0, "errored": 2, "failed": 0 },
+ "suites": [
+ {
+ "name": "rspec:pg",
+ "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ },
+ {
+ "name": "karma",
+ "summary": { "total": 3, "resolved": 0, "errored": 2, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [
+ {
+ "result": "error",
+ "name": "Test#sum when a is 1 and b is 2 returns summary",
+ "execution_time": 0.009411,
+ "system_output": "Failed: Error in render: 'TypeError: Cannot read property 'status' of undefined'"
+ },
+ {
+ "result": "error",
+ "name": "Test#sum when a is 100 and b is 200 returns summary",
+ "execution_time": 0.000162,
+ "system_output": "Failed: Error in render: 'TypeError: Cannot read property 'length' of undefined'"
+ }
+ ],
+ "resolved_errors": [],
+ "existing_errors": []
+ }
+ ]
+}
diff --git a/spec/javascripts/reports/mock_data/new_failures_report.json b/spec/javascripts/reports/mock_data/new_failures_report.json
index 930efe16f65..8b9c12c6271 100644
--- a/spec/javascripts/reports/mock_data/new_failures_report.json
+++ b/spec/javascripts/reports/mock_data/new_failures_report.json
@@ -1 +1,38 @@
-{"summary":{"total":11,"resolved":0,"failed":2},"suites":[{"name":"rspec:pg","summary":{"total":8,"resolved":0,"failed":2},"new_failures":[{"result":"failure","name":"Test#sum when a is 1 and b is 2 returns summary","execution_time":0.009411,"system_output":"Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'"},{"result":"failure","name":"Test#sum when a is 100 and b is 200 returns summary","execution_time":0.000162,"system_output":"Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'"}],"resolved_failures":[],"existing_failures":[]},{"name":"java ant","summary":{"total":3,"resolved":0,"failed":0},"new_failures":[],"resolved_failures":[],"existing_failures":[]}]} \ No newline at end of file
+{
+ "summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 2 },
+ "suites": [
+ {
+ "name": "rspec:pg",
+ "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 2 },
+ "new_failures": [
+ {
+ "result": "failure",
+ "name": "Test#sum when a is 1 and b is 2 returns summary",
+ "execution_time": 0.009411,
+ "system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'"
+ },
+ {
+ "result": "failure",
+ "name": "Test#sum when a is 100 and b is 200 returns summary",
+ "execution_time": 0.000162,
+ "system_output": "Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'"
+ }
+ ],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ },
+ {
+ "name": "java ant",
+ "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ }
+ ]
+}
diff --git a/spec/javascripts/reports/mock_data/no_failures_report.json b/spec/javascripts/reports/mock_data/no_failures_report.json
index 6c0675ff7dc..7da9e0c6211 100644
--- a/spec/javascripts/reports/mock_data/no_failures_report.json
+++ b/spec/javascripts/reports/mock_data/no_failures_report.json
@@ -1 +1,28 @@
-{"status":"success","summary":{"total":11,"resolved":0,"failed":0},"suites":[{"name":"rspec:pg","status":"success","summary":{"total":8,"resolved":0,"failed":0},"new_failures":[],"resolved_failures":[],"existing_failures":[]},{"name":"java ant","status":"success","summary":{"total":3,"resolved":0,"failed":0},"new_failures":[],"resolved_failures":[],"existing_failures":[]}]} \ No newline at end of file
+{
+ "status": "success",
+ "summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 0 },
+ "suites": [
+ {
+ "name": "rspec:pg",
+ "status": "success",
+ "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ },
+ {
+ "name": "java ant",
+ "status": "success",
+ "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 },
+ "new_failures": [],
+ "resolved_failures": [],
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
+ }
+ ]
+}
diff --git a/spec/javascripts/reports/mock_data/resolved_failures.json b/spec/javascripts/reports/mock_data/resolved_failures.json
index d1f347ce5e6..49de6aa840b 100644
--- a/spec/javascripts/reports/mock_data/resolved_failures.json
+++ b/spec/javascripts/reports/mock_data/resolved_failures.json
@@ -1,11 +1,11 @@
{
"status": "success",
- "summary": { "total": 11, "resolved": 2, "failed": 0 },
+ "summary": { "total": 11, "resolved": 4, "errored": 0, "failed": 0 },
"suites": [
{
"name": "rspec:pg",
"status": "success",
- "summary": { "total": 8, "resolved": 2, "failed": 0 },
+ "summary": { "total": 8, "resolved": 4, "errored": 0, "failed": 0 },
"new_failures": [],
"resolved_failures": [
{
@@ -23,15 +23,36 @@
"stack_trace": null
}
],
- "existing_failures": []
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [
+ {
+ "status": "success",
+ "name": "Test#sum when a is 4 and b is 4 returns summary",
+ "execution_time": 0.00342,
+ "system_output": null,
+ "stack_trace": null
+ },
+ {
+ "status": "success",
+ "name": "Test#sum when a is 40 and b is 400 returns summary",
+ "execution_time": 0.0000231,
+ "system_output": null,
+ "stack_trace": null
+ }
+ ],
+ "existing_errors": []
},
{
"name": "java ant",
"status": "success",
- "summary": { "total": 3, "resolved": 0, "failed": 0 },
+ "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 },
"new_failures": [],
"resolved_failures": [],
- "existing_failures": []
+ "existing_failures": [],
+ "new_errors": [],
+ "resolved_errors": [],
+ "existing_errors": []
}
]
}
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index 6a7fe7a5927..b91cf1dd3ed 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -99,7 +99,7 @@ describe Gitlab::Ci::Parsers::Test::Junit do
let(:testcase_content) { '<error>Some error</error>' }
it_behaves_like '<testcase> XML parser',
- ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED,
+ ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR,
'Some error'
end
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index 48eef0643b2..d731afe1fff 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -57,6 +57,17 @@ describe Gitlab::Ci::Reports::TestReportsComparer do
is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
end
end
+
+ context 'when there is an error test case in head suites' do
+ before do
+ head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_error)
+ end
+
+ it 'returns the total status in head suite' do
+ is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
+ end
+ end
end
describe '#total_count' do
@@ -75,7 +86,7 @@ describe Gitlab::Ci::Reports::TestReportsComparer do
describe '#resolved_count' do
subject { comparer.resolved_count }
- context 'when there is a resolved test case in head suites' do
+ context 'when there is a resolved failure test case in head suites' do
before do
base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
base_reports.get_suite('junit').add_test_case(create_test_case_java_failed)
@@ -88,6 +99,19 @@ describe Gitlab::Ci::Reports::TestReportsComparer do
end
end
+ context 'when there is a resolved error test case in head suites' do
+ before do
+ base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ base_reports.get_suite('junit').add_test_case(create_test_case_java_error)
+ head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+
+ it 'returns the correct count' do
+ is_expected.to eq(1)
+ end
+ end
+
context 'when there are no resolved test cases in head suites' do
before do
base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
@@ -127,4 +151,30 @@ describe Gitlab::Ci::Reports::TestReportsComparer do
end
end
end
+
+ describe '#error_count' do
+ subject { comparer.error_count }
+
+ context 'when there is an error test case in head suites' do
+ before do
+ head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_error)
+ end
+
+ it 'returns the correct count' do
+ is_expected.to eq(1)
+ end
+ end
+
+ context 'when there are no error test cases in head suites' do
+ before do
+ head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ head_reports.get_suite('junit').add_test_case(create_test_case_rspec_success)
+ end
+
+ it 'returns the correct count' do
+ is_expected.to eq(0)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
index cf4690bb334..2d2179a690b 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
@@ -9,8 +9,9 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
let(:name) { 'rpsec' }
let(:base_suite) { Gitlab::Ci::Reports::TestSuite.new(name) }
let(:head_suite) { Gitlab::Ci::Reports::TestSuite.new(name) }
- let(:test_case_success) { create_test_case_rspec_success }
- let(:test_case_failed) { create_test_case_rspec_failed }
+ let(:test_case_success) { create_test_case_java_success }
+ let(:test_case_failed) { create_test_case_java_failed }
+ let(:test_case_error) { create_test_case_java_error }
describe '#new_failures' do
subject { comparer.new_failures }
@@ -135,6 +136,129 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
end
end
+ describe '#new_errors' do
+ subject { comparer.new_errors }
+
+ context 'when head suite has a new error test case which does not exist in base' do
+ before do
+ base_suite.add_test_case(test_case_success)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'returns the error test case' do
+ is_expected.to eq([test_case_error])
+ end
+ end
+
+ context 'when head suite still has an error test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when head suite has a success test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_success)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#existing_errors' do
+ subject { comparer.existing_errors }
+
+ context 'when head suite has a new error test case which does not exist in base' do
+ before do
+ base_suite.add_test_case(test_case_success)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when head suite still has an error test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'returns the error test case' do
+ is_expected.to eq([test_case_error])
+ end
+ end
+
+ context 'when head suite has a success test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_success)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject { comparer.resolved_errors }
+
+ context 'when head suite has a new error test case which does not exist in base' do
+ before do
+ base_suite.add_test_case(test_case_success)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+
+ it 'returns the correct resolved count' do
+ expect(comparer.resolved_count).to eq(0)
+ end
+ end
+
+ context 'when head suite still has an error test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'does not return the error test case' do
+ is_expected.to be_empty
+ end
+
+ it 'returns the correct resolved count' do
+ expect(comparer.resolved_count).to eq(0)
+ end
+ end
+
+ context 'when head suite has a success test case which errored in base' do
+ before do
+ base_suite.add_test_case(test_case_error)
+ head_suite.add_test_case(test_case_success)
+ end
+
+ it 'returns the resolved test case' do
+ is_expected.to eq([test_case_success])
+ end
+
+ it 'returns the correct resolved count' do
+ expect(comparer.resolved_count).to eq(1)
+ end
+ end
+ end
+
describe '#total_count' do
subject { comparer.total_count }
@@ -208,7 +332,17 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
head_suite.add_test_case(test_case_failed)
end
- it 'returns the total status in head suite' do
+ it 'returns the total status in head suite as failed' do
+ is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
+ end
+ end
+
+ context 'when there is an error test case in head suite' do
+ before do
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'returns the total status in head suite as failed' do
is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
end
end
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index 8646db43bc8..217713fd899 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -74,6 +74,15 @@ describe Gitlab::Ci::Reports::TestSuite do
it { is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED) }
end
+
+ context 'when a test case errored' do
+ before do
+ test_suite.add_test_case(test_case_success)
+ test_suite.add_test_case(test_case_error)
+ end
+
+ it { is_expected.to eq(Gitlab::Ci::Reports::TestCase::STATUS_FAILED) }
+ end
end
Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type|
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7250258061a..4dadb310029 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -36,8 +36,6 @@ issues:
- vulnerability_links
- related_vulnerabilities
- user_mentions
-- blocked_by_issue_links
-- blocked_by_issues
events:
- author
- project
diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb
index 56df73161b4..34a775fc206 100644
--- a/spec/lib/gitlab/runtime_spec.rb
+++ b/spec/lib/gitlab/runtime_spec.rb
@@ -50,7 +50,7 @@ describe Gitlab::Runtime do
allow(puma_type).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2)
end
- it_behaves_like "valid runtime", :puma, 2
+ it_behaves_like "valid runtime", :puma, 3
end
context "unicorn" do
@@ -71,7 +71,7 @@ describe Gitlab::Runtime do
allow(sidekiq_type).to receive(:options).and_return(concurrency: 2)
end
- it_behaves_like "valid runtime", :sidekiq, 2
+ it_behaves_like "valid runtime", :sidekiq, 4
end
context "console" do
diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb
index 8d4afe9f00f..ad1aa915fbb 100644
--- a/spec/mailers/emails/pipelines_spec.rb
+++ b/spec/mailers/emails/pipelines_spec.rb
@@ -19,6 +19,25 @@ describe Emails::Pipelines do
expect(subject).to have_body_text status_text
end
+ context 'when pipeline on master branch has a merge request' do
+ let(:pipeline) { create(:ci_pipeline, ref: 'master', sha: sha, project: project) }
+
+ let!(:merge_request) do
+ create(:merge_request, source_branch: 'master', target_branch: 'feature',
+ source_project: project, target_project: project)
+ end
+
+ it 'has correct information that there is no merge request link' do
+ expect(subject)
+ .to have_subject "#{project.name} | Pipeline ##{pipeline.id} has " \
+ "#{status} for #{pipeline.source_ref} | " \
+ "#{pipeline.short_sha}".to_s
+
+ expect(subject).to have_body_text pipeline.source_ref
+ expect(subject).to have_body_text status_text
+ end
+ end
+
context 'when pipeline for merge requests' do
let(:pipeline) { merge_request.all_pipelines.first }
@@ -28,7 +47,7 @@ describe Emails::Pipelines do
target_project: project)
end
- it 'has a correct information with merge request link' do
+ it 'has correct information that there is a merge request link' do
expect(subject)
.to have_subject "#{project.name} | Pipeline ##{pipeline.id} has " \
"#{status} for #{pipeline.source_ref} | " \
@@ -39,6 +58,27 @@ describe Emails::Pipelines do
expect(subject).not_to have_body_text pipeline.ref
end
end
+
+ context 'when branch pipeline is set to a merge request as a head pipeline' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, ref: ref, sha: sha,
+ merge_requests_as_head_pipeline: [merge_request])
+ end
+
+ let(:merge_request) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+
+ it 'has correct information that there is a merge request link' do
+ expect(subject)
+ .to have_subject "#{project.name} | Pipeline ##{pipeline.id} has " \
+ "#{status} for #{pipeline.source_ref} | " \
+ "#{pipeline.short_sha} in !#{merge_request.iid}".to_s
+
+ expect(subject).to have_body_text merge_request.to_reference
+ expect(subject).to have_body_text pipeline.source_ref
+ end
+ end
end
describe '#pipeline_success_email' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index dc055244af7..6c90a1b5614 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -153,6 +153,16 @@ describe Project do
expect(project.container_expiration_policy).to be_persisted
end
+ it 'does not create another container expiration policy if there is already one' do
+ project = build(:project)
+
+ expect do
+ container_expiration_policy = create(:container_expiration_policy, project: project)
+
+ expect(project.container_expiration_policy).to eq(container_expiration_policy)
+ end.to change { ContainerExpirationPolicy.count }.by(1)
+ end
+
it 'automatically creates a Pages metadata row' do
expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum)
expect(project.pages_metadatum).to be_persisted
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
new file mode 100644
index 00000000000..0362fef2d2e
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -0,0 +1,244 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Updating an image DiffNote' do
+ include GraphqlHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:noteable) { create(:merge_request, :with_diffs) }
+ let_it_be(:original_body) { 'Original body' }
+ let_it_be(:original_position) do
+ Gitlab::Diff::Position.new(
+ old_path: 'files/images/any_image.png',
+ new_path: 'files/images/any_image.png',
+ width: 10,
+ height: 20,
+ x: 1,
+ y: 2,
+ diff_refs: noteable.diff_refs,
+ position_type: 'image'
+ )
+ end
+ let_it_be(:updated_body) { 'Updated body' }
+ let_it_be(:updated_width) { 50 }
+ let_it_be(:updated_height) { 100 }
+ let_it_be(:updated_x) { 5 }
+ let_it_be(:updated_y) { 10 }
+ let(:updated_position) do
+ {
+ width: updated_width,
+ height: updated_height,
+ x: updated_x,
+ y: updated_y
+ }
+ end
+ let!(:diff_note) do
+ create(:image_diff_note_on_merge_request,
+ noteable: noteable,
+ project: noteable.project,
+ note: original_body,
+ position: original_position)
+ end
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(diff_note).to_s,
+ body: updated_body,
+ position: updated_position
+ }
+
+ graphql_mutation(:update_image_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:update_image_diff_note)
+ end
+
+ context 'when the user does not have permission' do
+ let_it_be(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not update the DiffNote' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ diff_note.reload
+
+ expect(diff_note).to have_attributes(
+ note: original_body,
+ position: have_attributes(
+ width: original_position.width,
+ height: original_position.height,
+ x: original_position.x,
+ y: original_position.y
+ )
+ )
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { diff_note.author }
+
+ it 'updates the DiffNote' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ diff_note.reload
+
+ expect(diff_note).to have_attributes(
+ note: updated_body,
+ position: have_attributes(
+ width: updated_width,
+ height: updated_height,
+ x: updated_x,
+ y: updated_y
+ )
+ )
+ end
+
+ it 'returns the updated DiffNote' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']).to include(
+ 'body' => updated_body,
+ 'position' => hash_including(
+ 'width' => updated_width,
+ 'height' => updated_height,
+ 'x' => updated_x,
+ 'y' => updated_y
+ )
+ )
+ end
+
+ describe 'updating single properties at a time' do
+ where(:property, :new_value) do
+ :body | 'foo'
+ :width | 19
+ :height | 18
+ :x | 17
+ :y | 16
+ end
+
+ with_them do
+ # Properties that will be POSTed:
+ let(:updated_body) { value(:body) }
+ let(:updated_width) { value(:width) }
+ let(:updated_height) { value(:height) }
+ let(:updated_x) { value(:x) }
+ let(:updated_y) { value(:y) }
+ # Expectations of the properties:
+ let(:expected_body) { value(:body) || original_body }
+ let(:expected_width) { value(:width) || original_position.width }
+ let(:expected_height) { value(:height) || original_position.height }
+ let(:expected_x) { value(:x) || original_position.x }
+ let(:expected_y) { value(:y) || original_position.y }
+
+ def value(prop)
+ new_value if property == prop
+ end
+
+ it 'updates the DiffNote correctly' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ diff_note.reload
+
+ expect(diff_note).to have_attributes(
+ note: expected_body,
+ position: have_attributes(
+ width: expected_width,
+ height: expected_height,
+ x: expected_x,
+ y: expected_y
+ )
+ )
+ end
+ end
+
+ context 'when position is nil' do
+ let(:updated_position) { nil }
+
+ it 'updates the DiffNote correctly' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ diff_note.reload
+
+ expect(diff_note).to have_attributes(
+ note: updated_body,
+ position: original_position
+ )
+ end
+ end
+ end
+
+ context 'when both body and position args are blank' do
+ let(:updated_body) { nil }
+ let(:updated_position) { nil }
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['body or position arguments are required']
+ end
+
+ context 'when resource is not a DiffNote on an image' do
+ let!(:diff_note) { create(:diff_note_on_merge_request, note: original_body) }
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Resource is not an ImageDiffNote']
+ end
+
+ context 'when there are ActiveRecord validation errors' do
+ before do
+ expect(diff_note).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+
+ expect_next_instance_of(Notes::UpdateService) do |service|
+ expect(service).to receive(:execute).and_return(diff_note)
+ end
+ end
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'does not update the DiffNote' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ diff_note.reload
+
+ expect(diff_note).to have_attributes(
+ note: original_body,
+ position: have_attributes(
+ width: original_position.width,
+ height: original_position.height,
+ x: original_position.x,
+ y: original_position.y
+ )
+ )
+ end
+
+ it 'returns the DiffNote with its original body' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']).to include(
+ 'body' => original_body,
+ 'position' => hash_including(
+ 'width' => original_position.width,
+ 'height' => original_position.height,
+ 'x' => original_position.x,
+ 'y' => original_position.y
+ )
+ )
+ end
+ end
+
+ context 'when body only contains quick actions' do
+ let(:updated_body) { '/close' }
+
+ it 'returns a nil note and empty errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to include(
+ 'errors' => [],
+ 'note' => nil
+ )
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/update_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index 958f640995a..a5c6b72005e 100644
--- a/spec/requests/api/graphql/mutations/notes/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -22,7 +22,7 @@ describe 'Updating a Note' do
end
context 'when the user does not have permission' do
- let(:current_user) { create(:user) }
+ let_it_be(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
@@ -68,5 +68,18 @@ describe 'Updating a Note' do
expect(mutation_response['note']['body']).to eq(original_body)
end
end
+
+ context 'when body only contains quick actions' do
+ let(:updated_body) { '/close' }
+
+ it 'returns a nil note and empty errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to include(
+ 'errors' => [],
+ 'note' => nil
+ )
+ end
+ end
end
end
diff --git a/spec/serializers/test_reports_comparer_entity_spec.rb b/spec/serializers/test_reports_comparer_entity_spec.rb
index 2627ad536e4..e7dabc67325 100644
--- a/spec/serializers/test_reports_comparer_entity_spec.rb
+++ b/spec/serializers/test_reports_comparer_entity_spec.rb
@@ -24,7 +24,7 @@ describe TestReportsComparerEntity do
it 'contains correct compared test reports details' do
expect(subject[:status]).to eq('success')
- expect(subject[:summary]).to include(total: 2, resolved: 0, failed: 0)
+ expect(subject[:summary]).to include(total: 2, resolved: 0, failed: 0, errored: 0)
expect(subject[:suites].first[:name]).to eq('rspec')
expect(subject[:suites].first[:status]).to eq('success')
expect(subject[:suites].second[:name]).to eq('junit')
@@ -42,7 +42,7 @@ describe TestReportsComparerEntity do
it 'contains correct compared test reports details' do
expect(subject[:status]).to eq('failed')
- expect(subject[:summary]).to include(total: 2, resolved: 0, failed: 1)
+ expect(subject[:summary]).to include(total: 2, resolved: 0, failed: 1, errored: 0)
expect(subject[:suites].first[:name]).to eq('rspec')
expect(subject[:suites].first[:status]).to eq('success')
expect(subject[:suites].second[:name]).to eq('junit')
@@ -60,7 +60,7 @@ describe TestReportsComparerEntity do
it 'contains correct compared test reports details' do
expect(subject[:status]).to eq('success')
- expect(subject[:summary]).to include(total: 2, resolved: 1, failed: 0)
+ expect(subject[:summary]).to include(total: 2, resolved: 1, failed: 0, errored: 0)
expect(subject[:suites].first[:name]).to eq('rspec')
expect(subject[:suites].first[:status]).to eq('success')
expect(subject[:suites].second[:name]).to eq('junit')
diff --git a/spec/serializers/test_suite_comparer_entity_spec.rb b/spec/serializers/test_suite_comparer_entity_spec.rb
index e22387130a1..9790777a570 100644
--- a/spec/serializers/test_suite_comparer_entity_spec.rb
+++ b/spec/serializers/test_suite_comparer_entity_spec.rb
@@ -12,6 +12,7 @@ describe TestSuiteComparerEntity do
let(:head_suite) { Gitlab::Ci::Reports::TestSuite.new(name) }
let(:test_case_success) { create_test_case_rspec_success }
let(:test_case_failed) { create_test_case_rspec_failed }
+ let(:test_case_error) { create_test_case_rspec_error }
describe '#as_json' do
subject { entity.as_json }
@@ -25,7 +26,7 @@ describe TestSuiteComparerEntity do
it 'contains correct compared test suite details' do
expect(subject[:name]).to eq(name)
expect(subject[:status]).to eq('failed')
- expect(subject[:summary]).to include(total: 1, resolved: 0, failed: 1)
+ expect(subject[:summary]).to include(total: 1, resolved: 0, failed: 1, errored: 0)
subject[:new_failures].first.tap do |new_failure|
expect(new_failure[:status]).to eq(test_case_failed.status)
expect(new_failure[:name]).to eq(test_case_failed.name)
@@ -37,6 +38,27 @@ describe TestSuiteComparerEntity do
end
end
+ context 'when head suite has a new error test case which does not exist in base' do
+ before do
+ base_suite.add_test_case(test_case_success)
+ head_suite.add_test_case(test_case_error)
+ end
+
+ it 'contains correct compared test suite details' do
+ expect(subject[:name]).to eq(name)
+ expect(subject[:status]).to eq('failed')
+ expect(subject[:summary]).to include(total: 1, resolved: 0, failed: 0, errored: 1)
+ subject[:new_errors].first.tap do |new_error|
+ expect(new_error[:status]).to eq(test_case_error.status)
+ expect(new_error[:name]).to eq(test_case_error.name)
+ expect(new_error[:execution_time]).to eq(test_case_error.execution_time)
+ expect(new_error[:system_output]).to eq(test_case_error.system_output)
+ end
+ expect(subject[:resolved_failures]).to be_empty
+ expect(subject[:existing_failures]).to be_empty
+ end
+ end
+
context 'when head suite still has a failed test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
@@ -46,7 +68,7 @@ describe TestSuiteComparerEntity do
it 'contains correct compared test suite details' do
expect(subject[:name]).to eq(name)
expect(subject[:status]).to eq('failed')
- expect(subject[:summary]).to include(total: 1, resolved: 0, failed: 1)
+ expect(subject[:summary]).to include(total: 1, resolved: 0, failed: 1, errored: 0)
expect(subject[:new_failures]).to be_empty
expect(subject[:resolved_failures]).to be_empty
subject[:existing_failures].first.tap do |existing_failure|
@@ -67,7 +89,7 @@ describe TestSuiteComparerEntity do
it 'contains correct compared test suite details' do
expect(subject[:name]).to eq(name)
expect(subject[:status]).to eq('success')
- expect(subject[:summary]).to include(total: 1, resolved: 1, failed: 0)
+ expect(subject[:summary]).to include(total: 1, resolved: 1, failed: 0, errored: 0)
expect(subject[:new_failures]).to be_empty
subject[:resolved_failures].first.tap do |resolved_failure|
expect(resolved_failure[:status]).to eq(test_case_success.status)
@@ -88,42 +110,57 @@ describe TestSuiteComparerEntity do
context 'prefers new over existing and resolved' do
before do
3.times { add_new_failure }
+ 3.times { add_new_error }
3.times { add_existing_failure }
+ 3.times { add_existing_error }
3.times { add_resolved_failure }
+ 3.times { add_resolved_error }
end
- it 'returns 2 new failures, and 1 of resolved and existing' do
- expect(subject[:summary]).to include(total: 9, resolved: 3, failed: 6)
+ it 'returns 2 of each new category, and 1 of each resolved and existing' do
+ expect(subject[:summary]).to include(total: 18, resolved: 6, failed: 6, errored: 6)
expect(subject[:new_failures].count).to eq(2)
+ expect(subject[:new_errors].count).to eq(2)
expect(subject[:existing_failures].count).to eq(1)
+ expect(subject[:existing_errors].count).to eq(1)
expect(subject[:resolved_failures].count).to eq(1)
+ expect(subject[:resolved_errors].count).to eq(1)
end
end
context 'prefers existing over resolved' do
before do
3.times { add_existing_failure }
+ 3.times { add_existing_error }
3.times { add_resolved_failure }
+ 3.times { add_resolved_error }
end
- it 'returns 2 existing failures, and 1 resolved' do
- expect(subject[:summary]).to include(total: 6, resolved: 3, failed: 3)
+ it 'returns 2 of each existing category, and 1 of each resolved' do
+ expect(subject[:summary]).to include(total: 12, resolved: 6, failed: 3, errored: 3)
expect(subject[:new_failures].count).to eq(0)
+ expect(subject[:new_errors].count).to eq(0)
expect(subject[:existing_failures].count).to eq(2)
+ expect(subject[:existing_errors].count).to eq(2)
expect(subject[:resolved_failures].count).to eq(1)
+ expect(subject[:resolved_errors].count).to eq(1)
end
end
context 'limits amount of resolved' do
before do
3.times { add_resolved_failure }
+ 3.times { add_resolved_error }
end
- it 'returns 2 resolved failures' do
- expect(subject[:summary]).to include(total: 3, resolved: 3, failed: 0)
+ it 'returns 2 of each resolved category' do
+ expect(subject[:summary]).to include(total: 6, resolved: 6, failed: 0, errored: 0)
expect(subject[:new_failures].count).to eq(0)
+ expect(subject[:new_errors].count).to eq(0)
expect(subject[:existing_failures].count).to eq(0)
+ expect(subject[:existing_errors].count).to eq(0)
expect(subject[:resolved_failures].count).to eq(2)
+ expect(subject[:resolved_errors].count).to eq(2)
end
end
@@ -134,19 +171,38 @@ describe TestSuiteComparerEntity do
head_suite.add_test_case(failed_case)
end
+ def add_new_error
+ error_case = create_test_case_rspec_error(SecureRandom.hex)
+ head_suite.add_test_case(error_case)
+ end
+
def add_existing_failure
failed_case = create_test_case_rspec_failed(SecureRandom.hex)
base_suite.add_test_case(failed_case)
head_suite.add_test_case(failed_case)
end
+ def add_existing_error
+ error_case = create_test_case_rspec_error(SecureRandom.hex)
+ base_suite.add_test_case(error_case)
+ head_suite.add_test_case(error_case)
+ end
+
def add_resolved_failure
case_name = SecureRandom.hex
- failed_case = create_test_case_rspec_failed(case_name)
- success_case = create_test_case_rspec_success(case_name)
+ failed_case = create_test_case_java_failed(case_name)
+ success_case = create_test_case_java_success(case_name)
base_suite.add_test_case(failed_case)
head_suite.add_test_case(success_case)
end
+
+ def add_resolved_error
+ case_name = SecureRandom.hex
+ error_case = create_test_case_java_error(case_name)
+ success_case = create_test_case_java_success(case_name)
+ base_suite.add_test_case(error_case)
+ head_suite.add_test_case(success_case)
+ end
end
end
end
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index 7aa8f0ebf8f..1cf1cff51ed 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -143,6 +143,34 @@ describe Snippets::CreateService do
end
end
+ shared_examples 'creates repository' do
+ it do
+ subject
+
+ expect(snippet.repository_exists?).to be_truthy
+ end
+
+ context 'when snippet creation fails' do
+ let(:extra_opts) { { content: nil } }
+
+ it 'does not create repository' do
+ subject
+
+ expect(snippet.repository_exists?).to be_falsey
+ end
+ end
+
+ context 'when feature flag :version_snippets is disabled' do
+ it 'does not create snippet repository' do
+ stub_feature_flags(version_snippets: false)
+
+ subject
+
+ expect(snippet.repository_exists?).to be_falsey
+ end
+ end
+ end
+
context 'when Project Snippet' do
let_it_be(:project) { create(:project) }
@@ -155,6 +183,7 @@ describe Snippets::CreateService do
it_behaves_like 'spam check is performed'
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
+ it_behaves_like 'creates repository'
end
context 'when PersonalSnippet' do
@@ -165,6 +194,7 @@ describe Snippets::CreateService do
it_behaves_like 'spam check is performed'
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
+ it_behaves_like 'creates repository'
end
end
end
diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
index 2548afc1570..8092f87383d 100644
--- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
+++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb
@@ -123,7 +123,7 @@ RSpec.shared_examples 'cycle analytics label based stage' do
})
expect(stage).to be_invalid
- expect(stage.errors[:start_event_label]).to include("can't be blank")
+ expect(stage.errors[:start_event_label_id]).to include("can't be blank")
end
it 'returns validation error when `end_event_label_id` is missing' do
@@ -135,7 +135,7 @@ RSpec.shared_examples 'cycle analytics label based stage' do
})
expect(stage).to be_invalid
- expect(stage.errors[:end_event_label]).to include("can't be blank")
+ expect(stage.errors[:end_event_label_id]).to include("can't be blank")
end
end
@@ -145,7 +145,7 @@ RSpec.shared_examples 'cycle analytics label based stage' do
name: 'My Stage',
parent: parent,
start_event_identifier: :issue_label_added,
- start_event_label: group_label,
+ start_event_label_id: group_label.id,
end_event_identifier: :issue_closed
})
@@ -159,7 +159,7 @@ RSpec.shared_examples 'cycle analytics label based stage' do
name: 'My Stage',
parent: parent_in_subgroup,
start_event_identifier: :issue_label_added,
- start_event_label: group_label,
+ start_event_label_id: group_label.id,
end_event_identifier: :issue_closed
})
@@ -170,30 +170,30 @@ RSpec.shared_examples 'cycle analytics label based stage' do
context 'when label is defined for a different group' do
let(:error_message) { s_('CycleAnalyticsStage|is not available for the selected group') }
- it 'returns validation for `start_event_label`' do
+ it 'returns validation for `start_event_label_id`' do
stage = described_class.new({
name: 'My Stage',
parent: parent_outside_of_group_label_scope,
start_event_identifier: :issue_label_added,
- start_event_label: group_label,
+ start_event_label_id: group_label.id,
end_event_identifier: :issue_closed
})
expect(stage).to be_invalid
- expect(stage.errors[:start_event_label]).to include(error_message)
+ expect(stage.errors[:start_event_label_id]).to include(error_message)
end
- it 'returns validation for `end_event_label`' do
+ it 'returns validation for `end_event_label_id`' do
stage = described_class.new({
name: 'My Stage',
parent: parent_outside_of_group_label_scope,
start_event_identifier: :issue_closed,
end_event_identifier: :issue_label_added,
- end_event_label: group_label
+ end_event_label_id: group_label.id
})
expect(stage).to be_invalid
- expect(stage.errors[:end_event_label]).to include(error_message)
+ expect(stage.errors[:end_event_label_id]).to include(error_message)
end
end