Commit d01c9d24 authored by Nick Kipling's avatar Nick Kipling Committed by Natalia Tepluhina

Adding package tags to package list

Moved package tags component to be general
Added hideLabel prop
Added display of tags to package_list
Updated tests
parent bc7d8f23
---
title: Displays package tags next to the name on the new package list page
merge_request: 23675
author:
type: added
......@@ -15,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 PackageTags from './package_tags.vue';
import PackageTags from '../../shared/components/package_tags.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { generatePackageInfo } from '../utils';
......
......@@ -21,6 +21,7 @@ import {
} from '../constants';
import { TrackingActions } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
import PackageTags from '../../shared/components/package_tags.vue';
export default {
components: {
......@@ -32,6 +33,7 @@ export default {
TimeAgoTooltip,
GlModal,
Icon,
PackageTags,
},
mixins: [Tracking.mixin()],
data() {
......@@ -191,14 +193,19 @@ export default {
stacked="md"
>
<template #cell(name)="{value, item}">
<div ref="col-name" class="flex-truncate-parent">
<a
:href="item._links.web_path"
class="flex-truncate-child"
data-qa-selector="package_link"
<div
class="flex-truncate-parent d-flex align-items-center justify-content-end justify-content-md-start"
>
<a :href="item._links.web_path" data-qa-selector="package_link">
{{ value }}
</a>
<package-tags
v-if="item.tags && item.tags.length"
class="prepend-left-8"
:tags="item.tags"
hide-label
:tag-display-limit="1"
/>
</div>
</template>
......
<script>
import { GlBadge, GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { n__ } from '~/locale';
export default {
name: 'PackageTags',
......@@ -22,6 +23,11 @@ export default {
required: true,
default: () => [],
},
hideLabel: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
tagCount() {
......@@ -43,19 +49,35 @@ export default {
return '';
},
tagsDisplay() {
return n__('%d tag', '%d tags', this.tagCount);
},
},
methods: {
tagBadgeClass(index) {
return {
'd-none': true,
'd-block': this.tagCount === 1,
'd-md-block': this.tagCount > 1,
'append-right-4': index !== this.tagsToRender.length - 1,
};
},
},
};
</script>
<template>
<div class="d-flex align-items-center">
<div v-if="!hideLabel" ref="tagLabel" 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>
<strong class="append-right-8 js-tags-count">{{ tagsDisplay }}</strong>
</div>
<gl-badge
v-for="(tag, index) in tagsToRender"
:key="index"
ref="tagBadge"
class="append-right-4"
:class="tagBadgeClass(index)"
variant="info"
>{{ tag.name }}</gl-badge
>
......@@ -66,11 +88,20 @@ export default {
v-gl-tooltip
variant="light"
:title="moreTagsTooltip"
class="d-none d-md-block prepend-left-4"
><gl-sprintf message="+%{tags} more">
<template #tags>
{{ moreTagsDisplay }}
</template>
</gl-sprintf></gl-badge
>
<gl-badge
v-if="moreTagsDisplay && hideLabel"
ref="moreBadge"
variant="light"
class="d-md-none prepend-left-4"
>{{ tagsDisplay }}</gl-badge
>
</div>
</template>
......@@ -916,6 +916,7 @@ module EE
expose :project_id, if: ->(_, opts) { opts[:group] }
expose :project_path, if: ->(obj, opts) { opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) }
expose :build_info, using: BuildInfo
expose :tags
private
......
......@@ -6,7 +6,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 PackageTags from 'ee/packages/shared/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';
......
......@@ -3,6 +3,7 @@ import _ from 'underscore';
import Tracking from '~/tracking';
import { mount } from '@vue/test-utils';
import PackagesList from 'ee/packages/list/components/packages_list.vue';
import PackageTags from 'ee/packages/shared/components/package_tags.vue';
import * as SharedUtils from 'ee/packages/shared/utils';
import { TrackingActions } from 'ee/packages/shared/constants';
import stubChildren from 'helpers/stub_children';
......@@ -18,6 +19,7 @@ describe('packages_list', () => {
const findPackageListDeleteModal = () => wrapper.find({ ref: 'packageListDeleteModal' });
const findSortingItems = () => wrapper.findAll({ name: 'sorting-item-stub' });
const findFirstProjectColumn = () => wrapper.find({ ref: 'col-project' });
const findPackageTags = () => wrapper.findAll(PackageTags);
const mountOptions = {
stubs: {
......@@ -83,11 +85,16 @@ describe('packages_list', () => {
const sorting = findPackageListPagination();
expect(sorting.exists()).toBe(true);
});
it('contains a modal component', () => {
const sorting = findPackageListDeleteModal();
expect(sorting.exists()).toBe(true);
});
it('renders package tags when a package has tags', () => {
expect(findPackageTags()).toHaveLength(1);
});
describe('when the user can destroy the package', () => {
it('show the action column', () => {
const action = findFirstActionColumn();
......
......@@ -77,8 +77,6 @@ export const conanPackage = {
_links,
};
export const packageList = [mavenPackage, npmPackage, conanPackage];
export const mockTags = [
{
name: 'foo-1',
......@@ -94,6 +92,8 @@ export const mockTags = [
},
];
export const packageList = [mavenPackage, { ...npmPackage, tags: mockTags }, conanPackage];
export const mockPipelineInfo = {
id: 1,
ref: 'branch-name',
......
import { mount } from '@vue/test-utils';
import PackageTags from 'ee/packages/details/components/package_tags.vue';
import PackageTags from 'ee/packages/shared/components/package_tags.vue';
import { mockTags } from '../../mock_data';
describe('PackageTags', () => {
......@@ -16,6 +16,7 @@ describe('PackageTags', () => {
});
}
const tagLabel = () => wrapper.find({ ref: 'tagLabel' });
const tagBadges = () => wrapper.findAll({ ref: 'tagBadge' });
const moreBadge = () => wrapper.find({ ref: 'moreBadge' });
......@@ -23,23 +24,37 @@ describe('PackageTags', () => {
if (wrapper) wrapper.destroy();
});
describe('tag label', () => {
it('shows the tag label by default', () => {
createComponent();
expect(tagLabel().exists()).toBe(true);
});
it('hides when hideLabel prop is set to true', () => {
createComponent(mockTags, { hideLabel: true });
expect(tagLabel().exists()).toBe(false);
});
});
it('renders the correct number of tags', () => {
createComponent(mockTags.slice(0, 2));
expect(tagBadges().length).toBe(2);
expect(tagBadges()).toHaveLength(2);
expect(moreBadge().exists()).toBe(false);
});
it('does not render more than the configured tagDisplayLimit', () => {
createComponent(mockTags);
expect(tagBadges().length).toBe(2);
expect(tagBadges()).toHaveLength(2);
});
it('renders the more tags badge if there are more than the configured limit', () => {
createComponent(mockTags);
expect(tagBadges().length).toBe(2);
expect(tagBadges()).toHaveLength(2);
expect(moreBadge().exists()).toBe(true);
expect(moreBadge().text()).toContain('2');
});
......@@ -47,8 +62,60 @@ describe('PackageTags', () => {
it('renders the configured tagDisplayLimit when set in props', () => {
createComponent(mockTags, { tagDisplayLimit: 1 });
expect(tagBadges().length).toBe(1);
expect(tagBadges()).toHaveLength(1);
expect(moreBadge().exists()).toBe(true);
expect(moreBadge().text()).toContain('3');
});
describe('tagBadgeStyle', () => {
const defaultStyle = {
'd-none': true,
'd-block': false,
'd-md-block': false,
'append-right-4': false,
};
it('shows tag badge when there is only one', () => {
createComponent([mockTags[0]]);
const expectedStyle = {
...defaultStyle,
'd-block': true,
};
expect(wrapper.vm.tagBadgeClass(0)).toEqual(expectedStyle);
});
it('shows tag badge for medium or heigher resolutions', () => {
createComponent(mockTags);
const expectedStyle = {
...defaultStyle,
'd-md-block': true,
};
expect(wrapper.vm.tagBadgeClass(1)).toEqual(expectedStyle);
});
it('correctly appends right when there is more than one tag', () => {
createComponent(mockTags, {
tagDisplayLimit: 4,
});
const expectedStyleWithoutAppend = {
...defaultStyle,
'd-md-block': true,
};
const expectedStyleWithAppend = {
...expectedStyleWithoutAppend,
'append-right-4': true,
};
expect(wrapper.vm.tagBadgeClass(0)).toEqual(expectedStyleWithAppend);
expect(wrapper.vm.tagBadgeClass(1)).toEqual(expectedStyleWithAppend);
expect(wrapper.vm.tagBadgeClass(2)).toEqual(expectedStyleWithAppend);
expect(wrapper.vm.tagBadgeClass(3)).toEqual(expectedStyleWithoutAppend);
});
});
});
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