Commit 14dcfe00 authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by Mark Florian

Add dependency path in dependency list

This adds path to the existing dependency list location
if they are available
parent a73192b9
<script>
import { cloneDeep } from 'lodash';
import { GlBadge, GlIcon, GlLink, GlButton, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import { GlBadge, GlIcon, GlButton, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import { s__ } from '~/locale';
import DependencyLicenseLinks from './dependency_license_links.vue';
import DependencyVulnerabilities from './dependency_vulnerabilities.vue';
import DependencyLocation from './dependency_location.vue';
const tdClass = (value, key, item) => {
const classes = [];
......@@ -26,9 +27,9 @@ export default {
components: {
DependencyLicenseLinks,
DependencyVulnerabilities,
DependencyLocation,
GlBadge,
GlIcon,
GlLink,
GlButton,
GlSkeletonLoading,
GlTable,
......@@ -118,10 +119,7 @@ export default {
</template>
<template #cell(location)="{ item }">
<gl-link :href="item.location.blob_path">
<gl-icon name="doc-text" class="align-middle" />
{{ item.location.path }}
</gl-link>
<dependency-location :location="item.location" />
</template>
<template #cell(license)="{ item }">
......
<script>
import { n__ } from '~/locale';
import { GlIcon, GlLink, GlPopover, GlIntersperse } from '@gitlab/ui';
import DependencyPathViewer from './dependency_path_viewer.vue';
export const VISIBLE_DEPENDENCY_COUNT = 2;
export default {
name: 'DependencyLocation',
components: {
DependencyPathViewer,
GlIcon,
GlLink,
GlPopover,
GlIntersperse,
},
props: {
location: {
type: Object,
required: true,
},
},
computed: {
ancestors() {
return this.location.ancestors || [];
},
hasAncestors() {
return this.ancestors.length > 0;
},
visibleDependencies() {
return this.ancestors.slice(0, VISIBLE_DEPENDENCY_COUNT);
},
remainingDependenciesCount() {
return Math.max(0, this.ancestors.length - VISIBLE_DEPENDENCY_COUNT);
},
nMoreMessage() {
return n__('Dependencies|%d more', 'Dependencies|%d more', this.remainingDependenciesCount);
},
},
};
</script>
<template>
<gl-intersperse separator=" / " class="gl-text-gray-500">
<!-- We need to put an extra span to avoid separator between path & top level label -->
<span>
<gl-link :href="location.blob_path">
<gl-icon name="doc-text" class="gl-vertical-align-middle!" />
{{ location.path }}
</gl-link>
<span v-if="location.top_level">{{ s__('Dependencies|(top level)') }}</span>
</span>
<dependency-path-viewer v-if="hasAncestors" :dependencies="visibleDependencies" />
<!-- We need to put an extra span to avoid separator between link & popover -->
<span v-if="remainingDependenciesCount > 0">
<gl-link ref="moreLink">{{ nMoreMessage }}</gl-link>
<gl-popover
:target="() => $refs.moreLink.$el"
placement="top"
triggers="hover focus"
:title="s__('Dependencies|Dependency path')"
>
<dependency-path-viewer :dependencies="ancestors" />
<!-- footer -->
<div class="gl-mt-4">
<gl-icon
class="gl-vertical-align-middle! gl-text-blue-600"
name="information"
:size="12"
/>
<span class="gl-text-gray-500 gl-vertical-align-middle">
{{ s__('Dependencies|There may be multiple paths') }}
</span>
</div>
</gl-popover>
</span>
</gl-intersperse>
</template>
<script>
import { GlIntersperse } from '@gitlab/ui';
export default {
name: 'DependencyPathViewer',
components: {
GlIntersperse,
},
props: {
dependencies: {
type: Array,
required: true,
},
},
};
</script>
<template>
<gl-intersperse separator=" / ">
<span v-for="dependency in dependencies" :key="dependency.name">
<span>{{ dependency.name }}</span
><span v-if="dependency.version" class="gl-font-sm"> {{ dependency.version }}</span>
</span>
</gl-intersperse>
</template>
......@@ -14,7 +14,7 @@ describe('DependenciesTable component', () => {
wrapper = mount(DependenciesTable, {
...options,
propsData: { ...propsData },
stubs: { ...stubChildren(DependenciesTable), GlTable: false },
stubs: { ...stubChildren(DependenciesTable), GlTable: false, DependencyLocation: false },
});
};
......
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlIntersperse, GlPopover } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
import DependencyLocation from 'ee/dependencies/components/dependency_location.vue';
import DependencyPathViewer from 'ee/dependencies/components/dependency_path_viewer.vue';
import * as Paths from './mock_data';
describe('Dependency Location component', () => {
let wrapper;
const createComponent = ({ propsData, ...options } = {}) => {
wrapper = shallowMount(DependencyLocation, {
...options,
propsData: { ...propsData },
stubs: { GlLink, DependencyPathViewer, GlIntersperse },
});
};
const findPopover = () => wrapper.find(GlPopover);
afterEach(() => {
wrapper.destroy();
});
it.each`
name | location | path
${'no path'} | ${Paths.noPath} | ${'package.json'}
${'top level path'} | ${Paths.topLevelPath} | ${'package.json (top level)'}
${'short path'} | ${Paths.shortPath} | ${'package.json / swell 1.2 / emmajsq 10.11'}
${'long path'} | ${Paths.longPath} | ${'package.json / swell 1.2 / emmajsq 10.11 / 3 more'}
`('shows dependency path for $name', ({ location, path }) => {
createComponent({
propsData: {
location,
},
});
expect(trimText(wrapper.text())).toContain(path);
});
describe('popover', () => {
beforeEach(() => {
createComponent({
propsData: {
location: Paths.longPath,
},
});
});
it('shoud render the popover', () => {
expect(findPopover().exists()).toBe(true);
});
it('shoud have the complete path', () => {
expect(trimText(findPopover().text())).toBe(
'swell 1.2 / emmajsq 10.11 / zeb 12.1 / post 2.5 / core 1.0 There may be multiple paths',
);
});
});
describe('dependency with no dependency path', () => {
beforeEach(() => {
createComponent({
propsData: {
location: Paths.noPath,
},
});
});
it('should show the depedency name and link', () => {
const locationLink = wrapper.find(GlLink);
expect(locationLink.attributes().href).toBe('test.link');
expect(locationLink.text()).toBe('package.json');
});
it('should not render dependency path', () => {
const pathViewer = wrapper.find(DependencyPathViewer);
expect(pathViewer.exists()).toBe(false);
});
it('should not render the popover', () => {
expect(findPopover().exists()).toBe(false);
});
});
});
import { mount } from '@vue/test-utils';
import DependencyPathViewer from 'ee/dependencies/components/dependency_path_viewer.vue';
describe('DependencyPathViewer component', () => {
let wrapper;
const factory = (options = {}) => {
wrapper = mount(DependencyPathViewer, {
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
it.each`
dependencies | path
${[]} | ${''}
${[{ name: 'emmajsq' }]} | ${'emmajsq'}
${[{ name: 'emmajsq', version: '10.11' }]} | ${'emmajsq 10.11'}
${[{ name: 'emmajsq' }, { name: 'swell' }]} | ${'emmajsq / swell'}
${[{ name: 'emmajsq', version: '10.11' }, { name: 'swell', version: '1.2' }]} | ${'emmajsq 10.11 / swell 1.2'}
`('shows complete dependency path for $path', ({ dependencies, path }) => {
factory({
propsData: { dependencies },
});
expect(wrapper.text()).toBe(path);
});
});
export const longPath = {
ancestors: [
{
name: 'swell',
version: '1.2',
},
{
name: 'emmajsq',
version: '10.11',
},
{
name: 'zeb',
version: '12.1',
},
{
name: 'post',
version: '2.5',
},
{
name: 'core',
version: '1.0',
},
],
top_level: false,
blob_path: 'test.link',
path: 'package.json',
};
export const shortPath = {
ancestors: [
{
name: 'swell',
version: '1.2',
},
{
name: 'emmajsq',
version: '10.11',
},
],
top_level: false,
blob_path: 'test.link',
path: 'package.json',
};
export const noPath = {
ancestors: [],
top_level: false,
blob_path: 'test.link',
path: 'package.json',
};
export const topLevelPath = {
ancestors: [],
top_level: true,
blob_path: 'test.link',
path: 'package.json',
};
......@@ -7880,6 +7880,11 @@ msgid_plural "Dependencies|%d additional vulnerabilities not shown"
msgstr[0] ""
msgstr[1] ""
msgid "Dependencies|%d more"
msgid_plural "Dependencies|%d more"
msgstr[0] ""
msgstr[1] ""
msgid "Dependencies|%d vulnerability detected"
msgid_plural "Dependencies|%d vulnerabilities detected"
msgstr[0] ""
......@@ -7888,6 +7893,9 @@ msgstr[1] ""
msgid "Dependencies|%{remainingLicensesCount} more"
msgstr ""
msgid "Dependencies|(top level)"
msgstr ""
msgid "Dependencies|All"
msgstr ""
......@@ -7900,6 +7908,9 @@ msgstr ""
msgid "Dependencies|Component name"
msgstr ""
msgid "Dependencies|Dependency path"
msgstr ""
msgid "Dependencies|Export as JSON"
msgstr ""
......@@ -7918,6 +7929,9 @@ 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|There may be multiple paths"
msgstr ""
msgid "Dependencies|Toggle vulnerability list"
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