Commit abdc8e5e authored by Nick Kipling's avatar Nick Kipling

Add dependency links to NuGet package details

Adds new tab for dependency links
Created new component to render link content
Added logic for showing tab on page
Added tests for tab logic
Added tests for new component
Updated pot file
parent 64dc299f
<script> <script>
import { import {
GlBadge,
GlDeprecatedButton, GlDeprecatedButton,
GlIcon, GlIcon,
GlModal, GlModal,
...@@ -23,6 +24,7 @@ import NugetInstallation from './nuget_installation.vue'; ...@@ -23,6 +24,7 @@ import NugetInstallation from './nuget_installation.vue';
import PypiInstallation from './pypi_installation.vue'; import PypiInstallation from './pypi_installation.vue';
import PackagesListLoader from '../../shared/components/packages_list_loader.vue'; import PackagesListLoader from '../../shared/components/packages_list_loader.vue';
import PackageListRow from '../../shared/components/package_list_row.vue'; import PackageListRow from '../../shared/components/package_list_row.vue';
import DependencyRow from './dependency_row.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { generatePackageInfo } from '../utils'; import { generatePackageInfo } from '../utils';
...@@ -34,6 +36,7 @@ import { mapActions, mapState } from 'vuex'; ...@@ -34,6 +36,7 @@ import { mapActions, mapState } from 'vuex';
export default { export default {
name: 'PackagesApp', name: 'PackagesApp',
components: { components: {
GlBadge,
GlDeprecatedButton, GlDeprecatedButton,
GlEmptyState, GlEmptyState,
GlLink, GlLink,
...@@ -52,6 +55,7 @@ export default { ...@@ -52,6 +55,7 @@ export default {
PypiInstallation, PypiInstallation,
PackagesListLoader, PackagesListLoader,
PackageListRow, PackageListRow,
DependencyRow,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -154,6 +158,12 @@ export default { ...@@ -154,6 +158,12 @@ export default {
hasVersions() { hasVersions() {
return this.packageEntity.versions?.length > 0; return this.packageEntity.versions?.length > 0;
}, },
packageDependencies() {
return this.packageEntity.dependency_links || [];
},
showDependencies() {
return this.packageEntity.package_type === PackageType.NUGET;
},
}, },
methods: { methods: {
...mapActions(['fetchPackageVersions']), ...mapActions(['fetchPackageVersions']),
...@@ -265,6 +275,29 @@ export default { ...@@ -265,6 +275,29 @@ export default {
</gl-table> </gl-table>
</gl-tab> </gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
<template #title>
<span>{{ __('Dependencies') }}</span>
<gl-badge size="sm" data-testid="dependencies-badge">{{
packageDependencies.length
}}</gl-badge>
</template>
<template v-if="packageDependencies.length > 0">
<dependency-row
v-for="(dep, index) in packageDependencies"
:key="index"
:dependency="dep"
/>
</template>
<template v-else>
<p class="gl-mt-3" data-testid="no-dependencies-message">
{{ s__('PackageRegistry|This NuGet package has no dependencies.') }}
</p>
</template>
</gl-tab>
<gl-tab <gl-tab
:title="__('Versions')" :title="__('Versions')"
title-item-class="js-versions-tab" title-item-class="js-versions-tab"
...@@ -285,8 +318,8 @@ export default { ...@@ -285,8 +318,8 @@ export default {
/> />
</template> </template>
<template v-else class="gl-mt-3"> <template v-else>
<p data-testid="no-versions-message"> <p class="gl-mt-3" data-testid="no-versions-message">
{{ s__('PackageRegistry|There are no other versions of this package.') }} {{ s__('PackageRegistry|There are no other versions of this package.') }}
</p> </p>
</template> </template>
......
<script>
export default {
name: 'DependencyRow',
props: {
dependency: {
type: Object,
required: true,
},
},
computed: {
showVersion() {
return Boolean(this.dependency.version_pattern);
},
},
};
</script>
<template>
<div class="gl-responsive-table-row">
<div class="table-section section-50">
<strong class="gl-text-black-normal">{{ dependency.name }}</strong>
<span v-if="dependency.target_framework" data-testid="target-framework"
>({{ dependency.target_framework }})</span
>
</div>
<div
v-if="showVersion"
class="table-section section-50 gl-display-flex justify-content-md-end"
data-testid="version-pattern"
>
<span class="gl-text-black-normal">{{ dependency.version_pattern }}</span>
</div>
</div>
</template>
---
title: Adds a new Dependencies tab for NuGet packages on the package details page
merge_request: 33303
author:
type: added
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependencyRow renders full dependency 1`] = `
<div
class="gl-responsive-table-row"
>
<div
class="table-section section-50"
>
<strong
class="gl-text-black-normal"
>
Test.Dependency
</strong>
<span
data-testid="target-framework"
>
(.NETStandard2.0)
</span>
</div>
<div
class="table-section section-50 gl-display-flex justify-content-md-end"
data-testid="version-pattern"
>
<span
class="gl-text-black-normal"
>
2.3.7
</span>
</div>
</div>
`;
...@@ -15,6 +15,7 @@ import PackageListRow from 'ee/packages/shared/components/package_list_row.vue'; ...@@ -15,6 +15,7 @@ import PackageListRow from 'ee/packages/shared/components/package_list_row.vue';
import ConanInstallation from 'ee/packages/details/components/conan_installation.vue'; import ConanInstallation from 'ee/packages/details/components/conan_installation.vue';
import NugetInstallation from 'ee/packages/details/components/nuget_installation.vue'; import NugetInstallation from 'ee/packages/details/components/nuget_installation.vue';
import PypiInstallation from 'ee/packages/details/components/pypi_installation.vue'; import PypiInstallation from 'ee/packages/details/components/pypi_installation.vue';
import DependencyRow from 'ee/packages/details/components/dependency_row.vue';
import { import {
conanPackage, conanPackage,
mavenPackage, mavenPackage,
...@@ -89,6 +90,10 @@ describe('PackagesApp', () => { ...@@ -89,6 +90,10 @@ describe('PackagesApp', () => {
const packagesLoader = () => wrapper.find(PackagesListLoader); const packagesLoader = () => wrapper.find(PackagesListLoader);
const packagesVersionRows = () => wrapper.findAll(PackageListRow); const packagesVersionRows = () => wrapper.findAll(PackageListRow);
const noVersionsMessage = () => wrapper.find('[data-testid="no-versions-message"]'); const noVersionsMessage = () => wrapper.find('[data-testid="no-versions-message"]');
const dependenciesTab = () => wrapper.find('.js-dependencies-tab > a');
const dependenciesCountBadge = () => wrapper.find('[data-testid="dependencies-badge"]');
const noDependenciesMessage = () => wrapper.find('[data-testid="no-dependencies-message"]');
const dependencyRows = () => wrapper.findAll(DependencyRow);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -203,6 +208,45 @@ describe('PackagesApp', () => { ...@@ -203,6 +208,45 @@ describe('PackagesApp', () => {
}); });
}); });
describe('dependency links', () => {
it('does not show the dependency links for a non nuget package', () => {
createComponent();
expect(dependenciesTab().exists()).toBe(false);
});
it('shows the dependencies tab with 0 count when a nuget package with no dependencies', () => {
createComponent({
packageEntity: {
...nugetPackage,
dependency_links: [],
},
});
return wrapper.vm.$nextTick(() => {
const dependenciesBadge = dependenciesCountBadge();
expect(dependenciesTab().exists()).toBe(true);
expect(dependenciesBadge.exists()).toBe(true);
expect(dependenciesBadge.text()).toBe('0');
expect(noDependenciesMessage().exists()).toBe(true);
});
});
it('renders the correct number of dependency rows for a nuget package', () => {
createComponent({ packageEntity: nugetPackage });
return wrapper.vm.$nextTick(() => {
const dependenciesBadge = dependenciesCountBadge();
expect(dependenciesTab().exists()).toBe(true);
expect(dependenciesBadge.exists()).toBe(true);
expect(dependenciesBadge.text()).toBe(nugetPackage.dependency_links.length.toString());
expect(dependencyRows()).toHaveLength(nugetPackage.dependency_links.length);
});
});
});
describe('tracking', () => { describe('tracking', () => {
let eventSpy; let eventSpy;
let utilSpy; let utilSpy;
......
import { shallowMount } from '@vue/test-utils';
import DependencyRow from 'ee/packages/details/components/dependency_row.vue';
import { dependencyLinks } from '../../mock_data';
describe('DependencyRow', () => {
let wrapper;
const [withoutFramework, withoutVersion, fullLink] = dependencyLinks;
function createComponent({ dependencyLink = fullLink } = {}) {
wrapper = shallowMount(DependencyRow, {
propsData: {
dependency: dependencyLink,
},
});
}
const dependencyVersion = () => wrapper.find('[data-testid="version-pattern"]');
const dependencyFramework = () => wrapper.find('[data-testid="target-framework"]');
afterEach(() => {
wrapper.destroy();
});
describe('renders', () => {
it('full dependency', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
});
describe('version', () => {
it('does not render any version information when not supplied', () => {
createComponent({ dependencyLink: withoutVersion });
expect(dependencyVersion().exists()).toBe(false);
});
it('does render version info when it exists', () => {
createComponent();
expect(dependencyVersion().exists()).toBe(true);
expect(dependencyVersion().text()).toBe(fullLink.version_pattern);
});
});
describe('target framework', () => {
it('does not render any framework information when not supplied', () => {
createComponent({ dependencyLink: withoutFramework });
expect(dependencyFramework().exists()).toBe(false);
});
it('does render framework info when it exists', () => {
createComponent();
expect(dependencyFramework().exists()).toBe(true);
expect(dependencyFramework().text()).toBe(`(${fullLink.target_framework})`);
});
});
});
...@@ -92,6 +92,13 @@ export const conanPackage = { ...@@ -92,6 +92,13 @@ export const conanPackage = {
_links, _links,
}; };
export const dependencyLinks = [
{ name: 'Moqi', version_pattern: '2.5.6' },
{ name: 'Castle.Core', version_pattern: '' },
{ name: 'Test.Dependency', version_pattern: '2.3.7', target_framework: '.NETStandard2.0' },
{ name: 'Newtonsoft.Json', version_pattern: '12.0.3', target_framework: '.NETStandard2.0' },
];
export const nugetPackage = { export const nugetPackage = {
created_at: '2015-12-10', created_at: '2015-12-10',
id: 4, id: 4,
...@@ -102,6 +109,7 @@ export const nugetPackage = { ...@@ -102,6 +109,7 @@ export const nugetPackage = {
tags: [], tags: [],
updated_at: '2015-12-10', updated_at: '2015-12-10',
version: '1.0.0', version: '1.0.0',
dependency_links: dependencyLinks,
}; };
export const pypiPackage = { export const pypiPackage = {
......
...@@ -15367,6 +15367,9 @@ msgstr "" ...@@ -15367,6 +15367,9 @@ msgstr ""
msgid "PackageRegistry|There was a problem fetching the details for this package." msgid "PackageRegistry|There was a problem fetching the details for this package."
msgstr "" msgstr ""
msgid "PackageRegistry|This NuGet package has no dependencies."
msgstr ""
msgid "PackageRegistry|To widen your search, change or remove the filters above." msgid "PackageRegistry|To widen your search, change or remove the filters above."
msgstr "" 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