Commit 04d6297d authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '36422-add-nuget-dependencies-to-package-details' into 'master'

Add NuGet dependency links to package details

See merge request gitlab-org/gitlab!33303
parents b81221f2 e4f7c6ec
<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,27 @@ export default { ...@@ -265,6 +275,27 @@ 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>
<p v-else class="gl-mt-3" data-testid="no-dependencies-message">
{{ s__('PackageRegistry|This NuGet package has no dependencies.') }}
</p>
</gl-tab>
<gl-tab <gl-tab
:title="__('Versions')" :title="__('Versions')"
title-item-class="js-versions-tab" title-item-class="js-versions-tab"
...@@ -285,11 +316,9 @@ export default { ...@@ -285,11 +316,9 @@ export default {
/> />
</template> </template>
<template v-else class="gl-mt-3"> <p v-else class="gl-mt-3" data-testid="no-versions-message">
<p 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>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
......
<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,21 @@ export const conanPackage = { ...@@ -92,6 +92,21 @@ export const conanPackage = {
_links, _links,
}; };
export const dependencyLinks = {
withoutFramework: { name: 'Moqi', version_pattern: '2.5.6' },
withoutVersion: { name: 'Castle.Core', version_pattern: '' },
fullLink: {
name: 'Test.Dependency',
version_pattern: '2.3.7',
target_framework: '.NETStandard2.0',
},
anotherFullLink: {
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 +117,7 @@ export const nugetPackage = { ...@@ -102,6 +117,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: Object.values(dependencyLinks),
}; };
export const pypiPackage = { export const pypiPackage = {
......
...@@ -15411,6 +15411,9 @@ msgstr "" ...@@ -15411,6 +15411,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