Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-07 09:09:42 +00:00
parent 132f8ac520
commit 5b6e9de025
28 changed files with 925 additions and 107 deletions

View file

@ -1,4 +0,0 @@
fragment Count on InstanceStatisticsMeasurement {
count
recordedAt
}

View file

@ -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,22 +205,26 @@ 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>
<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="label-bold gl-font-lg" for="board-new-name">{{ __('Title') }}</label>
<label class="gl-font-weight-bold gl-font-lg" for="board-new-name">
{{ $options.i18n.titleFieldLabel }}
</label>
<input
id="board-new-name"
ref="name"
@ -204,7 +232,7 @@ export default {
class="form-control"
data-qa-selector="board_name_field"
type="text"
:placeholder="__('Enter board name')"
:placeholder="$options.i18n.titleFieldPlaceholder"
@keyup.enter="submit"
/>
</div>
@ -228,6 +256,5 @@ export default {
:weights="weights"
/>
</form>
</template>
</deprecated-modal>
</gl-modal>
</template>

View file

@ -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')"
>

View file

@ -91,6 +91,7 @@
body.modal-open {
overflow: hidden;
padding-right: 0 !important;
}
.modal-no-backdrop {

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class CodequalityReportsComparerSerializer < BaseSerializer
entity CodequalityReportsComparerEntity
end

View file

@ -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

View file

@ -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)

View file

@ -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')

View file

@ -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)

View file

@ -0,0 +1,5 @@
---
title: Fix typo on merge locally step
merge_request: 49330
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Convert fa-caret-down icons to chevron-down SVG
merge_request: 49332
author:
type: changed

View file

@ -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).

View file

@ -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,

View file

@ -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

View file

@ -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`.
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

View file

@ -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

View file

@ -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 ""

View file

@ -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
}

View file

@ -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
}

View file

@ -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';

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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?