Răsfoiți Sursa

show ExportingProgressBar

Yuki Takei 6 ani în urmă
părinte
comite
3a04f34ebe

+ 40 - 9
src/client/js/components/Admin/Export/ExportPage.jsx

@@ -11,6 +11,7 @@ import WebsocketContainer from '../../../services/WebsocketContainer';
 
 
 import ExportZipFormModal from './ExportZipFormModal';
 import ExportZipFormModal from './ExportZipFormModal';
 import ZipFileTable from './ZipFileTable';
 import ZipFileTable from './ZipFileTable';
+import ExportingProgressBar from './ExportingProgressBar';
 
 
 class ExportPage extends React.Component {
 class ExportPage extends React.Component {
 
 
@@ -20,8 +21,10 @@ class ExportPage extends React.Component {
     this.state = {
     this.state = {
       collections: [],
       collections: [],
       zipFileStats: [],
       zipFileStats: [],
+      progressList: [],
       isExportModalOpen: false,
       isExportModalOpen: false,
       isExporting: false,
       isExporting: false,
+      isExported: false,
     };
     };
 
 
     this.onZipFileStatAdd = this.onZipFileStatAdd.bind(this);
     this.onZipFileStatAdd = this.onZipFileStatAdd.bind(this);
@@ -34,16 +37,18 @@ class ExportPage extends React.Component {
   async componentWillMount() {
   async componentWillMount() {
     // TODO:: use apiv3.get
     // TODO:: use apiv3.get
     // eslint-disable-next-line no-unused-vars
     // eslint-disable-next-line no-unused-vars
-    const [{ collections }, { zipFileStats, isExporting }] = await Promise.all([
+    const [{ collections }, { status }] = await Promise.all([
       this.props.appContainer.apiGet('/v3/mongo/collections', {}),
       this.props.appContainer.apiGet('/v3/mongo/collections', {}),
       this.props.appContainer.apiGet('/v3/export/status', {}),
       this.props.appContainer.apiGet('/v3/export/status', {}),
     ]);
     ]);
     // TODO: toastSuccess, toastError
     // TODO: toastSuccess, toastError
 
 
+    const { zipFileStats, isExporting, progressList } = status;
     this.setState({
     this.setState({
       collections: ['pages', 'revisions'],
       collections: ['pages', 'revisions'],
       zipFileStats,
       zipFileStats,
       isExporting,
       isExporting,
+      progressList,
     }); // FIXME: delete this line and uncomment the line below
     }); // FIXME: delete this line and uncomment the line below
     // this.setState({ collections, zipFileStats, isExporting });
     // this.setState({ collections, zipFileStats, isExporting });
 
 
@@ -53,12 +58,17 @@ class ExportPage extends React.Component {
   setupWebsocketEventHandler() {
   setupWebsocketEventHandler() {
     const socket = this.props.websocketContainer.getWebSocket();
     const socket = this.props.websocketContainer.getWebSocket();
 
 
-    socket.on('admin:onProgressForExport', (data) => {
-      console.log(data);
+    socket.on('admin:onProgressForExport', ({ currentCount, totalCount, progressList }) => {
+      const isExporting = currentCount !== totalCount;
+      this.setState({ isExporting, progressList });
     });
     });
 
 
-    socket.on('admin:onTerminateForExport', (data) => {
-      console.log(data);
+    socket.on('admin:onTerminateForExport', ({ currentCount, totalCount, progressList }) => {
+      this.setState({
+        isExporting: false,
+        isExported: true,
+        progressList,
+      });
     });
     });
   }
   }
 
 
@@ -112,14 +122,34 @@ class ExportPage extends React.Component {
     this.setState({ isExportModalOpen: false });
     this.setState({ isExportModalOpen: false });
   }
   }
 
 
-  exportingRequestedHandler() {
-    // TODO: implement
-    this.setState({ isExporting: true });
+  /**
+   * @params {object} export status data
+   */
+  exportingRequestedHandler(status) {
+    const { zipFileStats, isExporting, progressList } = status;
+    this.setState({ zipFileStats, isExporting, progressList });
+  }
+
+  renderProgressBars() {
+    return this.state.progressList.map((progressData) => {
+      const { collectionName, currentCount, totalCount } = progressData;
+      return (
+        <div className="px-3 w-50" key={collectionName}>
+          <ExportingProgressBar
+            collectionName={collectionName}
+            currentCount={currentCount}
+            totalCount={totalCount}
+          />
+        </div>
+      );
+    });
   }
   }
 
 
   render() {
   render() {
     const { t } = this.props;
     const { t } = this.props;
 
 
+    const showExportingData = (this.state.isExported || this.state.isExporting) && (this.state.progressList != null);
+
     return (
     return (
       <Fragment>
       <Fragment>
         <div className="alert alert-warning">
         <div className="alert alert-warning">
@@ -130,9 +160,10 @@ class ExportPage extends React.Component {
 
 
         <button type="button" className="btn btn-default" onClick={this.openExportModal}>{t('export_management.create_new_exported_data')}</button>
         <button type="button" className="btn btn-default" onClick={this.openExportModal}>{t('export_management.create_new_exported_data')}</button>
 
 
-        { this.state.isExporting && (
+        { showExportingData && (
           <div className="mt-5">
           <div className="mt-5">
             <h3>{t('export_management.exporting_data_list')}</h3>
             <h3>{t('export_management.exporting_data_list')}</h3>
+            { this.renderProgressBars() }
           </div>
           </div>
         ) }
         ) }
 
 

+ 2 - 1
src/client/js/components/Admin/Export/ExportZipFormModal.jsx

@@ -71,7 +71,8 @@ class ExportZipFormModal extends React.Component {
         timeOut: '1200',
         timeOut: '1200',
         extendedTimeOut: '150',
         extendedTimeOut: '150',
       });
       });
-      this.props.onExportingRequested();
+
+      this.props.onExportingRequested(result.status);
       this.props.onClose();
       this.props.onClose();
 
 
     }
     }

+ 29 - 14
src/server/routes/apiv3/export.js

@@ -11,6 +11,23 @@ const router = express.Router();
  * @swagger
  * @swagger
  *  tags:
  *  tags:
  *    name: Export
  *    name: Export
+ *
+ *  definitions:
+ *    ExportStatus:
+ *    properties:
+ *      zipFileStats:
+ *        type: array
+ *        items:
+ *          type: object
+ *          description: the property of each file
+ *      progressList:
+ *        type: array
+ *        items:
+ *          type: object
+ *          description: progress data for each exporting collections
+ *      isExporting:
+ *        type: boolean
+ *        description: whether the current exporting job exists or not
  */
  */
 
 
 module.exports = (crowi) => {
 module.exports = (crowi) => {
@@ -27,8 +44,8 @@ module.exports = (crowi) => {
   this.adminEvent.on('onProgressForExport', (data) => {
   this.adminEvent.on('onProgressForExport', (data) => {
     crowi.getIo().sockets.emit('admin:onProgressForExport', data);
     crowi.getIo().sockets.emit('admin:onProgressForExport', data);
   });
   });
-  this.adminEvent.on('onTerminateForExport', () => {
-    crowi.getIo().sockets.emit('admin:onTerminateForExport');
+  this.adminEvent.on('onTerminateForExport', (data) => {
+    crowi.getIo().sockets.emit('admin:onTerminateForExport', data);
   });
   });
 
 
 
 
@@ -46,23 +63,16 @@ module.exports = (crowi) => {
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
    *                properties:
    *                properties:
-   *                  zipFileStats:
-   *                    type: array
-   *                    items:
-   *                      type: object
-   *                      description: the property of each file
-   *                  isExporting:
-   *                    type: boolean
-   *                    description: whether the current exporting job exists or not
+   *                  status:
+   *                    type: ExportStatus
    */
    */
   router.get('/status', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
   router.get('/status', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
-    const { zipFileStats, isExporting } = await exportService.getStatus();
+    const status = await exportService.getStatus();
 
 
     // TODO: use res.apiv3
     // TODO: use res.apiv3
     return res.json({
     return res.json({
       ok: true,
       ok: true,
-      zipFileStats,
-      isExporting,
+      status,
     });
     });
   });
   });
 
 
@@ -79,7 +89,9 @@ module.exports = (crowi) => {
    *          content:
    *          content:
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
-   *                type: object
+   *                properties:
+   *                  status:
+   *                    type: ExportStatus
    */
    */
   router.post('/', accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
   router.post('/', accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
     // TODO: add express validator
     // TODO: add express validator
@@ -90,9 +102,12 @@ module.exports = (crowi) => {
 
 
       exportService.export(models);
       exportService.export(models);
 
 
+      const status = await exportService.getStatus();
+
       // TODO: use res.apiv3
       // TODO: use res.apiv3
       return res.status(200).json({
       return res.status(200).json({
         ok: true,
         ok: true,
+        status,
       });
       });
     }
     }
     catch (err) {
     catch (err) {

+ 18 - 16
src/server/service/export.js

@@ -17,7 +17,7 @@ class ExportingProgress {
 
 
 }
 }
 
 
-class ExportStatus {
+class ExportingStatus {
 
 
   constructor() {
   constructor() {
     this.totalCount = 0;
     this.totalCount = 0;
@@ -64,14 +64,14 @@ class ExportService {
 
 
     this.adminEvent = crowi.event('admin');
     this.adminEvent = crowi.event('admin');
 
 
-    this.currentExportStatus = null;
+    this.currentExportingStatus = null;
   }
   }
 
 
   /**
   /**
    * parse all zip files in downloads dir
    * parse all zip files in downloads dir
    *
    *
    * @memberOf ExportService
    * @memberOf ExportService
-   * @return {object} info for zip files and whether currentExportStatus exists
+   * @return {object} info for zip files and whether currentExportingStatus exists
    */
    */
   async getStatus() {
   async getStatus() {
     const zipFiles = fs.readdirSync(this.baseDir).filter((file) => { return path.extname(file) === '.zip' });
     const zipFiles = fs.readdirSync(this.baseDir).filter((file) => { return path.extname(file) === '.zip' });
@@ -83,11 +83,12 @@ class ExportService {
     // filter null object (broken zip)
     // filter null object (broken zip)
     const filtered = zipFileStats.filter(element => element != null);
     const filtered = zipFileStats.filter(element => element != null);
 
 
-    const isExporting = this.currentExportStatus != null;
+    const isExporting = this.currentExportingStatus != null;
 
 
     return {
     return {
       zipFileStats: filtered,
       zipFileStats: filtered,
       isExporting,
       isExporting,
+      progressList: isExporting ? this.currentExportingStatus.progressList : null,
     };
     };
   }
   }
 
 
@@ -193,7 +194,7 @@ class ExportService {
     const transformStream = this.generateTransformStream();
     const transformStream = this.generateTransformStream();
 
 
     // log configuration
     // log configuration
-    const exportProgress = this.currentExportStatus.progressMap[collectionName];
+    const exportProgress = this.currentExportingStatus.progressMap[collectionName];
     const logStream = this.generateLogStream(exportProgress);
     const logStream = this.generateLogStream(exportProgress);
 
 
     // create WritableStream
     // create WritableStream
@@ -237,18 +238,18 @@ class ExportService {
   }
   }
 
 
   async export(models) {
   async export(models) {
-    if (this.currentExportStatus != null) {
+    if (this.currentExportingStatus != null) {
       throw new Error('There is an exporting process running.');
       throw new Error('There is an exporting process running.');
     }
     }
 
 
-    this.currentExportStatus = new ExportStatus();
-    await this.currentExportStatus.init(models);
+    this.currentExportingStatus = new ExportingStatus();
+    await this.currentExportingStatus.init(models);
 
 
     try {
     try {
       await this.exportCollectionsToZippedJson(models);
       await this.exportCollectionsToZippedJson(models);
     }
     }
     finally {
     finally {
-      this.currentExportStatus = null;
+      this.currentExportingStatus = null;
     }
     }
 
 
   }
   }
@@ -284,18 +285,19 @@ class ExportService {
    * @param {ExportProgress} exportProgress
    * @param {ExportProgress} exportProgress
    */
    */
   emitProgressEvent(exportProgress) {
   emitProgressEvent(exportProgress) {
-    const { currentCount, totalCount, progressList } = this.currentExportStatus;
+    const { currentCount, totalCount, progressList } = this.currentExportingStatus;
+    const data = {
+      currentCount,
+      totalCount,
+      progressList,
+    };
 
 
     // send event (in progress in global)
     // send event (in progress in global)
     if (currentCount !== totalCount) {
     if (currentCount !== totalCount) {
-      this.adminEvent.emit('onProgressForExport', {
-        currentCount,
-        totalCount,
-        progressList,
-      });
+      this.adminEvent.emit('onProgressForExport', data);
     }
     }
     else {
     else {
-      this.adminEvent.emit('onTerminateForExport');
+      this.adminEvent.emit('onTerminateForExport', data);
     }
     }
   }
   }