Commit fa64e45e authored by Maksym Tishchenko's avatar Maksym Tishchenko
Browse files

File repository: Download folder as ZIP and Extract ZIP

parent 6c88e3ec
......@@ -39,7 +39,8 @@
"openActionList": "Open action list",
"previousPeriod": "< Previous period",
"nextPeriod": "Next period >",
"assignBarcode": "Assign Barcode"
"assignBarcode": "Assign Barcode",
"unzip": "Unzip"
},
"time": {
"now": "Now",
......
......@@ -27,6 +27,8 @@ const URL_CREATE_GALLERY = UrlTemplate.parse('/api/v1/repository/gallery');
const URL_REMOVE_GALLERY = UrlTemplate.parse('/api/v1/repository/gallery');
const URL_UPLOAD_FILE = UrlTemplate.parse('/api/v1/repository/upload');
const URL_UPLOAD_FOLDER_METADATA = UrlTemplate.parse('/api/v1/repository/upload/folder-metadata');
const URL_DOWNLOAD_FOLDER_AS_ZIP = UrlTemplate.parse(`/api/v1/repository/folder/download/{folderUuid}`);
const URL_EXTRACT_ZIP = UrlTemplate.parse(`/api/v1/repository/file/extract/{fileUuid}`);
/**
* Repository service
......@@ -409,6 +411,49 @@ class RepositoryService {
}).then(({ data }) => data as Page<RepositoryFile>);
};
/**
* downloadFolderAsZip at /api/v1/repository/folder/download/{folderUuid}
*
* @param folderUuid undefined
* @param xhrConfig additional xhr config
*/
public downloadFolderAsZip = (folderUuid: string, xhrConfig?: AxiosRequestConfig): Promise<Blob> => {
const apiUrl = URL_DOWNLOAD_FOLDER_AS_ZIP.expand({ folderUuid });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'GET',
responseType: 'blob',
headers: {
'Accept': 'application/zip, */*'
},
...content,
}).then(({ data }) => data as Blob);
}
/**
* extractZip at /api/v1/repository/file/extract/{fileUuid}
*
* @param fileUuid undefined
* @param xhrConfig additional xhr config
*/
public extractZip = (fileUuid: string, xhrConfig?: AxiosRequestConfig): Promise<RepositoryFile[]> => {
const apiUrl = URL_EXTRACT_ZIP.expand({ fileUuid });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as RepositoryFile[]);
}
}
export default RepositoryService;
......@@ -862,10 +862,14 @@
"createGalleryAlert": "Image gallery at {{folderPath, string}} could not be created.",
"deleteFolder": "Delete folder",
"deleteFolderAlert": "Folder {{folderPath, string}} could not be deleted.",
"downloadFolder": "Download folder",
"downloadSuccess": "Successfully downloaded folder {{folderPath, string}}",
"downloadFolderStart": "Downloading folder {{folderPath, string}}",
"title": "File repository",
"downloadFolderMetadata": "Download metadata",
"uploadFolderMetadata": "Upload metadata",
"uploadFailed": "Some of files weren't loaded"
"uploadFailed": "Some of files weren't loaded",
"unzipSuccess": "Successfully unzipped"
}
},
"dialog": {
......
......@@ -39,6 +39,7 @@ import { IPageRequest } from '@gringlobal-ce/client/model/page';
// service
import { KpiService } from '@gringlobal-ce/client/service';
import { getI18n } from "react-i18next";
export const kpiAdminSagas = [
takeEvery(SAGA_ADMIN_RECEIVE_EXECS, listExecutionsSaga),
......@@ -141,6 +142,8 @@ function* runExecutionSaga(action) {
}
function* listExecutionRunsSaga(action) {
const i18n = getI18n()
yield put({
type: 'API',
target: ADMIN_APPEND_EXEC_RUNS,
......@@ -148,7 +151,7 @@ function* listExecutionRunsSaga(action) {
params: [ action.payload.name, action.payload.page ],
onSuccess: (execRun) => {
return (function* () {
yield call(sagaShowSnackbar, 'kpi.common.executionRunsLoaded');
yield call(sagaShowSnackbar, i18n.t('kpi.common.executionRunsLoaded'));
return execRun;
})();
},
......
......@@ -33,7 +33,9 @@ import {
SAGA_UPDATE_FOLDER_METADATA,
SAGA_CREATE_IMAGE_GALLERY,
TOGGLE_FOLDER_DIALOG, UPLOAD_FILE_FAILURE, UPLOAD_FILE_SUCCESS,
TOGGLE_FOLDER_DIALOG,
UPLOAD_FILE_FAILURE,
UPLOAD_FILE_SUCCESS,
} from 'repository/constants';
// Model
......@@ -44,6 +46,7 @@ import { Page } from '@gringlobal-ce/client/model/page';
// Service
import { RepositoryService } from '@gringlobal-ce/client/service';
import { getI18n } from "react-i18next";
export const repositoryAdminSagas = [
takeEvery(SAGA_RECEIVE_FOLDER_DETAILS, getFolderSaga),
......@@ -154,7 +157,6 @@ function* removeFilesSaga(action) {
});
}
// Create or load folder at specified path
export const ensureFolder = (path: string) => ({
type: SAGA_UPDATE_SUBFOLDER_LIST,
......@@ -273,6 +275,7 @@ export const uploadFiles = (path: string, files: File[]) => ({
function* uploadFilesSaga(action) {
const { files, path } = action.payload;
const i18n = getI18n()
yield all(files.map((file, index) => (
put({
......@@ -305,7 +308,7 @@ function* uploadFilesSaga(action) {
yield call(getFolderSaga, { payload: { path } });
}
if (failure) {
yield call(sagaShowSnackbar, 'repository.admin.p.repositoryBrowser.uploadFailed');
yield call(sagaShowSnackbar, i18n.t('repository.admin.p.repositoryBrowser.uploadFailed'));
}
}
......
......@@ -24,6 +24,6 @@ export const SAGA_UPDATE_FILE = 'saga/repository/admin/SAGA_UPDATE_FILE';
export const SAGA_UPLOAD_FILE = 'saga/repository/admin/SAGA_UPLOAD_FILE';
export const SAGA_UPLOAD_FILES = 'saga/repository/admin/SAGA_UPLOAD_FILES';
export const SAGA_UPDATE_FOLDER_METADATA = 'saga/repository/admin/SAGA_UPDATE_FOLDER_METADATA';
export const SAGA_CREATE_IMAGE_GALLERY = 'saga/repository/admin/SAGA_CREATE_IMAGE_GALLERY';
export const SAGA_CREATE_IMAGE_GALLERY = 'saga/repository/admin/SAGA_CREATE_IMAGE_GALLERY'
export const TOGGLE_FOLDER_DIALOG = 'success/repository/admin/TOGGLE_FOLDER_DIALOG';
......@@ -16,10 +16,14 @@
"createGalleryAlert": "Image gallery at {{folderPath, string}} could not be created.",
"deleteFolder": "Delete folder",
"deleteFolderAlert": "Folder {{folderPath, string}} could not be deleted.",
"downloadFolder": "Download folder",
"downloadSuccess": "Successfully downloaded folder {{folderPath, string}}",
"downloadFolderStart": "Downloading folder {{folderPath, string}}",
"title": "File repository",
"downloadFolderMetadata": "Download metadata",
"uploadFolderMetadata": "Upload metadata",
"uploadFailed": "Some of files weren't loaded"
"uploadFailed": "Some of files weren't loaded",
"unzipSuccess": "Successfully unzipped"
}
},
"dialog": {
......
......@@ -3,7 +3,18 @@ import { connect } from 'react-redux';
import { WithTranslation, withTranslation } from 'react-i18next';
import { normalize } from 'path';
import { getFolder, uploadFiles, uploadFolderMetadata, deleteFolder, createGallery, removeFiles, loadMoreFiles, loadMoreFolders, toggleFolderDialog, updateFile } from 'repository/action/admin';
import {
getFolder,
uploadFiles,
uploadFolderMetadata,
deleteFolder,
createGallery,
removeFiles,
loadMoreFiles,
loadMoreFolders,
toggleFolderDialog,
updateFile,
} from 'repository/action/admin';
import RepositoryFile from '@gringlobal-ce/client/model/repository/RepositoryFile';
import RepositoryFolder from '@gringlobal-ce/client/model/repository/RepositoryFolder';
......@@ -33,6 +44,8 @@ import { ScrollToTopOnMount } from '@gringlobal-ce/client/ui/common/scrollers';
import ButtonBar from '@gringlobal-ce/client/ui/common/button/ButtonBar';
import PageTitle from '@gringlobal-ce/client/ui/common/PageTitle';
import FileForm from 'repository/ui/c/FileForm';
import { RepositoryService } from "@gringlobal-ce/client/service";
import { showSnackbar } from "@gringlobal-ce/client/action/snackbar";
class RepositoryBrowser extends React.Component<PropsFromRedux & WithTranslation, any> {
public state = {
......@@ -110,6 +123,17 @@ class RepositoryBrowser extends React.Component<PropsFromRedux & WithTranslation
createGallery(folderPath, folder.folder.name);
};
protected extractZip = (uuid: string) => {
const { t, showSnackbar, getFolder, folderPath } = this.props;
RepositoryService.extractZip(uuid).then(() => {
showSnackbar(t('repository.admin.p.repositoryBrowser.unzipSuccess'))
getFolder(folderPath);
}).catch((e) => {
showSnackbar(e.data?.error || e.toString())
})
};
protected goToGallery = (e) => {
const { navigateTo, folderPath } = this.props;
navigateTo(`/admin/repository/g${folderPath}/`);
......@@ -126,7 +150,7 @@ class RepositoryBrowser extends React.Component<PropsFromRedux & WithTranslation
protected renderFile = (file: RepositoryFile) => (
<Grid key={ file.uuid } item xs={ 12 }>
<FileCard file={ file } deleteFile={ this.deleteFile } editFile={ this.openFileDialog }/>
<FileCard file={ file } deleteFile={ this.deleteFile } editFile={ this.openFileDialog } extractZip={ this.extractZip }/>
</Grid>
);
......@@ -153,7 +177,7 @@ class RepositoryBrowser extends React.Component<PropsFromRedux & WithTranslation
public render() {
const { folder, loading, error, root, path, t } = this.props;
const { folder, loading, apiUrl, error, root, path, t } = this.props;
const { fileDialogIsOpen, selectedFile } = this.state;
const parentFolder = new RepositoryFolder();
......@@ -203,6 +227,7 @@ class RepositoryBrowser extends React.Component<PropsFromRedux & WithTranslation
folder.gallery ? <Button key="viewg" variant="contained" onClick={ this.goToGallery }>{ t('repository.admin.p.repositoryBrowser.viewGallery') }</Button> : null }
{ folder.folder && folder.folder._permissions.delete && <Button onClick={ this.deleteFolder } key="deletef" variant="contained">{ t('repository.admin.p.repositoryBrowser.deleteFolder') }</Button> }
{ folder.folder && folder.folder._permissions.write && <UpdateFolderDialog/> }
{ folder.folder && <a href={ `${apiUrl}/api/v1/repository/folder/download/${folder.folder.uuid}` }><Button variant="contained">{ t('repository.admin.p.repositoryBrowser.downloadFolder') }</Button></a> }
{ folder.folder && folder.folder._permissions.manage && <Permissions clazz={ RepositoryFolder.clazz } id={ folder.folder.id } variant="contained" /> }
</ButtonBar>
}
......@@ -266,6 +291,7 @@ const mapDispatch = {
createGallery,
navigateTo,
toggleFolderDialog,
showSnackbar
}
type PropsFromRedux = ReturnType<typeof mapStateToProps> & typeof mapDispatch;
......
......@@ -83,10 +83,11 @@ interface IFileCard {
edit?: boolean;
deleteFile: (uuid: string) => any;
editFile: (file: RepositoryFile) => any;
extractZip: (uuid: string) => any;
apiUrl: string;
}
const FileCard = ({ file, classes, compact = false, edit = false, deleteFile, editFile, t, apiUrl, ...other }: WithTranslation & IFileCard) => {
const FileCard = ({ file, classes, compact = false, edit = false, deleteFile, editFile, extractZip, t, apiUrl, ...other }: WithTranslation & IFileCard) => {
if (!file) {
return null;
......@@ -120,6 +121,7 @@ const FileCard = ({ file, classes, compact = false, edit = false, deleteFile, ed
title={ <span><a href={ repositoryDownloadUrl(file) }><DownloadIcon /></a> <span>{ file.originalFilename }</span></span> }
action={
<div className={ classes.actions }>
{ file && file.extension === '.zip' && <span><ActionButton action={ () => extractZip(file.uuid) } title={ t('common:action.unzip') } /></span> }
<span><ActionButton action={ () => editFile(file) } title={ t('common:action.edit') } /></span>
<span><ActionButton action={ () => deleteFile(file.uuid) } title={ t('common:action.delete') } /></span>
{ file && file._permissions.manage && <Permissions clazz={ className } id={ file.id } variant="contained" /> }
......
......@@ -23,6 +23,7 @@ import { sagaNavigate } from '@gringlobal-ce/client/action/navigation';
import { sagaShowSnackbar } from '@gringlobal-ce/client/action/snackbar';
// Util
import { dereferenceReferences3 } from '@gringlobal-ce/client/utilities';
import { getI18n } from "react-i18next";
export const userAdminSagas = [
......@@ -156,9 +157,11 @@ export const removeClientSecret = (id) => ({
function* removeClientSecretSaga(action) {
const { id } = action.payload;
const i18n = getI18n()
try {
yield call(OAuthManagementService.removeSecret, id);
yield call(sagaShowSnackbar, 'user.admin.p.oAuthDetails.removeSuccess');
yield call(sagaShowSnackbar, i18n.t('user.admin.p.oAuthDetails.removeSuccess'));
} catch(e) {
yield call(sagaShowSnackbar, e.data && e.data.error || e.toString());
}
......
Supports Markdown
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