diff --git a/jsx/DataTable.js b/jsx/DataTable.js index 68ede754a9..87e7a6f5b2 100644 --- a/jsx/DataTable.js +++ b/jsx/DataTable.js @@ -117,21 +117,26 @@ class DataTable extends Component { */ downloadCSV(filteredRowIndexes) { + const fields = this.props.fields.map((field, index) => ({field, index})); + const visibleFields = fields.filter(({field}) => field.show !== false); + const headers = this.props.fields.map((field) => field.label); let csvData = filteredRowIndexes.map((id) => this.props.data[id]); // Map cell data to proper values if applicable. if (this.props.getMappedCell) { csvData = csvData - .map((row, i) => this.props.fields - .map((field, j) => this.props.getMappedCell( + .map((row) => visibleFields + .map(({field, index}) => this.props.getMappedCell( field.label, - row[j], + row[index], row, - this.props.fields.map( - (val) => val.label, - ), - j + headers, + index )) ); + } else { + csvData = csvData.map( + (row) => visibleFields.map(({index}) => row[index]) + ); } let csvworker = new Worker(loris.BaseURL + '/js/workers/savecsv.js'); @@ -152,7 +157,7 @@ class DataTable extends Component { document.body.removeChild(link); } }); - const headerList = this.props.fields.map((field) => field.label); + const headerList = visibleFields.map(({field}) => field.label); csvworker.postMessage({ cmd: 'SaveFile', data: csvData, diff --git a/modules/examiner/jsx/examinerIndex.js b/modules/examiner/jsx/examinerIndex.js index ad32589904..7dd90e287c 100644 --- a/modules/examiner/jsx/examinerIndex.js +++ b/modules/examiner/jsx/examinerIndex.js @@ -59,7 +59,9 @@ class ExaminerIndex extends Component { this.fetchData = this.fetchData.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.setFormData = this.setFormData.bind(this); + this.filterVisibleRows = this.filterVisibleRows.bind(this); this.formatColumn = this.formatColumn.bind(this); + this.mapCSVCell = this.mapCSVCell.bind(this); this.renderAddExaminerForm = this.renderAddExaminerForm.bind(this); this.openModal = this.openModal.bind(this); this.closeModal = this.closeModal.bind(this); @@ -104,6 +106,51 @@ class ExaminerIndex extends Component { }); } + /** + * Get the subset of site IDs visible to the current user. + * + * @return {object} + */ + getVisibleSites() { + return this.state.data.fieldOptions?.sites || {}; + } + + /** + * Keep only site IDs visible to the current user. + * + * @param {string[]|number[]} siteIds - examiner site IDs + * @return {string[]|number[]} + */ + filterVisibleSiteIds(siteIds) { + const visibleSites = this.getVisibleSites(); + if (!Array.isArray(siteIds)) { + return []; + } + return siteIds.filter((centerId) => visibleSites[centerId] != null); + } + + /** + * Convert visible site IDs into site labels. + * + * @param {string[]|number[]} siteIds - examiner site IDs + * @return {string[]} + */ + mapVisibleSiteNames(siteIds) { + const visibleSites = this.getVisibleSites(); + return this.filterVisibleSiteIds(siteIds) + .map((centerId) => visibleSites[centerId]); + } + + /** + * Limit rows to examiners that share at least one visible site. + * + * @param {Array[]} rows - examiner data rows + * @return {Array[]} + */ + filterVisibleRows(rows) { + return rows.filter((row) => this.filterVisibleSiteIds(row[2]).length > 0); + } + /** * Handles the submission of the Add Examiner form * @@ -184,16 +231,11 @@ class ExaminerIndex extends Component { result = {t('None', {ns: 'loris'})}; } } else if (column === 'Site' || column === labelSite) { - // If user has multiple sites, join array of sites into string + const siteNames = this.mapVisibleSiteNames(cell); result = ( - {cell - .filter((centerId) => this.state.data.fieldOptions.sites[centerId] - != null) - .map((centerId) => this.state.data.fieldOptions.sites[centerId]) - .join(', ')} - + {siteNames.join(', ')} ); - if (cell.length === 0) { + if (siteNames.length === 0) { result = ( {t( 'This user has no site affiliations', @@ -205,6 +247,29 @@ class ExaminerIndex extends Component { return result; } + /** + * Map raw row values to CSV-safe display values. + * + * @param {string} column - column label + * @param {*} cell - raw cell value + * @return {*} mapped cell value + */ + mapCSVCell(column, cell) { + const {t} = this.props; + const labelCertification = t('Certification', {ns: 'examiner'}); + const labelSite = t('Site', {ns: 'loris', count: 1}); + + if (column === labelSite) { + return this.mapVisibleSiteNames(cell).join(', '); + } + + if (column === labelCertification && cell === null) { + return t('None', {ns: 'loris'}); + } + + return cell; + } + /** * Executed when modal is opened. */ @@ -308,6 +373,7 @@ class ExaminerIndex extends Component { * queried columns in _setupVariables() in examiner.class.inc */ const options = this.state.data.fieldOptions; + const data = this.filterVisibleRows(this.state.data.Data); const fields = [ {label: t('Examiner', {ns: 'examiner'}), show: true, filter: { name: 'examiner', @@ -345,9 +411,10 @@ class ExaminerIndex extends Component {