Commit 0db9a197 authored by Payton Burdette's avatar Payton Burdette Committed by Douglas Barbosa Alexandre

Move pipeline warnings to Vue

Move over lint warnings to
the Vue implementation of run
manual pipeline.
parent 7ae4af48
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
GlSearchBoxByType, GlSearchBoxByType,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __, n__ } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { VARIABLE_TYPE, FILE_TYPE } from '../constants'; import { VARIABLE_TYPE, FILE_TYPE } from '../constants';
...@@ -29,6 +29,8 @@ export default { ...@@ -29,6 +29,8 @@ export default {
), ),
formElementClasses: 'gl-mr-3 gl-mb-3 table-section section-15', formElementClasses: 'gl-mr-3 gl-mb-3 table-section section-15',
errorTitle: __('The form contains the following error:'), errorTitle: __('The form contains the following error:'),
warningTitle: __('The form contains the following warning:'),
maxWarningsSummary: __('%{total} warnings found: showing first %{warningsDisplayed}'),
components: { components: {
GlAlert, GlAlert,
GlButton, GlButton,
...@@ -74,13 +76,20 @@ export default { ...@@ -74,13 +76,20 @@ export default {
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
maxWarnings: {
type: Number,
required: true,
},
}, },
data() { data() {
return { return {
searchTerm: '', searchTerm: '',
refValue: this.refParam, refValue: this.refParam,
variables: {}, variables: {},
error: false, error: null,
warnings: [],
totalWarnings: 0,
isWarningDismissed: false,
}; };
}, },
computed: { computed: {
...@@ -91,6 +100,18 @@ export default { ...@@ -91,6 +100,18 @@ export default {
variablesLength() { variablesLength() {
return Object.keys(this.variables).length; return Object.keys(this.variables).length;
}, },
overMaxWarningsLimit() {
return this.totalWarnings > this.maxWarnings;
},
warningsSummary() {
return n__('%d warning found:', '%d warnings found:', this.warnings.length);
},
summaryMessage() {
return this.overMaxWarningsLimit ? this.$options.maxWarningsSummary : this.warningsSummary;
},
shouldShowWarning() {
return this.warnings.length > 0 && !this.isWarningDismissed;
},
}, },
created() { created() {
if (this.variableParams) { if (this.variableParams) {
...@@ -154,8 +175,11 @@ export default { ...@@ -154,8 +175,11 @@ export default {
redirectTo(`${this.pipelinesPath}/${data.id}`); redirectTo(`${this.pipelinesPath}/${data.id}`);
}) })
.catch(err => { .catch(err => {
const [message] = err.response.data.base; const { errors, warnings, total_warnings: totalWarnings } = err.response.data;
this.error = message; const [error] = errors;
this.error = error;
this.warnings = warnings;
this.totalWarnings = totalWarnings;
}); });
}, },
}, },
...@@ -170,8 +194,37 @@ export default { ...@@ -170,8 +194,37 @@ export default {
:dismissible="false" :dismissible="false"
variant="danger" variant="danger"
class="gl-mb-4" class="gl-mb-4"
data-testid="run-pipeline-error-alert"
>{{ error }}</gl-alert >{{ error }}</gl-alert
> >
<gl-alert
v-if="shouldShowWarning"
:title="$options.warningTitle"
variant="warning"
class="gl-mb-4"
data-testid="run-pipeline-warning-alert"
@dismiss="isWarningDismissed = true"
>
<details>
<summary>
<gl-sprintf :message="summaryMessage">
<template #total>
{{ totalWarnings }}
</template>
<template #warningsDisplayed>
{{ maxWarnings }}
</template>
</gl-sprintf>
</summary>
<p
v-for="(warning, index) in warnings"
:key="`warning-${index}`"
data-testid="run-pipeline-warning"
>
{{ warning }}
</p>
</details>
</gl-alert>
<gl-form-group :label="s__('Pipeline|Run for')"> <gl-form-group :label="s__('Pipeline|Run for')">
<gl-dropdown :text="refValue" block> <gl-dropdown :text="refValue" block>
<gl-search-box-by-type <gl-search-box-by-type
......
...@@ -11,6 +11,7 @@ export default () => { ...@@ -11,6 +11,7 @@ export default () => {
fileParam, fileParam,
refNames, refNames,
settingsLink, settingsLink,
maxWarnings,
} = el?.dataset; } = el?.dataset;
const variableParams = JSON.parse(varParam); const variableParams = JSON.parse(varParam);
...@@ -29,6 +30,7 @@ export default () => { ...@@ -29,6 +30,7 @@ export default () => {
fileParams, fileParams,
refs, refs,
settingsLink, settingsLink,
maxWarnings: Number(maxWarnings),
}, },
}); });
}, },
......
...@@ -78,7 +78,10 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -78,7 +78,10 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipeline), .represent(@pipeline),
status: :created status: :created
else else
render json: @pipeline.errors, status: :bad_request render json: { errors: @pipeline.error_messages.map(&:content),
warnings: @pipeline.warning_messages(limit: ::Gitlab::Ci::Warnings::MAX_LIMIT).map(&:content),
total_warnings: @pipeline.warning_messages.length },
status: :bad_request
end end
end end
end end
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%hr %hr
- if Feature.enabled?(:new_pipeline_form) - if Feature.enabled?(:new_pipeline_form)
#js-new-pipeline{ data: { project_id: @project.id, pipelines_path: project_pipelines_path(@project), ref_param: params[:ref] || @project.default_branch, var_param: params[:var].to_json, file_param: params[:file_var].to_json, ref_names: @project.repository.ref_names.to_json.html_safe, settings_link: project_settings_ci_cd_path(@project) } } #js-new-pipeline{ data: { project_id: @project.id, pipelines_path: project_pipelines_path(@project), ref_param: params[:ref] || @project.default_branch, var_param: params[:var].to_json, file_param: params[:file_var].to_json, ref_names: @project.repository.ref_names.to_json.html_safe, settings_link: project_settings_ci_cd_path(@project), max_warnings: ::Gitlab::Ci::Warnings::MAX_LIMIT } }
- else - else
= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f| = form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f|
......
...@@ -8,8 +8,6 @@ msgid "" ...@@ -8,8 +8,6 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-11 14:23+1000\n"
"PO-Revision-Date: 2020-09-11 14:23+1000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -307,6 +305,11 @@ msgid_plural "%d vulnerabilities dismissed" ...@@ -307,6 +305,11 @@ msgid_plural "%d vulnerabilities dismissed"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%d warning found:"
msgid_plural "%d warnings found:"
msgstr[0] ""
msgstr[1] ""
msgid "%s additional commit has been omitted to prevent performance issues." msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] "" msgstr[0] ""
...@@ -824,6 +827,9 @@ msgstr "" ...@@ -824,6 +827,9 @@ msgstr ""
msgid "%{total} open issue weight" msgid "%{total} open issue weight"
msgstr "" msgstr ""
msgid "%{total} warnings found: showing first %{warningsDisplayed}"
msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc." msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr "" msgstr ""
...@@ -25039,6 +25045,9 @@ msgstr "" ...@@ -25039,6 +25045,9 @@ msgstr ""
msgid "The form contains the following error:" msgid "The form contains the following error:"
msgstr "" msgstr ""
msgid "The form contains the following warning:"
msgstr ""
msgid "The global settings require you to enable Two-Factor Authentication for your account." msgid "The global settings require you to enable Two-Factor Authentication for your account."
msgstr "" msgstr ""
......
...@@ -801,6 +801,11 @@ RSpec.describe Projects::PipelinesController do ...@@ -801,6 +801,11 @@ RSpec.describe Projects::PipelinesController do
context 'with an invalid .gitlab-ci.yml file' do context 'with an invalid .gitlab-ci.yml file' do
before do before do
stub_ci_pipeline_yaml_file(YAML.dump({ stub_ci_pipeline_yaml_file(YAML.dump({
build: {
stage: 'build',
script: 'echo',
rules: [{ when: 'always' }]
},
test: { test: {
stage: 'invalid', stage: 'invalid',
script: 'echo' script: 'echo'
...@@ -812,9 +817,13 @@ RSpec.describe Projects::PipelinesController do ...@@ -812,9 +817,13 @@ RSpec.describe Projects::PipelinesController do
expect { subject }.not_to change { project.ci_pipelines.count } expect { subject }.not_to change { project.ci_pipelines.count }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['base']).to include( expect(json_response['errors']).to eq([
'test job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post' 'test job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post'
])
expect(json_response['warnings'][0]).to include(
'jobs:build may allow multiple pipelines to run for a single action due to `rules:when`'
) )
expect(json_response['total_warnings']).to eq(1)
end end
end end
end end
......
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlForm } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlForm, GlSprintf } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue'; import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue';
import { mockRefs, mockParams, mockPostParams, mockProjectId } from '../mock_data'; import { mockRefs, mockParams, mockPostParams, mockProjectId, mockError } from '../mock_data';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
...@@ -28,6 +28,10 @@ describe('Pipeline New Form', () => { ...@@ -28,6 +28,10 @@ describe('Pipeline New Form', () => {
const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]'); const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]'); const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]');
const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]'); const findKeyInputs = () => wrapper.findAll('[data-testid="pipeline-form-ci-variable-key"]');
const findErrorAlert = () => wrapper.find('[data-testid="run-pipeline-error-alert"]');
const findWarningAlert = () => wrapper.find('[data-testid="run-pipeline-warning-alert"]');
const findWarningAlertSummary = () => findWarningAlert().find(GlSprintf);
const findWarnings = () => wrapper.findAll('[data-testid="run-pipeline-warning"]');
const getExpectedPostParams = () => JSON.parse(mock.history.post[0].data); const getExpectedPostParams = () => JSON.parse(mock.history.post[0].data);
const createComponent = (term = '', props = {}, method = shallowMount) => { const createComponent = (term = '', props = {}, method = shallowMount) => {
...@@ -38,6 +42,7 @@ describe('Pipeline New Form', () => { ...@@ -38,6 +42,7 @@ describe('Pipeline New Form', () => {
refs: mockRefs, refs: mockRefs,
defaultBranch: 'master', defaultBranch: 'master',
settingsLink: '', settingsLink: '',
maxWarnings: 25,
...props, ...props,
}, },
data() { data() {
...@@ -50,8 +55,6 @@ describe('Pipeline New Form', () => { ...@@ -50,8 +55,6 @@ describe('Pipeline New Form', () => {
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onPost(pipelinesPath).reply(200, postResponse);
}); });
afterEach(() => { afterEach(() => {
...@@ -62,6 +65,10 @@ describe('Pipeline New Form', () => { ...@@ -62,6 +65,10 @@ describe('Pipeline New Form', () => {
}); });
describe('Dropdown with branches and tags', () => { describe('Dropdown with branches and tags', () => {
beforeEach(() => {
mock.onPost(pipelinesPath).reply(200, postResponse);
});
it('displays dropdown with all branches and tags', () => { it('displays dropdown with all branches and tags', () => {
createComponent(); createComponent();
expect(findDropdownItems()).toHaveLength(mockRefs.length); expect(findDropdownItems()).toHaveLength(mockRefs.length);
...@@ -82,6 +89,8 @@ describe('Pipeline New Form', () => { ...@@ -82,6 +89,8 @@ describe('Pipeline New Form', () => {
describe('Form', () => { describe('Form', () => {
beforeEach(() => { beforeEach(() => {
createComponent('', mockParams, mount); createComponent('', mockParams, mount);
mock.onPost(pipelinesPath).reply(200, postResponse);
}); });
it('displays the correct values for the provided query params', async () => { it('displays the correct values for the provided query params', async () => {
expect(findDropdown().props('text')).toBe('tag-1'); expect(findDropdown().props('text')).toBe('tag-1');
...@@ -124,4 +133,34 @@ describe('Pipeline New Form', () => { ...@@ -124,4 +133,34 @@ describe('Pipeline New Form', () => {
expect(findVariableRows()).toHaveLength(4); expect(findVariableRows()).toHaveLength(4);
}); });
}); });
describe('Form errors and warnings', () => {
beforeEach(() => {
createComponent();
mock.onPost(pipelinesPath).reply(400, mockError);
findForm().vm.$emit('submit', dummySubmitEvent);
return waitForPromises();
});
it('shows both error and warning', () => {
expect(findErrorAlert().exists()).toBe(true);
expect(findWarningAlert().exists()).toBe(true);
});
it('shows the correct error', () => {
expect(findErrorAlert().text()).toBe(mockError.errors[0]);
});
it('shows the correct warning title', () => {
const { length } = mockError.warnings;
expect(findWarningAlertSummary().attributes('message')).toBe(`${length} warnings found:`);
});
it('shows the correct amount of warnings', () => {
expect(findWarnings()).toHaveLength(mockError.warnings.length);
});
});
}); });
...@@ -19,3 +19,15 @@ export const mockPostParams = { ...@@ -19,3 +19,15 @@ export const mockPostParams = {
{ key: 'test_file', value: 'test_file_val', variable_type: 'file' }, { key: 'test_file', value: 'test_file_val', variable_type: 'file' },
], ],
}; };
export const mockError = {
errors: [
'test job: chosen stage does not exist; available stages are .pre, build, test, deploy, .post',
],
warnings: [
'jobs:build1 may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings',
'jobs:build2 may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings',
'jobs:build3 may allow multiple pipelines to run for a single action due to `rules:when` clause with no `workflow:rules` - read more: https://docs.gitlab.com/ee/ci/troubleshooting.html#pipeline-warnings',
],
total_warnings: 7,
};
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