Commit b0753c26 authored by Nathan Friend's avatar Nathan Friend

Merge branch '212867-clean-up-dependency_list_ui-for-app' into 'master'

Remove feature flag from Dependency List app

See merge request gitlab-org/gitlab!31094
parents 36b16256 519b67df
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import {
GlBadge,
GlEmptyState,
GlIcon,
GlLoadingIcon,
GlSprintf,
GlTab,
GlTabs,
GlLink,
GlDeprecatedButton,
} from '@gitlab/ui';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import DependenciesActions from './dependencies_actions.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue';
......@@ -24,20 +20,16 @@ export default {
name: 'DependenciesApp',
components: {
DependenciesActions,
GlBadge,
GlIcon,
GlEmptyState,
GlLoadingIcon,
GlSprintf,
GlTab,
GlTabs,
GlLink,
GlDeprecatedButton,
DependencyListIncompleteAlert,
DependencyListJobFailedAlert,
PaginatedDependenciesTable,
},
mixins: [glFeatureFlagsMixin()],
props: {
endpoint: {
type: String,
......@@ -188,36 +180,11 @@ export default {
</span>
</p>
</div>
<dependencies-actions
v-if="glFeatures.dependencyListUi"
class="mt-2"
:namespace="currentList"
/>
<dependencies-actions class="mt-2" :namespace="currentList" />
</header>
<article v-if="glFeatures.dependencyListUi">
<article>
<paginated-dependencies-table :namespace="currentList" />
</article>
<gl-tabs v-else v-model="currentListIndex" content-class="pt-0">
<gl-tab
v-for="listType in listTypes"
:key="listType.namespace"
:disabled="isTabDisabled(listType.namespace)"
>
<template #title>
{{ listType.label }}
<gl-badge pill :data-qa-selector="qaCountSelector(listType.label)">
{{ totals[listType.namespace] }}
</gl-badge>
</template>
<paginated-dependencies-table :namespace="listType.namespace" />
</gl-tab>
<template #tabs-end>
<li class="d-flex align-items-center ml-sm-auto">
<dependencies-actions :namespace="currentList" class="my-2 my-sm-0" />
</li>
</template>
</gl-tabs>
</section>
</template>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependenciesApp component given the dependencyListUi feature flag is enabled on creation given the dependency list job has not yet run shows only the empty state 1`] = `
Object {
"compact": false,
"description": "The dependency list details information about the components used within your project.",
"primaryButtonLink": null,
"primaryButtonText": null,
"secondaryButtonLink": null,
"secondaryButtonText": null,
"svgPath": "/bar.svg",
"title": "View dependency details for your project",
}
`;
exports[`DependenciesApp component given the dependencyListUi feature flag is enabled on creation given there are no dependencies detected shows only the empty state 1`] = `
Object {
"compact": false,
"description": "It seems like the Dependency Scanning job ran successfully, but no dependencies have been detected in your project.",
"primaryButtonLink": null,
"primaryButtonText": null,
"secondaryButtonLink": null,
"secondaryButtonText": null,
"svgPath": "/bar.svg",
"title": "Dependency List has no entries",
}
`;
exports[`DependenciesApp component on creation given the dependency list job has not yet run shows only the empty state 1`] = `
Object {
"compact": false,
......
import { GlBadge, GlEmptyState, GlLoadingIcon, GlTab, GlLink } from '@gitlab/ui';
import { GlEmptyState, GlLoadingIcon, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/dependencies/store';
import { addListType } from 'ee/dependencies/store/utils';
import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue';
......@@ -16,7 +15,6 @@ describe('DependenciesApp component', () => {
let store;
let wrapper;
const { namespace: allNamespace } = DEPENDENCY_LIST_TYPES.all;
const { namespace: vulnerableNamespace } = DEPENDENCY_LIST_TYPES.vulnerable;
const basicAppProps = {
endpoint: '/foo',
......@@ -27,11 +25,9 @@ describe('DependenciesApp component', () => {
const factory = ({ props = basicAppProps, ...options } = {}) => {
store = createStore();
addListType(store, DEPENDENCY_LIST_TYPES.vulnerable);
jest.spyOn(store, 'dispatch').mockImplementation();
const canBeStubbed = component => !['GlSprintf', 'GlTab', 'GlTabs'].includes(component);
const stubs = Object.keys(DependenciesApp.components).filter(canBeStubbed);
const stubs = Object.keys(DependenciesApp.components).filter(name => name !== 'GlSprintf');
wrapper = mount(DependenciesApp, {
store,
......@@ -52,20 +48,18 @@ describe('DependenciesApp component', () => {
};
const setStateLoaded = () => {
[allNamespace, vulnerableNamespace].forEach((namespace, i, { length }) => {
const total = length - i;
Object.assign(store.state[namespace], {
initialized: true,
isLoading: false,
dependencies: Array(total)
.fill(null)
.map((_, id) => ({ id })),
});
store.state[namespace].pageInfo.total = total;
store.state[namespace].reportInfo.status = REPORT_STATUS.ok;
store.state[namespace].reportInfo.generatedAt = getDateInPast(new Date(), 7);
store.state[namespace].reportInfo.jobPath = '/jobs/foo/321';
const total = 2;
Object.assign(store.state[allNamespace], {
initialized: true,
isLoading: false,
dependencies: Array(total)
.fill(null)
.map((_, id) => ({ id })),
});
store.state[allNamespace].pageInfo.total = total;
store.state[allNamespace].reportInfo.status = REPORT_STATUS.ok;
store.state[allNamespace].reportInfo.generatedAt = getDateInPast(new Date(), 7);
store.state[allNamespace].reportInfo.jobPath = '/jobs/foo/321';
};
const setStateJobFailed = () => {
......@@ -102,9 +96,6 @@ describe('DependenciesApp component', () => {
const findJobFailedAlert = () => wrapper.find(DependencyListJobFailedAlert);
const findIncompleteListAlert = () => wrapper.find(DependencyListIncompleteAlert);
const findDependenciesTables = () => wrapper.findAll(PaginatedDependenciesTable);
const findTabControls = () => wrapper.findAll('.gl-tab-nav-item');
const findVulnerableTabControl = () => findTabControls().at(1);
const findVulnerableTabComponent = () => wrapper.findAll(GlTab).at(1);
const findHeader = () => wrapper.find('section > header');
const findHeaderHelpLink = () => findHeader().find(GlLink);
......@@ -130,13 +121,6 @@ describe('DependenciesApp component', () => {
expect(tables.at(0).props()).toEqual({ namespace: allNamespace });
};
const expectDependenciesTables = () => {
const tables = findDependenciesTables();
expect(tables).toHaveLength(2);
expect(tables.at(0).props()).toEqual({ namespace: allNamespace });
expect(tables.at(1).props()).toEqual({ namespace: vulnerableNamespace });
};
const expectHeader = () => {
expect(findHeader().exists()).toBe(true);
};
......@@ -185,9 +169,9 @@ describe('DependenciesApp component', () => {
return wrapper.vm.$nextTick();
});
it('shows both dependencies tables with the correct props', () => {
it('shows the dependencies table with the correct props', () => {
expectHeader();
expectDependenciesTables();
expectDependenciesTable();
});
it('shows a link to the latest job', () => {
......@@ -202,77 +186,10 @@ describe('DependenciesApp component', () => {
expect(findHeaderHelpLink().attributes('href')).toBe(TEST_HOST);
});
it('displays the tabs correctly', () => {
const expected = [
{
text: 'All',
total: '2',
},
{
text: 'Vulnerable',
total: '1',
},
];
const tabs = findTabControls();
expected.forEach(({ text, total }, i) => {
const tab = tabs.at(i);
expect(tab.text()).toEqual(expect.stringContaining(text));
expect(
tab
.find(GlBadge)
.text()
.trim(),
).toEqual(total);
});
});
it('passes the correct namespace to dependencies actions component', () => {
expectComponentWithProps(DependenciesActions, { namespace: allNamespace });
});
describe('given the user clicks on the vulnerable tab', () => {
beforeEach(() => {
findVulnerableTabControl().trigger('click');
return wrapper.vm.$nextTick();
});
it('changes the current list', () => {
expect(store.dispatch).toHaveBeenCalledWith('setCurrentList', vulnerableNamespace);
});
});
describe('given the current list is the vulnerable dependencies list', () => {
const namespace = vulnerableNamespace;
beforeEach(() => {
store.state.currentList = namespace;
return wrapper.vm.$nextTick();
});
it('passes the correct namespace to dependencies actions component', () => {
expectComponentWithProps(DependenciesActions, { namespace });
});
});
it('has enabled vulnerable tab', () => {
expect(findVulnerableTabComponent().classes('disabled')).toBe(false);
});
describe('given there are no vulnerable dependencies', () => {
beforeEach(() => {
store.state[vulnerableNamespace].dependencies = [];
store.state[vulnerableNamespace].pageInfo.total = 0;
return wrapper.vm.$nextTick();
});
it('disables the vulnerable tab', () => {
expect(findVulnerableTabComponent().classes('disabled')).toBe(true);
});
});
describe('given the user has public permissions', () => {
beforeEach(() => {
store.state[allNamespace].reportInfo.generatedAt = '';
......@@ -308,7 +225,7 @@ describe('DependenciesApp component', () => {
});
});
it('shows both dependencies tables with the correct props', expectDependenciesTables);
it('shows the dependencies table with the correct props', expectDependenciesTable);
describe('when the job failure alert emits the dismiss event', () => {
beforeEach(() => {
......@@ -334,7 +251,7 @@ describe('DependenciesApp component', () => {
expectComponentWithProps(DependencyListIncompleteAlert);
});
it('shows both dependencies tables with the correct props', expectDependenciesTables);
it('shows the dependencies table with the correct props', expectDependenciesTable);
describe('when the incomplete-list alert emits the dismiss event', () => {
beforeEach(() => {
......@@ -362,157 +279,4 @@ describe('DependenciesApp component', () => {
});
});
});
describe('given the dependencyListUi feature flag is enabled', () => {
describe('on creation', () => {
beforeEach(() => {
factory({ provide: { glFeatures: { dependencyListUi: true } } });
});
it('dispatches the correct initial actions', () => {
expect(store.dispatch.mock.calls).toEqual([
['setDependenciesEndpoint', basicAppProps.endpoint],
['fetchDependencies'],
]);
});
it('shows only the loading icon', () => {
expectComponentWithProps(GlLoadingIcon);
expectNoHeader();
expectNoDependenciesTables();
});
describe('given the dependency list job has not yet run', () => {
beforeEach(() => {
setStateJobNotRun();
return wrapper.vm.$nextTick();
});
it('shows only the empty state', () => {
expectComponentWithProps(GlEmptyState, { svgPath: basicAppProps.emptyStateSvgPath });
expectComponentPropsToMatchSnapshot(GlEmptyState);
expectNoHeader();
expectNoDependenciesTables();
});
});
describe('given a list of dependencies and ok report', () => {
beforeEach(() => {
setStateLoaded();
return wrapper.vm.$nextTick();
});
it('shows the dependencies table with the correct props', () => {
expectHeader();
expectDependenciesTable();
});
it('shows a link to the latest job', () => {
expect(findHeaderJobLink().attributes('href')).toBe('/jobs/foo/321');
});
it('shows when the last job ran', () => {
expect(findHeader().text()).toContain('1 week ago');
});
it('shows a link to the dependencies documentation page', () => {
expect(findHeaderHelpLink().attributes('href')).toBe(TEST_HOST);
});
it('passes the correct namespace to dependencies actions component', () => {
expectComponentWithProps(DependenciesActions, { namespace: allNamespace });
});
describe('given the user has public permissions', () => {
beforeEach(() => {
store.state[allNamespace].reportInfo.generatedAt = '';
store.state[allNamespace].reportInfo.jobPath = '';
return wrapper.vm.$nextTick();
});
it('shows the header', () => {
expectHeader();
});
it('does not show when the last job ran', () => {
expect(findHeader().text()).not.toContain('1 week ago');
});
it('does not show a link to the latest job', () => {
expect(findHeaderJobLink().exists()).toBe(false);
});
});
});
describe('given the dependency list job failed', () => {
beforeEach(() => {
setStateJobFailed();
return wrapper.vm.$nextTick();
});
it('passes the correct props to the job failure alert', () => {
expectComponentWithProps(DependencyListJobFailedAlert, {
jobPath: '/jobs/foo/321',
});
});
it('shows the dependencies table with the correct props', expectDependenciesTable);
describe('when the job failure alert emits the dismiss event', () => {
beforeEach(() => {
const alertWrapper = findJobFailedAlert();
alertWrapper.vm.$emit('dismiss');
return wrapper.vm.$nextTick();
});
it('does not render the job failure alert', () => {
expect(findJobFailedAlert().exists()).toBe(false);
});
});
});
describe('given a dependency list which is known to be incomplete', () => {
beforeEach(() => {
setStateListIncomplete();
return wrapper.vm.$nextTick();
});
it('passes the correct props to the incomplete-list alert', () => {
expectComponentWithProps(DependencyListIncompleteAlert);
});
it('shows the dependencies table with the correct props', expectDependenciesTable);
describe('when the incomplete-list alert emits the dismiss event', () => {
beforeEach(() => {
const alertWrapper = findIncompleteListAlert();
alertWrapper.vm.$emit('dismiss');
return wrapper.vm.$nextTick();
});
it('does not render the incomplete-list alert', () => {
expect(findIncompleteListAlert().exists()).toBe(false);
});
});
});
describe('given there are no dependencies detected', () => {
beforeEach(() => {
setStateNoDependencies();
});
it('shows only the empty state', () => {
expectComponentWithProps(GlEmptyState, { svgPath: basicAppProps.emptyStateSvgPath });
expectComponentPropsToMatchSnapshot(GlEmptyState);
expectNoHeader();
expectNoDependenciesTables();
});
});
});
});
});
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