Merge pull request #19388 from fyyyyy/feature/zip-download-in-dry-run-results

Feature/zip download in dry run results
This commit is contained in:
Patrik Oldsberg
2023-09-12 11:42:40 +02:00
committed by GitHub
7 changed files with 125 additions and 3 deletions
+1
View File
@@ -81,6 +81,7 @@
"immer": "^9.0.1",
"json-schema": "^0.4.0",
"json-schema-library": "^7.3.9",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"qs": "^6.9.4",
@@ -40,7 +40,7 @@ interface DryRunOptions {
files: Array<{ path: string; content: string }>;
}
interface DryRunResult extends ScaffolderDryRunResponse {
export interface DryRunResult extends ScaffolderDryRunResponse {
id: number;
}
@@ -25,8 +25,10 @@ import { makeStyles } from '@material-ui/core/styles';
import CancelIcon from '@material-ui/icons/Cancel';
import CheckIcon from '@material-ui/icons/Check';
import DeleteIcon from '@material-ui/icons/Delete';
import DownloadIcon from '@material-ui/icons/GetApp';
import React from 'react';
import { useDryRun } from '../DryRunContext';
import { downloadBlob } from '../../../lib/download';
const useStyles = makeStyles((theme: BackstageTheme) => ({
root: {
@@ -53,6 +55,17 @@ export function DryRunResultsList() {
<List className={classes.root} dense>
{dryRun.results.map(result => {
const failed = result.log.some(l => l.body.status === 'failed');
let isLoading = false;
async function downloadResult() {
isLoading = true;
await downloadDirectoryContents(
result.directoryContents,
`dry-run-result-${result.id}.zip`,
);
isLoading = false;
}
return (
<ListItem
button
@@ -67,9 +80,19 @@ export function DryRunResultsList() {
</ListItemIcon>
<ListItemText primary={`Result ${result.id}`} />
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="download"
title="Download as .zip"
disabled={isLoading}
onClick={() => downloadResult()}
>
<DownloadIcon />
</IconButton>
<IconButton
edge="end"
aria-label="delete"
title="Delete result"
onClick={() => dryRun.deleteResult(result.id)}
>
<DeleteIcon />
@@ -81,3 +104,26 @@ export function DryRunResultsList() {
</List>
);
}
async function downloadDirectoryContents(
directoryContents: {
path: string;
base64Content: string;
executable: boolean;
}[],
name: string,
) {
const { default: JSZip } = await import('jszip');
const zip = new JSZip();
for (const d of directoryContents) {
// Decode text content from base64 to ascii
const converted = atob(d.base64Content);
// add folder/file to zip
await zip.file(d.path, converted);
}
const blob = await zip.generateAsync({ type: 'blob' });
downloadBlob(blob, name);
}
@@ -0,0 +1,24 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function downloadBlob(blob: Blob, name: string) {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = name;
a.click();
URL.revokeObjectURL(a.href);
a.remove();
}
@@ -0,0 +1,17 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { downloadBlob } from './helpers';