Commit 519b67df authored by Mark Florian's avatar Mark Florian

Remove feature flag from Dependency List app

Part of [Clean up dependency_list_ui feature flag and old code
paths][1].

[1]: https://gitlab.com/gitlab-org/gitlab/-/issues/212867
parent aa11be4a
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { import {
GlBadge,
GlEmptyState, GlEmptyState,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlSprintf, GlSprintf,
GlTab,
GlTabs,
GlLink, GlLink,
GlDeprecatedButton, GlDeprecatedButton,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import DependenciesActions from './dependencies_actions.vue'; import DependenciesActions from './dependencies_actions.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue'; import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue'; import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue';
...@@ -24,20 +20,16 @@ export default { ...@@ -24,20 +20,16 @@ export default {
name: 'DependenciesApp', name: 'DependenciesApp',
components: { components: {
DependenciesActions, DependenciesActions,
GlBadge,
GlIcon, GlIcon,
GlEmptyState, GlEmptyState,
GlLoadingIcon, GlLoadingIcon,
GlSprintf, GlSprintf,
GlTab,
GlTabs,
GlLink, GlLink,
GlDeprecatedButton, GlDeprecatedButton,
DependencyListIncompleteAlert, DependencyListIncompleteAlert,
DependencyListJobFailedAlert, DependencyListJobFailedAlert,
PaginatedDependenciesTable, PaginatedDependenciesTable,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
endpoint: { endpoint: {
type: String, type: String,
...@@ -188,36 +180,11 @@ export default { ...@@ -188,36 +180,11 @@ export default {
</span> </span>
</p> </p>
</div> </div>
<dependencies-actions <dependencies-actions class="mt-2" :namespace="currentList" />
v-if="glFeatures.dependencyListUi"
class="mt-2"
:namespace="currentList"
/>
</header> </header>
<article v-if="glFeatures.dependencyListUi"> <article>
<paginated-dependencies-table :namespace="currentList" /> <paginated-dependencies-table :namespace="currentList" />
</article> </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> </section>
</template> </template>
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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`] = ` exports[`DependenciesApp component on creation given the dependency list job has not yet run shows only the empty state 1`] = `
Object { Object {
"compact": false, "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 { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/dependencies/store'; import createStore from 'ee/dependencies/store';
import { addListType } from 'ee/dependencies/store/utils';
import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants'; import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants'; import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue'; import DependenciesApp from 'ee/dependencies/components/app.vue';
...@@ -16,7 +15,6 @@ describe('DependenciesApp component', () => { ...@@ -16,7 +15,6 @@ describe('DependenciesApp component', () => {
let store; let store;
let wrapper; let wrapper;
const { namespace: allNamespace } = DEPENDENCY_LIST_TYPES.all; const { namespace: allNamespace } = DEPENDENCY_LIST_TYPES.all;
const { namespace: vulnerableNamespace } = DEPENDENCY_LIST_TYPES.vulnerable;
const basicAppProps = { const basicAppProps = {
endpoint: '/foo', endpoint: '/foo',
...@@ -27,11 +25,9 @@ describe('DependenciesApp component', () => { ...@@ -27,11 +25,9 @@ describe('DependenciesApp component', () => {
const factory = ({ props = basicAppProps, ...options } = {}) => { const factory = ({ props = basicAppProps, ...options } = {}) => {
store = createStore(); store = createStore();
addListType(store, DEPENDENCY_LIST_TYPES.vulnerable);
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation();
const canBeStubbed = component => !['GlSprintf', 'GlTab', 'GlTabs'].includes(component); const stubs = Object.keys(DependenciesApp.components).filter(name => name !== 'GlSprintf');
const stubs = Object.keys(DependenciesApp.components).filter(canBeStubbed);
wrapper = mount(DependenciesApp, { wrapper = mount(DependenciesApp, {
store, store,
...@@ -52,20 +48,18 @@ describe('DependenciesApp component', () => { ...@@ -52,20 +48,18 @@ describe('DependenciesApp component', () => {
}; };
const setStateLoaded = () => { const setStateLoaded = () => {
[allNamespace, vulnerableNamespace].forEach((namespace, i, { length }) => { const total = 2;
const total = length - i; Object.assign(store.state[allNamespace], {
Object.assign(store.state[namespace], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
dependencies: Array(total) dependencies: Array(total)
.fill(null) .fill(null)
.map((_, id) => ({ id })), .map((_, id) => ({ id })),
}); });
store.state[namespace].pageInfo.total = total; store.state[allNamespace].pageInfo.total = total;
store.state[namespace].reportInfo.status = REPORT_STATUS.ok; store.state[allNamespace].reportInfo.status = REPORT_STATUS.ok;
store.state[namespace].reportInfo.generatedAt = getDateInPast(new Date(), 7); store.state[allNamespace].reportInfo.generatedAt = getDateInPast(new Date(), 7);
store.state[namespace].reportInfo.jobPath = '/jobs/foo/321'; store.state[allNamespace].reportInfo.jobPath = '/jobs/foo/321';
});
}; };
const setStateJobFailed = () => { const setStateJobFailed = () => {
...@@ -102,9 +96,6 @@ describe('DependenciesApp component', () => { ...@@ -102,9 +96,6 @@ describe('DependenciesApp component', () => {
const findJobFailedAlert = () => wrapper.find(DependencyListJobFailedAlert); const findJobFailedAlert = () => wrapper.find(DependencyListJobFailedAlert);
const findIncompleteListAlert = () => wrapper.find(DependencyListIncompleteAlert); const findIncompleteListAlert = () => wrapper.find(DependencyListIncompleteAlert);
const findDependenciesTables = () => wrapper.findAll(PaginatedDependenciesTable); 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 findHeader = () => wrapper.find('section > header');
const findHeaderHelpLink = () => findHeader().find(GlLink); const findHeaderHelpLink = () => findHeader().find(GlLink);
...@@ -130,13 +121,6 @@ describe('DependenciesApp component', () => { ...@@ -130,13 +121,6 @@ describe('DependenciesApp component', () => {
expect(tables.at(0).props()).toEqual({ namespace: allNamespace }); 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 = () => { const expectHeader = () => {
expect(findHeader().exists()).toBe(true); expect(findHeader().exists()).toBe(true);
}; };
...@@ -178,225 +162,6 @@ describe('DependenciesApp component', () => { ...@@ -178,225 +162,6 @@ describe('DependenciesApp component', () => {
}); });
}); });
describe('given a list of dependencies and ok report', () => {
beforeEach(() => {
setStateLoaded();
return wrapper.vm.$nextTick();
});
it('shows both dependencies tables with the correct props', () => {
expectHeader();
expectDependenciesTables();
});
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('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 = '';
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 both dependencies tables with the correct props', expectDependenciesTables);
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 both dependencies tables with the correct props', expectDependenciesTables);
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();
});
});
});
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', () => { describe('given a list of dependencies and ok report', () => {
beforeEach(() => { beforeEach(() => {
setStateLoaded(); setStateLoaded();
...@@ -514,5 +279,4 @@ describe('DependenciesApp component', () => { ...@@ -514,5 +279,4 @@ describe('DependenciesApp component', () => {
}); });
}); });
}); });
});
}); });
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