Commit 820f7c47 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '329636-failed-status-external-status-checks' into 'master'

Add support for failing status checks

See merge request gitlab-org/gitlab!81005
parents 159ecd7d 5bf86243
......@@ -18,6 +18,7 @@ export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
export const ICON_PENDING = 'pending';
export const ICON_FAILED = 'failed';
export const status = {
LOADING,
......
# frozen_string_literal: true
class AddStatusToStatusCheckResponses < Gitlab::Database::Migration[1.0]
def change
add_column :status_check_responses, :status, :integer, default: 0, null: false, limit: 2
end
end
d2d236e9ee5fa6e9c1ee97431543e871b78e469b812444bd9386dfecf849947b
\ No newline at end of file
......@@ -20660,7 +20660,8 @@ CREATE TABLE status_check_responses (
merge_request_id bigint NOT NULL,
external_approval_rule_id bigint,
sha bytea NOT NULL,
external_status_check_id bigint NOT NULL
external_status_check_id bigint NOT NULL,
status smallint DEFAULT 0 NOT NULL
);
CREATE SEQUENCE status_check_responses_id_seq
......@@ -44,6 +44,9 @@ GET /projects/:id/merge_requests/:merge_request_iid/status_checks
## Set status of an external status check
> - Introduced in GitLab 14.9, `passed` status to pass external status checks.
> - `pass` status to pass checks is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/339039) in GitLab 14.9. Replaced with `passed`.
For a single merge request, use the API to inform GitLab that a merge request has passed a check by an external service.
To set the status of an external check, the personal access token used must belong to a user with at least the developer role on the target project of the merge request.
......@@ -56,12 +59,13 @@ POST /projects/:id/merge_requests/:merge_request_iid/status_check_responses
**Parameters:**
| Attribute | Type | Required | Description |
| -------------------------- | ------- | -------- | ------------------------------------- |
| -------------------------- | ------- | -------- | ---------------------------------------------------------------------------- |
| `id` | integer | yes | ID of a project |
| `merge_request_iid` | integer | yes | IID of a merge request |
| `sha` | string | yes | SHA at `HEAD` of the source branch |
| `external_status_check_id` | integer | yes | ID of an external status check |
| `status` | string | no | Set to `pass` to pass the check |
| `status` | string | no | Set to `passed` to pass the check or `failed` to fail it (GitLab 14.9 and later with feature flag enabled) |
| `status` | string | no | Set to `pass` to pass the check (GitLab 14.0 to GitLab 14.8) |
NOTE:
`sha` must be the SHA at the `HEAD` of the merge request's source branch.
......
export const PASSED = 'passed';
export const APPROVED = 'approved';
export const PENDING = 'pending';
export const FAILED = 'failed';
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { componentNames } from 'ee/reports/components/issue_body';
import { helpPagePath } from '~/helpers/help_page_helper';
import axios from '~/lib/utils/axios_utils';
import { sprintf, s__ } from '~/locale';
import ReportSection from '~/reports/components/report_section.vue';
import { status } from '~/reports/constants';
import { APPROVED, PENDING } from './constants';
import { APPROVED, PASSED, PENDING, FAILED } from './constants';
export default {
name: 'StatusChecksReportsApp',
components: {
GlLink,
GlSprintf,
ReportSection,
},
componentNames,
......@@ -31,21 +27,26 @@ export default {
},
computed: {
approvedStatusChecks() {
return this.statusChecks.filter((s) => s.status === APPROVED);
return this.statusChecks.filter((s) => [PASSED, APPROVED].includes(s.status));
},
pendingStatusChecks() {
return this.statusChecks.filter((s) => s.status === PENDING);
},
failedStatusChecks() {
return this.statusChecks.filter((s) => s.status === FAILED);
},
hasStatusChecks() {
return this.statusChecks.length > 0;
},
headingReportText() {
if (this.pendingStatusChecks.length > 0) {
return sprintf(s__('StatusCheck|%{pending} pending'), {
if (this.approvedStatusChecks.length === this.statusChecks.length) {
return s__('StatusCheck|All passed');
}
return sprintf(s__('StatusCheck| %{failed} failed, and %{pending} pending'), {
pending: this.pendingStatusChecks.length,
failed: this.failedStatusChecks.length,
});
}
return s__('StatusCheck|All passed');
},
},
mounted() {
......@@ -67,14 +68,8 @@ export default {
},
i18n: {
heading: s__('StatusCheck|Status checks'),
subHeading: s__(
'StatusCheck|When this merge request is updated, a call is sent to the following APIs to confirm their status. %{linkStart}Learn more%{linkEnd}.',
),
errorText: s__('StatusCheck|Failed to load status checks.'),
},
docsLink: helpPagePath('user/project/merge_requests/status_checks.md', {
anchor: 'status-checks-widget',
}),
};
</script>
......@@ -85,6 +80,7 @@ export default {
:error-text="$options.i18n.errorText"
:has-issues="hasStatusChecks"
:resolved-issues="approvedStatusChecks"
:unresolved-issues="failedStatusChecks"
:neutral-issues="pendingStatusChecks"
:component="$options.componentNames.StatusCheckIssueBody"
:show-report-section-status-icon="false"
......@@ -99,15 +95,5 @@ export default {
<strong class="gl-p-1">{{ headingReportText }}</strong>
</p>
</template>
<template #sub-heading>
<span class="gl-text-gray-500 gl-font-sm">
<gl-sprintf :message="$options.i18n.subHeading">
<template #link="{ content }">
<gl-link class="gl-font-sm" :href="$options.docsLink">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
</template>
</report-section>
</template>
<script>
import SummaryRow from '~/reports/components/summary_row.vue';
import { ICON_SUCCESS, ICON_PENDING } from '~/reports/constants';
import { APPROVED } from '../../reports/status_checks_report/constants';
import { ICON_SUCCESS, ICON_PENDING, ICON_FAILED } from '~/reports/constants';
import { PASSED, APPROVED, FAILED } from '../../reports/status_checks_report/constants';
export default {
name: 'StatusCheckIssueBody',
......@@ -16,10 +16,16 @@ export default {
},
computed: {
statusIcon() {
if (this.issue.status === APPROVED) {
switch (this.issue.status) {
case APPROVED:
return ICON_SUCCESS;
}
case PASSED:
return ICON_SUCCESS;
case FAILED:
return ICON_FAILED;
default:
return ICON_PENDING;
}
},
},
};
......@@ -29,7 +35,7 @@ export default {
<div class="gl-w-full" :data-testid="`mr-status-check-issue-${issue.id}`">
<summary-row :status-icon="statusIcon" nested-summary>
<template #summary>
<span>{{ issue.name }}, {{ issue.external_url }}</span>
<span>{{ issue.name }}: {{ issue.external_url }}</span>
</template>
</summary-row>
</div>
......
......@@ -5,7 +5,7 @@ import {
EXTENSION_SUMMARY_FAILED_CLASS,
EXTENSION_SUMMARY_NEUTRAL_CLASS,
} from '~/vue_merge_request_widget/constants';
import { APPROVED, PENDING } from 'ee/reports/status_checks_report/constants';
import { APPROVED, PASSED, PENDING } from 'ee/reports/status_checks_report/constants';
export default {
name: 'WidgetStatusChecks',
......@@ -48,7 +48,7 @@ export default {
return {
subject: s__('StatusCheck|Status checks'),
meta: reports.join(__(', and ')),
meta: reports.join(__(', ')),
};
},
statusIcon({ pending = [], failed = [] }) {
......@@ -102,6 +102,9 @@ export default {
statusChecks.forEach((statusCheck) => {
switch (statusCheck.status) {
case PASSED:
approved.push(this.createReportRow(statusCheck, EXTENSION_ICONS.success));
break;
case APPROVED:
approved.push(this.createReportRow(statusCheck, EXTENSION_ICONS.success));
break;
......
......@@ -18,6 +18,7 @@ module EE
push_frontend_feature_flag(:missing_mr_security_scan_types, @project)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions_user, current_user, default_enabled: :yaml)
push_frontend_feature_flag(:status_checks_add_status_field, default_enabled: :yaml)
end
before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports,
......
......@@ -27,6 +27,10 @@ module MergeRequests
ApprovalRules::ExternalApprovalRulePayloadWorker.perform_async(self.id, payload_data(data))
end
def status(merge_request, sha)
merge_request.status_check_responses.where(external_status_check: self, sha: sha).last&.status || 'pending'
end
def approved?(merge_request, sha)
merge_request.status_check_responses.where(external_status_check: self, sha: sha).exists?
end
......
......@@ -11,6 +11,8 @@ module MergeRequests
belongs_to :merge_request
belongs_to :external_status_check, class_name: 'MergeRequests::ExternalStatusCheck'
enum status: %w(passed failed)
validates :merge_request, presence: true
validates :external_status_check, presence: true
validates :sha, presence: true
......
---
name: status_checks_add_status_field
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81005
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353836
milestone: '14.9'
type: development
group: group::compliance
default_enabled: false
......@@ -10,9 +10,13 @@ module API
expose :status
def status
if ::Feature.enabled?(:status_checks_add_status_field, object.project, default_enabled: :yaml)
object.status(options[:merge_request], options[:sha])
else
object.approved?(options[:merge_request], options[:sha]) ? 'approved' : 'pending'
end
end
end
end
end
end
......@@ -106,16 +106,19 @@ module API
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :external_status_check_id, type: Integer, desc: 'The ID of a external status check'
requires :sha, type: String, desc: 'The current SHA at HEAD of the merge request.'
optional :status, type: String, desc: 'Status of the merge request status check', default: 'passed', values: %w(passed failed)
end
post 'status_check_responses' do
merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
status = ::Feature.enabled?(:status_checks_add_status_field, merge_request.project, default_enabled: :yaml) ? params[:status] : 'passed'
status_check = merge_request.project.external_status_checks.find(params[:external_status_check_id])
check_sha_param!(params, merge_request)
not_found! unless current_user.can?(:provide_status_check_response, merge_request)
approval = merge_request.status_check_responses.create!(external_status_check: status_check, sha: params[:sha])
approval = merge_request.status_check_responses.create!(external_status_check: status_check, sha: params[:sha], status: status)
present(approval, with: Entities::MergeRequests::StatusCheckResponse)
end
......
......@@ -4,5 +4,7 @@ FactoryBot.define do
factory :status_check_response, class: 'MergeRequests::StatusCheckResponse' do
merge_request
external_status_check
sha { 'aabccddee' }
status { 'passed' }
end
end
......@@ -3,13 +3,17 @@
require 'spec_helper'
RSpec.describe 'Merge request > User sees status checks widget', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:check1) { create(:external_status_check, project: project) }
let_it_be(:check2) { create(:external_status_check, project: project) }
let_it_be(:check_pending) { create(:external_status_check, project: project) }
let_it_be(:check_failed) { create(:external_status_check, project: project) }
let_it_be(:check_passed) { create(:external_status_check, project: project) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:status_check_response) { create(:status_check_response, external_status_check: check1, merge_request: merge_request, sha: merge_request.source_branch_sha) }
let_it_be(:status_check_response_passed) { create(:status_check_response, external_status_check: check_passed, merge_request: merge_request, sha: merge_request.source_branch_sha, status: 'passed') }
let_it_be(:status_check_response_failed) { create(:status_check_response, external_status_check: check_failed, merge_request: merge_request, sha: merge_request.source_branch_sha, status: 'failed') }
shared_examples 'no status checks widget' do
it 'does not show the widget' do
......@@ -19,13 +23,12 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do
before do
stub_licensed_features(external_status_checks: true)
stub_feature_flags(refactor_mr_widgets_extensions: false)
stub_feature_flags(refactor_mr_widgets_extensions_user: false)
end
context 'user is authorized' do
before do
stub_feature_flags(refactor_mr_widgets_extensions: false)
stub_feature_flags(refactor_mr_widgets_extensions_user: false)
project.add_maintainer(user)
sign_in(user)
......@@ -33,45 +36,25 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do
end
it 'shows the widget' do
expect(page).to have_content('Status checks 1 pending')
end
it 'shows the status check issues', :aggregate_failures do
within '[data-test-id="mr-status-checks"]' do
click_button 'Expand'
end
[check1, check2].each do |rule|
within "[data-testid='mr-status-check-issue-#{rule.id}']" do
icon_type = rule.approved?(merge_request, merge_request.source_branch_sha) ? 'success' : 'pending'
expect(page).to have_css(".ci-status-icon-#{icon_type}")
expect(page).to have_content("#{rule.name}, #{rule.external_url}")
expect(page).to have_content('Status checks 1 failed, and 1 pending')
end
end
end
end
context 'widget extension flag is enabled' do
before do
project.add_maintainer(user)
sign_in(user)
visit project_merge_request_path(project, merge_request)
where(:check, :icon_class) do
lazy { check_pending } | '.ci-status-icon-pending'
lazy { check_passed } | '.ci-status-icon-success'
lazy { check_failed } | '.ci-status-icon-failed'
end
it 'shows the widget' do
expect(page).to have_content('Status checks 1 pending')
with_them do
it 'is rendered correctly', :aggregate_failures do
within '[data-test-id="mr-status-checks"]' do
click_button 'Expand'
end
it 'shows the status check issues', :aggregate_failures do
within '[data-testid="widget-extension"]' do
find('[data-testid="toggle-button"]').click
within "[data-testid='mr-status-check-issue-#{check.id}']" do
expect(page).to have_css(icon_class)
expect(page).to have_content("#{check.name}: #{check.external_url}")
end
[check1, check2].each do |rule|
icon_type = rule.approved?(merge_request, merge_request.source_branch_sha) ? 'success' : 'neutral'
expect(page).to have_css("[data-testid='status-#{icon_type}-icon']")
expect(page).to have_content("#{rule.name}: #{rule.external_url}")
end
end
end
......
......@@ -9,7 +9,7 @@ exports[`Grouped test reports app when mounted matches the default state compone
<div class=\\"gl-display-flex gl-align-items-center\\">
<p class=\\"gl-line-height-normal gl-m-0\\">Status checks</p>
<!---->
</div> <span class=\\"gl-text-gray-500 gl-font-sm\\">When this merge request is updated, a call is sent to the following APIs to confirm their status. <gl-link-stub href=\\"/help/user/project/merge_requests/status_checks.md#status-checks-widget\\" class=\\"gl-font-sm\\">Learn more</gl-link-stub>.</span>
</div>
</div>
<!---->
</div>
......
......@@ -3,7 +3,7 @@ export const approvedChecks = [
id: 1,
name: 'Foo',
external_url: 'http://foo',
status: 'approved',
status: 'passed',
},
];
......@@ -28,3 +28,5 @@ export const failedChecks = [
export const pendingAndFailedChecks = [...pendingChecks, ...failedChecks];
export const approvedAndPendingChecks = [...approvedChecks, ...pendingChecks];
export const approvedFailedAndPending = [...approvedChecks, ...failedChecks, ...pendingChecks];
......@@ -8,7 +8,13 @@ import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import ReportSection from '~/reports/components/report_section.vue';
import { status as reportStatus } from '~/reports/constants';
import { approvedChecks, pendingChecks, approvedAndPendingChecks } from './mock_data';
import {
approvedChecks,
pendingChecks,
failedChecks,
approvedAndPendingChecks,
pendingAndFailedChecks,
} from './mock_data';
jest.mock('~/flash');
......@@ -34,6 +40,9 @@ describe('Grouped test reports app', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
window.gon.features = {
statusChecksAddStatusField: true,
};
});
afterEach(() => {
......@@ -73,11 +82,14 @@ describe('Grouped test reports app', () => {
};
describe.each`
state | response | text | resolvedIssues | neutralIssues
${'approved'} | ${approvedChecks} | ${'All passed'} | ${approvedChecks} | ${[]}
${'pending'} | ${pendingChecks} | ${'1 pending'} | ${[]} | ${pendingChecks}
${'mixed'} | ${approvedAndPendingChecks} | ${'1 pending'} | ${approvedChecks} | ${pendingChecks}
`('and the status checks are $state', ({ response, text, resolvedIssues, neutralIssues }) => {
state | response | text | resolvedIssues | neutralIssues | unresolvedIssues
${'approved'} | ${approvedChecks} | ${'All passed'} | ${approvedChecks} | ${[]} | ${[]}
${'pending'} | ${pendingChecks} | ${'0 failed, and 1 pending'} | ${[]} | ${pendingChecks} | ${[]}
${'approved and pending'} | ${approvedAndPendingChecks} | ${'0 failed, and 1 pending'} | ${approvedChecks} | ${pendingChecks} | ${[]}
${'pending and failed'} | ${pendingAndFailedChecks} | ${'1 failed, and 1 pending'} | ${[]} | ${pendingChecks} | ${failedChecks}
`(
'and the status checks are $state',
({ response, text, resolvedIssues, neutralIssues, unresolvedIssues }) => {
beforeEach(() => {
return mountWithResponse(httpStatus.OK, response);
});
......@@ -88,6 +100,7 @@ describe('Grouped test reports app', () => {
it('sets the issues on the report', () => {
expect(findReport().props('hasIssues')).toBe(true);
expect(findReport().props('unresolvedIssues')).toStrictEqual(unresolvedIssues);
expect(findReport().props('resolvedIssues')).toStrictEqual(resolvedIssues);
expect(findReport().props('neutralIssues')).toStrictEqual(neutralIssues);
});
......@@ -95,7 +108,8 @@ describe('Grouped test reports app', () => {
it(`renders '${text}' in the report section`, () => {
expect(findReport().text()).toContain(text);
});
});
},
);
describe('and an error occurred', () => {
beforeEach(() => {
......
......@@ -31,13 +31,15 @@ describe('status check issue body', () => {
});
it('renders the status check name and external URL', () => {
expect(wrapper.text()).toBe(`${defaultStatusCheck.name}, ${defaultStatusCheck.external_url}`);
expect(wrapper.text()).toBe(`${defaultStatusCheck.name}: ${defaultStatusCheck.external_url}`);
});
it.each`
status | icon
${'passed'} | ${'success'}
${'approved'} | ${'success'}
${'pending'} | ${'pending'}
${'failed'} | ${'failed'}
`('sets the status-icon to $icon when the check status is $status', ({ status, icon }) => {
createComponent({ status });
......
......@@ -78,7 +78,7 @@ describe('Status checks extension', () => {
${'approved'} | ${approvedChecks} | ${'Status checks all passed'}
${'pending'} | ${pendingChecks} | ${'1 pending'}
${'approved and pending'} | ${approvedAndPendingChecks} | ${'1 pending'}
${'failed and pending'} | ${pendingAndFailedChecks} | ${'1 failed, and 1 pending'}
${'failed and pending'} | ${pendingAndFailedChecks} | ${'1 failed, 1 pending'}
`('and the status checks are $state', ({ response, text }) => {
beforeEach(async () => {
await setupWithResponse(httpStatus.OK, response);
......
......@@ -120,5 +120,71 @@ RSpec.describe MergeRequests::ExternalStatusCheck, type: :model do
it { is_expected.to be false }
end
describe 'status' do
let_it_be(:rule) { create(:external_status_check) }
let_it_be(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
subject { rule.status(merge_request, merge_request.source_branch_sha) }
context 'when a rule has a positive status check response' do
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: merge_request.source_branch_sha, status: 'passed') }
it { is_expected.to eq('passed') }
context 'when a rule also has a positive check response from an old sha' do
before do
create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: 'abc1234', status: 'passed')
end
it { is_expected.to eq('passed') }
end
end
context 'when a rule has a negative status check response' do
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: merge_request.source_branch_sha, status: 'failed') }
it { is_expected.to eq('failed') }
end
context 'when a rule has no status check response' do
it { is_expected.to eq('pending') }
end
end
describe 'status' do
let_it_be(:rule) { create(:external_status_check) }
let_it_be(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
subject { rule.status(merge_request, merge_request.source_branch_sha) }
context 'when a rule has a positive status check response' do
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: merge_request.source_branch_sha, status: 'passed') }
it { is_expected.to eq('passed') }
context 'when a rule also has a positive check response from an old sha' do
before do
create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: 'abc1234', status: 'passed')
end
it { is_expected.to eq('passed') }
end
end
context 'when a rule has a negative status check response' do
let_it_be(:status_check_response) { create(:status_check_response, merge_request: merge_request, external_status_check: rule, sha: merge_request.source_branch_sha, status: 'failed') }
it { is_expected.to eq('failed') }
end
context 'when a rule has no status check response' do
it { is_expected.to eq('pending') }
end
end
end
end
......@@ -8,6 +8,8 @@ RSpec.describe MergeRequests::StatusCheckResponse, type: :model do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:external_status_check).class_name('MergeRequests::ExternalStatusCheck') }
it { is_expected.to define_enum_for(:status) }
it { is_expected.to validate_presence_of(:merge_request) }
it { is_expected.to validate_presence_of(:external_status_check) }
it { is_expected.to validate_presence_of(:sha) }
......
......@@ -16,6 +16,7 @@ RSpec.describe API::StatusChecks do
let(:single_object_url) { "/projects/#{project.id}/external_status_checks/#{rule.id}" }
let(:collection_url) { "/projects/#{project.id}/external_status_checks" }
let(:sha) { merge_request.source_branch_sha }
let(:status) { '' }
subject { get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_checks", user), params: { external_status_check_id: rule.id, sha: sha } }
......@@ -43,6 +44,19 @@ RSpec.describe API::StatusChecks do
expect(json_response.size).to eq(3)
end
it 'has the correct status values' do
subject
expect(json_response[0]["status"]).to eq('passed')
expect(json_response[1]["status"]).to eq('pending')
expect(json_response[2]["status"]).to eq('pending')
end
context 'when status_checks_add_status_field is disabled' do
before do
stub_feature_flags(status_checks_add_status_field: false)
end
it 'has the correct status values' do
subject
......@@ -53,9 +67,12 @@ RSpec.describe API::StatusChecks do
end
end
end
end
describe 'POST :id/:merge_requests/:merge_request_iid/status_check_responses' do
subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: rule.id, sha: sha } }
subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: rule.id, sha: sha, status: status } }
let(:status) { 'passed' }
context 'permissions' do
using RSpec::Parameterized::TableSyntax
......@@ -94,6 +111,22 @@ RSpec.describe API::StatusChecks do
project.add_user(user, :maintainer)
end
context 'when status_checks_add_status_field flag is disabled' do
before do
stub_feature_flags(status_checks_add_status_field: false)
end
context 'status is failed' do
let(:status) { 'failed' }
it 'is overridden to passed' do
subject
expect(MergeRequests::StatusCheckResponse.last.status).to eq("passed")
end
end
end
context 'when external status check ID does not belong to the requested project' do
let_it_be(:rule) { create(:external_status_check) }
......
......@@ -1280,7 +1280,7 @@ msgstr ""
msgid "+%{tags} more"
msgstr ""
msgid ", and "
msgid ", "
msgstr ""
msgid ", or "
......@@ -35202,6 +35202,9 @@ msgstr ""
msgid "Status: %{title}"
msgstr ""
msgid "StatusCheck| %{failed} failed, and %{pending} pending"
msgstr ""
msgid "StatusCheck|%{failed} failed"
msgstr ""
......@@ -35274,9 +35277,6 @@ msgstr ""
msgid "StatusCheck|Update status check"
msgstr ""
msgid "StatusCheck|When this merge request is updated, a call is sent to the following APIs to confirm their status. %{linkStart}Learn more%{linkEnd}."
msgstr ""
msgid "StatusCheck|You are about to remove the %{name} status check."
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment