Commit 5650ee04 authored by Savas Vedova's avatar Savas Vedova

Add a utility to download files

- Migrate old custom implementations
- Provide a utility function to download inline files or from endpoints
parent 59530486
/**
* Helper function to trigger a download.
*
* - If the `fileName` is `_blank` it will open the file in a new tab.
* - If `fileData` is provided, it will inline the content and use data URLs to
* download the file. In this case the `url` property will be ignored. Please
* note that `fileData` needs to be Base64 encoded.
*/
export default ({ fileName, url, fileData }) => {
let href = url;
if (fileData) {
href = `data:text/plain;base64,${fileData}`;
}
const anchor = document.createElement('a');
anchor.download = fileName;
anchor.href = href;
anchor.click();
};
......@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import pollUntilComplete from '~/lib/utils/poll_until_complete';
import download from '~/lib/utils/downloader';
export const STORAGE_KEY = 'vulnerability_csv_export_popover_dismissed';
......@@ -46,10 +47,10 @@ export default {
.post(this.vulnerabilitiesExportEndpoint)
.then(({ data }) => pollUntilComplete(data._links.self))
.then(({ data }) => {
const anchor = document.createElement('a');
anchor.download = `csv-export-${formatDate(new Date(), 'isoDateTime')}.csv`;
anchor.href = data._links.download;
anchor.click();
download({
fileName: `csv-export-${formatDate(new Date(), 'isoDateTime')}.csv`,
url: data._links.download,
});
})
.catch(() => {
createFlash(s__('SecurityReports|There was an error while generating the report.'));
......
import $ from 'jquery';
import _ from 'lodash';
import downloadPatchHelper from 'ee/vue_shared/security_reports/store/utils/download_patch_helper';
import download from '~/lib/utils/downloader';
import axios from '~/lib/utils/axios_utils';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { s__, n__, sprintf } from '~/locale';
......@@ -450,7 +450,7 @@ export const downloadPatch = ({ state }) => {
https://gitlab.com/gitlab-org/gitlab-ui/issues/188#note_165808493
*/
const { vulnerability } = state.modal;
downloadPatchHelper(vulnerability.remediations[0].diff);
download({ fileData: vulnerability.remediations[0].diff, fileName: `remediation.patch` });
$('#modal-mrwidget-security-issue').modal('hide');
};
......
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import download from '~/lib/utils/downloader';
import pollUntilComplete from '~/lib/utils/poll_until_complete';
import { s__, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import toast from '~/vue_shared/plugins/global_toast';
import * as types from './mutation_types';
import downloadPatchHelper from './utils/download_patch_helper';
/**
* A lot of this file has duplicate actions to
......@@ -445,7 +445,7 @@ export const downloadPatch = ({ state }) => {
https://gitlab.com/gitlab-org/gitlab-ui/issues/188#note_165808493
*/
const { vulnerability } = state.modal;
downloadPatchHelper(vulnerability.remediations[0].diff);
download({ fileData: vulnerability.remediations[0].diff, fileName: 'remediation.patch' });
$('#modal-mrwidget-security-issue').modal('hide');
};
......
const downloadPatchHelper = (patch, opts = {}) => {
const mergedOpts = {
isEncoded: true,
...opts,
};
const url = `data:text/plain;base64,${mergedOpts.isEncoded ? patch : btoa(patch)}`;
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'remediation.patch');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
export { downloadPatchHelper as default };
......@@ -31,6 +31,10 @@ describe('vulnerabilities count actions', () => {
state = initialState();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('setPipelineId', () => {
const pipelineId = 123;
......@@ -443,9 +447,8 @@ describe('openModal', () => {
describe('downloadPatch', () => {
it('creates a download link and clicks on it to download the file', () => {
jest.spyOn(document, 'createElement');
jest.spyOn(document.body, 'appendChild');
jest.spyOn(document.body, 'removeChild');
const a = { click: jest.fn() };
jest.spyOn(document, 'createElement').mockImplementation(() => a);
actions.downloadPatch({
state: {
......@@ -462,8 +465,10 @@ describe('downloadPatch', () => {
});
expect(document.createElement).toHaveBeenCalledTimes(1);
expect(document.body.appendChild).toHaveBeenCalledTimes(1);
expect(document.body.removeChild).toHaveBeenCalledTimes(1);
expect(document.createElement).toHaveBeenCalledWith('a');
expect(a.click).toHaveBeenCalledTimes(1);
expect(a.download).toBe('remediation.patch');
expect(a.href).toContain('data:text/plain;base64');
});
});
......
......@@ -94,6 +94,10 @@ const createDismissedVulnerability = options =>
isDismissed: true,
});
afterEach(() => {
jest.clearAllMocks();
});
describe('security reports actions', () => {
let mockedState;
let mock;
......@@ -870,9 +874,8 @@ describe('security reports actions', () => {
describe('downloadPatch', () => {
it('creates a download link and clicks on it to download the file', () => {
jest.spyOn(document, 'createElement');
jest.spyOn(document.body, 'appendChild');
jest.spyOn(document.body, 'removeChild');
const a = { click: jest.fn() };
jest.spyOn(document, 'createElement').mockImplementation(() => a);
downloadPatch({
state: {
......@@ -889,8 +892,10 @@ describe('security reports actions', () => {
});
expect(document.createElement).toHaveBeenCalledTimes(1);
expect(document.body.appendChild).toHaveBeenCalledTimes(1);
expect(document.body.removeChild).toHaveBeenCalledTimes(1);
expect(document.createElement).toHaveBeenCalledWith('a');
expect(a.click).toHaveBeenCalledTimes(1);
expect(a.download).toBe('remediation.patch');
expect(a.href).toContain('data:text/plain;base64');
});
});
......
import downloadPatchHelper from 'ee/vue_shared/security_reports/store/utils/download_patch_helper';
describe('downloadPatchHelper', () => {
beforeEach(() => {
jest.spyOn(document, 'createElement');
jest.spyOn(document.body, 'appendChild');
jest.spyOn(document.body, 'removeChild');
});
describe('with a base64 encoded string', () => {
it('creates a download link and clicks on it to download the file', done => {
const base64String = btoa('abcdef');
document.onclick = e => {
expect(e.target.download).toBe('remediation.patch');
expect(e.target.href).toBe('data:text/plain;base64,YWJjZGVm');
done();
};
downloadPatchHelper(base64String);
expect(document.createElement).toHaveBeenCalledWith('a');
expect(document.body.appendChild).toHaveBeenCalledTimes(1);
expect(document.body.removeChild).toHaveBeenCalledTimes(1);
});
});
describe('without a base64 encoded string', () => {
it('creates a download link and clicks on it to download the file', done => {
const unencodedString = 'abcdef';
document.onclick = e => {
expect(e.target.download).toBe('remediation.patch');
expect(e.target.href).toBe('data:text/plain;base64,YWJjZGVm');
done();
};
downloadPatchHelper(unencodedString, { isEncoded: false });
expect(document.createElement).toHaveBeenCalledWith('a');
expect(document.body.appendChild).toHaveBeenCalledTimes(1);
expect(document.body.removeChild).toHaveBeenCalledTimes(1);
});
});
});
import downloader from '~/lib/utils/downloader';
describe('Downloader', () => {
let a;
beforeEach(() => {
a = { click: jest.fn() };
jest.spyOn(document, 'createElement').mockImplementation(() => a);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('when inline file content is provided', () => {
const fileData = 'inline content';
const fileName = 'test.csv';
it('uses the data urls to download the file', () => {
downloader({ fileName, fileData });
expect(document.createElement).toHaveBeenCalledWith('a');
expect(a.download).toBe(fileName);
expect(a.href).toBe(`data:text/plain;base64,${fileData}`);
expect(a.click).toHaveBeenCalledTimes(1);
});
});
describe('when an endpoint is provided', () => {
const url = 'https://gitlab.com/test.csv';
const fileName = 'test.csv';
it('uses the endpoint to download the file', () => {
downloader({ fileName, url });
expect(document.createElement).toHaveBeenCalledWith('a');
expect(a.download).toBe(fileName);
expect(a.href).toBe(url);
expect(a.click).toHaveBeenCalledTimes(1);
});
});
});
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