Commit f67016c6 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '11059-dependency-list-app-component-ee' into 'master'

Add Dependency List app component

See merge request gitlab-org/gitlab-ee!13684
parents 5eac6d0f d7f592b8
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlBadge, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import DependenciesActions from './dependencies_actions.vue';
import DependenciesTable from './dependencies_table.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue';
export default {
name: 'DependenciesApp',
components: {
DependenciesActions,
DependenciesTable,
GlBadge,
GlEmptyState,
GlLoadingIcon,
DependencyListIncompleteAlert,
DependencyListJobFailedAlert,
Pagination,
},
props: {
endpoint: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
documentationPath: {
type: String,
required: true,
},
},
data() {
return {
isIncompleteAlertDismissed: false,
isJobFailedAlertDismissed: false,
};
},
computed: {
...mapGetters(['isJobNotSetUp', 'isJobFailed', 'isIncomplete']),
...mapState([
'initialized',
'isLoading',
'errorLoading',
'dependencies',
'pageInfo',
'reportInfo',
]),
shouldShowPagination() {
return Boolean(!this.isLoading && !this.errorLoading && this.pageInfo && this.pageInfo.total);
},
},
created() {
this.setDependenciesEndpoint(this.endpoint);
this.fetchDependencies();
},
methods: {
...mapActions(['setDependenciesEndpoint', 'fetchDependencies']),
fetchPage(page) {
this.fetchDependencies({ page });
},
dismissIncompleteListAlert() {
this.isIncompleteAlertDismissed = true;
},
dismissJobFailedAlert() {
this.isJobFailedAlertDismissed = true;
},
},
};
</script>
<template>
<gl-loading-icon v-if="!initialized" size="md" class="mt-4" />
<gl-empty-state
v-else-if="isJobNotSetUp"
:title="__('View dependency details for your project')"
:description="
__('The dependency list details information about the components used within your project.')
"
:svg-path="emptyStateSvgPath"
:primary-button-link="documentationPath"
:primary-button-text="__('Learn more about the dependency list')"
/>
<div v-else>
<dependency-list-incomplete-alert
v-if="isIncomplete && !isIncompleteAlertDismissed"
@close="dismissIncompleteListAlert"
/>
<dependency-list-job-failed-alert
v-if="isJobFailed && !isJobFailedAlertDismissed"
:job-path="reportInfo.jobPath"
@close="dismissJobFailedAlert"
/>
<div class="d-sm-flex justify-content-between align-items-baseline my-2">
<h4 class="h5">
{{ __('Dependencies') }}
<gl-badge v-if="pageInfo.total" pill>{{ pageInfo.total }}</gl-badge>
</h4>
<dependencies-actions />
</div>
<dependencies-table :dependencies="dependencies" :is-loading="isLoading" />
<pagination
v-if="shouldShowPagination"
:change="fetchPage"
:page-info="pageInfo"
class="justify-content-center prepend-top-default"
/>
</div>
</template>
import Vue from 'vue';
import DependenciesApp from './components/app.vue';
import createStore from './store';
export default () => {
const el = document.querySelector('#js-dependencies-app');
const { endpoint, emptyStateSvgPath, documentationPath } = el.dataset;
const store = createStore();
return new Vue({
el,
store,
components: {
DependenciesApp,
},
render(createElement) {
return createElement(DependenciesApp, {
props: {
endpoint,
emptyStateSvgPath,
documentationPath,
},
});
},
});
};
import initDependenciesApp from 'ee/dependencies';
document.addEventListener('DOMContentLoaded', initDependenciesApp);
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependenciesApp component on creation given a dependency list which is known to be incomplete matches the snapshot 1`] = `
<div>
<dependencylistincompletealert-stub />
<!---->
<div
class="d-sm-flex justify-content-between align-items-baseline my-2"
>
<h4
class="h5"
>
Dependencies
<glbadge-stub
pill=""
>
100
</glbadge-stub>
</h4>
<dependenciesactions-stub />
</div>
<dependenciestable-stub
dependencies="foo,bar"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
/>
</div>
`;
exports[`DependenciesApp component on creation given a fetch error matches the snapshot 1`] = `
<div>
<!---->
<!---->
<div
class="d-sm-flex justify-content-between align-items-baseline my-2"
>
<h4
class="h5"
>
Dependencies
<!---->
</h4>
<dependenciesactions-stub />
</div>
<dependenciestable-stub
dependencies=""
/>
<!---->
</div>
`;
exports[`DependenciesApp component on creation given a list of dependencies and ok report matches the snapshot 1`] = `
<div>
<!---->
<!---->
<div
class="d-sm-flex justify-content-between align-items-baseline my-2"
>
<h4
class="h5"
>
Dependencies
<glbadge-stub
pill=""
>
100
</glbadge-stub>
</h4>
<dependenciesactions-stub />
</div>
<dependenciestable-stub
dependencies="foo,bar"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
/>
</div>
`;
exports[`DependenciesApp component on creation given the dependency list job failed matches the snapshot 1`] = `
<div>
<!---->
<dependencylistjobfailedalert-stub
jobpath="/jobs/foo/321"
/>
<div
class="d-sm-flex justify-content-between align-items-baseline my-2"
>
<h4
class="h5"
>
Dependencies
<!---->
</h4>
<dependenciesactions-stub />
</div>
<dependenciestable-stub
dependencies=""
/>
<!---->
</div>
`;
exports[`DependenciesApp component on creation given the dependency list job has not yet run matches the snapshot 1`] = `
<glemptystate-stub
description="The dependency list details information about the components used within your project."
primarybuttonlink="http://test.host"
primarybuttontext="Learn more about the dependency list"
svgpath="/bar.svg"
title="View dependency details for your project"
/>
`;
exports[`DependenciesApp component on creation matches the snapshot 1`] = `
<glloadingicon-stub
class="mt-4"
color="orange"
label="Loading"
size="md"
/>
`;
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/dependencies/store';
import { REPORT_STATUS } from 'ee/dependencies/store/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue';
import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue';
import DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from 'ee/dependencies/components/dependency_list_job_failed_alert.vue';
import Pagination from '~/vue_shared/components/pagination_links.vue';
describe('DependenciesApp component', () => {
let store;
let wrapper;
const basicAppProps = {
endpoint: '/foo',
emptyStateSvgPath: '/bar.svg',
documentationPath: TEST_HOST,
};
const factory = (props = basicAppProps) => {
const localVue = createLocalVue();
store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(DependenciesApp, {
localVue,
store,
sync: false,
propsData: { ...props },
});
};
const expectComponentWithProps = (Component, props = {}) => {
const componentWrapper = wrapper.find(Component);
expect(componentWrapper.isVisible()).toBe(true);
expect(componentWrapper.props()).toEqual(expect.objectContaining(props));
};
afterEach(() => {
wrapper.destroy();
});
describe('on creation', () => {
let dependencies;
beforeEach(() => {
factory();
});
it('dispatches the correct initial actions', () => {
expect(store.dispatch.mock.calls).toEqual([
['setDependenciesEndpoint', basicAppProps.endpoint],
['fetchDependencies'],
]);
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe('given a list of dependencies and ok report', () => {
beforeEach(() => {
dependencies = ['foo', 'bar'];
Object.assign(store.state, {
initialized: true,
isLoading: false,
dependencies,
});
store.state.pageInfo.total = 100;
store.state.reportInfo.status = REPORT_STATUS.ok;
return wrapper.vm.$nextTick();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state.pageInfo,
change: wrapper.vm.fetchPage,
});
});
});
describe('given the dependency list job has not yet run', () => {
beforeEach(() => {
dependencies = [];
Object.assign(store.state, {
initialized: true,
isLoading: false,
dependencies,
});
store.state.pageInfo.total = 0;
store.state.reportInfo.status = REPORT_STATUS.jobNotSetUp;
return wrapper.vm.$nextTick();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('given the dependency list job failed', () => {
beforeEach(() => {
dependencies = [];
Object.assign(store.state, {
initialized: true,
isLoading: false,
dependencies,
});
store.state.pageInfo.total = 0;
store.state.reportInfo.status = REPORT_STATUS.jobFailed;
store.state.reportInfo.jobPath = '/jobs/foo/321';
return wrapper.vm.$nextTick();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the job failure alert', () => {
expectComponentWithProps(DependencyListJobFailedAlert, {
jobPath: store.state.reportInfo.jobPath,
});
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('does not show pagination', () => {
expect(wrapper.find(Pagination).exists()).toBe(false);
});
describe('when the job failure alert emits the close event', () => {
beforeEach(() => {
const alertWrapper = wrapper.find(DependencyListJobFailedAlert);
alertWrapper.vm.$emit('close');
return wrapper.vm.$nextTick();
});
it('does not render the job failure alert', () => {
expect(wrapper.find(DependencyListJobFailedAlert).exists()).toBe(false);
});
});
});
describe('given a dependency list which is known to be incomplete', () => {
beforeEach(() => {
dependencies = ['foo', 'bar'];
Object.assign(store.state, {
initialized: true,
isLoading: false,
dependencies,
});
store.state.pageInfo.total = 100;
store.state.reportInfo.status = REPORT_STATUS.incomplete;
return wrapper.vm.$nextTick();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the incomplete-list alert', () => {
const alert = wrapper.find(DependencyListIncompleteAlert);
expect(alert.isVisible()).toBe(true);
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state.pageInfo,
change: wrapper.vm.fetchPage,
});
});
describe('when the incomplete-list alert emits the close event', () => {
beforeEach(() => {
const alertWrapper = wrapper.find(DependencyListIncompleteAlert);
alertWrapper.vm.$emit('close');
return wrapper.vm.$nextTick();
});
it('does not render the incomplete-list alert', () => {
expect(wrapper.find(DependencyListIncompleteAlert).exists()).toBe(false);
});
});
});
describe('given a fetch error', () => {
beforeEach(() => {
dependencies = [];
Object.assign(store.state, {
initialized: true,
isLoading: false,
errorLoading: true,
dependencies,
});
return wrapper.vm.$nextTick();
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('does not show pagination', () => {
expect(wrapper.find(Pagination).exists()).toBe(false);
});
});
});
});
......@@ -4063,6 +4063,9 @@ msgstr ""
msgid "Deny"
msgstr ""
msgid "Dependencies"
msgstr ""
msgid "Dependencies|Component"
msgstr ""
......@@ -7676,6 +7679,9 @@ msgstr ""
msgid "Learn more about signing commits"
msgstr ""
msgid "Learn more about the dependency list"
msgstr ""
msgid "Learn more in the"
msgstr ""
......@@ -12953,6 +12959,9 @@ msgstr ""
msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
msgstr ""
msgid "The dependency list details information about the components used within your project."
msgstr ""
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
......@@ -14622,6 +14631,9 @@ msgstr ""
msgid "View app"
msgstr ""
msgid "View dependency details for your project"
msgstr ""
msgid "View deployment"
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