From 2d894cb84a90b5c98a2b9d491b21a917f6c78b0a Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 13:47:25 +0200 Subject: [PATCH 01/41] add export to Json --- src/legacy/components/ImportExport.component.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 91fc4cf..69820d3 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -71,6 +71,19 @@ class ImportExport extends React.Component { } }; + exportToJsonAndSave = async () => { + const { d2, columns, filterOptions, settings } = this.props; + const orgUnitsField = settings.get("organisationUnitsField"); + this.setState({ isProcessing: true }); + + try { + const jsonString = await exportToJson(d2, columns, filterOptions, { orgUnitsField }); + this.saveJson(jsonString, "users"); + } finally { + this.closeMenu(); + this.setState({ isProcessing: false }); + } + }; exportEmptyTemplate = async () => { this.setState({ isProcessing: true }); @@ -110,7 +123,7 @@ class ImportExport extends React.Component { render() { const { isMenuOpen, anchorEl, isProcessing } = this.state; - const { popoverConfig, closeMenu, importFromCsv, exportToCsvAndSave, exportEmptyTemplate } = this; + const { popoverConfig, closeMenu, importFromCsv, exportToCsvAndSave, exportEmptyTemplate, exportToJsonAndSave } = this; const { t } = this; return ( @@ -136,6 +149,7 @@ class ImportExport extends React.Component { primaryText={t("export_empty_template")} onClick={exportEmptyTemplate} /> + } primaryText={t("export_to_Json")} onClick={exportToJsonAndSave}/> From 46f078a45d629c65b53e953a4f3d9badfe677f5b Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 13:52:22 +0200 Subject: [PATCH 02/41] Update userHelpers.js --- src/legacy/models/userHelpers.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 11b87ba..a4772ba 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -451,6 +451,18 @@ async function exportTemplateToCsv() { return Papa.unparse(table); } +/* Get users from Dhis2 API and export given columns to a Json string */ +async function exportToJson (d2, columns, filterOptions, { orgUnitsField }) { + const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; + const { users } = await getUserList(d2, filters, listOptions); + const userRows = users.map(user => _.at(getPlainUser(user, { orgUnitsField }), columns)); + const header = columns.map(getColumnNameFromProperty); + const table = [header, ...userRows]; + + return Papa.unparse(table); +} + + async function importFromCsv(d2, file, { maxUsers, orgUnitsField }) { return new Promise((resolve, reject) => { Papa.parse(file, { From 62087b6894e53d5fd6e9aba9773be2a4bd7c24cb Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 13:53:51 +0200 Subject: [PATCH 03/41] Update ImportExport.component.js --- src/legacy/components/ImportExport.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 69820d3..879a027 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -11,7 +11,7 @@ import FileSaver from "file-saver"; import moment from "moment"; import fileDialog from "file-dialog"; -import { exportToCsv, exportTemplateToCsv, importFromCsv } from "../models/userHelpers"; +import { exportToCsv, exportTemplateToCsv, importFromCsv, exportToJson } from "../models/userHelpers"; import snackActions from "../Snackbar/snack.actions"; import ModalLoadingMask from "./ModalLoadingMask.component"; From fdf722902890b82f88a67f36b6b3296da4eb85dd Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 16:42:28 +0200 Subject: [PATCH 04/41] Update userHelpers.js --- src/legacy/models/userHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index a4772ba..158a612 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -549,6 +549,7 @@ export { exportToCsv, exportTemplateToCsv, importFromCsv, + exportToJson, updateUsers, saveUsers, parseResponse, From 480164652e91f112af2d33a079d1452bafc14215 Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 17:07:02 +0200 Subject: [PATCH 05/41] update exportToJson function --- src/legacy/models/userHelpers.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 158a612..2716ffa 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -452,14 +452,12 @@ async function exportTemplateToCsv() { } /* Get users from Dhis2 API and export given columns to a Json string */ -async function exportToJson (d2, columns, filterOptions, { orgUnitsField }) { +async function exportToJson (d2, columns, filterOptions) { const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; const { users } = await getUserList(d2, filters, listOptions); - const userRows = users.map(user => _.at(getPlainUser(user, { orgUnitsField }), columns)); - const header = columns.map(getColumnNameFromProperty); - const table = [header, ...userRows]; - - return Papa.unparse(table); + + const table = JSON.stringify(users, null, 4); + return table; } From 0c9adb991d823bb2c7f8036d908f8e330fc01d62 Mon Sep 17 00:00:00 2001 From: MarieJousse <104760844+MarieJousse@users.noreply.github.com> Date: Fri, 24 Jun 2022 17:27:20 +0200 Subject: [PATCH 06/41] Update ImportExport.component.js --- src/legacy/components/ImportExport.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 879a027..671f443 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -149,7 +149,7 @@ class ImportExport extends React.Component { primaryText={t("export_empty_template")} onClick={exportEmptyTemplate} /> - } primaryText={t("export_to_Json")} onClick={exportToJsonAndSave}/> + } primaryText={t("Export to Json")} onClick={exportToJsonAndSave}/> From 36a8336b0bd3547bbd4df87b4fa25f73829ff377 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 1 Jul 2022 08:53:07 +0200 Subject: [PATCH 07/41] Add column sorted JSON export --- .../components/ImportExport.component.js | 9 ++++++ src/legacy/models/userHelpers.js | 32 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 671f443..091d917 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -84,6 +84,7 @@ class ImportExport extends React.Component { this.setState({ isProcessing: false }); } }; + exportEmptyTemplate = async () => { this.setState({ isProcessing: true }); @@ -104,6 +105,14 @@ class ImportExport extends React.Component { snackActions.show({ message: `${this.t("table_exported")}: ${filename}` }); }; + saveJson = (contents, name) => { + const blob = new Blob([contents], { type: "text/plain;charset=utf-8" }); + const datetime = moment().format("YYYY-MM-DD_HH-mm-ss"); + const filename = `${name}-${datetime}.json`; + FileSaver.saveAs(blob, filename); + snackActions.show({ message: `${this.t("table_exported")}: ${filename}` }); + }; + importFromCsv = () => { const { onImport, maxUsers, settings } = this.props; const orgUnitsField = settings.get("organisationUnitsField"); diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 2716ffa..9f8ce18 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -112,6 +112,11 @@ function namesFromCollection(collection, field) { .join(fieldSplitChar); } +function namesArrayFromCollection(collection, field) { + return _(collection.toArray ? collection.toArray() : collection) + .map(field); +} + function collectionFromNames(user, rowIndex, field, objectsByName) { const value = user[field]; const names = (value || "") @@ -163,6 +168,24 @@ function getPlainUser(user, { orgUnitsField }) { }; } +function getPlainJsonUser(user, { orgUnitsField }) { + const userCredentials = user.userCredentials || {}; + + return { + ...user, + username: userCredentials.username, + lastUpdated: formatDate(user.lastUpdated), + lastLogin: formatDate(userCredentials.lastLogin), + created: formatDate(user.created), + userRoles: namesArrayFromCollection(userCredentials.userRoles, "displayName"), + userGroups: namesArrayFromCollection(user.userGroups, "displayName"), + organisationUnits: namesArrayFromCollection(user.organisationUnits, orgUnitsField), + dataViewOrganisationUnits: namesArrayFromCollection(user.dataViewOrganisationUnits, orgUnitsField), + disabled: userCredentials.disabled, + openId: userCredentials.openId, + }; +} + function getPlainUserFromRow(user, modelValuesByField, rowIndex) { const byField = modelValuesByField; const relationships = { @@ -452,12 +475,13 @@ async function exportTemplateToCsv() { } /* Get users from Dhis2 API and export given columns to a Json string */ -async function exportToJson (d2, columns, filterOptions) { +async function exportToJson (d2, columns, filterOptions, { orgUnitsField }) { const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; const { users } = await getUserList(d2, filters, listOptions); - - const table = JSON.stringify(users, null, 4); - return table; + const usersObject = users.map(user => _.pick(getPlainJsonUser(user, { orgUnitsField }), columns)); + const usersJson = JSON.stringify(usersObject, null, 4) + + return usersJson; } From 4fe7f2b05108445a890c0930b4d29c4132bcc7e1 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Thu, 7 Jul 2022 19:37:18 +0200 Subject: [PATCH 08/41] Add canManage query to UserD2ApiRepository. --- src/data/repositories/UserD2ApiRepository.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/data/repositories/UserD2ApiRepository.ts b/src/data/repositories/UserD2ApiRepository.ts index 315878f..ceae569 100644 --- a/src/data/repositories/UserD2ApiRepository.ts +++ b/src/data/repositories/UserD2ApiRepository.ts @@ -29,7 +29,7 @@ export class UserD2ApiRepository implements UserRepository { } public list(options: ListOptions): FutureData> { - const { page, pageSize, search, sorting = { field: "firstName", order: "asc" }, filters } = options; + const { page, pageSize, search, sorting = { field: "firstName", order: "asc" }, filters, canManage } = options; const otherFilters = _.mapValues(filters, items => (items ? { [items[0]]: items[1] } : undefined)); return apiToFuture( @@ -38,6 +38,7 @@ export class UserD2ApiRepository implements UserRepository { page, pageSize, query: search !== "" ? search : undefined, + canManage: canManage !== false ? true : undefined, filter: otherFilters, order: `${sorting.field}:${sorting.order}`, }) @@ -48,7 +49,7 @@ export class UserD2ApiRepository implements UserRepository { } public listAllIds(options: ListOptions): FutureData { - const { search, sorting = { field: "firstName", order: "asc" }, filters } = options; + const { search, sorting = { field: "firstName", order: "asc" }, filters, canManage } = options; const otherFilters = _.mapValues(filters, items => (items ? { [items[0]]: items[1] } : undefined)); return apiToFuture( @@ -56,6 +57,7 @@ export class UserD2ApiRepository implements UserRepository { fields: { id: true }, paging: false, query: search !== "" ? search : undefined, + canManage: canManage !== false ? true : undefined, filter: otherFilters, order: `${sorting.field}:${sorting.order}`, }) @@ -155,9 +157,9 @@ export class UserD2ApiRepository implements UserRepository { userRoles: strategy === "merge" ? _.uniqBy( - [..._.differenceBy(user.userRoles, commonRoles, ({ id }) => id), ...update], - ({ id }) => id - ) + [..._.differenceBy(user.userRoles, commonRoles, ({ id }) => id), ...update], + ({ id }) => id + ) : update, }; }); @@ -179,9 +181,9 @@ export class UserD2ApiRepository implements UserRepository { userGroups: strategy === "merge" ? _.uniqBy( - [..._.differenceBy(user.userGroups, commonGroups, ({ id }) => id), ...update], - ({ id }) => id - ) + [..._.differenceBy(user.userGroups, commonGroups, ({ id }) => id), ...update], + ({ id }) => id + ) : update, }; }); From 96ac0e179ded86bc23d8f4ecedf53d7e4194fbee Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Thu, 7 Jul 2022 19:39:13 +0200 Subject: [PATCH 09/41] Add canManage to UserRepository ListOptions interface. --- src/domain/repositories/UserRepository.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/domain/repositories/UserRepository.ts b/src/domain/repositories/UserRepository.ts index 62b5d2d..4a0e9a0 100644 --- a/src/domain/repositories/UserRepository.ts +++ b/src/domain/repositories/UserRepository.ts @@ -22,6 +22,7 @@ export interface ListOptions { search?: string; sorting?: { field: string; order: "asc" | "desc" }; filters?: ListFilters; + canManage?: boolean; } export type ListFilterType = "in" | "eq"; From 90e257635dde8bc55551217206371ebe76d63946 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Thu, 7 Jul 2022 19:41:30 +0200 Subject: [PATCH 10/41] Add canManage to UserListTable and List.component.js --- src/legacy/List/List.component.js | 3 +++ src/webapp/components/user-list-table/UserListTable.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index 73ce98f..7225c0c 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -290,6 +290,8 @@ export class ListHybrid extends React.Component { _onFiltersChange = filters => { this.setState({ filters }, this.filterList); + const canManage = filters.canManage; + this.setState({ filters, canManage }, this.filterList); }; _disableUsersSaved = () => this.setUsersEnableState(this.state.disableUsers.users, this.state.disableUsers.action); @@ -325,6 +327,7 @@ export class ListHybrid extends React.Component { loading={this.state.isLoading} openSettings={this._openSettings} filters={this.state.filters?.filters} + canManage={this.state?.canManage} onChangeVisibleColumns={this._updateVisibleColumns} > diff --git a/src/webapp/components/user-list-table/UserListTable.tsx b/src/webapp/components/user-list-table/UserListTable.tsx index f9ffc59..282e2b2 100644 --- a/src/webapp/components/user-list-table/UserListTable.tsx +++ b/src/webapp/components/user-list-table/UserListTable.tsx @@ -32,6 +32,7 @@ export const UserListTable: React.FC = ({ openSettings, onChangeVisibleColumns, filters, + canManage, children, }) => { const { compositionRoot, currentUser } = useAppContext(); @@ -246,10 +247,11 @@ export const UserListTable: React.FC = ({ pageSize, sorting, filters, + canManage, }) .toPromise(); }, - [compositionRoot, filters, reloadKey] + [compositionRoot, filters, canManage, reloadKey] ); const refreshAllIds = useCallback( @@ -259,10 +261,11 @@ export const UserListTable: React.FC = ({ search, sorting, filters, + canManage, }) .toPromise(); }, - [compositionRoot, filters] + [compositionRoot, filters, canManage] ); const tableProps = useObjectsTable(baseConfig, refreshRows, refreshAllIds); @@ -365,6 +368,7 @@ function isStateActionVisible(action: string) { export interface UserListTableProps extends Pick, "loading"> { openSettings: () => void; filters: ListFilters; + canManage: boolean; onChangeVisibleColumns: (columns: string[]) => void; } From 1a42196d59f90b485b6340d90287f6ae1b1336d4 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Thu, 7 Jul 2022 20:11:59 +0200 Subject: [PATCH 11/41] Fix canManage type. --- src/data/repositories/UserD2ApiRepository.ts | 4 ++-- src/domain/repositories/UserRepository.ts | 2 +- src/webapp/components/user-list-table/UserListTable.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/repositories/UserD2ApiRepository.ts b/src/data/repositories/UserD2ApiRepository.ts index ceae569..3a21c40 100644 --- a/src/data/repositories/UserD2ApiRepository.ts +++ b/src/data/repositories/UserD2ApiRepository.ts @@ -38,7 +38,7 @@ export class UserD2ApiRepository implements UserRepository { page, pageSize, query: search !== "" ? search : undefined, - canManage: canManage !== false ? true : undefined, + canManage: canManage === "true" ? "true" : undefined, filter: otherFilters, order: `${sorting.field}:${sorting.order}`, }) @@ -57,7 +57,7 @@ export class UserD2ApiRepository implements UserRepository { fields: { id: true }, paging: false, query: search !== "" ? search : undefined, - canManage: canManage !== false ? true : undefined, + canManage: canManage === "true" ? "true" : undefined, filter: otherFilters, order: `${sorting.field}:${sorting.order}`, }) diff --git a/src/domain/repositories/UserRepository.ts b/src/domain/repositories/UserRepository.ts index 4a0e9a0..372d532 100644 --- a/src/domain/repositories/UserRepository.ts +++ b/src/domain/repositories/UserRepository.ts @@ -22,7 +22,7 @@ export interface ListOptions { search?: string; sorting?: { field: string; order: "asc" | "desc" }; filters?: ListFilters; - canManage?: boolean; + canManage?: string; } export type ListFilterType = "in" | "eq"; diff --git a/src/webapp/components/user-list-table/UserListTable.tsx b/src/webapp/components/user-list-table/UserListTable.tsx index 282e2b2..cb09614 100644 --- a/src/webapp/components/user-list-table/UserListTable.tsx +++ b/src/webapp/components/user-list-table/UserListTable.tsx @@ -368,7 +368,7 @@ function isStateActionVisible(action: string) { export interface UserListTableProps extends Pick, "loading"> { openSettings: () => void; filters: ListFilters; - canManage: boolean; + canManage: string; onChangeVisibleColumns: (columns: string[]) => void; } From 74839a430dfc831dbaff2119bcf14256fc6d5af6 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:28:05 +0200 Subject: [PATCH 12/41] Add paging fix. Fix _onFiltersChange duplicate line on repository code. --- src/legacy/List/List.component.js | 1 - .../user-list-table/UserListTable.tsx | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index 7225c0c..369de1d 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -289,7 +289,6 @@ export class ListHybrid extends React.Component { }; _onFiltersChange = filters => { - this.setState({ filters }, this.filterList); const canManage = filters.canManage; this.setState({ filters, canManage }, this.filterList); }; diff --git a/src/webapp/components/user-list-table/UserListTable.tsx b/src/webapp/components/user-list-table/UserListTable.tsx index cb09614..e375341 100644 --- a/src/webapp/components/user-list-table/UserListTable.tsx +++ b/src/webapp/components/user-list-table/UserListTable.tsx @@ -233,13 +233,29 @@ export const UserListTable: React.FC = ({ }, [openSettings, enableReplicate, editUsers, onReorderColumns, reload, navigate]); const refreshRows = useCallback( - ( + async ( search: string, { page, pageSize }: TablePagination, sorting: TableSorting ): Promise<{ objects: User[]; pager: Pager }> => { console.debug("Reloading", reloadKey); + // SEE: src/legacy/models/userList.js LINE 29+ + if (canManage === "true") { + const userIdList = await compositionRoot.users + .listAllIds({ + search, + sorting, + filters, + canManage, + }) + .toPromise(); + + if (userIdList) { + filters["id"] = ["in", userIdList] + } + } + return compositionRoot.users .list({ search, From df05de5e73e128bd6899db6596ce02e17f07c8a4 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:31:54 +0200 Subject: [PATCH 13/41] Add Export to... translation --- public/old-i18n/i18n_module_ar.properties | 2 ++ public/old-i18n/i18n_module_en.properties | 2 ++ public/old-i18n/i18n_module_es.properties | 2 ++ public/old-i18n/i18n_module_fr.properties | 2 ++ 4 files changed, 8 insertions(+) diff --git a/public/old-i18n/i18n_module_ar.properties b/public/old-i18n/i18n_module_ar.properties index e5fcc4d..7276500 100644 --- a/public/old-i18n/i18n_module_ar.properties +++ b/public/old-i18n/i18n_module_ar.properties @@ -115,6 +115,8 @@ import_export=Import/Export import=Import export=Export table_exported=Table exported +export_to_CSV=Export to CSV +export_to_JSON=Export to JSON layout_settings=Layout settings warnings=$$n$$ warning(s) while importing CSV and_n_more_warnings=[... and $$n$$ more warning(s) ...] diff --git a/public/old-i18n/i18n_module_en.properties b/public/old-i18n/i18n_module_en.properties index 0167b58..85e1b77 100644 --- a/public/old-i18n/i18n_module_en.properties +++ b/public/old-i18n/i18n_module_en.properties @@ -115,6 +115,8 @@ import_export=Import/Export import=Import export=Export table_exported=Table exported +export_to_CSV=Export to CSV +export_to_JSON=Export to JSON layout_settings=Layout settings warnings=$$n$$ warning(s) while importing CSV and_n_more_warnings=[... and $$n$$ more warning(s) ...] diff --git a/public/old-i18n/i18n_module_es.properties b/public/old-i18n/i18n_module_es.properties index 02d9465..6485eb2 100644 --- a/public/old-i18n/i18n_module_es.properties +++ b/public/old-i18n/i18n_module_es.properties @@ -116,6 +116,8 @@ import_export=Importar/Exportar import=Importar export=Exportar table_exported=Tabla exportada +export_to_CSV=Exportar a CSV +export_to_JSON=Exportar a JSON layout_settings=Opciones de columnas warnings=$$n$$ avisos(s) al importar el CSV and_n_more_warnings=[... y $$n$$ avisos más ...] diff --git a/public/old-i18n/i18n_module_fr.properties b/public/old-i18n/i18n_module_fr.properties index 78a65a3..cd2dab5 100644 --- a/public/old-i18n/i18n_module_fr.properties +++ b/public/old-i18n/i18n_module_fr.properties @@ -114,6 +114,8 @@ multiple_matches=Multiple matches found import_export=Import/Export import=Import export=Export +export_to_CSV=Exporter vers CSV +export_to_JSON=Exporter vers JSON table_exported=Table exported layout_settings=Layout settings warnings=$$n$$ warning(s) while importing CSV From adf630381f450e1bfee98bf74997b90cae4656bc Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:46:51 +0200 Subject: [PATCH 14/41] userHelpers.js: add importFromJson() function. --- src/legacy/models/userHelpers.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 9f8ce18..136801b 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -500,6 +500,22 @@ async function importFromCsv(d2, file, { maxUsers, orgUnitsField }) { }); } +async function importFromJson(d2, file, { maxUsers, orgUnitsField }) { + const jsonText = await new Response(file).text() + let jsonObject = JSON.parse(jsonText); + + jsonObject.forEach((obj) => { + obj.userRoles = obj.userRoles.join('||') ?? undefined; + obj.userGroups = obj.userGroups.join('||') ?? undefined; + obj.organisationUnits = obj.organisationUnits.join('||') ?? undefined; + obj.dataViewOrganisationUnits = obj.dataViewOrganisationUnits.join('||') ?? undefined; + }) + + const csvText = Papa.unparse(jsonObject, { delimiter: "," }); + + return importFromCsv(d2, csvText, { maxUsers, orgUnitsField }) +} + async function getExistingUsers(d2, options = {}) { const api = d2.Api.getApi(); const { users } = await api.get("/users", { @@ -572,6 +588,7 @@ export { exportTemplateToCsv, importFromCsv, exportToJson, + importFromJson, updateUsers, saveUsers, parseResponse, From ade66a15d621fd07b0df61387a4429a8e48c072e Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:49:04 +0200 Subject: [PATCH 15/41] ImportExport.component.js: Add importFromFile() CSV/JSON import function. --- .../components/ImportExport.component.js | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 091d917..8917ddd 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -11,7 +11,7 @@ import FileSaver from "file-saver"; import moment from "moment"; import fileDialog from "file-dialog"; -import { exportToCsv, exportTemplateToCsv, importFromCsv, exportToJson } from "../models/userHelpers"; +import { exportToCsv, exportTemplateToCsv, importFromCsv, exportToJson, importFromJson } from "../models/userHelpers"; import snackActions from "../Snackbar/snack.actions"; import ModalLoadingMask from "./ModalLoadingMask.component"; @@ -113,14 +113,18 @@ class ImportExport extends React.Component { snackActions.show({ message: `${this.t("table_exported")}: ${filename}` }); }; - importFromCsv = () => { + importFromFile = () => { const { onImport, maxUsers, settings } = this.props; const orgUnitsField = settings.get("organisationUnitsField"); - fileDialog({ accept: ".csv" }) + fileDialog({ accept: ["text/csv", "application/json"] }) .then(files => { this.setState({ isProcessing: true }); - return importFromCsv(this.props.d2, files[0], { maxUsers, orgUnitsField }); + if (files[0].type === "text/csv") { + return importFromCsv(this.props.d2, files[0], { maxUsers, orgUnitsField }); + } else if (files[0].type === "application/json") { + return importFromJson(this.props.d2, files.item(0), { maxUsers, orgUnitsField }); + } }) .then(result => onImport(result)) .catch(err => snackActions.show({ message: err.toString() })) @@ -132,7 +136,7 @@ class ImportExport extends React.Component { render() { const { isMenuOpen, anchorEl, isProcessing } = this.state; - const { popoverConfig, closeMenu, importFromCsv, exportToCsvAndSave, exportEmptyTemplate, exportToJsonAndSave } = this; + const { popoverConfig, closeMenu, importFromFile, exportToCsvAndSave, exportEmptyTemplate, exportToJsonAndSave } = this; const { t } = this; return ( @@ -151,14 +155,14 @@ class ImportExport extends React.Component { onRequestClose={closeMenu} > - } primaryText={t("import")} onClick={importFromCsv} /> - } primaryText={t("export")} onClick={exportToCsvAndSave} /> + } primaryText={t("import")} onClick={importFromFile} /> + } primaryText={t("export_to_CSV")} onClick={exportToCsvAndSave} /> + } primaryText={t("export_to_JSON")} onClick={exportToJsonAndSave}/> } primaryText={t("export_empty_template")} onClick={exportEmptyTemplate} /> - } primaryText={t("Export to Json")} onClick={exportToJsonAndSave}/> From 297c3f9ac2f9b37f3b7c41a82f69be0d8605a84e Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:56:41 +0200 Subject: [PATCH 16/41] Merge saveCsv and saveJson to saveFile function. --- .../components/ImportExport.component.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 8917ddd..73ae4e3 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -64,7 +64,7 @@ class ImportExport extends React.Component { try { const csvString = await exportToCsv(d2, columns, filterOptions, { orgUnitsField }); - this.saveCsv(csvString, "users"); + this.saveFile(csvString, "users", "csv"); } finally { this.closeMenu(); this.setState({ isProcessing: false }); @@ -78,7 +78,7 @@ class ImportExport extends React.Component { try { const jsonString = await exportToJson(d2, columns, filterOptions, { orgUnitsField }); - this.saveJson(jsonString, "users"); + this.saveFile(jsonString, "users", "json"); } finally { this.closeMenu(); this.setState({ isProcessing: false }); @@ -90,25 +90,17 @@ class ImportExport extends React.Component { try { const csvString = await exportTemplateToCsv(this.props.d2); - this.saveCsv(csvString, "empty-user-template"); + this.saveFile(csvString, "empty-user-template", "csv"); } finally { this.closeMenu(); this.setState({ isProcessing: false }); } }; - saveCsv = (contents, name) => { + saveFile = (contents, name, fileType) => { const blob = new Blob([contents], { type: "text/plain;charset=utf-8" }); const datetime = moment().format("YYYY-MM-DD_HH-mm-ss"); - const filename = `${name}-${datetime}.csv`; - FileSaver.saveAs(blob, filename); - snackActions.show({ message: `${this.t("table_exported")}: ${filename}` }); - }; - - saveJson = (contents, name) => { - const blob = new Blob([contents], { type: "text/plain;charset=utf-8" }); - const datetime = moment().format("YYYY-MM-DD_HH-mm-ss"); - const filename = `${name}-${datetime}.json`; + const filename = `${name}-${datetime}.${fileType}`; FileSaver.saveAs(blob, filename); snackActions.show({ message: `${this.t("table_exported")}: ${filename}` }); }; From e1d0bfb8a0bb068fdbc5829bd6036bafd1493874 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:26:32 +0200 Subject: [PATCH 17/41] Better code formatting --- .../components/ImportExport.component.js | 21 +++++++++++++--- src/legacy/models/userHelpers.js | 24 +++++++++---------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 73ae4e3..fa5b0c1 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -128,7 +128,14 @@ class ImportExport extends React.Component { render() { const { isMenuOpen, anchorEl, isProcessing } = this.state; - const { popoverConfig, closeMenu, importFromFile, exportToCsvAndSave, exportEmptyTemplate, exportToJsonAndSave } = this; + const { + popoverConfig, + closeMenu, + importFromFile, + exportToCsvAndSave, + exportEmptyTemplate, + exportToJsonAndSave, + } = this; const { t } = this; return ( @@ -148,8 +155,16 @@ class ImportExport extends React.Component { > } primaryText={t("import")} onClick={importFromFile} /> - } primaryText={t("export_to_CSV")} onClick={exportToCsvAndSave} /> - } primaryText={t("export_to_JSON")} onClick={exportToJsonAndSave}/> + } + primaryText={t("export_to_CSV")} + onClick={exportToCsvAndSave} + /> + } + primaryText={t("export_to_JSON")} + onClick={exportToJsonAndSave} + /> } primaryText={t("export_empty_template")} diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 136801b..5d9b2f1 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -113,8 +113,7 @@ function namesFromCollection(collection, field) { } function namesArrayFromCollection(collection, field) { - return _(collection.toArray ? collection.toArray() : collection) - .map(field); + return _(collection.toArray ? collection.toArray() : collection).map(field); } function collectionFromNames(user, rowIndex, field, objectsByName) { @@ -475,15 +474,14 @@ async function exportTemplateToCsv() { } /* Get users from Dhis2 API and export given columns to a Json string */ -async function exportToJson (d2, columns, filterOptions, { orgUnitsField }) { +async function exportToJson(d2, columns, filterOptions, { orgUnitsField }) { const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; const { users } = await getUserList(d2, filters, listOptions); const usersObject = users.map(user => _.pick(getPlainJsonUser(user, { orgUnitsField }), columns)); - const usersJson = JSON.stringify(usersObject, null, 4) + const usersJson = JSON.stringify(usersObject, null, 4); return usersJson; } - async function importFromCsv(d2, file, { maxUsers, orgUnitsField }) { return new Promise((resolve, reject) => { @@ -501,19 +499,19 @@ async function importFromCsv(d2, file, { maxUsers, orgUnitsField }) { } async function importFromJson(d2, file, { maxUsers, orgUnitsField }) { - const jsonText = await new Response(file).text() + const jsonText = await new Response(file).text(); let jsonObject = JSON.parse(jsonText); - jsonObject.forEach((obj) => { - obj.userRoles = obj.userRoles.join('||') ?? undefined; - obj.userGroups = obj.userGroups.join('||') ?? undefined; - obj.organisationUnits = obj.organisationUnits.join('||') ?? undefined; - obj.dataViewOrganisationUnits = obj.dataViewOrganisationUnits.join('||') ?? undefined; - }) + jsonObject.forEach(obj => { + obj.userRoles = obj.userRoles.join("||") ?? undefined; + obj.userGroups = obj.userGroups.join("||") ?? undefined; + obj.organisationUnits = obj.organisationUnits.join("||") ?? undefined; + obj.dataViewOrganisationUnits = obj.dataViewOrganisationUnits.join("||") ?? undefined; + }); const csvText = Papa.unparse(jsonObject, { delimiter: "," }); - return importFromCsv(d2, csvText, { maxUsers, orgUnitsField }) + return importFromCsv(d2, csvText, { maxUsers, orgUnitsField }); } async function getExistingUsers(d2, options = {}) { From 5d39ddff6a80ed38f751074bfcfa7eb680e0d7a6 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:35:35 +0200 Subject: [PATCH 18/41] Fix missing ;. Code formatting. --- .../components/user-list-table/UserListTable.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/webapp/components/user-list-table/UserListTable.tsx b/src/webapp/components/user-list-table/UserListTable.tsx index e375341..f730b5a 100644 --- a/src/webapp/components/user-list-table/UserListTable.tsx +++ b/src/webapp/components/user-list-table/UserListTable.tsx @@ -242,30 +242,26 @@ export const UserListTable: React.FC = ({ // SEE: src/legacy/models/userList.js LINE 29+ if (canManage === "true") { - const userIdList = await compositionRoot.users - .listAllIds({ + const userIdList = await compositionRoot.users.listAllIds({ search, sorting, filters, canManage, - }) - .toPromise(); + }).toPromise(); if (userIdList) { - filters["id"] = ["in", userIdList] + filters["id"] = ["in", userIdList]; } } - return compositionRoot.users - .list({ + return compositionRoot.users.list({ search, page, pageSize, sorting, filters, canManage, - }) - .toPromise(); + }).toPromise(); }, [compositionRoot, filters, canManage, reloadKey] ); From 6de953bd015b27ef53b1660ae05fcee6581bfacb Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Tue, 11 Oct 2022 20:51:18 +0200 Subject: [PATCH 19/41] Fix icons --- src/legacy/components/ImportExport.component.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index fa5b0c1..5a492af 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -5,8 +5,8 @@ import Popover from "material-ui/Popover/Popover"; import Menu from "material-ui/Menu/Menu"; import MenuItem from "material-ui/MenuItem/MenuItem"; import ImportExportIcon from "material-ui/svg-icons/communication/import-export"; -import ExportIcon from "material-ui/svg-icons/navigation/arrow-upward"; -import ImportIcon from "material-ui/svg-icons/navigation/arrow-downward"; +import ImportIcon from "material-ui/svg-icons/navigation/arrow-upward"; +import ExportIcon from "material-ui/svg-icons/navigation/arrow-downward"; import FileSaver from "file-saver"; import moment from "moment"; import fileDialog from "file-dialog"; @@ -166,7 +166,7 @@ class ImportExport extends React.Component { onClick={exportToJsonAndSave} /> } + leftIcon={} primaryText={t("export_empty_template")} onClick={exportEmptyTemplate} /> From 0a4b063bf257fad67da3b7c9bea3d353119a5832 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:50:08 +0200 Subject: [PATCH 20/41] Fix importFromJson TypeError. Merged functions: namesFromCollection & namesArrayFromCollection => namesFromCollection, getPlainJsonUser & getPlainUser => getPlainUser and exportToCsv & exportToJson => exportUsers. Add csvWarningsToJsonWarning function to adapt the CSV warnings to JSON warnings. --- public/old-i18n/i18n_module_ar.properties | 2 +- public/old-i18n/i18n_module_en.properties | 2 +- public/old-i18n/i18n_module_es.properties | 2 +- public/old-i18n/i18n_module_fr.properties | 2 +- .../components/ImportExport.component.js | 6 +- src/legacy/models/userHelpers.js | 91 ++++++++----------- 6 files changed, 45 insertions(+), 60 deletions(-) diff --git a/public/old-i18n/i18n_module_ar.properties b/public/old-i18n/i18n_module_ar.properties index 58dfbd8..67d2fcb 100644 --- a/public/old-i18n/i18n_module_ar.properties +++ b/public/old-i18n/i18n_module_ar.properties @@ -120,7 +120,7 @@ table_exported=Table exported export_to_CSV=Export to CSV export_to_JSON=Export to JSON layout_settings=Layout settings -warnings=$$n$$ warning(s) while importing CSV +warnings=$$n$$ warning(s) while importing file and_n_more_warnings=[... and $$n$$ more warning(s) ...] errors_on_table=$$n$$ invalid users found, check in-line errors in table import_successful=$$n$$ users successfully imported diff --git a/public/old-i18n/i18n_module_en.properties b/public/old-i18n/i18n_module_en.properties index 6250cb0..9f8ed01 100644 --- a/public/old-i18n/i18n_module_en.properties +++ b/public/old-i18n/i18n_module_en.properties @@ -120,7 +120,7 @@ table_exported=Table exported export_to_CSV=Export to CSV export_to_JSON=Export to JSON layout_settings=Layout settings -warnings=$$n$$ warning(s) while importing CSV +warnings=$$n$$ warning(s) while importing file and_n_more_warnings=[... and $$n$$ more warning(s) ...] errors_on_table=$$n$$ invalid users found, check in-line errors in table import_successful=$$n$$ users successfully imported diff --git a/public/old-i18n/i18n_module_es.properties b/public/old-i18n/i18n_module_es.properties index 66f5c97..bcf5a66 100644 --- a/public/old-i18n/i18n_module_es.properties +++ b/public/old-i18n/i18n_module_es.properties @@ -121,7 +121,7 @@ table_exported=Tabla exportada export_to_CSV=Exportar a CSV export_to_JSON=Exportar a JSON layout_settings=Opciones de columnas -warnings=$$n$$ avisos(s) al importar el CSV +warnings=$$n$$ avisos(s) al importar el archivo and_n_more_warnings=[... y $$n$$ avisos más ...] errors_on_table=$$n$$ usuarios inválidos, compruebe los errores marcados en la tabla import_successful=$$n$$ usuarios creados con éxito diff --git a/public/old-i18n/i18n_module_fr.properties b/public/old-i18n/i18n_module_fr.properties index 3ba0b66..f38fe31 100644 --- a/public/old-i18n/i18n_module_fr.properties +++ b/public/old-i18n/i18n_module_fr.properties @@ -120,7 +120,7 @@ export_to_CSV=Exporter vers CSV export_to_JSON=Exporter vers JSON table_exported=Table exported layout_settings=Layout settings -warnings=$$n$$ warning(s) while importing CSV +warnings=$$n$$ warning(s) while importing file and_n_more_warnings=[... and $$n$$ more warning(s) ...] errors_on_table=$$n$$ invalid users, check in-line errors in table import_successful=$$n$$ users successfully imported diff --git a/src/legacy/components/ImportExport.component.js b/src/legacy/components/ImportExport.component.js index 5a492af..bfcf70d 100644 --- a/src/legacy/components/ImportExport.component.js +++ b/src/legacy/components/ImportExport.component.js @@ -11,7 +11,7 @@ import FileSaver from "file-saver"; import moment from "moment"; import fileDialog from "file-dialog"; -import { exportToCsv, exportTemplateToCsv, importFromCsv, exportToJson, importFromJson } from "../models/userHelpers"; +import { exportTemplateToCsv, importFromCsv, importFromJson, exportUsers } from "../models/userHelpers"; import snackActions from "../Snackbar/snack.actions"; import ModalLoadingMask from "./ModalLoadingMask.component"; @@ -63,7 +63,7 @@ class ImportExport extends React.Component { this.setState({ isProcessing: true }); try { - const csvString = await exportToCsv(d2, columns, filterOptions, { orgUnitsField }); + const csvString = await exportUsers(d2, columns, filterOptions, { orgUnitsField }, false); this.saveFile(csvString, "users", "csv"); } finally { this.closeMenu(); @@ -77,7 +77,7 @@ class ImportExport extends React.Component { this.setState({ isProcessing: true }); try { - const jsonString = await exportToJson(d2, columns, filterOptions, { orgUnitsField }); + const jsonString = await exportUsers(d2, columns, filterOptions, { orgUnitsField }, true); this.saveFile(jsonString, "users", "json"); } finally { this.closeMenu(); diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index c4b2ff1..f29673f 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -107,14 +107,14 @@ function formatDate(stringDate) { return stringDate ? moment(stringDate).format("YYYY-MM-DD HH:mm:ss") : null; } -function namesFromCollection(collection, field) { - return _(collection.toArray ? collection.toArray() : collection) - .map(field) - .join(fieldSplitChar); -} +function namesFromCollection(collection, field, toArray) { + const namesArray = _(collection.toArray ? collection.toArray() : collection).map(field); -function namesArrayFromCollection(collection, field) { - return _(collection.toArray ? collection.toArray() : collection).map(field); + if (toArray) { + return namesArray; + } else { + return namesArray.join(fieldSplitChar); + } } function collectionFromNames(user, rowIndex, field, objectsByName) { @@ -150,7 +150,7 @@ function collectionFromNames(user, rowIndex, field, objectsByName) { return { objects, warnings, info }; } -function getPlainUser(user, { orgUnitsField }) { +function getPlainUser(user, { orgUnitsField }, toArray) { const userCredentials = user.userCredentials || {}; return { @@ -159,28 +159,10 @@ function getPlainUser(user, { orgUnitsField }) { lastUpdated: formatDate(user.lastUpdated), lastLogin: formatDate(userCredentials.lastLogin), created: formatDate(user.created), - userRoles: namesFromCollection(userCredentials.userRoles, "displayName"), - userGroups: namesFromCollection(user.userGroups, "displayName"), - organisationUnits: namesFromCollection(user.organisationUnits, orgUnitsField), - dataViewOrganisationUnits: namesFromCollection(user.dataViewOrganisationUnits, orgUnitsField), - disabled: userCredentials.disabled, - openId: userCredentials.openId, - }; -} - -function getPlainJsonUser(user, { orgUnitsField }) { - const userCredentials = user.userCredentials || {}; - - return { - ...user, - username: userCredentials.username, - lastUpdated: formatDate(user.lastUpdated), - lastLogin: formatDate(userCredentials.lastLogin), - created: formatDate(user.created), - userRoles: namesArrayFromCollection(userCredentials.userRoles, "displayName"), - userGroups: namesArrayFromCollection(user.userGroups, "displayName"), - organisationUnits: namesArrayFromCollection(user.organisationUnits, orgUnitsField), - dataViewOrganisationUnits: namesArrayFromCollection(user.dataViewOrganisationUnits, orgUnitsField), + userRoles: namesFromCollection(userCredentials.userRoles, "displayName", toArray), + userGroups: namesFromCollection(user.userGroups, "displayName", toArray), + organisationUnits: namesFromCollection(user.organisationUnits, orgUnitsField, toArray), + dataViewOrganisationUnits: namesFromCollection(user.dataViewOrganisationUnits, orgUnitsField, toArray), disabled: userCredentials.disabled, openId: userCredentials.openId, }; @@ -449,15 +431,21 @@ async function saveCopyInUsers(d2, users, copyUserGroups) { } } -/* Get users from Dhis2 API and export given columns to a CSV string */ -async function exportToCsv(d2, columns, filterOptions, { orgUnitsField }) { +/* Get users from Dhis2 API and export given columns to a CSV or JSON string */ +async function exportUsers(d2, columns, filterOptions, { orgUnitsField }, exportToJSON) { const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; const { users } = await getUserList(d2, filters, listOptions); - const userRows = users.map(user => _.at(getPlainUser(user, { orgUnitsField }), columns)); - const header = columns.map(getColumnNameFromProperty); - const table = [header, ...userRows]; - return Papa.unparse(table); + if (exportToJSON) { + const userRows = users.map(user => _.pick(getPlainUser(user, { orgUnitsField }, exportToJSON), columns)); + return JSON.stringify(userRows, null, 4); + } else { + const userRows = users.map(user => _.at(getPlainUser(user, { orgUnitsField }, exportToJSON), columns)); + const header = columns.map(getColumnNameFromProperty); + const table = [header, ...userRows]; + + return Papa.unparse(table); + } } async function exportTemplateToCsv() { @@ -474,16 +462,6 @@ async function exportTemplateToCsv() { return Papa.unparse(table); } -/* Get users from Dhis2 API and export given columns to a Json string */ -async function exportToJson(d2, columns, filterOptions, { orgUnitsField }) { - const { filters, ...listOptions } = { ...filterOptions, pageSize: 1e6 }; - const { users } = await getUserList(d2, filters, listOptions); - const usersObject = users.map(user => _.pick(getPlainJsonUser(user, { orgUnitsField }), columns)); - const usersJson = JSON.stringify(usersObject, null, 4); - - return usersJson; -} - async function importFromCsv(d2, file, { maxUsers, orgUnitsField }) { return new Promise((resolve, reject) => { Papa.parse(file, { @@ -504,15 +482,17 @@ async function importFromJson(d2, file, { maxUsers, orgUnitsField }) { let jsonObject = JSON.parse(jsonText); jsonObject.forEach(obj => { - obj.userRoles = obj.userRoles.join("||") ?? undefined; - obj.userGroups = obj.userGroups.join("||") ?? undefined; - obj.organisationUnits = obj.organisationUnits.join("||") ?? undefined; - obj.dataViewOrganisationUnits = obj.dataViewOrganisationUnits.join("||") ?? undefined; + obj.userRoles = obj.userRoles?.join("||"); + obj.userGroups = obj.userGroups?.join("||"); + obj.organisationUnits = obj.organisationUnits?.join("||"); + obj.dataViewOrganisationUnits = obj.dataViewOrganisationUnits?.join("||"); }); const csvText = Papa.unparse(jsonObject, { delimiter: "," }); + const response = await importFromCsv(d2, csvText, { maxUsers, orgUnitsField }); + const jsonWarnings = csvWarningsToJsonWarning(response.warnings) - return importFromCsv(d2, csvText, { maxUsers, orgUnitsField }); + return { ...response, warnings: jsonWarnings }; } async function getExistingUsers(d2, options = {}) { @@ -525,6 +505,12 @@ async function getExistingUsers(d2, options = {}) { return users; } +function csvWarningsToJsonWarning(warnings) { + return warnings.map(warning => { + return `${warning}`.replace(/csv-row=\d+ /, "").replace("csv-column", "json-key"); + }) +} + function addItems(items1, items2, shouldAdd, updateStrategy) { if (!shouldAdd) { return items1; @@ -583,10 +569,9 @@ function getPayload(d2, parentUser, destUsers, fields, updateStrategy) { } export { - exportToCsv, exportTemplateToCsv, importFromCsv, - exportToJson, + exportUsers, importFromJson, updateUsers, saveUsers, From 8daa023ed0f371586f71101e42666ab00baacc58 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Wed, 19 Oct 2022 11:06:23 +0200 Subject: [PATCH 21/41] Add missing semicolon --- src/legacy/models/userHelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index f29673f..3acc628 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -490,7 +490,7 @@ async function importFromJson(d2, file, { maxUsers, orgUnitsField }) { const csvText = Papa.unparse(jsonObject, { delimiter: "," }); const response = await importFromCsv(d2, csvText, { maxUsers, orgUnitsField }); - const jsonWarnings = csvWarningsToJsonWarning(response.warnings) + const jsonWarnings = csvWarningsToJsonWarning(response.warnings); return { ...response, warnings: jsonWarnings }; } @@ -508,7 +508,7 @@ async function getExistingUsers(d2, options = {}) { function csvWarningsToJsonWarning(warnings) { return warnings.map(warning => { return `${warning}`.replace(/csv-row=\d+ /, "").replace("csv-column", "json-key"); - }) + }); } function addItems(items1, items2, shouldAdd, updateStrategy) { From 3b7500777d541593de1a321adb584458c3a2221e Mon Sep 17 00:00:00 2001 From: deeonwuli Date: Thu, 8 Dec 2022 15:31:57 +0100 Subject: [PATCH 22/41] fix bug with transfer component --- .../MultiSelectorDialog.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/webapp/components/multi-selector-dialog/MultiSelectorDialog.tsx b/src/webapp/components/multi-selector-dialog/MultiSelectorDialog.tsx index 57c4026..aadb3d9 100644 --- a/src/webapp/components/multi-selector-dialog/MultiSelectorDialog.tsx +++ b/src/webapp/components/multi-selector-dialog/MultiSelectorDialog.tsx @@ -19,6 +19,8 @@ export const MultiSelectorDialog: React.FC = ({ type, const [users, setUsers] = useState([]); const [items, setItems] = useState([]); const [selected, setSelected] = useState([]); + const [filtered, setFiltered] = useState([]); + const [filteredValue, setFilteredValue] = useState(""); const [updateStrategy, setUpdateStrategy] = useState("merge"); const title = getTitle(type, users); @@ -92,8 +94,24 @@ export const MultiSelectorDialog: React.FC = ({ type, setSelected(selected)} + selected={ + filtered.length === 0 && filteredValue !== "" ? [] : filtered.length !== 0 ? filtered : selected + } + onChange={({ selected: picked }) => { + setSelected(filtered.length !== 0 ? _.difference(selected, filtered) : picked); + setFiltered([]); + }} + onFilterChangePicked={filtered => { + setFilteredValue(filtered.value); + const filteredItems = items.filter(item => + item.name.toLowerCase().includes(filtered.value.toLowerCase()) + ); + const filteredIds = selected.filter(select => { + return filteredItems.some(filtered => filtered.id === select); + }); + + setFiltered(filteredIds); + }} filterable={true} filterablePicked={true} filterPlaceholder={i18n.t("Search")} From c46ca0f5026bba236380d842600a21980064e22a Mon Sep 17 00:00:00 2001 From: deeonwuli Date: Tue, 20 Dec 2022 21:40:19 +0100 Subject: [PATCH 23/41] fix assign to org units bug --- src/legacy/List/context.actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/List/context.actions.js b/src/legacy/List/context.actions.js index 9c0cdb5..7d5ac28 100644 --- a/src/legacy/List/context.actions.js +++ b/src/legacy/List/context.actions.js @@ -11,7 +11,7 @@ export async function assignToOrgUnits(userIds, field, titleKey) { filter: `id:in:[${userIds.join(",")}]`, }; const users = (await d2.models.users.list(listOptions)).toArray(); - const usernames = users.map(user => user.userCredentials.username); + const usernames = users.map(user => (user.userCredentials ? user.userCredentials.username : user.username)); const info = _m.joinString(d2.i18n.getTranslation.bind(d2.i18n), usernames, 3, ", "); const userOrgUnitRoots = await getOrgUnitsRoots(); From 5f1d7d06d7a9b1dde619002243085b088c68921c Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Wed, 18 Jan 2023 11:21:38 +0100 Subject: [PATCH 24/41] Fix selected containing inadequate data, add VSCode folders to .gitignore --- .gitignore | 4 ++++ .../components/user-form/components/OrgUnitSelectorFF.tsx | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f93cc1a..a0caee1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ cypress/fixtures/ # IntelliJ .idea/* + +# VSCode +.vscode +remote-debug-profile \ No newline at end of file diff --git a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx index 6b393de..a0c6f1f 100644 --- a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx +++ b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx @@ -23,7 +23,8 @@ export const OrgUnitSelectorFF = ({ input, meta, validationText, ...rest }: OrgU const message = validationText ?? meta.error ?? meta.submitError; const onChange = useCallback( - ({ selected }: { selected: string[] }) => { + (selected: string[]) => { + selected = selected.flatMap(item => item.split("/").at(-1) ?? []); input.onChange(selected.map(id => ({ id }))); }, [input] From 8e335cb93e075ae20a99d3af135f7665dd2c4258 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Thu, 19 Jan 2023 11:19:39 +0100 Subject: [PATCH 25/41] Avoid reusing variables --- .../components/user-form/components/OrgUnitSelectorFF.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx index a0c6f1f..75874db 100644 --- a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx +++ b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx @@ -24,8 +24,8 @@ export const OrgUnitSelectorFF = ({ input, meta, validationText, ...rest }: OrgU const onChange = useCallback( (selected: string[]) => { - selected = selected.flatMap(item => item.split("/").at(-1) ?? []); - input.onChange(selected.map(id => ({ id }))); + const selectedIds = selected.flatMap(item => item.split("/").at(-1) ?? []); + input.onChange(selectedIds.map(id => ({ id }))); }, [input] ); From bec0f0d67123ccdd35261b71685ab4af394d6c62 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 23 Jan 2023 13:20:35 +0100 Subject: [PATCH 26/41] Add userCredentials to fields, as it doesn't retrieves by default in 2.38 --- .../CopyInUserBatchModelsMultiSelect.model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js b/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js index 6ad59fc..b141efc 100644 --- a/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js +++ b/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js @@ -32,7 +32,7 @@ export default class CopyInUserBatchModelsMultiSelectModel { } async getUserInfo(ids) { const users = await getExistingUsers(this.d2, { - fields: ":owner,userGroups[id]", + fields: ":owner,userCredentials,userGroups[id]", filter: "id:in:[" + ids.join(",") + "]", }); return users; From bd65711a5f23397a969822ec48fc196ace678da8 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Thu, 26 Jan 2023 12:35:03 +0100 Subject: [PATCH 27/41] Add EOL --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a0caee1..4270ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,4 @@ cypress/fixtures/ # VSCode .vscode -remote-debug-profile \ No newline at end of file +remote-debug-profile From 014b6a44ad8fb9b6fce6ca593822a3202b0f02ee Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Thu, 26 Jan 2023 12:35:28 +0100 Subject: [PATCH 28/41] Return future run on useEffect so it can be canceled --- .../components/user-form/components/OrgUnitSelectorFF.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx index 75874db..2c5d604 100644 --- a/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx +++ b/src/webapp/components/user-form/components/OrgUnitSelectorFF.tsx @@ -32,7 +32,7 @@ export const OrgUnitSelectorFF = ({ input, meta, validationText, ...rest }: OrgU useEffect(() => { const ids = input.value.map(({ id }: NamedRef) => id); - compositionRoot.metadata.getOrgUnitPaths(ids).run( + return compositionRoot.metadata.getOrgUnitPaths(ids).run( items => setSelectedPaths(items.map(({ path }) => path)), error => console.error(error) ); From 176201cd321b790ce60bb6d620fe4a7e7539cde1 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 19:31:46 +0100 Subject: [PATCH 29/41] Disable externalAuth for "Replicate user from table" action --- src/legacy/models/user.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index 1cca920..1f38eec 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -60,7 +60,9 @@ class User { } async replicate(newUsersAttributes) { - const ownedProperties = this.d2.models.user.getOwnedPropertyNames(); + // NOTE: externalAuth makes the replicate function fail because the IDs has to be unique + const externalAuthKeys = ["externalAuth", "openId", "ldapId"]; + const ownedProperties = this.d2.models.user.getOwnedPropertyNames().filter(item => !externalAuthKeys.includes(item)); const userJson = pick(ownedProperties, this.attributes); const newUsers = newUsersAttributes.map(newUserAttributes => merge(userJson, newUserAttributes)); const userGroupIds = this.attributes.userGroups.map(userGroup => userGroup.id); From 5fba55569769f77cf86621a7dc1e87ecc461ec10 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 19:42:22 +0100 Subject: [PATCH 30/41] Fix "Replicate user from table" action using original username --- src/legacy/models/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index 1f38eec..e9bc4a0 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -40,6 +40,7 @@ class User { const newUsersAttributes = newUserFields.map(userFields => ({ id: userFields.id, + username: userFields.username, email: optional(userFields.email), firstName: optional(userFields.firstName), surname: optional(userFields.surname), From e70ba11276bf903afb2c8772d60b3fa03b333005 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 19:49:30 +0100 Subject: [PATCH 31/41] prettify, remove unused items from replicateFromPlainFields --- src/legacy/models/user.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index e9bc4a0..21a50ee 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -46,15 +46,13 @@ class User { surname: optional(userFields.surname), userCredentials: { id: generateUid(), - openId: nullable(userFields.openId), - ldapId: nullable(userFields.ldapId), code: nullable(userFields.code), userInfo: { id: userFields.id }, username: userFields.username, password: userFields.password, }, - organisationUnits: optional(userFields.organisationUnits), - dataViewOrganisationUnits: optional(userFields.dataViewOrganisationUnits), + organisationUnits: userFields.organisationUnits?.map(item => ({ id: optional(item.id) })), + dataViewOrganisationUnits: userFields.dataViewOrganisationUnits?.map(item => ({ id: optional(item.id) })), })); return this.replicate(newUsersAttributes); @@ -63,7 +61,9 @@ class User { async replicate(newUsersAttributes) { // NOTE: externalAuth makes the replicate function fail because the IDs has to be unique const externalAuthKeys = ["externalAuth", "openId", "ldapId"]; - const ownedProperties = this.d2.models.user.getOwnedPropertyNames().filter(item => !externalAuthKeys.includes(item)); + const ownedProperties = this.d2.models.user + .getOwnedPropertyNames() + .filter(item => !externalAuthKeys.includes(item)); const userJson = pick(ownedProperties, this.attributes); const newUsers = newUsersAttributes.map(newUserAttributes => merge(userJson, newUserAttributes)); const userGroupIds = this.attributes.userGroups.map(userGroup => userGroup.id); From ae9972acdbf1001ef3b73ac7bfcedb3f3a4b883c Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 20:13:38 +0100 Subject: [PATCH 32/41] Fix "delete" action not working --- src/legacy/List/List.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index 20e069e..b81fb55 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -134,7 +134,7 @@ export class ListHybrid extends React.Component { const deleteUserStoreDisposable = deleteUserStore.subscribe(async ({ datasets }) => { if (datasets !== undefined) { const existingUsers = await getExistingUsers(this.context.d2, { - fields: ":owner", + fields: ":owner,userCredentials", filter: "id:in:[" + datasets.join(",") + "]", }); this.setState({ removeUsers: { open: true, users: existingUsers } }); From c84b16507556a49c7031d93cce480e64aa8a818c Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 20:31:37 +0100 Subject: [PATCH 33/41] Rename selected users var from datasets to users --- src/legacy/List/List.component.js | 6 +++--- src/webapp/components/user-list-table/UserListTable.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index b81fb55..2fa3fb4 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -131,11 +131,11 @@ export class ListHybrid extends React.Component { this.setState({ disableUsers: { open: true, users: existingUsers, action } }); }); - const deleteUserStoreDisposable = deleteUserStore.subscribe(async ({ datasets }) => { - if (datasets !== undefined) { + const deleteUserStoreDisposable = deleteUserStore.subscribe(async ({ users }) => { + if (users !== undefined) { const existingUsers = await getExistingUsers(this.context.d2, { fields: ":owner,userCredentials", - filter: "id:in:[" + datasets.join(",") + "]", + filter: "id:in:[" + users.join(",") + "]", }); this.setState({ removeUsers: { open: true, users: existingUsers } }); } diff --git a/src/webapp/components/user-list-table/UserListTable.tsx b/src/webapp/components/user-list-table/UserListTable.tsx index 21e5553..a32f37a 100644 --- a/src/webapp/components/user-list-table/UserListTable.tsx +++ b/src/webapp/components/user-list-table/UserListTable.tsx @@ -184,7 +184,7 @@ export const UserListTable: React.FC = ({ text: i18n.t("Remove"), icon: delete, multiple: true, - onClick: datasets => deleteUserStore.setState({ datasets }), + onClick: users => deleteUserStore.setState({ users }), isActive: checkAccess(["delete"]), }, { From ab348aed52410bf006ea212c39ed32471d102d34 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 30 Jan 2023 21:11:29 +0100 Subject: [PATCH 34/41] Fix "Replicate user from table" action using original user creation and login date --- src/legacy/models/user.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index 21a50ee..e6eab34 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -59,11 +59,15 @@ class User { } async replicate(newUsersAttributes) { - // NOTE: externalAuth makes the replicate function fail because the IDs has to be unique - const externalAuthKeys = ["externalAuth", "openId", "ldapId"]; + /* + NOTE: + externalAuth makes the replicate function fail because the IDs has to be unique + lastLogin and created should not be copied from original user + */ + const unusedProperties = ["externalAuth", "openId", "ldapId", "lastLogin", "created"]; const ownedProperties = this.d2.models.user .getOwnedPropertyNames() - .filter(item => !externalAuthKeys.includes(item)); + .filter(item => !unusedProperties.includes(item)); const userJson = pick(ownedProperties, this.attributes); const newUsers = newUsersAttributes.map(newUserAttributes => merge(userJson, newUserAttributes)); const userGroupIds = this.attributes.userGroups.map(userGroup => userGroup.id); From 0addfdb1af43aa60648fbfe6617fa643bb744b1e Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Mon, 6 Feb 2023 13:20:29 +0100 Subject: [PATCH 35/41] Fix "Replicate user from ..." action using original created by property --- src/legacy/models/user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index e6eab34..7a70b48 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -62,9 +62,9 @@ class User { /* NOTE: externalAuth makes the replicate function fail because the IDs has to be unique - lastLogin and created should not be copied from original user + lastLogin, createdBy and created should not be copied from original user */ - const unusedProperties = ["externalAuth", "openId", "ldapId", "lastLogin", "created"]; + const unusedProperties = ["externalAuth", "openId", "ldapId", "lastLogin", "created", "createdBy"]; const ownedProperties = this.d2.models.user .getOwnedPropertyNames() .filter(item => !unusedProperties.includes(item)); From 5a706471193cfbf91ef87d512f7eba155042385b Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:14:48 +0100 Subject: [PATCH 36/41] Fix "disable" action not working in 2.38 --- src/legacy/List/List.component.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index 2fa3fb4..d5c70df 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -158,7 +158,13 @@ export class ListHybrid extends React.Component { setUsersEnableState = async (users, action) => { const newValue = action === "disable"; const response = await updateUsers(this.context.d2, users, user => { - return user.userCredentials.disabled !== newValue ? set("userCredentials.disabled", newValue, user) : null; + if (user?.userCredentials?.disabled !== newValue) { + return set("userCredentials.disabled", newValue, user) + } else if (user?.disabled !== newValue) { + return set("disabled", newValue, user) + } else { + return null; + } }); if (response.success) { From a76e91445d17591f8bf193ea12224331695f8266 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Tue, 7 Feb 2023 15:02:59 +0100 Subject: [PATCH 37/41] Fix lastLogin being populated in 2.36, Fix userCredentials missing from ownedProperties. --- src/legacy/models/user.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index 7a70b48..bbff7dc 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -68,7 +68,9 @@ class User { const ownedProperties = this.d2.models.user .getOwnedPropertyNames() .filter(item => !unusedProperties.includes(item)); + if (!ownedProperties.includes("userCredentials")) ownedProperties.push("userCredentials"); const userJson = pick(ownedProperties, this.attributes); + if (userJson.userCredentials?.lastLogin !== undefined) userJson.userCredentials.lastLogin = undefined; const newUsers = newUsersAttributes.map(newUserAttributes => merge(userJson, newUserAttributes)); const userGroupIds = this.attributes.userGroups.map(userGroup => userGroup.id); const { userGroups } = await this.api.get("/userGroups", { From 1e569ba1855bf04f1facd8defd3a58152afd5e9d Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Tue, 7 Feb 2023 15:39:45 +0100 Subject: [PATCH 38/41] Fix lastUpdatedBy and createdBy populating in 2.36 --- src/legacy/models/user.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/legacy/models/user.js b/src/legacy/models/user.js index bbff7dc..f507307 100644 --- a/src/legacy/models/user.js +++ b/src/legacy/models/user.js @@ -70,7 +70,12 @@ class User { .filter(item => !unusedProperties.includes(item)); if (!ownedProperties.includes("userCredentials")) ownedProperties.push("userCredentials"); const userJson = pick(ownedProperties, this.attributes); - if (userJson.userCredentials?.lastLogin !== undefined) userJson.userCredentials.lastLogin = undefined; + + if (userJson.userCredentials?.lastLogin !== undefined) delete userJson.userCredentials.lastLogin; + if (userJson.userCredentials?.lastUpdatedBy !== undefined) delete userJson.userCredentials.lastUpdatedBy; + if (userJson.userCredentials?.createdBy !== undefined) delete userJson.userCredentials.createdBy; + if (userJson.userCredentials?.user !== undefined) delete userJson.userCredentials.user; + const newUsers = newUsersAttributes.map(newUserAttributes => merge(userJson, newUserAttributes)); const userGroupIds = this.attributes.userGroups.map(userGroup => userGroup.id); const { userGroups } = await this.api.get("/userGroups", { From 3239a3ef2c23326e0160f650885114933b81e699 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Tue, 14 Feb 2023 00:37:14 +0100 Subject: [PATCH 39/41] Fix import CSV with overwrite --- src/legacy/models/userHelpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/legacy/models/userHelpers.js b/src/legacy/models/userHelpers.js index 3a484e6..1b48b72 100644 --- a/src/legacy/models/userHelpers.js +++ b/src/legacy/models/userHelpers.js @@ -388,7 +388,7 @@ function postMetadata(api, payload) { async function updateUsers(d2, users, mapper) { const api = d2.Api.getApi(); const existingUsers = await getExistingUsers(d2, { - fields: ":owner", + fields: ":owner,userCredentials", filter: "id:in:[" + _(users).map("id").join(",") + "]", }); const usersToSave = _(existingUsers).map(mapper).compact().value(); @@ -407,7 +407,7 @@ async function getUserGroupsToSaveAndPostMetadata(d2, api, users, existingUsersT async function saveUsers(d2, users) { const api = d2.Api.getApi(); const existingUsersToUpdate = await getExistingUsers(d2, { - fields: ":owner,userGroups[id]", + fields: ":owner,userCredentials,userGroups[id]", filter: "userCredentials.username:in:[" + _(users).map("username").join(",") + "]", }); const usersToSave = getUsersToSave(users, existingUsersToUpdate); @@ -418,7 +418,7 @@ async function saveCopyInUsers(d2, users, copyUserGroups) { const api = d2.Api.getApi(); if (copyUserGroups) { const existingUsersToUpdate = await getExistingUsers(d2, { - fields: ":owner,userGroups[id]", + fields: ":owner,userCredentials,userGroups[id]", filter: "userCredentials.username:in:[" + _(users).map("userCredentials.username").join(",") + "]", }); return getUserGroupsToSaveAndPostMetadata(d2, api, users, existingUsersToUpdate); From bdb28e3486e93cb111182fee12c5caf7b4adb028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Foche=20P=C3=A9rez?= Date: Tue, 14 Feb 2023 08:58:39 +0100 Subject: [PATCH 40/41] version bumped --- package.json | 2 +- src/legacy/List/List.component.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7dd4e4f..a4ee83b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "user-extended-app", "description": "DHIS2 Extended User app", - "version": "1.1.1", + "version": "1.2.0", "license": "GPL-3.0", "author": "EyeSeeTea team", "homepage": ".", diff --git a/src/legacy/List/List.component.js b/src/legacy/List/List.component.js index ded1722..7ddbc64 100644 --- a/src/legacy/List/List.component.js +++ b/src/legacy/List/List.component.js @@ -159,9 +159,9 @@ export class ListHybrid extends React.Component { const newValue = action === "disable"; const response = await updateUsers(this.context.d2, users, user => { if (user?.userCredentials?.disabled !== newValue) { - return set("userCredentials.disabled", newValue, user) + return set("userCredentials.disabled", newValue, user); } else if (user?.disabled !== newValue) { - return set("disabled", newValue, user) + return set("disabled", newValue, user); } else { return null; } From 16f5b63a48f241210e8a8eb3702f0b4a885658e4 Mon Sep 17 00:00:00 2001 From: nshandra <34254522+nshandra@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:11:30 +0100 Subject: [PATCH 41/41] Fix "Copy in user" failing POST not triggering an error message --- .../CopyInUserBatchModelsMultiSelect.model.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js b/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js index b141efc..65ebdf8 100644 --- a/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js +++ b/src/legacy/components/batch-models-multi-select/CopyInUserBatchModelsMultiSelect.model.js @@ -48,6 +48,7 @@ export default class CopyInUserBatchModelsMultiSelectModel { copyAccessElements, updateStrategy ); + if (!payload.success) throw new Error(`${payload.error}`); return payload; }