summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-07 09:09:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-07 09:09:42 +0000
commit5b6e9de025786e5f46443d408f1eeadb8e8b4f7b (patch)
tree1a15ffc690c86f8b10d5e36efab6afd3bfe83655
parent132f8ac520f09e61136eafcbcc3e7c2b2598e705 (diff)
downloadgitlab-ce-5b6e9de025786e5f46443d408f1eeadb8e8b4f7b.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/count.fragment.graphql4
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue165
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue2
-rw-r--r--app/assets/stylesheets/framework/modal.scss1
-rw-r--r--app/serializers/codequality_degradation_entity.rb14
-rw-r--r--app/serializers/codequality_reports_comparer_entity.rb15
-rw-r--r--app/serializers/codequality_reports_comparer_serializer.rb5
-rw-r--r--app/services/projects/container_repository/delete_tags_service.rb1
-rw-r--r--app/views/projects/deployments/_actions.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--changelogs/unreleased/291078-fix-typo-merge-locally.yml5
-rw-r--r--changelogs/unreleased/mw-convert-fa-caret-down-to-svg.yml5
-rw-r--r--doc/administration/geo/index.md10
-rw-r--r--doc/administration/geo/replication/troubleshooting.md5
-rw-r--r--doc/ci/pipelines/index.md12
-rw-r--r--doc/development/rake_tasks.md9
-rw-r--r--lib/gitlab/ci/reports/codequality_reports_comparer.rb57
-rw-r--r--locale/gitlab.pot62
-rw-r--r--spec/fixtures/api/schemas/entities/codequality_degradation.json24
-rw-r--r--spec/fixtures/api/schemas/entities/codequality_reports_comparer.json43
-rw-r--r--spec/frontend/boards/components/board_form_spec.js4
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb308
-rw-r--r--spec/serializers/codequality_degradation_entity_spec.rb88
-rw-r--r--spec/serializers/codequality_reports_comparer_entity_spec.rb85
-rw-r--r--spec/serializers/codequality_reports_comparer_serializer_spec.rb92
-rw-r--r--spec/services/projects/container_repository/delete_tags_service_spec.rb4
28 files changed, 925 insertions, 107 deletions
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/count.fragment.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/count.fragment.graphql
deleted file mode 100644
index 40cef95c2e7..00000000000
--- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/count.fragment.graphql
+++ /dev/null
@@ -1,4 +0,0 @@
-fragment Count on InstanceStatisticsMeasurement {
- count
- recordedAt
-}
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index d464379400d..74d538d68ed 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,7 +1,7 @@
<script>
-import { __ } from '~/locale';
+import { GlModal } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
import { deprecatedCreateFlash as Flash } from '~/flash';
-import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import { visitUrl } from '~/lib/utils/url_utility';
import boardsStore from '~/boards/stores/boards_store';
@@ -19,10 +19,28 @@ const boardDefaults = {
hide_closed_list: false,
};
+const formType = {
+ new: 'new',
+ delete: 'delete',
+ edit: 'edit',
+};
+
export default {
+ i18n: {
+ [formType.new]: { title: s__('Board|Create new board'), btnText: s__('Board|Create board') },
+ [formType.delete]: { title: s__('Board|Delete board'), btnText: __('Delete') },
+ [formType.edit]: { title: s__('Board|Edit board'), btnText: __('Save changes') },
+ scopeModalTitle: s__('Board|Board scope'),
+ cancelButtonText: __('Cancel'),
+ deleteErrorMessage: s__('Board|Failed to delete board. Please try again.'),
+ saveErrorMessage: __('Unable to save your changes. Please try again.'),
+ deleteConfirmationMessage: s__('Board|Are you sure you want to delete this board?'),
+ titleFieldLabel: __('Title'),
+ titleFieldPlaceholder: s__('Board|Enter board name'),
+ },
components: {
BoardScope: () => import('ee_component/boards/components/board_scope.vue'),
- DeprecatedModal,
+ GlModal,
BoardConfigurationOptions,
},
props: {
@@ -74,25 +92,16 @@ export default {
},
computed: {
isNewForm() {
- return this.currentPage === 'new';
+ return this.currentPage === formType.new;
},
isDeleteForm() {
- return this.currentPage === 'delete';
+ return this.currentPage === formType.delete;
},
isEditForm() {
- return this.currentPage === 'edit';
- },
- isVisible() {
- return this.currentPage !== '';
+ return this.currentPage === formType.edit;
},
buttonText() {
- if (this.isNewForm) {
- return __('Create board');
- }
- if (this.isDeleteForm) {
- return __('Delete');
- }
- return __('Save changes');
+ return this.$options.i18n[this.currentPage].btnText;
},
buttonKind() {
if (this.isNewForm) {
@@ -104,16 +113,11 @@ export default {
return 'info';
},
title() {
- if (this.isNewForm) {
- return __('Create new board');
- }
- if (this.isDeleteForm) {
- return __('Delete board');
- }
if (this.readonly) {
- return __('Board scope');
+ return this.$options.i18n.scopeModalTitle;
}
- return __('Edit board');
+
+ return this.$options.i18n[this.currentPage].title;
},
readonly() {
return !this.canAdminBoard;
@@ -121,6 +125,24 @@ export default {
submitDisabled() {
return this.isLoading || this.board.name.length === 0;
},
+ primaryProps() {
+ return {
+ text: this.buttonText,
+ attributes: [
+ {
+ variant: this.buttonKind,
+ disabled: this.submitDisabled,
+ loading: this.isLoading,
+ 'data-qa-selector': 'save_changes_button',
+ },
+ ],
+ };
+ },
+ cancelProps() {
+ return {
+ text: this.$options.i18n.cancelButtonText,
+ };
+ },
},
mounted() {
this.resetFormState();
@@ -136,10 +158,11 @@ export default {
boardsStore
.deleteBoard(this.currentBoard)
.then(() => {
+ this.isLoading = false;
visitUrl(boardsStore.rootPath);
})
.catch(() => {
- Flash(__('Failed to delete board. Please try again.'));
+ Flash(this.$options.i18n.deleteErrorMessage);
this.isLoading = false;
});
} else {
@@ -157,10 +180,11 @@ export default {
return resp.data ? resp.data : resp;
})
.then(data => {
+ this.isLoading = false;
visitUrl(data.board_path);
})
.catch(() => {
- Flash(__('Unable to save your changes. Please try again.'));
+ Flash(this.$options.i18n.saveErrorMessage);
this.isLoading = false;
});
}
@@ -181,53 +205,56 @@ export default {
</script>
<template>
- <deprecated-modal
- v-show="isVisible"
+ <gl-modal
+ modal-id="board-config-modal"
+ modal-class="board-config-modal"
+ content-class="gl-absolute gl-top-7"
+ visible
:hide-footer="readonly"
:title="title"
- :primary-button-label="buttonText"
- :kind="buttonKind"
- :submit-disabled="submitDisabled"
- modal-dialog-class="board-config-modal"
+ :action-primary="primaryProps"
+ :action-cancel="cancelProps"
+ @primary="submit"
@cancel="cancel"
- @submit="submit"
+ @close="cancel"
+ @hide.prevent
>
- <template #body>
- <p v-if="isDeleteForm">{{ __('Are you sure you want to delete this board?') }}</p>
- <form v-else class="js-board-config-modal" @submit.prevent>
- <div v-if="!readonly" class="gl-mb-5">
- <label class="label-bold gl-font-lg" for="board-new-name">{{ __('Title') }}</label>
- <input
- id="board-new-name"
- ref="name"
- v-model="board.name"
- class="form-control"
- data-qa-selector="board_name_field"
- type="text"
- :placeholder="__('Enter board name')"
- @keyup.enter="submit"
- />
- </div>
-
- <board-configuration-options
- :is-new-form="isNewForm"
- :board="board"
- :current-board="currentBoard"
+ <p v-if="isDeleteForm">{{ $options.i18n.deleteConfirmationMessage }}</p>
+ <form v-else class="js-board-config-modal" @submit.prevent>
+ <div v-if="!readonly" class="gl-mb-5">
+ <label class="gl-font-weight-bold gl-font-lg" for="board-new-name">
+ {{ $options.i18n.titleFieldLabel }}
+ </label>
+ <input
+ id="board-new-name"
+ ref="name"
+ v-model="board.name"
+ class="form-control"
+ data-qa-selector="board_name_field"
+ type="text"
+ :placeholder="$options.i18n.titleFieldPlaceholder"
+ @keyup.enter="submit"
/>
+ </div>
- <board-scope
- v-if="scopedIssueBoardFeatureEnabled"
- :collapse-scope="isNewForm"
- :board="board"
- :can-admin-board="canAdminBoard"
- :labels-path="labelsPath"
- :labels-web-url="labelsWebUrl"
- :enable-scoped-labels="enableScopedLabels"
- :project-id="projectId"
- :group-id="groupId"
- :weights="weights"
- />
- </form>
- </template>
- </deprecated-modal>
+ <board-configuration-options
+ :is-new-form="isNewForm"
+ :board="board"
+ :current-board="currentBoard"
+ />
+
+ <board-scope
+ v-if="scopedIssueBoardFeatureEnabled"
+ :collapse-scope="isNewForm"
+ :board="board"
+ :can-admin-board="canAdminBoard"
+ :labels-path="labelsPath"
+ :labels-web-url="labelsWebUrl"
+ :enable-scoped-labels="enableScopedLabels"
+ :project-id="projectId"
+ :group-id="groupId"
+ :weights="weights"
+ />
+ </form>
+ </gl-modal>
</template>
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 435ea3f8b7a..7fc91bc4aee 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -7,6 +7,7 @@ import {
GlDropdownDivider,
GlDropdownSectionHeader,
GlDropdownItem,
+ GlModalDirective,
} from '@gitlab/ui';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -31,6 +32,9 @@ export default {
GlDropdownSectionHeader,
GlDropdownItem,
},
+ directives: {
+ GlModalDirective,
+ },
props: {
currentBoard: {
type: Object,
@@ -313,6 +317,7 @@ export default {
<gl-dropdown-item
v-if="multipleIssueBoardsAvailable"
+ v-gl-modal-directive="'board-config-modal'"
data-qa-selector="create_new_board_button"
@click.prevent="showPage('new')"
>
@@ -321,6 +326,7 @@ export default {
<gl-dropdown-item
v-if="showDelete"
+ v-gl-modal-directive="'board-config-modal'"
class="text-danger js-delete-board"
@click.prevent="showPage('delete')"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
index 531a7dbaac0..785e8ef8e8f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_how_to_merge_modal.vue
@@ -82,7 +82,7 @@ export default {
mergeInfo2() {
return this.isFork
? `git fetch origin\ngit checkout "${this.targetBranch}"\ngit merge --no-ff "${this.sourceProjectPath}-${this.sourceBranch}"`
- : `git fetch origin\ngit checkout "${this.targetBranch}"\ngit merge --no-ff " ${this.sourceBranch}"`;
+ : `git fetch origin\ngit checkout "${this.targetBranch}"\ngit merge --no-ff "${this.sourceBranch}"`;
},
mergeInfo3() {
return this.canMerge
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 372e3bed6e0..2dbeacb0f8c 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -91,6 +91,7 @@
body.modal-open {
overflow: hidden;
+ padding-right: 0 !important;
}
.modal-no-backdrop {
diff --git a/app/serializers/codequality_degradation_entity.rb b/app/serializers/codequality_degradation_entity.rb
new file mode 100644
index 00000000000..be561052507
--- /dev/null
+++ b/app/serializers/codequality_degradation_entity.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CodequalityDegradationEntity < Grape::Entity
+ expose :description
+ expose :severity
+
+ expose :file_path do |degradation|
+ degradation.dig(:location, :path)
+ end
+
+ expose :line do |degradation|
+ degradation.dig(:location, :lines, :begin) || degradation.dig(:location, :positions, :begin, :line)
+ end
+end
diff --git a/app/serializers/codequality_reports_comparer_entity.rb b/app/serializers/codequality_reports_comparer_entity.rb
new file mode 100644
index 00000000000..1de4e56c57d
--- /dev/null
+++ b/app/serializers/codequality_reports_comparer_entity.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CodequalityReportsComparerEntity < Grape::Entity
+ expose :status
+
+ expose :new_errors, using: CodequalityDegradationEntity
+ expose :resolved_errors, using: CodequalityDegradationEntity
+ expose :existing_errors, using: CodequalityDegradationEntity
+
+ expose :summary do
+ expose :total_count, as: :total
+ expose :resolved_count, as: :resolved
+ expose :errors_count, as: :errored
+ end
+end
diff --git a/app/serializers/codequality_reports_comparer_serializer.rb b/app/serializers/codequality_reports_comparer_serializer.rb
new file mode 100644
index 00000000000..2c6eb33aa9f
--- /dev/null
+++ b/app/serializers/codequality_reports_comparer_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class CodequalityReportsComparerSerializer < BaseSerializer
+ entity CodequalityReportsComparerEntity
+end
diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb
index 505ddaf50e3..410cf6c624e 100644
--- a/app/services/projects/container_repository/delete_tags_service.rb
+++ b/app/services/projects/container_repository/delete_tags_service.rb
@@ -36,6 +36,7 @@ module Projects
def log_response(response)
log_data = LOG_DATA_BASE.merge(
container_repository_id: @container_repository.id,
+ project_id: @container_repository.project_id,
message: 'deleted tags',
deleted_tags_count: response[:deleted]&.size
).compact
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 7f4b99f1a3f..c0fe143020a 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -5,7 +5,7 @@
.dropdown
%button.dropdown.dropdown-new.btn.gl-button.btn-default.has-tooltip{ type: 'button', 'data-toggle' => 'dropdown', title: s_('Environments|Deploy to...') }
= sprite_icon('play')
- = icon('caret-down')
+ = sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right
- actions.each do |action|
- next unless can?(current_user, :update_build, action)
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 6429cf31bc3..8edaacf7552 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -4,7 +4,7 @@
Showing
%button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown", display: "static" } }<
= pluralize(diff_files.size, "changed file")
- = icon("caret-down", class: "gl-ml-2")
+ = sprite_icon("chevron-down", css_class: "gl-ml-2")
%span.diff-stats-additions-deletions-expanded#diff-stats
with
%strong.cgreen= pluralize(sum_added_lines, 'addition')
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 1ed37c7a5c4..fd52f7f40d2 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -8,7 +8,7 @@
%a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span.js-clone-dropdown-label
= default_clone_protocol.upcase
- = icon('caret-down')
+ = sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
%li
= ssh_clone_button(container)
diff --git a/changelogs/unreleased/291078-fix-typo-merge-locally.yml b/changelogs/unreleased/291078-fix-typo-merge-locally.yml
new file mode 100644
index 00000000000..9ec110f02ba
--- /dev/null
+++ b/changelogs/unreleased/291078-fix-typo-merge-locally.yml
@@ -0,0 +1,5 @@
+---
+title: Fix typo on merge locally step
+merge_request: 49330
+author:
+type: fixed
diff --git a/changelogs/unreleased/mw-convert-fa-caret-down-to-svg.yml b/changelogs/unreleased/mw-convert-fa-caret-down-to-svg.yml
new file mode 100644
index 00000000000..cf5b9aba0c3
--- /dev/null
+++ b/changelogs/unreleased/mw-convert-fa-caret-down-to-svg.yml
@@ -0,0 +1,5 @@
+---
+title: Convert fa-caret-down icons to chevron-down SVG
+merge_request: 49332
+author:
+type: changed
diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md
index bc1f5a96f86..aac4411637c 100644
--- a/doc/administration/geo/index.md
+++ b/doc/administration/geo/index.md
@@ -257,6 +257,16 @@ For more information on tuning Geo, see [Tuning Geo](replication/tuning.md).
For an example of how to set up a location-aware Git remote URL with AWS Route53, see [Location-aware Git remote URL with AWS Route53](replication/location_aware_git_url.md).
+### Backfill
+
+Once a **secondary** node is set up, it will start replicating missing data from
+the **primary** node in a process known as **backfill**. You can monitor the
+synchronization process on each Geo node from the **primary** node's **Geo Nodes**
+dashboard in your browser.
+
+Failures that happen during a backfill are scheduled to be retried at the end
+of the backfill.
+
## Remove Geo node
For more information on removing a Geo node, see [Removing **secondary** Geo nodes](replication/remove_geo_node.md).
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 2dbffee2729..d6519d2c8f5 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -425,6 +425,11 @@ GitLab you are running. GitLab versions 11.11.x or 12.0.x are affected by
To resolve the issue, upgrade to GitLab 12.1 or newer.
+### Failures during backfill
+
+During a [backfill](../index.md#backfill), failures are scheduled to be retried at the end
+of the backfill queue, therefore these failures only clear up **after** the backfill completes.
+
### Resetting Geo **secondary** node replication
If you get a **secondary** node in a broken state and want to reset the replication state,
diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md
index 8cdb8789d3f..bea7a20d4b5 100644
--- a/doc/ci/pipelines/index.md
+++ b/doc/ci/pipelines/index.md
@@ -261,6 +261,18 @@ The union of A, B, and C is (1, 4) and (6, 7). Therefore, the total running time
(4 - 1) + (7 - 6) => 4
```
+#### How pipeline quota usage is calculated
+
+Pipeline quota usage is calculated as the sum of the duration of each individual job. This is slightly different to how pipeline _duration_ is [calculated](#how-pipeline-duration-is-calculated). Pipeline quota usage doesn't consider any overlap of jobs running in parallel.
+
+For example, a pipeline consists of the following jobs:
+
+- Job A takes 3 minutes.
+- Job B takes 3 minutes.
+- Job C takes 2 minutes.
+
+The pipeline quota usage is the sum of each job's duration. In this example, 8 runner minutes would be used, calculated as: 3 + 3 + 2.
+
### Pipeline security on protected branches
A strict security model is enforced when pipelines are executed on
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index bb4ff5dd7c4..09e8f780fe1 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -276,8 +276,13 @@ In its current state, the Rake task:
This uses some features from `graphql-docs` gem like its schema parser and helper methods.
The docs generator code comes from our side giving us more flexibility, like using Haml templates and generating Markdown files.
-To edit the template used, please take a look at `lib/gitlab/graphql/docs/templates/default.md.haml`.
-The actual renderer is at `Gitlab::Graphql::Docs::Renderer`.
+To edit the content, you may need to edit the following:
+
+- The template. You can edit the template at `lib/gitlab/graphql/docs/templates/default.md.haml`.
+ The actual renderer is at `Gitlab::Graphql::Docs::Renderer`.
+- The applicable `description` field in the code, which
+ [Updates machine-readable schema files](#update-machine-readable-schema-files),
+ which is then used by the `rake` task described earlier.
`@parsed_schema` is an instance variable that the `graphql-docs` gem expects to have available.
`Gitlab::Graphql::Docs::Helper` defines the `object` method we currently use. This is also where you
diff --git a/lib/gitlab/ci/reports/codequality_reports_comparer.rb b/lib/gitlab/ci/reports/codequality_reports_comparer.rb
new file mode 100644
index 00000000000..a442745fc50
--- /dev/null
+++ b/lib/gitlab/ci/reports/codequality_reports_comparer.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Reports
+ class CodequalityReportsComparer
+ include Gitlab::Utils::StrongMemoize
+
+ STATUS_SUCCESS = 'success'
+ STATUS_FAILED = 'failed'
+
+ attr_reader :base_report, :head_report
+
+ def initialize(base_report, head_report)
+ @base_report = base_report || CodequalityReports.new
+ @head_report = head_report
+ end
+
+ def status
+ head_report.degradations_count > 0 ? STATUS_FAILED : STATUS_SUCCESS
+ end
+
+ def existing_errors
+ strong_memoize(:existing_errors) do
+ base_report.all_degradations & head_report.all_degradations
+ end
+ end
+
+ def new_errors
+ strong_memoize(:new_errors) do
+ fingerprints = head_report.degradations.keys - base_report.degradations.keys
+ head_report.degradations.fetch_values(*fingerprints)
+ end
+ end
+
+ def resolved_errors
+ strong_memoize(:resolved_errors) do
+ fingerprints = base_report.degradations.keys - head_report.degradations.keys
+ base_report.degradations.fetch_values(*fingerprints)
+ end
+ end
+
+ def errors_count
+ head_report.degradations_count
+ end
+
+ def resolved_count
+ resolved_errors.size
+ end
+
+ def total_count
+ existing_errors.size + new_errors.size
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index abeeb0dbcaa..e46dbc7bc97 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -947,7 +947,7 @@ msgstr ""
msgid "(No changes)"
msgstr ""
-msgid "(UTC%{offset}) %{timezone}"
+msgid "(UTC %{offset}) %{timezone}"
msgstr ""
msgid "(check progress)"
@@ -1253,6 +1253,9 @@ msgstr ""
msgid "A merge request approval is required when the license compliance report contains a denied license."
msgstr ""
+msgid "A merge request hasn't yet been merged"
+msgstr ""
+
msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
msgstr ""
@@ -3683,9 +3686,6 @@ msgstr ""
msgid "Are you sure you want to delete this SSH key?"
msgstr ""
-msgid "Are you sure you want to delete this board?"
-msgstr ""
-
msgid "Are you sure you want to delete this device? This action cannot be undone."
msgstr ""
@@ -4486,9 +4486,6 @@ msgstr ""
msgid "Blog"
msgstr ""
-msgid "Board scope"
-msgstr ""
-
msgid "Board scope affects which issues are displayed for anyone who visits this board"
msgstr ""
@@ -4543,6 +4540,30 @@ msgstr ""
msgid "Boards|View scope"
msgstr ""
+msgid "Board|Are you sure you want to delete this board?"
+msgstr ""
+
+msgid "Board|Board scope"
+msgstr ""
+
+msgid "Board|Create board"
+msgstr ""
+
+msgid "Board|Create new board"
+msgstr ""
+
+msgid "Board|Delete board"
+msgstr ""
+
+msgid "Board|Edit board"
+msgstr ""
+
+msgid "Board|Enter board name"
+msgstr ""
+
+msgid "Board|Failed to delete board. Please try again."
+msgstr ""
+
msgid "Board|Load more issues"
msgstr ""
@@ -7943,9 +7964,6 @@ msgstr ""
msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the %{code_open}repo%{code_close} scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
-msgid "Create board"
-msgstr ""
-
msgid "Create branch"
msgstr ""
@@ -8003,9 +8021,6 @@ msgstr ""
msgid "Create new Value Stream"
msgstr ""
-msgid "Create new board"
-msgstr ""
-
msgid "Create new branch"
msgstr ""
@@ -8926,9 +8941,6 @@ msgstr ""
msgid "Delete badge"
msgstr ""
-msgid "Delete board"
-msgstr ""
-
msgid "Delete comment"
msgstr ""
@@ -10062,9 +10074,6 @@ msgstr ""
msgid "Edit application"
msgstr ""
-msgid "Edit board"
-msgstr ""
-
msgid "Edit comment"
msgstr ""
@@ -10479,9 +10488,6 @@ msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
-msgid "Enter board name"
-msgstr ""
-
msgid "Enter domain"
msgstr ""
@@ -11490,9 +11496,6 @@ msgstr ""
msgid "Failed to create wiki"
msgstr ""
-msgid "Failed to delete board. Please try again."
-msgstr ""
-
msgid "Failed to deploy to"
msgstr ""
@@ -19187,6 +19190,12 @@ msgstr ""
msgid "OnCallSchedules|The schedule could not be updated. Please try again."
msgstr ""
+msgid "OnCallSchedules|Try adding a rotation"
+msgstr ""
+
+msgid "OnCallSchedules|Your schedule has been successfully created and all alerts from this project will now be routed to this schedule. Currently, only one schedule can be created per project. More coming soon! To add individual users to this schedule, use the add a rotation button."
+msgstr ""
+
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr ""
@@ -27115,6 +27124,9 @@ msgstr ""
msgid "The CSV export will be created in the background. Once finished, it will be sent to %{strong_open}%{email}%{strong_close} in an attachment."
msgstr ""
+msgid "The Compliance Dashboard gives you the ability to see a group's merge request activity by providing a high-level view for all projects in the group."
+msgstr ""
+
msgid "The GitLab user to which the Jira user %{jiraDisplayName} will be mapped"
msgstr ""
diff --git a/spec/fixtures/api/schemas/entities/codequality_degradation.json b/spec/fixtures/api/schemas/entities/codequality_degradation.json
new file mode 100644
index 00000000000..6cf20ee8b9e
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/codequality_degradation.json
@@ -0,0 +1,24 @@
+{
+ "type": "object",
+ "required": [
+ "description",
+ "severity",
+ "file_path",
+ "line"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "severity": {
+ "type": "string"
+ },
+ "file_path": {
+ "type": "string"
+ },
+ "line": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json b/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json
new file mode 100644
index 00000000000..afe82f5e632
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/codequality_reports_comparer.json
@@ -0,0 +1,43 @@
+{
+ "type": "object",
+ "required": ["status", "summary", "new_errors", "resolved_errors", "existing_errors"],
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "summary": {
+ "type": "object",
+ "properties": {
+ "total": {
+ "type": "integer"
+ },
+ "resolved": {
+ "type": "integer"
+ },
+ "errored": {
+ "type": "integer"
+ }
+ },
+ "required": ["total", "resolved", "errored"]
+ },
+ "new_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ },
+ "resolved_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ },
+ "existing_errors": {
+ "type": "array",
+ "items": {
+ "$ref": "codequality_degradation.json"
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 65d8070192c..447628d2663 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,9 +1,9 @@
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'jest/helpers/test_constants';
+import { GlModal } from '@gitlab/ui';
import boardsStore from '~/boards/stores/boards_store';
import boardForm from '~/boards/components/board_form.vue';
-import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
describe('board_form.vue', () => {
let wrapper;
@@ -14,7 +14,7 @@ describe('board_form.vue', () => {
labelsWebUrl: `${TEST_HOST}/-/labels`,
};
- const findModal = () => wrapper.find(DeprecatedModal);
+ const findModal = () => wrapper.find(GlModal);
beforeEach(() => {
boardsStore.state.currentPage = 'edit';
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
new file mode 100644
index 00000000000..7053d54381b
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#status' do
+ subject(:report_status) { comparer.status }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns status failed' do
+ expect(report_status).to eq(described_class::STATUS_FAILED)
+ end
+ end
+
+ context 'when head report does not have errors' do
+ it 'returns status success' do
+ expect(report_status).to eq(described_class::STATUS_SUCCESS)
+ end
+ end
+ end
+
+ describe '#errors_count' do
+ subject(:errors_count) { comparer.errors_count }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the number of new errors' do
+ expect(errors_count).to eq(1)
+ end
+ end
+
+ context 'when head report does not have an error' do
+ it 'returns zero' do
+ expect(errors_count).to be_zero
+ end
+ end
+ end
+
+ describe '#resolved_count' do
+ subject(:resolved_count) { comparer.resolved_count }
+
+ context 'when base report has an error and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'counts the base report error as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'counts the base report errors as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to eq(0)
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to be_zero
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject(:total_count) { comparer.total_count }
+
+ context 'when base report has an error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(total_count).to be_zero
+ end
+ end
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'includes the head report error in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(2)
+ end
+ end
+ end
+
+ describe '#existing_errors' do
+ subject(:existing_errors) { comparer.existing_errors }
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes the base report errors' do
+ expect(existing_errors).to contain_exactly(degradation_1)
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+ end
+
+ describe '#new_errors' do
+ subject(:new_errors) { comparer.new_errors }
+
+ context 'when base report has errors and head has more errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors not found in the base report' do
+ expect(new_errors).to eq([degradation_2])
+ end
+ end
+
+ context 'when base report has an error and head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(new_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the head report error' do
+ expect(new_errors).to eq([degradation_1])
+ end
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject(:resolved_errors) { comparer.resolved_errors }
+
+ context 'when base report errors are still found in the head report' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns the base report error' do
+ expect(resolved_errors).to eq([degradation_1])
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/serializers/codequality_degradation_entity_spec.rb b/spec/serializers/codequality_degradation_entity_spec.rb
new file mode 100644
index 00000000000..fc969195e35
--- /dev/null
+++ b/spec/serializers/codequality_degradation_entity_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityDegradationEntity do
+ let(:entity) { described_class.new(codequality_degradation) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when codequality contains an error' do
+ context 'when line is included in location' do
+ let(:codequality_degradation) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ it 'contains correct codequality degradation details', :aggregate_failures do
+ expect(subject[:description]).to eq("Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.")
+ expect(subject[:severity]).to eq("major")
+ expect(subject[:file_path]).to eq("foo.rb")
+ expect(subject[:line]).to eq(10)
+ end
+ end
+
+ context 'when line is included in positions' do
+ let(:codequality_degradation) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 24,
+ "line": 14
+ },
+ "end": {
+ "column": 49,
+ "line": 14
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ it 'contains correct codequality degradation details', :aggregate_failures do
+ expect(subject[:description]).to eq("Avoid parameter lists longer than 5 parameters. [12/5]")
+ expect(subject[:severity]).to eq("minor")
+ expect(subject[:file_path]).to eq("foo.rb")
+ expect(subject[:line]).to eq(14)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/serializers/codequality_reports_comparer_entity_spec.rb b/spec/serializers/codequality_reports_comparer_entity_spec.rb
new file mode 100644
index 00000000000..7a79c30cc3f
--- /dev/null
+++ b/spec/serializers/codequality_reports_comparer_entity_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityReportsComparerEntity do
+ let(:entity) { described_class.new(comparer) }
+ let(:comparer) { Gitlab::Ci::Reports::CodequalityReportsComparer.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when base and head report have errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'contains correct compared codequality report details', :aggregate_failures do
+ expect(subject[:status]).to eq(Gitlab::Ci::Reports::CodequalityReportsComparer::STATUS_FAILED)
+ expect(subject[:resolved_errors].first).to include(:description, :severity, :file_path, :line)
+ expect(subject[:new_errors].first).to include(:description, :severity, :file_path, :line)
+ expect(subject[:existing_errors]).to be_empty
+ expect(subject[:summary]).to include(total: 1, resolved: 1, errored: 1)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/codequality_reports_comparer_serializer_spec.rb b/spec/serializers/codequality_reports_comparer_serializer_spec.rb
new file mode 100644
index 00000000000..3c47bfb6adc
--- /dev/null
+++ b/spec/serializers/codequality_reports_comparer_serializer_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CodequalityReportsComparerSerializer do
+ let(:project) { double(:project) }
+ let(:serializer) { described_class.new(project: project).represent(comparer) }
+ let(:comparer) { Gitlab::Ci::Reports::CodequalityReportsComparer.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#to_json' do
+ subject { serializer.as_json }
+
+ context 'when base report has error and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+
+ context 'when base report has no error and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/codequality_reports_comparer')
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb
index a012ec29be5..4da416d9698 100644
--- a/spec/services/projects/container_repository/delete_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: 'deleted tags',
container_repository_id: repository.id,
+ project_id: repository.project_id,
deleted_tags_count: tags.size
)
@@ -32,7 +33,8 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
log_data = {
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: message,
- container_repository_id: repository.id
+ container_repository_id: repository.id,
+ project_id: repository.project_id
}
log_data.merge!(extra_log) if extra_log.any?