Commit 5fd7b605 authored by Nick Kipling's avatar Nick Kipling Committed by Phil Hughes

Added package tags to the UI

Created new package-tags component
Updated app.vue to display new component
Replaced icon with gl-icon in app.vue
Added tests for package-tags
Updated pot file
parent cb10f5b0
---
title: NPM dist tags will now be displayed on the package details page.
merge_request: 23061
author:
type: added
<script>
import {
GlButton,
GlIcon,
GlModal,
GlModalDirective,
GlTooltipDirective,
......@@ -14,7 +15,7 @@ import PackageInformation from './information.vue';
import NpmInstallation from './npm_installation.vue';
import MavenInstallation from './maven_installation.vue';
import ConanInstallation from './conan_installation.vue';
import Icon from '~/vue_shared/components/icon.vue';
import PackageTags from './package_tags.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { generatePackageInfo } from '../utils';
......@@ -30,8 +31,9 @@ export default {
GlLink,
GlModal,
GlTable,
Icon,
GlIcon,
PackageInformation,
PackageTags,
NpmInstallation,
MavenInstallation,
ConanInstallation,
......@@ -104,6 +106,9 @@ export default {
isValidPackage() {
return Boolean(this.packageEntity.name);
},
hasTagsToDisplay() {
return Boolean(this.packageEntity.tags && this.packageEntity.tags.length);
},
canDeletePackage() {
return this.canDelete && this.destroyPath;
},
......@@ -208,7 +213,11 @@ export default {
<div v-else class="packages-app">
<div class="detail-page-header d-flex justify-content-between">
<strong class="js-version-title">{{ packageEntity.version }}</strong>
<div class="d-flex align-items-center">
<gl-icon name="fork" class="append-right-8" />
<strong class="append-right-default js-version-title">{{ packageEntity.version }}</strong>
<package-tags v-if="hasTagsToDisplay" :tags="packageEntity.tags" />
</div>
<gl-button
v-if="canDeletePackage"
v-gl-modal="'delete-modal'"
......@@ -260,7 +269,7 @@ export default {
tbody-tr-class="js-file-row"
>
<template #name="items">
<icon name="doc-code" class="space-right" />
<gl-icon name="doc-code" class="space-right" />
<gl-link
:href="items.item.downloadPath"
class="js-file-download"
......
<script>
import { GlBadge, GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
export default {
name: 'PackageTags',
components: {
GlBadge,
GlIcon,
GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
tagDisplayLimit: {
type: Number,
required: false,
default: 2,
},
tags: {
type: Array,
required: true,
default: () => [],
},
},
computed: {
tagCount() {
return this.tags.length;
},
tagsToRender() {
return this.tags.slice(0, this.tagDisplayLimit);
},
moreTagsDisplay() {
return Math.max(0, this.tags.length - this.tagDisplayLimit);
},
moreTagsTooltip() {
if (this.moreTagsDisplay) {
return this.tags
.slice(this.tagDisplayLimit)
.map(x => x.name)
.join(', ');
}
return '';
},
},
};
</script>
<template>
<div class="d-flex align-items-center">
<gl-icon name="labels" class="append-right-8" />
<strong class="append-right-8 js-tags-count">{{ n__('%d tag', '%d tags', tagCount) }}</strong>
<gl-badge
v-for="(tag, index) in tagsToRender"
:key="index"
ref="tagBadge"
class="append-right-4"
variant="info"
>{{ tag.name }}</gl-badge
>
<gl-badge
v-if="moreTagsDisplay"
ref="moreBadge"
v-gl-tooltip
variant="light"
:title="moreTagsTooltip"
><gl-sprintf message="+%{tags} more">
<template #tags>
{{ moreTagsDisplay }}
</template>
</gl-sprintf></gl-badge
>
</div>
</template>
......@@ -5,7 +5,7 @@
.row
.col-12
#js-vue-packages-detail{ data: { package: @package.to_json(include: [:conan_metadatum, :maven_metadatum, :package_files]),
#js-vue-packages-detail{ data: { package: @package.to_json(include: [:conan_metadatum, :maven_metadatum, :package_files, :tags]),
package_files: @package_files.to_json(methods: :download_path),
can_delete: can?(current_user, :destroy_package, @project).to_s,
destroy_path: project_package_path(@project, @package),
......
......@@ -5,6 +5,7 @@ import PackagesApp from 'ee/packages/details/components/app.vue';
import PackageInformation from 'ee/packages/details/components/information.vue';
import NpmInstallation from 'ee/packages/details/components/npm_installation.vue';
import MavenInstallation from 'ee/packages/details/components/maven_installation.vue';
import PackageTags from 'ee/packages/details/components/package_tags.vue';
import * as SharedUtils from 'ee/packages/shared/utils';
import { TrackingActions } from 'ee/packages/shared/constants';
import ConanInstallation from 'ee/packages/details/components/conan_installation.vue';
......@@ -50,6 +51,7 @@ describe('PackagesApp', () => {
const deleteButton = () => wrapper.find('.js-delete-button');
const deleteModal = () => wrapper.find(GlModal);
const modalDeleteButton = () => wrapper.find({ ref: 'modal-delete-button' });
const packageTags = () => wrapper.find(PackageTags);
afterEach(() => {
wrapper.destroy();
......@@ -143,6 +145,25 @@ describe('PackagesApp', () => {
});
});
describe('package tags', () => {
it('displays the package-tags component when the package has tags', () => {
createComponent({
packageEntity: {
...npmPackage,
tags: [{ name: 'foo' }],
},
});
expect(packageTags().exists()).toBe(true);
});
it('does not display the package-tags component when there are no tags', () => {
createComponent();
expect(packageTags().exists()).toBe(false);
});
});
describe('tracking', () => {
let eventSpy;
let utilSpy;
......
import { mount } from '@vue/test-utils';
import PackageTags from 'ee/packages/details/components/package_tags.vue';
import { mockTags } from '../../mock_data';
describe('PackageTags', () => {
let wrapper;
function createComponent(tags = [], props = {}) {
const propsData = {
tags,
...props,
};
wrapper = mount(PackageTags, {
propsData,
});
}
const tagBadges = () => wrapper.findAll({ ref: 'tagBadge' });
const moreBadge = () => wrapper.find({ ref: 'moreBadge' });
afterEach(() => {
if (wrapper) wrapper.destroy();
});
it('renders the correct number of tags', () => {
createComponent(mockTags.slice(0, 2));
expect(tagBadges().length).toBe(2);
expect(moreBadge().exists()).toBe(false);
});
it('does not render more than the configured tagDisplayLimit', () => {
createComponent(mockTags);
expect(tagBadges().length).toBe(2);
});
it('renders the more tags badge if there are more than the configured limit', () => {
createComponent(mockTags);
expect(tagBadges().length).toBe(2);
expect(moreBadge().exists()).toBe(true);
expect(moreBadge().text()).toContain('2');
});
it('renders the configured tagDisplayLimit when set in props', () => {
createComponent(mockTags, { tagDisplayLimit: 1 });
expect(tagBadges().length).toBe(1);
expect(moreBadge().exists()).toBe(true);
expect(moreBadge().text()).toContain('3');
});
});
......@@ -75,3 +75,18 @@ export const conanPackage = {
};
export const packageList = [mavenPackage, npmPackage, conanPackage];
export const mockTags = [
{
name: 'foo-1',
},
{
name: 'foo-2',
},
{
name: 'foo-3',
},
{
name: 'foo-4',
},
];
......@@ -173,6 +173,11 @@ msgid_plural "%d staged changes"
msgstr[0] ""
msgstr[1] ""
msgid "%d tag"
msgid_plural "%d tags"
msgstr[0] ""
msgstr[1] ""
msgid "%d unstaged change"
msgid_plural "%d unstaged changes"
msgstr[0] ""
......
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