Commit 4b78a5d8 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Brandon Labuschagne

Add new package_files component

- source
- tests
parent 6de7a81f
...@@ -5,29 +5,26 @@ import { ...@@ -5,29 +5,26 @@ import {
GlModal, GlModal,
GlModalDirective, GlModalDirective,
GlTooltipDirective, GlTooltipDirective,
GlLink,
GlEmptyState, GlEmptyState,
GlTab, GlTab,
GlTabs, GlTabs,
GlTable,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { s__ } from '~/locale';
import { objectToQueryString } from '~/lib/utils/common_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import PackageHistory from './package_history.vue'; import PackageHistory from './package_history.vue';
import PackageTitle from './package_title.vue'; import PackageTitle from './package_title.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 { packageTypeToTrackCategory } from '../../shared/utils';
import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import DependencyRow from './dependency_row.vue'; import DependencyRow from './dependency_row.vue';
import AdditionalMetadata from './additional_metadata.vue'; import AdditionalMetadata from './additional_metadata.vue';
import InstallationCommands from './installation_commands.vue'; import InstallationCommands from './installation_commands.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import PackageFiles from './package_files.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { __, s__ } from '~/locale';
import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
import { objectToQueryString } from '~/lib/utils/common_utils';
export default { export default {
name: 'PackagesApp', name: 'PackagesApp',
...@@ -35,12 +32,9 @@ export default { ...@@ -35,12 +32,9 @@ export default {
GlBadge, GlBadge,
GlButton, GlButton,
GlEmptyState, GlEmptyState,
GlLink,
GlModal, GlModal,
GlTab, GlTab,
GlTabs, GlTabs,
GlTable,
FileIcon,
GlSprintf, GlSprintf,
PackageTitle, PackageTitle,
PackagesListLoader, PackagesListLoader,
...@@ -49,12 +43,13 @@ export default { ...@@ -49,12 +43,13 @@ export default {
PackageHistory, PackageHistory,
AdditionalMetadata, AdditionalMetadata,
InstallationCommands, InstallationCommands,
PackageFiles,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
mixins: [timeagoMixin, Tracking.mixin()], mixins: [Tracking.mixin()],
trackingActions: { ...TrackingActions }, trackingActions: { ...TrackingActions },
computed: { computed: {
...mapState([ ...mapState([
...@@ -72,14 +67,6 @@ export default { ...@@ -72,14 +67,6 @@ export default {
isValidPackage() { isValidPackage() {
return Boolean(this.packageEntity.name); return Boolean(this.packageEntity.name);
}, },
filesTableRows() {
return this.packageFiles.map(x => ({
name: x.file_name,
downloadPath: x.download_path,
size: this.formatSize(x.size),
created: x.created_at,
}));
},
tracking() { tracking() {
return { return {
category: packageTypeToTrackCategory(this.packageEntity.package_type), category: packageTypeToTrackCategory(this.packageEntity.package_type),
...@@ -128,22 +115,6 @@ export default { ...@@ -128,22 +115,6 @@ export default {
`PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`, `PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`,
), ),
}, },
filesTableHeaderFields: [
{
key: 'name',
label: __('Name'),
tdClass: 'd-flex align-items-center',
},
{
key: 'size',
label: __('Size'),
},
{
key: 'created',
label: __('Created'),
class: 'text-right',
},
],
}; };
</script> </script>
...@@ -185,35 +156,11 @@ export default { ...@@ -185,35 +156,11 @@ export default {
<additional-metadata :package-entity="packageEntity" /> <additional-metadata :package-entity="packageEntity" />
</div> </div>
<template v-if="showFiles"> <package-files
<h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3> v-if="showFiles"
<gl-table :package-files="packageFiles"
:fields="$options.filesTableHeaderFields" @download-file="track($options.trackingActions.PULL_PACKAGE)"
:items="filesTableRows" />
tbody-tr-class="js-file-row"
>
<template #cell(name)="{ item }">
<gl-link
:href="item.downloadPath"
class="js-file-download gl-relative"
@click="track($options.trackingActions.PULL_PACKAGE)"
>
<file-icon
:file-name="item.name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ item.name }}</span>
</gl-link>
</template>
<template #cell(created)="{ item }">
<span v-gl-tooltip :title="tooltipTitle(item.created)">{{
timeFormatted(item.created)
}}</span>
</template>
</gl-table>
</template>
</gl-tab> </gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab"> <gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
......
<script>
import { GlLink, GlTable } from '@gitlab/ui';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
export default {
name: 'PackageFiles',
components: {
GlLink,
GlTable,
FileIcon,
TimeAgoTooltip,
},
mixins: [Tracking.mixin()],
props: {
packageFiles: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
filesTableRows() {
return this.packageFiles.map(pf => ({
...pf,
size: this.formatSize(pf.size),
}));
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
},
filesTableHeaderFields: [
{
key: 'name',
label: __('Name'),
tdClass: 'gl-display-flex gl-align-items-center',
},
{
key: 'size',
label: __('Size'),
},
{
key: 'created',
label: __('Created'),
class: 'gl-text-right',
},
],
};
</script>
<template>
<div>
<h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
<gl-table
:fields="$options.filesTableHeaderFields"
:items="filesTableRows"
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
>
<template #cell(name)="{ item }">
<gl-link
:href="item.download_path"
class="gl-relative"
data-testid="download-link"
@click="$emit('download-file')"
>
<file-icon
:file-name="item.file_name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ item.file_name }}</span>
</gl-link>
</template>
<template #cell(created)="{ item }">
<time-ago-tooltip :time="item.created_at" />
</template>
</gl-table>
</div>
</template>
...@@ -16,6 +16,7 @@ import DependencyRow from '~/packages/details/components/dependency_row.vue'; ...@@ -16,6 +16,7 @@ import DependencyRow from '~/packages/details/components/dependency_row.vue';
import PackageHistory from '~/packages/details/components/package_history.vue'; import PackageHistory from '~/packages/details/components/package_history.vue';
import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue'; import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue'; import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import PackageFiles from '~/packages/details/components/package_files.vue';
import { import {
composerPackage, composerPackage,
...@@ -23,7 +24,6 @@ import { ...@@ -23,7 +24,6 @@ import {
mavenPackage, mavenPackage,
mavenFiles, mavenFiles,
npmPackage, npmPackage,
npmFiles,
nugetPackage, nugetPackage,
} from '../../mock_data'; } from '../../mock_data';
...@@ -82,8 +82,6 @@ describe('PackagesApp', () => { ...@@ -82,8 +82,6 @@ describe('PackagesApp', () => {
const packageTitle = () => wrapper.find(PackageTitle); const packageTitle = () => wrapper.find(PackageTitle);
const emptyState = () => wrapper.find(GlEmptyState); const emptyState = () => wrapper.find(GlEmptyState);
const allFileRows = () => wrapper.findAll('.js-file-row');
const firstFileDownloadLink = () => wrapper.find('.js-file-download');
const deleteButton = () => wrapper.find('.js-delete-button'); const deleteButton = () => wrapper.find('.js-delete-button');
const deleteModal = () => wrapper.find(GlModal); const deleteModal = () => wrapper.find(GlModal);
const modalDeleteButton = () => wrapper.find({ ref: 'modal-delete-button' }); const modalDeleteButton = () => wrapper.find({ ref: 'modal-delete-button' });
...@@ -98,6 +96,7 @@ describe('PackagesApp', () => { ...@@ -98,6 +96,7 @@ describe('PackagesApp', () => {
const findPackageHistory = () => wrapper.find(PackageHistory); const findPackageHistory = () => wrapper.find(PackageHistory);
const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata); const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
const findInstallationCommands = () => wrapper.find(InstallationCommands); const findInstallationCommands = () => wrapper.find(InstallationCommands);
const findPackageFiles = () => wrapper.find(PackageFiles);
beforeEach(() => { beforeEach(() => {
delete window.location; delete window.location;
...@@ -144,28 +143,7 @@ describe('PackagesApp', () => { ...@@ -144,28 +143,7 @@ describe('PackagesApp', () => {
it('hides the files table if package type is COMPOSER', () => { it('hides the files table if package type is COMPOSER', () => {
createComponent({ packageEntity: composerPackage }); createComponent({ packageEntity: composerPackage });
expect(allFileRows().exists()).toBe(false); expect(findPackageFiles().exists()).toBe(false);
});
it('renders a single file for an npm package as they only contain one file', () => {
createComponent({ packageEntity: npmPackage, packageFiles: npmFiles });
expect(allFileRows()).toExist();
expect(allFileRows()).toHaveLength(1);
});
it('renders multiple files for a package that contains more than one file', () => {
createComponent();
expect(allFileRows()).toExist();
expect(allFileRows()).toHaveLength(2);
});
it('allows the user to download a package file by rendering a download link', () => {
createComponent();
expect(allFileRows()).toExist();
expect(firstFileDownloadLink().vm.$attrs.href).toContain('download');
}); });
describe('deleting packages', () => { describe('deleting packages', () => {
...@@ -331,7 +309,7 @@ describe('PackagesApp', () => { ...@@ -331,7 +309,7 @@ describe('PackagesApp', () => {
it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => { it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
createComponent({ packageEntity: conanPackage }); createComponent({ packageEntity: conanPackage });
firstFileDownloadLink().vm.$emit('click'); findPackageFiles().vm.$emit('download-file');
expect(eventSpy).toHaveBeenCalledWith( expect(eventSpy).toHaveBeenCalledWith(
category, category,
TrackingActions.PULL_PACKAGE, TrackingActions.PULL_PACKAGE,
......
import { mount } from '@vue/test-utils';
import stubChildren from 'helpers/stub_children';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import component from '~/packages/details/components/package_files.vue';
import { npmFiles, mavenFiles } from '../../mock_data';
describe('Package Files', () => {
let wrapper;
const findAllRows = () => wrapper.findAll('[data-testid="file-row"');
const findFirstRow = () => findAllRows().at(0);
const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"');
const findFirstRowFileIcon = () => findFirstRow().find(FileIcon);
const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
const createComponent = (packageFiles = npmFiles) => {
wrapper = mount(component, {
propsData: {
packageFiles,
},
stubs: {
...stubChildren(component),
GlTable: false,
GlLink: '<div><slot></slot></div>',
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('rows', () => {
it('renders a single file for an npm package', () => {
createComponent();
expect(findAllRows()).toHaveLength(1);
});
it('renders multiple files for a package that contains more than one file', () => {
createComponent(mavenFiles);
expect(findAllRows()).toHaveLength(2);
});
});
describe('link', () => {
it('exists', () => {
createComponent();
expect(findFirstRowDownloadLink().exists()).toBe(true);
});
it('has the correct attrs bound', () => {
createComponent();
expect(findFirstRowDownloadLink().attributes('href')).toBe(npmFiles[0].download_path);
});
it('emits "download-file" event on click', () => {
createComponent();
findFirstRowDownloadLink().vm.$emit('click');
expect(wrapper.emitted('download-file')).toEqual([[]]);
});
});
describe('file-icon', () => {
it('exists', () => {
createComponent();
expect(findFirstRowFileIcon().exists()).toBe(true);
});
it('has the correct props bound', () => {
createComponent();
expect(findFirstRowFileIcon().props('fileName')).toBe(npmFiles[0].file_name);
});
});
describe('time-ago tooltip', () => {
it('exists', () => {
createComponent();
expect(findFirstRowCreatedAt().exists()).toBe(true);
});
it('has the correct props bound', () => {
createComponent();
expect(findFirstRowCreatedAt().props('time')).toBe(npmFiles[0].created_at);
});
});
});
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