diff --git a/openbis_ng_ui/src/js/common/consts/ids.js b/openbis_ng_ui/src/js/common/consts/ids.js index 54947bbc7613256812696e0e6321e2c7bf8f4c19..0861f9e06075a0fc0f3d0fbb4a3fabf009ee182b 100644 --- a/openbis_ng_ui/src/js/common/consts/ids.js +++ b/openbis_ng_ui/src/js/common/consts/ids.js @@ -1,5 +1,7 @@ // app const WEB_APP_ID = 'openbis_ng_ui' +const WEB_APP_SERVICE = 'openbis-ng-ui-service' +const EXPORT_SERVICE = 'xls-export' // grids const OBJECT_TYPES_GRID_ID = 'object_types_grid' @@ -31,6 +33,10 @@ export default { // app WEB_APP_ID, + // service, + WEB_APP_SERVICE, + EXPORT_SERVICE, + // grids OBJECT_TYPES_GRID_ID, COLLECTION_TYPES_GRID_ID, diff --git a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx index c54e15a602de2c59d7b21585c2cdead9801a711f..ecb936950d3997342b9384ff92cbad09f71b2e71 100644 --- a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx @@ -224,13 +224,17 @@ class Grid extends React.PureComponent { } renderExports() { - const { id, multiselectable } = this.props + const { id, exportable, multiselectable } = this.props const { rows, exportOptions } = this.state + if (!exportable) { + return null + } + return ( <GridExports id={id} - disabled={rows.length === 0} + disabled={!exportable || rows.length === 0} exportOptions={exportOptions} multiselectable={multiselectable} onExport={this.controller.handleExport} diff --git a/openbis_ng_ui/src/js/components/common/grid/GridController.js b/openbis_ng_ui/src/js/components/common/grid/GridController.js index 19f1578c3c23bb67ccc2d230cf7c1d0171cfdcf3..e43161b6b7169ab24e17a41cb26423a8fbc6858a 100644 --- a/openbis_ng_ui/src/js/components/common/grid/GridController.js +++ b/openbis_ng_ui/src/js/components/common/grid/GridController.js @@ -1,7 +1,5 @@ import _ from 'lodash' import autoBind from 'auto-bind' -import FileSaver from 'file-saver' -import CsvStringify from 'csv-stringify' import GridFilterOptions from '@src/js/components/common/grid/GridFilterOptions.js' import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js' import GridPagingOptions from '@src/js/components/common/grid/GridPagingOptions.js' @@ -1022,122 +1020,106 @@ export default class GridController { } async handleExport() { - const { exportOptions } = this.context.getState() - - function _stringToUtf16ByteArray(str) { - var bytes = [] - bytes.push(255, 254) - for (var i = 0; i < str.length; ++i) { - var charCode = str.charCodeAt(i) - bytes.push(charCode & 0xff) //low byte - bytes.push((charCode & 0xff00) >>> 8) //high byte (might be 0) - } - return bytes - } - - function _exportColumnsFromData(namePrefix, rows, columns) { - const arrayOfRowArrays = [] - - const headers = columns.map(column => column.name) - arrayOfRowArrays.push(headers) - - rows.forEach(row => { - var rowAsArray = [] - columns.forEach(column => { - var rowValue = column.getValue({ - row, - column, - operation: 'export', - exportOptions - }) - if (!rowValue) { - rowValue = '' - } else { - var specialCharsRemover = document.createElement('textarea') - specialCharsRemover.innerHTML = rowValue - rowValue = specialCharsRemover.value //Removes special HTML Chars - rowValue = String(rowValue).replace(/\r?\n|\r|\t/g, ' ') //Remove carriage returns and tabs - - if (exportOptions.values === GridExportOptions.RICH_TEXT) { - // do nothing with the value - } else if (exportOptions.values === GridExportOptions.PLAIN_TEXT) { - rowValue = String(rowValue).replace(/<(?:.|\n)*?>/gm, '') - } else { - throw Error('Unsupported values option: ' + exportOptions.values) - } - } - rowAsArray.push(rowValue) - }) - arrayOfRowArrays.push(rowAsArray) - }) - - CsvStringify( - { - header: false, - delimiter: '\t', - quoted: false - }, - arrayOfRowArrays, - function (err, tsv) { - var utf16bytes = _stringToUtf16ByteArray(tsv) - var utf16bytesArray = new Uint8Array(utf16bytes.length) - utf16bytesArray.set(utf16bytes, 0) - var blob = new Blob([utf16bytesArray], { - type: 'text/tsv;charset=UTF-16LE;' - }) - FileSaver.saveAs(blob, 'exportedTable' + namePrefix + '.tsv') - } - ) - } - const state = this.context.getState() const props = this.context.getProps() - var data = [] - var columns = [] - var prefix = '' + const { scheduleExport, loadExported } = props - if (exportOptions.columns === GridExportOptions.ALL_COLUMNS) { - columns = this.getAllColumns() - prefix += 'AllColumns' - } else if (exportOptions.columns === GridExportOptions.VISIBLE_COLUMNS) { - columns = this.getVisibleColumns() - prefix += 'VisibleColumns' - } else { - throw Error('Unsupported columns option: ' + exportOptions.columns) + if (!scheduleExport || !loadExported) { + return } - columns = columns.filter(column => column.exportable) + let exportedRows = [] - if (exportOptions.rows === GridExportOptions.ALL_PAGES) { + if (state.exportOptions.rows === GridExportOptions.ALL_PAGES) { if (state.local) { - data = state.sortedRows + exportedRows = state.sortedRows } else if (props.loadRows) { + const columns = {} + + state.allColumns.forEach(column => { + columns[column.name] = column + }) + const loadedResult = await props.loadRows({ + columns: columns, + filterMode: state.filterMode, filters: state.filters, globalFilter: state.globalFilter, page: 0, pageSize: 1000000, sortings: state.sortings }) - data = loadedResult.rows + exportedRows = loadedResult.rows } - - prefix += 'AllPages' - _exportColumnsFromData(prefix, data, columns) - } else if (exportOptions.rows === GridExportOptions.CURRENT_PAGE) { - data = state.rows - prefix += 'CurrentPage' - _exportColumnsFromData(prefix, data, columns) - } else if (exportOptions.rows === GridExportOptions.SELECTED_ROWS) { - data = Object.values(state.multiselectedRows).map( + } else if (state.exportOptions.rows === GridExportOptions.CURRENT_PAGE) { + exportedRows = state.rows + } else if (state.exportOptions.rows === GridExportOptions.SELECTED_ROWS) { + exportedRows = Object.values(state.multiselectedRows).map( selectedRow => selectedRow.data ) - prefix += 'SelectedRows' - _exportColumnsFromData(prefix, data, columns) } else { - throw Error('Unsupported rows option: ' + exportOptions.columns) + throw Error('Unsupported rows option: ' + state.exportOptions.columns) + } + + if (exportedRows.some(row => _.isEmpty(row.exportableId))) { + throw Error( + "Some of the rows to be exported do not have 'exportableId' set." + ) } + + let exportedProperties = {} + + if (state.exportOptions.columns === GridExportOptions.ALL_COLUMNS) { + exportedProperties = {} + } else if ( + state.exportOptions.columns === GridExportOptions.VISIBLE_COLUMNS + ) { + const { newAllColumns } = await this._loadColumns( + exportedRows, + state.columnsVisibility, + state.columnsSorting + ) + + newAllColumns.forEach(column => { + if (column.exportableProperty) { + const propertyCode = column.exportableProperty.code + const propertyTypesMap = column.exportableProperty.types + + Object.keys(propertyTypesMap).forEach(kind => { + const propertyTypesForKind = propertyTypesMap[kind] + + propertyTypesForKind.forEach(propertyTypePermId => { + let exportedPropertiesForKind = exportedProperties[kind] + + if (!exportedPropertiesForKind) { + exportedProperties[kind] = exportedPropertiesForKind = {} + } + + let exportedPropertiesForKindAndType = + exportedPropertiesForKind[propertyTypePermId] + + if (!exportedPropertiesForKindAndType) { + exportedPropertiesForKind[propertyTypePermId] = + exportedPropertiesForKindAndType = [] + } + + exportedPropertiesForKindAndType.push(propertyCode) + }) + }) + } + }) + } else { + throw Error('Unsupported columns option: ' + state.exportOptions.columns) + } + + const exportedIds = exportedRows.map(row => row.exportableId) + + scheduleExport({ + exportedIds: exportedIds, + exportedProperties: exportedProperties, + exportedValues: state.exportOptions.values + }) } async handleExportOptionsChange(exportOptions) { diff --git a/openbis_ng_ui/src/js/components/common/grid/GridWithSettings.jsx b/openbis_ng_ui/src/js/components/common/grid/GridWithSettings.jsx index 5e1ef524efb97f30c0cb1afee4b0720b815252a2..c35a1f01a380e4758cd297c1983cb645cdf76bee 100644 --- a/openbis_ng_ui/src/js/components/common/grid/GridWithSettings.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/GridWithSettings.jsx @@ -1,6 +1,7 @@ import React from 'react' import autoBind from 'auto-bind' import Grid from '@src/js/components/common/grid/Grid.jsx' +import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js' import openbis from '@src/js/services/openbis.js' import ids from '@src/js/common/consts/ids.js' import logger from '@src/js/common/logger.js' @@ -22,6 +23,8 @@ export default class GridWithSettings extends React.PureComponent { {...this.props} loadSettings={this.loadSettings} onSettingsChange={this.onSettingsChange} + scheduleExport={this.props.exportable ? this.scheduleExport : null} + loadExported={this.props.exportable ? this.loadExported : null} /> ) } @@ -59,4 +62,27 @@ export default class GridWithSettings extends React.PureComponent { await openbis.updatePersons([update]) } + + async scheduleExport({ exportedIds, exportedProperties, exportedValues }) { + const serviceId = new openbis.CustomASServiceCode(ids.EXPORT_SERVICE) + + const serviceOptions = new openbis.CustomASServiceExecutionOptions() + serviceOptions.withParameter('method', 'export') + serviceOptions.withParameter('file_name', this.props.id) + serviceOptions.withParameter('ids', exportedIds) + serviceOptions.withParameter('export_referred', true) + serviceOptions.withParameter('export_properties', exportedProperties) + + if (exportedValues === GridExportOptions.PLAIN_TEXT) { + serviceOptions.withParameter('text_formatting', 'PLAIN') + } else if (exportedValues === GridExportOptions.RICH_TEXT) { + serviceOptions.withParameter('text_formatting', 'RICH') + } else { + throw Error('Unsupported text formatting ' + exportedValues) + } + + return await openbis.executeService(serviceId, serviceOptions) + } + + async loadExported() {} } diff --git a/openbis_ng_ui/src/js/components/tools/form/activeUserReport/ActiveUserReportFacade.js b/openbis_ng_ui/src/js/components/tools/form/activeUserReport/ActiveUserReportFacade.js index 8916c740266435182460e267787d94bb0e5b527a..29244e8a6a4966b37349d687c7b15f98f07f4a88 100644 --- a/openbis_ng_ui/src/js/components/tools/form/activeUserReport/ActiveUserReportFacade.js +++ b/openbis_ng_ui/src/js/components/tools/form/activeUserReport/ActiveUserReportFacade.js @@ -1,8 +1,9 @@ import openbis from '@src/js/services/openbis.js' +import ids from '@src/js/common/consts/ids.js' export default class ActiveUserReportFacade { async sendReport() { - const serviceId = new openbis.CustomASServiceCode('openbis-ng-ui-service') + const serviceId = new openbis.CustomASServiceCode(ids.WEB_APP_SERVICE) const serviceOptions = new openbis.CustomASServiceExecutionOptions() serviceOptions.withParameter('method', 'sendCountActiveUsersEmail') return openbis.executeService(serviceId, serviceOptions) diff --git a/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx index ca7e09d3b7847f4e6a148e930e5412d625083c6c..1a0003e327cd103bc0da3adbf86db99281779ea4 100644 --- a/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx @@ -10,8 +10,14 @@ class EntityTypesGrid extends React.PureComponent { render() { logger.log(logger.DEBUG, 'EntityTypesGrid.render') - const { id, rows, selectedRowId, onSelectedRowChange, controllerRef } = - this.props + const { + id, + rows, + exportable, + selectedRowId, + onSelectedRowChange, + controllerRef + } = this.props return ( <GridWithSettings @@ -21,6 +27,7 @@ class EntityTypesGrid extends React.PureComponent { columns={this.getColumns()} rows={rows} sort='code' + exportable={exportable} selectable={true} selectedRowId={selectedRowId} onSelectedRowChange={onSelectedRowChange} diff --git a/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx index 1b879db7380652004fe3e866d6e006eadc837db9..2fa5b20155ec90946e3cc64d49a778d42a0ddaf5 100644 --- a/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx @@ -104,6 +104,7 @@ class PropertyTypesGrid extends React.PureComponent { ]} rows={rows} sort='code' + exportable={false} selectable={true} selectedRowId={selectedRowId} onSelectedRowChange={onSelectedRowChange} diff --git a/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx index 34ab388f9c33b788dbb15a1f2e4aad03cb8f7eb7..68260c1ab9cf3437afb25ec25b3ddb4183a6775a 100644 --- a/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx @@ -38,6 +38,7 @@ class VocabularyTypesGrid extends React.PureComponent { ]} rows={rows} sort='code' + exportable={true} selectable={true} selectedRowId={selectedRowId} onSelectedRowChange={onSelectedRowChange} diff --git a/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx b/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx index 35ead6de702997109cfc2926404b4f6b85b1e65d..91e2f83ea6f7c929a109e36ba01230bba9f69029 100644 --- a/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx +++ b/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx @@ -73,6 +73,10 @@ class TypeSearch extends React.Component { .filter(result.objects, this.props.searchText, ['code', 'description']) .map(object => ({ id: _.get(object, 'code'), + exportableId: { + exportable_kind: 'SAMPLE_TYPE', + perm_id: object.getPermId().getPermId() + }, code: _.get(object, 'code'), description: _.get(object, 'description'), subcodeUnique: _.get(object, 'subcodeUnique', false), @@ -103,6 +107,10 @@ class TypeSearch extends React.Component { .filter(result.objects, this.props.searchText, ['code', 'description']) .map(object => ({ id: _.get(object, 'code'), + exportableId: { + exportable_kind: 'EXPERIMENT_TYPE', + perm_id: object.getPermId().getPermId() + }, code: _.get(object, 'code'), description: _.get(object, 'description'), validationPlugin: _.get(object, 'validationPlugin.name') @@ -130,6 +138,10 @@ class TypeSearch extends React.Component { .filter(result.objects, this.props.searchText, ['code', 'description']) .map(object => ({ id: _.get(object, 'code'), + exportableId: { + exportable_kind: 'DATASET_TYPE', + perm_id: object.getPermId().getPermId() + }, code: _.get(object, 'code'), description: _.get(object, 'description'), validationPlugin: _.get(object, 'validationPlugin.name'), @@ -184,6 +196,10 @@ class TypeSearch extends React.Component { .filter(result.objects, this.props.searchText, ['code', 'description']) .map(object => ({ id: object.code, + exportableId: { + exportable_kind: 'VOCABULARY', + perm_id: object.getPermId().getPermId() + }, code: object.code, description: object.description, urlTemplate: object.urlTemplate @@ -382,6 +398,7 @@ class TypeSearch extends React.Component { } kind={openbis.EntityKind.SAMPLE} rows={this.state.objectTypes} + exportable={true} onSelectedRowChange={this.handleSelectedRowChange( objectTypes.OBJECT_TYPE )} @@ -407,6 +424,7 @@ class TypeSearch extends React.Component { } kind={openbis.EntityKind.EXPERIMENT} rows={this.state.collectionTypes} + exportable={true} onSelectedRowChange={this.handleSelectedRowChange( objectTypes.COLLECTION_TYPE )} @@ -430,6 +448,7 @@ class TypeSearch extends React.Component { } kind={openbis.EntityKind.DATA_SET} rows={this.state.dataSetTypes} + exportable={true} onSelectedRowChange={this.handleSelectedRowChange( objectTypes.DATA_SET_TYPE )} @@ -455,6 +474,7 @@ class TypeSearch extends React.Component { } kind={openbis.EntityKind.MATERIAL} rows={this.state.materialTypes} + exportable={false} onSelectedRowChange={this.handleSelectedRowChange( objectTypes.MATERIAL_TYPE )}