diff --git a/static/css/sass/cdr_ui_styles.scss b/static/css/sass/cdr_ui_styles.scss
index c3cbf4ff1a..5c81ff61f6 100644
--- a/static/css/sass/cdr_ui_styles.scss
+++ b/static/css/sass/cdr_ui_styles.scss
@@ -704,6 +704,14 @@ table.dataTable {
border-radius: 5px;
}
+.is-narrow.item-actions {
+ text-align: right;
+
+ .actionlink {
+ margin-bottom: 0;
+ }
+}
+
.item-actions {
.actionlink {
a.action {
@@ -1038,6 +1046,14 @@ table.dataTable {
}
}
+ .is-narrow.item-actions {
+ text-align: left;
+
+ .actionlink {
+ margin-bottom: 3px;
+ }
+ }
+
.record-metadata {
justify-content: center;
display: grid;
diff --git a/static/js/vue-cdr-access/package-lock.json b/static/js/vue-cdr-access/package-lock.json
index d05061ff38..10a2853fa7 100644
--- a/static/js/vue-cdr-access/package-lock.json
+++ b/static/js/vue-cdr-access/package-lock.json
@@ -51,9 +51,9 @@
}
},
"node_modules/@adobe/css-tools": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
- "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
+ "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==",
"dev": true
},
"node_modules/@antfu/utils": {
@@ -10261,9 +10261,9 @@
},
"dependencies": {
"@adobe/css-tools": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
- "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
+ "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==",
"dev": true
},
"@antfu/utils": {
diff --git a/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue b/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue
index e8406e1c9f..007e1fe83c 100644
--- a/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue
+++ b/static/js/vue-cdr-access/src/components/full_record/restrictedContent.vue
@@ -20,6 +20,9 @@
View
+
+
+
{{ $t('full_record.available_date', { available_date: formatDate(recordData.briefObject.embargoDate) }) }}
@@ -28,12 +31,15 @@
+
+
\ No newline at end of file
diff --git a/static/js/vue-cdr-access/src/translations.js b/static/js/vue-cdr-access/src/translations.js
index 5a4c299428..cb9af99745 100644
--- a/static/js/vue-cdr-access/src/translations.js
+++ b/static/js/vue-cdr-access/src/translations.js
@@ -42,12 +42,17 @@ export default {
available_date: "Available after {available_date}",
collection_id: "Archival Collection ID",
contains: "Contains",
+ copied_link: "Download URL, {text}, copied to clipboard",
+ copied_link_failed: "Unable to copy download URL, {text}, to clipboard",
+ created_link: "Created link {link} expires in {expire_time}",
+ created_link_failed: "Unable to create single use link for {uuid}",
creator: "Creator",
date_added: "Date Added",
date_created: "Date Created",
detailed_metadata: "Detailed Metadata",
download: "Download",
download_file: "Download file",
+ download_single_use: "Generate Single-Use Link",
download_title: "Download {title}",
download_unavailable: "Download Unavailable",
edit: "Edit",
diff --git a/static/js/vue-cdr-access/tests/unit/restrictedContent.spec.js b/static/js/vue-cdr-access/tests/unit/restrictedContent.spec.js
index eddd18b8d9..20debbd2d1 100644
--- a/static/js/vue-cdr-access/tests/unit/restrictedContent.spec.js
+++ b/static/js/vue-cdr-access/tests/unit/restrictedContent.spec.js
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router';
import restrictedContent from '@/components/full_record/restrictedContent.vue';
import displayWrapper from '@/components/displayWrapper.vue';
+import singleUseLink from '@/components/full_record/singleUseLink.vue';
import {createI18n} from 'vue-i18n';
import translations from '@/translations';
import cloneDeep from 'lodash.clonedeep';
@@ -484,6 +485,26 @@ describe('restrictedContent.vue', () => {
expect(wrapper.find('.download').exists()).toBe(false);
});
+ it('displays a single use link button for files with the proper permissions', async () => {
+ const updated_data = cloneDeep(record);
+ updated_data.briefObject.permissions = ['viewAccessCopies', 'viewHidden', 'viewOriginal'];
+ await wrapper.setProps({
+ recordData: updated_data
+ });
+ expect(wrapper.findComponent(singleUseLink).exists()).toBe(true);
+ });
+
+ it('does not display a single use link button for files without the proper permissions', async () => {
+ const updated_data = cloneDeep(record);
+ updated_data.dataFileUrl = 'content/4db695c0-5fd5-4abf-9248-2e115d43f57d';
+ updated_data.resourceType = 'File';
+ updated_data.briefObject.permissions = ['viewAccessCopies'];
+ await wrapper.setProps({
+ recordData: updated_data
+ });
+ expect(wrapper.findComponent(singleUseLink).exists()).toBe(false);
+ });
+
it('displays embargo information for files', async () => {
const updated_data = cloneDeep(record);
updated_data.briefObject.embargoDate = '2199-12-31T20:34:01.799Z';
diff --git a/static/js/vue-cdr-access/tests/unit/singleUseLink.spec.js b/static/js/vue-cdr-access/tests/unit/singleUseLink.spec.js
new file mode 100644
index 0000000000..fb7e4841c8
--- /dev/null
+++ b/static/js/vue-cdr-access/tests/unit/singleUseLink.spec.js
@@ -0,0 +1,80 @@
+import { shallowMount } from '@vue/test-utils'
+import singleUseLink from '@/components/full_record/singleUseLink.vue';
+import {createI18n} from 'vue-i18n';
+import translations from '@/translations';
+import moxios from 'moxios';
+
+const uuid = '9f7f3746-0237-4261-96a2-4b4765d4ae03';
+const response_date = { link: `https://test.edu`, expires: '24hrs', id: uuid };
+let wrapper;
+
+describe('singleUseLink.vue', () => {
+ const i18n = createI18n({
+ locale: 'en',
+ fallbackLocale: 'en',
+ messages: translations
+ });
+
+ beforeEach(() => {
+ wrapper = shallowMount(singleUseLink, {
+ global: {
+ plugins: [i18n]
+ },
+ props: {
+ uuid: '9f7f3746-0237-4261-96a2-4b4765d4ae03'
+ }
+ });
+
+ moxios.install();
+ });
+
+ afterEach(function () {
+ moxios.uninstall();
+ });
+
+ it("creates single use links", (done) => {
+ expect(wrapper.find('.download-link-wrapper').exists()).toBe(false);
+
+ moxios.stubRequest(`/services/api/single_use_link/create/${uuid}`, {
+ status: 200,
+ response: JSON.stringify(response_date)
+ });
+
+ moxios.wait(async () => {
+ await wrapper.find('#single-use-link').trigger('click');
+ expect(wrapper.find('.download-link-wrapper').exists()).toBe(true);
+ expect(wrapper.find('.download-link-wrapper div').text())
+ .toEqual(`Created link ${response_date.link} expires in ${response_date.expires}`);
+ expect(wrapper.find('.download-link-wrapper a').exists()).toBe(true); // Copy button
+ done();
+ });
+ });
+
+ it("does not create single use links on response errors", (done) => {
+ expect(wrapper.find('.download-link-wrapper').exists()).toBe(false);
+
+ moxios.stubRequest(`/services/api/single_use_link/create/${uuid}`, {
+ status: 404,
+ response: JSON.stringify('No record here')
+ });
+
+ moxios.wait(async () => {
+ await wrapper.find('#single-use-link').trigger('click');
+ expect(wrapper.find('.download-link-wrapper').exists()).toBe(false);
+ done();
+ });
+ });
+
+ it("copies single use links", async () => {
+ Object.assign(window.navigator, {
+ clipboard: {
+ writeText: jest.fn().mockImplementation(() => Promise.resolve()),
+ },
+ });
+
+ await wrapper.setData({ single_use_links: [response_date] });
+ await wrapper.find('.download-link-wrapper a').trigger('click');
+ expect(window.navigator.clipboard.writeText)
+ .toHaveBeenCalledWith(response_date.link);
+ });
+});