Commit 191949a6 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch '323709-publish-package-hash-on-package-page' into 'master'

Resolve "Publish package hash on Package Page"

See merge request gitlab-org/gitlab!63279
parents be84e5fb df041756
<script>
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
export default {
name: 'FileSha',
components: {
DetailsRow,
ClipboardButton,
},
props: {
sha: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
},
i18n: {
copyButtonTitle: s__('PackageRegistry|Copy SHA'),
},
};
</script>
<template>
<details-row dashed>
<div class="gl-px-4">
{{ title }}:
{{ sha }}
<clipboard-button
:text="sha"
:title="$options.i18n.copyButtonTitle"
category="tertiary"
size="small"
/>
</div>
</details-row>
</template>
<script>
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui';
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
import { last } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
import FileSha from '~/packages/details/components/file_sha.vue';
import Tracking from '~/tracking';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
......@@ -15,8 +16,10 @@ export default {
GlIcon,
GlDropdown,
GlDropdownItem,
GlButton,
FileIcon,
TimeAgoTooltip,
FileSha,
},
mixins: [Tracking.mixin()],
props: {
......@@ -76,6 +79,9 @@ export default {
formatSize(size) {
return numberToHumanSize(size);
},
hasDetails(item) {
return item.file_sha256 || item.file_md5 || item.file_sha1;
},
},
i18n: {
deleteFile: __('Delete file'),
......@@ -91,7 +97,15 @@ export default {
:items="filesTableRows"
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
>
<template #cell(name)="{ item }">
<template #cell(name)="{ item, toggleDetails, detailsShowing }">
<gl-button
v-if="hasDetails(item)"
:icon="detailsShowing ? 'angle-up' : 'angle-down'"
:aria-label="detailsShowing ? __('Collapse') : __('Expand')"
category="tertiary"
size="small"
@click="toggleDetails"
/>
<gl-link
:href="item.download_path"
class="gl-text-gray-500"
......@@ -131,6 +145,21 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
</template>
<template #row-details="{ item }">
<div
class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
>
<file-sha
v-if="item.file_sha256"
data-testid="sha-256"
title="SHA-256"
:sha="item.file_sha256"
/>
<file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" />
<file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" />
</div>
</template>
</gl-table>
</div>
</template>
......@@ -8,7 +8,8 @@ export default {
props: {
icon: {
type: String,
required: true,
required: false,
default: null,
},
padding: {
type: String,
......@@ -34,7 +35,7 @@ export default {
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all"
:class="[padding, borderClass]"
>
<gl-icon :name="icon" class="gl-mr-4" />
<gl-icon v-if="icon" :name="icon" class="gl-mr-4" />
<span>
<slot></slot>
</span>
......
......@@ -23336,6 +23336,9 @@ msgstr ""
msgid "PackageRegistry|Copy Pip command"
msgstr ""
msgid "PackageRegistry|Copy SHA"
msgstr ""
msgid "PackageRegistry|Copy add Gradle Groovy DSL repository command"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FileSha renders 1`] = `
<div
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all gl-py-2 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
>
<!---->
<span>
<div
class="gl-px-4"
>
bar:
foo
<gl-button-stub
aria-label="Copy this value"
buttontextclasses=""
category="tertiary"
data-clipboard-text="foo"
icon="copy-to-clipboard"
size="small"
title="Copy SHA"
variant="default"
/>
</div>
</span>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import FileSha from '~/packages/details/components/file_sha.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
describe('FileSha', () => {
let wrapper;
const defaultProps = { sha: 'foo', title: 'bar' };
function createComponent() {
wrapper = shallowMount(FileSha, {
propsData: {
...defaultProps,
},
stubs: {
ClipboardButton,
DetailsRow,
},
});
}
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
});
import { GlDropdown } from '@gitlab/ui';
import { GlDropdown, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue/';
import stubChildren from 'helpers/stub_children';
import component from '~/packages/details/components/package_files.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
......@@ -20,6 +21,8 @@ describe('Package Files', () => {
const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`);
const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => {
wrapper = mount(component, {
......@@ -181,4 +184,76 @@ describe('Package Files', () => {
});
});
});
describe('additional details', () => {
describe('details toggle button', () => {
it('exists', () => {
createComponent();
expect(findFirstToggleDetailsButton().exists()).toBe(true);
});
it('is hidden when no details is present', () => {
const [{ ...noShaFile }] = npmFiles;
noShaFile.file_sha256 = null;
noShaFile.file_md5 = null;
noShaFile.file_sha1 = null;
createComponent({ packageFiles: [noShaFile] });
expect(findFirstToggleDetailsButton().exists()).toBe(false);
});
it('toggles the details row', async () => {
createComponent();
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
findFirstToggleDetailsButton().vm.$emit('click');
await nextTick();
expect(findFirstRowShaComponent('sha-256').exists()).toBe(true);
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-up');
findFirstToggleDetailsButton().vm.$emit('click');
await nextTick();
expect(findFirstRowShaComponent('sha-256').exists()).toBe(false);
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
});
});
describe('file shas', () => {
const showShaFiles = () => {
findFirstToggleDetailsButton().vm.$emit('click');
return nextTick();
};
it.each`
selector | title | sha
${'sha-256'} | ${'SHA-256'} | ${'file_sha256'}
${'md5'} | ${'MD5'} | ${'file_md5'}
${'sha-1'} | ${'SHA-1'} | ${'file_sha1'}
`('has a $title row', async ({ selector, title, sha }) => {
createComponent();
await showShaFiles();
expect(findFirstRowShaComponent(selector).props()).toMatchObject({
title,
sha,
});
});
it('does not display a row when the data is missing', async () => {
const [{ ...missingMd5 }] = npmFiles;
missingMd5.file_md5 = null;
createComponent({ packageFiles: [missingMd5] });
await showShaFiles();
expect(findFirstRowShaComponent('md5').exists()).toBe(false);
});
});
});
});
......@@ -79,6 +79,9 @@ export const npmFiles = [
pipelines: [
{ id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
],
file_sha256: 'file_sha256',
file_md5: 'file_md5',
file_sha1: 'file_sha1',
},
];
......
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