Commit 13e95cde authored by Mark Florian's avatar Mark Florian

Merge branch 'djadmin-dependency-path' into 'master'

Add dependency path in dependency list

See merge request gitlab-org/gitlab!38570
parents 51714d1d 14dcfe00
<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