Commit f75d31ac authored by Mark Florian's avatar Mark Florian

Add Dependency List alert components

This forms part of the [Dependency List MVC][1].

These alerts are shown during the edge cases documented in that issue.

This adds a new somewhat generic alert component that for the most part
matches the [GitLab design spec][2], but it's local to the dependency
list feature. This local implementation is expected to be replaced soon
with a generic implementation of alerts, implemented in
[`gitlab-ui`][3]. Once that's done, this feature-specific implementation
can be removed/swapped out in favour of that.

[1]: https://gitlab.com/gitlab-org/gitlab-ee/issues/10075
[2]: https://design.gitlab.com/components/alerts/
[3]: https://gitlab.com/gitlab-org/gitlab-ui/issues/210
parent 4eb4ff3e
export const WARNING = 'warning';
export const DANGER = 'danger';
export const WARNING_ALERT_CLASS = 'warning_message';
export const DANGER_ALERT_CLASS = 'danger_message';
export const WARNING_TEXT_CLASS = 'text-warning-900';
export const DANGER_TEXT_CLASS = 'text-danger-900';
<script>
import { GlButton } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import {
DANGER,
DANGER_ALERT_CLASS,
DANGER_TEXT_CLASS,
WARNING,
WARNING_ALERT_CLASS,
WARNING_TEXT_CLASS,
} from './constants';
export default {
name: 'DependencyListAlert',
components: {
GlButton,
Icon,
},
props: {
type: {
type: String,
required: false,
default: DANGER,
validator: value => [WARNING, DANGER].includes(value),
},
headerText: {
type: String,
required: false,
default: '',
},
},
computed: {
textClass() {
return (
{
[WARNING]: WARNING_TEXT_CLASS,
[DANGER]: DANGER_TEXT_CLASS,
}[this.type] || ''
);
},
alertClass() {
return (
{
[WARNING]: WARNING_ALERT_CLASS,
[DANGER]: DANGER_ALERT_CLASS,
}[this.type] || ''
);
},
},
};
</script>
<template>
<div :class="[alertClass, textClass]">
<gl-button
:class="['btn-blank float-right mr-1 mt-1 js-close', textClass]"
:aria-label="__('Close')"
@click="$emit('close')"
>
<icon name="close" aria-hidden="true" />
</gl-button>
<h4 v-if="headerText" :class="textClass">{{ headerText }}</h4>
<slot></slot>
</div>
</template>
<script>
import DependencyListAlert from './dependency_list_alert.vue';
export default {
name: 'DependencyListIncompleteAlert',
components: {
DependencyListAlert,
},
data() {
return {
dependencyFiles: [
'package-lock.json',
'composer.lock',
'Gemfile.lock',
'gems.locked',
'yarn.lock',
'requirements.txt',
'pom.xml',
],
};
},
};
</script>
<template>
<dependency-list-alert
type="warning"
:header-text="s__('Dependencies|Unsupported file(s) detected')"
v-on="$listeners"
>
<p>
{{
__(
'One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types.',
)
}}
</p>
<ul>
<li v-for="file in dependencyFiles" :key="file">{{ file }}</li>
</ul>
</dependency-list-alert>
</template>
<script>
import { GlButton } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import DependencyListAlert from './dependency_list_alert.vue';
export default {
name: 'DependencyListJobFailedAlert',
components: {
DependencyListAlert,
GlButton,
},
props: {
jobPath: {
type: String,
required: true,
},
},
data() {
return {
message: sprintf(
s__(
'Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again.',
),
{ codeStartTag: '<code>', codeEndTag: '</code>' },
false,
),
};
},
};
</script>
<template>
<dependency-list-alert
type="danger"
:header-text="s__('Dependencies|Job failed to generate the dependency list')"
v-on="$listeners"
>
<p v-html="message"></p>
<gl-button :href="jobPath" class="btn-inverted btn-danger mb-2">
{{ __('View job') }}
</gl-button>
</dependency-list-alert>
</template>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependencyListAlert component given no props matches the snapshot 1`] = `
<div
class="danger_message text-danger-900"
>
<glbutton-stub
aria-label="Close"
class="btn-blank float-right mr-1 mt-1 js-close text-danger-900"
>
<icon-stub
aria-hidden="true"
cssclasses=""
name="close"
size="16"
/>
</glbutton-stub>
<!---->
<p>
foo
<span>
bar
</span>
</p>
</div>
`;
exports[`DependencyListAlert component given the headerText prop matches the snapshot 1`] = `
<div
class="danger_message text-danger-900"
>
<glbutton-stub
aria-label="Close"
class="btn-blank float-right mr-1 mt-1 js-close text-danger-900"
>
<icon-stub
aria-hidden="true"
cssclasses=""
name="close"
size="16"
/>
</glbutton-stub>
<h4
class="text-danger-900"
>
A header
</h4>
<p>
foo
<span>
bar
</span>
</p>
</div>
`;
exports[`DependencyListAlert component given the warning type and headerText props matches the snapshot 1`] = `
<div
class="warning_message text-warning-900"
>
<glbutton-stub
aria-label="Close"
class="btn-blank float-right mr-1 mt-1 js-close text-warning-900"
>
<icon-stub
aria-hidden="true"
cssclasses=""
name="close"
size="16"
/>
</glbutton-stub>
<h4
class="text-warning-900"
>
Some header
</h4>
<p>
foo
<span>
bar
</span>
</p>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependencyListIncompleteAlert component matches the snapshot 1`] = `
<dependencylistalert-stub
headertext="Unsupported file(s) detected"
type="warning"
>
<p>
One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types.
</p>
<ul>
<li>
package-lock.json
</li>
<li>
composer.lock
</li>
<li>
Gemfile.lock
</li>
<li>
gems.locked
</li>
<li>
yarn.lock
</li>
<li>
requirements.txt
</li>
<li>
pom.xml
</li>
</ul>
</dependencylistalert-stub>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependencyListJobFailedAlert component matches the snapshot 1`] = `
<dependencylistalert-stub
headertext="Job failed to generate the dependency list"
type="danger"
>
<p>
The
<code>
dependency_scanning
</code>
job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again.
</p>
<glbutton-stub
class="btn-inverted btn-danger mb-2"
href="/jobs/foo/3210"
>
View job
</glbutton-stub>
</dependencylistalert-stub>
`;
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { WARNING } from 'ee/dependencies/components/constants';
import DependencyListAlert from 'ee/dependencies/components/dependency_list_alert.vue';
describe('DependencyListAlert component', () => {
let wrapper;
const factory = (props = {}) => {
const localVue = createLocalVue();
wrapper = shallowMount(localVue.extend(DependencyListAlert), {
localVue,
sync: false,
propsData: { ...props },
slots: {
default: '<p>foo <span>bar</span></p>',
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('given no props', () => {
beforeEach(() => {
factory();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('given the warning type and headerText props', () => {
beforeEach(() => {
factory({ type: WARNING, headerText: 'Some header' });
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('given the headerText prop', () => {
beforeEach(() => {
factory({ headerText: 'A header' });
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('clicking on the close button', () => {
beforeEach(() => {
factory();
wrapper.find('.js-close').vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('emits the close event', () => {
expect(wrapper.emitted().close.length).toBe(1);
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import DependencyListAlert from 'ee/dependencies/components/dependency_list_alert.vue';
import DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue';
describe('DependencyListIncompleteAlert component', () => {
let wrapper;
const factory = (options = {}) => {
const localVue = createLocalVue();
wrapper = shallowMount(localVue.extend(DependencyListIncompleteAlert), {
localVue,
sync: false,
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
it('matches the snapshot', () => {
factory();
expect(wrapper.element).toMatchSnapshot();
});
describe('when the generic alert component emits a close event', () => {
let closeListenerSpy;
beforeEach(() => {
closeListenerSpy = jest.fn();
factory({
listeners: {
close: closeListenerSpy,
},
});
wrapper.find(DependencyListAlert).vm.$emit('close');
});
it('calls the given listener', () => {
expect(closeListenerSpy).toHaveBeenCalledTimes(1);
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import DependencyListAlert from 'ee/dependencies/components/dependency_list_alert.vue';
import DependencyListJobFailedAlert from 'ee/dependencies/components/dependency_list_job_failed_alert.vue';
describe('DependencyListJobFailedAlert component', () => {
let wrapper;
const factory = (options = {}) => {
const localVue = createLocalVue();
wrapper = shallowMount(localVue.extend(DependencyListJobFailedAlert), {
localVue,
sync: false,
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
it('matches the snapshot', () => {
factory({ propsData: { jobPath: '/jobs/foo/3210' } });
expect(wrapper.element).toMatchSnapshot();
});
describe('when the generic alert component emits a close event', () => {
let closeListenerSpy;
beforeEach(() => {
closeListenerSpy = jest.fn();
factory({
propsData: { jobPath: '/jobs/foo/3210' },
listeners: {
close: closeListenerSpy,
},
});
wrapper.find(DependencyListAlert).vm.$emit('close');
});
it('calls the given listener', () => {
expect(closeListenerSpy).toHaveBeenCalledTimes(1);
});
});
});
......@@ -4058,12 +4058,21 @@ msgstr ""
msgid "Dependencies|Component"
msgstr ""
msgid "Dependencies|Job failed to generate the dependency list"
msgstr ""
msgid "Dependencies|Location"
msgstr ""
msgid "Dependencies|Packager"
msgstr ""
msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
msgstr ""
msgid "Dependencies|Unsupported file(s) detected"
msgstr ""
msgid "Dependencies|Version"
msgstr ""
......@@ -8940,6 +8949,9 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
msgid "One or more of your dependency files are not supported, and the dependency list may be incomplete. Below is a list of supported file types."
msgstr ""
msgid "Only admins"
msgstr ""
......@@ -14509,6 +14521,9 @@ msgstr ""
msgid "View it on GitLab"
msgstr ""
msgid "View job"
msgstr ""
msgid "View job trace"
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