modified export csv handling
used .join() + .map()
put double quote and escaped double quotes in text to keep commas inside text
handled null headers to still have CRLF
for charging stations, logs, transactions, statistics and users
MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -1023,7 +1023,7 @@ export default class ChargingStationService {
siteName: chargingStation.siteArea.site.name,
siteAreaName: chargingStation.siteArea.name,
chargingStationName: chargingStation.id
- }, req.user.locale, writeHeader);
+ }, writeHeader);
// Send OCPP Params
res.write(dataToExport);
writeHeader = false;
@@ -1525,80 +1525,102 @@ export default class ChargingStationService {
return chargingStations;
}
- private static convertOCPPParamsToCSV(ocppParams: OCPPParams, userLocale: string, writeHeader = true): string {
- let csv = '';
+ private static convertOCPPParamsToCSV(ocppParams: OCPPParams, writeHeader = true): string {
+ let headers = null;
// Header
if (writeHeader) {
- csv = 'chargingStation' + Constants.CSV_SEPARATOR;
- csv += 'name' + Constants.CSV_SEPARATOR;
- csv += 'value' + Constants.CSV_SEPARATOR;
- csv += 'siteArea' + Constants.CSV_SEPARATOR;
- csv += 'site' + Constants.CR_LF;
+ const headerArray = [
+ 'chargingStation',
+ 'name',
+ 'value',
+ 'siteArea',
+ 'site'
+ ];
+ headers = headerArray.join(Constants.CSV_SEPARATOR);
}
// Content
- for (const param of ocppParams.params) {
- csv += ocppParams.chargingStationName + Constants.CSV_SEPARATOR;
- csv += param.key + Constants.CSV_SEPARATOR;
- csv += Utils.replaceSpecialCharsInCSVValueParam(param.value) + Constants.CSV_SEPARATOR;
- csv += ocppParams.siteAreaName + Constants.CSV_SEPARATOR;
- csv += ocppParams.siteName + Constants.CR_LF;
- }
- return csv;
+ const rows = ocppParams.params.map((param) => {
+ const row = [
+ ocppParams.chargingStationName,
+ param.key,
+ Utils.replaceSpecialCharsInCSVValueParam(param.value),
+ ocppParams.siteAreaName,
+ ocppParams.siteName,
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
}
private static convertToCSV(req: Request, chargingStations: ChargingStation[], writeHeader = true): string {
- let csv = '';
+ let headers = null;
const i18nManager = I18nManager.getInstanceForLocale(req.user.locale);
// Header
if (writeHeader) {
- csv = 'name' + Constants.CSV_SEPARATOR;
- csv += 'createdOn' + Constants.CSV_SEPARATOR;
- csv += 'numberOfConnectors' + Constants.CSV_SEPARATOR;
- csv += 'siteArea' + Constants.CSV_SEPARATOR;
- csv += 'latitude' + Constants.CSV_SEPARATOR;
- csv += 'longitude' + Constants.CSV_SEPARATOR;
- csv += 'chargePointSerialNumber' + Constants.CSV_SEPARATOR;
- csv += 'model' + Constants.CSV_SEPARATOR;
- csv += 'chargeBoxSerialNumber' + Constants.CSV_SEPARATOR;
- csv += 'vendor' + Constants.CSV_SEPARATOR;
- csv += 'firmwareVersion' + Constants.CSV_SEPARATOR;
- csv += 'ocppVersion' + Constants.CSV_SEPARATOR;
- csv += 'ocppProtocol' + Constants.CSV_SEPARATOR;
- csv += 'lastSeen' + Constants.CSV_SEPARATOR;
- csv += 'lastReboot' + Constants.CSV_SEPARATOR;
- csv += 'maxPower' + Constants.CSV_SEPARATOR;
- csv += 'powerLimitUnit' + Constants.CR_LF;
+ const headerArray = [
+ 'name',
+ 'createdOn',
+ 'numberOfConnectors',
+ 'siteArea',
+ 'latitude',
+ 'longitude',
+ 'chargePointSerialNumber',
+ 'model',
+ 'chargeBoxSerialNumber',
+ 'vendor',
+ 'firmwareVersion',
+ 'ocppVersion',
+ 'ocppProtocol',
+ 'lastSeen',
+ 'lastReboot',
+ 'maxPower',
+ 'powerLimitUnit'
+ ];
+ headers = headerArray.join(Constants.CSV_SEPARATOR);
}
// Content
- for (const chargingStation of chargingStations) {
- csv += chargingStation.id + Constants.CSV_SEPARATOR;
+ const rows = chargingStations.map((chargingStation) => {
+ const row = [
+ chargingStation.id,
+ getCreatedOnCell(chargingStation, i18nManager),
+ chargingStation.connectors ? chargingStation.connectors.length : '0',
+ chargingStation?.siteArea?.name ? chargingStation.siteArea.name : '',
+ getCoordinatesCell(chargingStation),
+ chargingStation.chargePointSerialNumber,
+ chargingStation.chargePointModel,
+ chargingStation.chargeBoxSerialNumber,
+ chargingStation.chargePointVendor,
+ chargingStation.firmwareVersion,
+ chargingStation.ocppVersion,
+ chargingStation.ocppProtocol,
+ i18nManager.formatDateTime(chargingStation.lastSeen, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.lastSeen, 'LT'),
+ i18nManager.formatDateTime(chargingStation.lastReboot, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.lastReboot, 'LT'),
+ chargingStation.maximumPower,
+ chargingStation.powerLimitUnit
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ // Build createdOn cell
+ const getCreatedOnCell = (chargingStation: ChargingStation, i18nManager: I18nManager) => {
if (chargingStation.createdOn) {
- csv += i18nManager.formatDateTime(chargingStation.createdOn, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.createdOn, 'LT') + Constants.CSV_SEPARATOR;
+ return [i18nManager.formatDateTime(chargingStation.createdOn, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.createdOn, 'LT')];
} else {
- csv += i18nManager.translate('general.invalidDate') + ' ' + i18nManager.translate('general.invalidTime') + Constants.CSV_SEPARATOR;
+ return [i18nManager.translate('general.invalidDate') + ' ' + i18nManager.translate('general.invalidTime')];
}
- csv += (chargingStation.connectors ? chargingStation.connectors.length : '0') + Constants.CSV_SEPARATOR;
- csv += (chargingStation?.siteArea?.name ? chargingStation.siteArea.name : '') + Constants.CSV_SEPARATOR;
+ }
+ // Build coordinates cell
+ const getCoordinatesCell = (chargingStation: ChargingStation) => {
if (chargingStation.coordinates && chargingStation.coordinates.length === 2) {
- csv += chargingStation.coordinates[1] + Constants.CSV_SEPARATOR;
- csv += chargingStation.coordinates[0] + Constants.CSV_SEPARATOR;
+ return [chargingStation.coordinates[1], chargingStation.coordinates[0]]
} else {
- csv += '' + Constants.CSV_SEPARATOR;
- csv += '' + Constants.CSV_SEPARATOR;
- }
- csv += chargingStation.chargePointSerialNumber + Constants.CSV_SEPARATOR;
- csv += chargingStation.chargePointModel + Constants.CSV_SEPARATOR;
- csv += chargingStation.chargeBoxSerialNumber + Constants.CSV_SEPARATOR;
- csv += chargingStation.chargePointVendor + Constants.CSV_SEPARATOR;
- csv += chargingStation.firmwareVersion + Constants.CSV_SEPARATOR;
- csv += chargingStation.ocppVersion + Constants.CSV_SEPARATOR;
- csv += chargingStation.ocppProtocol + Constants.CSV_SEPARATOR;
- csv += i18nManager.formatDateTime(chargingStation.lastSeen, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.lastSeen, 'LT') + Constants.CSV_SEPARATOR;
- csv += i18nManager.formatDateTime(chargingStation.lastReboot, 'L') + ' ' + i18nManager.formatDateTime(chargingStation.lastReboot, 'LT') + Constants.CSV_SEPARATOR;
- csv += chargingStation.maximumPower + Constants.CSV_SEPARATOR;
- csv += chargingStation.powerLimitUnit + Constants.CR_LF;
- }
- return csv;
+ return ['', '']
+ }
+ }
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
}
private static async getChargingStationsForQrCode(req: Request): Promise<DataResult<ChargingStation>> {

MODIFY
src/server/rest/v1/service/LoggingService.ts
src/server/rest/v1/service/LoggingService.ts
@@ -55,36 +55,43 @@ export default class LoggingService {
}
private static convertToCSV(req: Request, loggings: Log[], writeHeader = true): string {
- let csv = '';
+ let headers = null;
// Header
if (writeHeader) {
- csv = 'date' + Constants.CSV_SEPARATOR;
- csv += 'time' + Constants.CSV_SEPARATOR;
- csv += 'level' + Constants.CSV_SEPARATOR;
- csv += 'type' + Constants.CSV_SEPARATOR;
- csv += 'action' + Constants.CSV_SEPARATOR;
- csv += 'message' + Constants.CSV_SEPARATOR;
- csv += 'method' + Constants.CSV_SEPARATOR;
- csv += 'module' + Constants.CSV_SEPARATOR;
- csv += 'source' + Constants.CSV_SEPARATOR;
- csv += 'host' + Constants.CSV_SEPARATOR;
- csv += 'process' + Constants.CR_LF;
+ const headerArray = [
+ 'date',
+ 'time',
+ 'level',
+ 'type',
+ 'action',
+ 'message',
+ 'method',
+ 'module',
+ 'source',
+ 'host',
+ 'process'
+ ]
}
// Content
- for (const log of loggings) {
- csv += moment(log.timestamp).format('YYYY-MM-DD') + Constants.CSV_SEPARATOR;
- csv += moment(log.timestamp).format('HH:mm:ss') + Constants.CSV_SEPARATOR;
- csv += log.level + Constants.CSV_SEPARATOR;
- csv += log.type + Constants.CSV_SEPARATOR;
- csv += log.action + Constants.CSV_SEPARATOR;
- csv += log.message + Constants.CSV_SEPARATOR;
- csv += log.method + Constants.CSV_SEPARATOR;
- csv += log.module + Constants.CSV_SEPARATOR;
- csv += log.source + Constants.CSV_SEPARATOR;
- csv += log.host + Constants.CSV_SEPARATOR;
- csv += log.process + Constants.CR_LF;
- }
- return csv;
+ const rows = loggings.map((log) => {
+ const row = [
+ moment(log.timestamp).format('YYYY-MM-DD'),
+ moment(log.timestamp).format('HH:mm:ss'),
+ log.level,
+ log.type,
+ log.action,
+ log.message,
+ log.method,
+ log.module,
+ log.source,
+ log.host,
+ log.process
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
}
private static async getLogs(req: Request): Promise<DataResult<Log>> {

MODIFY
src/server/rest/v1/service/StatisticService.ts
src/server/rest/v1/service/StatisticService.ts
@@ -476,37 +476,38 @@ export default class StatisticService {
if (!unknownUser) {
unknownUser = 'Unknown';
}
- let csv: string;
+ // Build header row
+ let headers: string;
if (dataCategory === 'C') {
- csv = 'chargingStation' + Constants.CSV_SEPARATOR;
+ headers = 'chargingStation' + Constants.CSV_SEPARATOR;
} else {
- csv = 'user' + Constants.CSV_SEPARATOR;
+ headers = 'user' + Constants.CSV_SEPARATOR;
}
if (year && year !== '0') {
- csv += 'year' + Constants.CSV_SEPARATOR;
+ headers += 'year' + Constants.CSV_SEPARATOR;
if (dataScope && dataScope === 'month') {
- csv += 'month' + Constants.CSV_SEPARATOR;
+ headers += 'month' + Constants.CSV_SEPARATOR;
}
}
switch (dataType) {
case 'Consumption':
- csv += 'consumption' + Constants.CR_LF;
+ headers += 'consumption' + Constants.CR_LF;
break;
case 'Usage':
- csv += 'usage' + Constants.CR_LF;
+ headers += 'usage' + Constants.CR_LF;
break;
case 'Inactivity':
- csv += 'inactivity' + Constants.CR_LF;
+ headers += 'inactivity' + Constants.CR_LF;
break;
case 'Transactions':
- csv += 'numberOfSessions' + Constants.CR_LF;
+ headers += 'numberOfSessions' + Constants.CR_LF;
break;
case 'Pricing':
- csv += 'price' + Constants.CSV_SEPARATOR;
- csv += 'priceUnit' + Constants.CR_LF;
+ headers += 'price' + Constants.CSV_SEPARATOR;
+ headers += 'priceUnit' + Constants.CR_LF;
break;
default:
- return csv;
+ return headers;
}
let index: number;
let transaction: any;
@@ -637,28 +638,32 @@ export default class StatisticService {
return 0;
});
}
-
// Now build the export file
let number: number;
- for (transaction of transactions) {
- csv += (dataCategory === 'C') ? transaction._id.chargeBox + Constants.CSV_SEPARATOR : Utils.buildUserFullName(transaction.user, false) + Constants.CSV_SEPARATOR;
- csv += (year && year !== '0') ? year + Constants.CSV_SEPARATOR : '';
- csv += (transaction._id.month > 0) ? transaction._id.month + Constants.CSV_SEPARATOR : '';
+ const rows = transactions.map((transaction) => {
number = Utils.truncTo(transaction.total, 2);
// Use raw numbers - it makes no sense to format numbers here,
// anyway only locale 'en-US' is supported here as could be seen by:
// const supportedLocales = Intl.NumberFormat.supportedLocalesOf(['fr-FR', 'en-US', 'de-DE']);
- if (dataType === 'Pricing') {
+ const row = [
+ dataCategory === 'C' ? transaction._id.chargeBox : Utils.buildUserFullName(transaction.user, false),
+ year && year !== '0' ? year : '',
+ transaction._id.month > 0 ? transaction._id.month : '',
+ dataType === 'Pricing' ? getPricingCell(transaction) : number.toString()
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ // Build pricing cell
+ const getPricingCell = (transaction: any) => {
if (transaction._id.unit) {
- csv += number.toString() + Constants.CSV_SEPARATOR + transaction._id.unit + Constants.CR_LF;
- } else {
- csv += number.toString() + Constants.CSV_SEPARATOR + ' ' + Constants.CR_LF;
- }
+ return [number.toString(), transaction._id.unit];
} else {
- csv += number.toString() + Constants.CR_LF;
+ return [number.toString(), ' '];
}
}
+ return [headers, rows].join(Constants.CR_LF);
}
- return csv;
}
}

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -889,48 +889,56 @@ export default class TransactionService {
}
public static convertToCSV(req: Request, transactions: Transaction[], writeHeader = true): string {
- let csv = '';
+ let headers = null;
// Header
if (writeHeader) {
- csv = 'id' + Constants.CSV_SEPARATOR;
- csv += 'chargingStationID' + Constants.CSV_SEPARATOR;
- csv += 'connector' + Constants.CSV_SEPARATOR;
- csv += 'userID' + Constants.CSV_SEPARATOR;
- csv += 'user' + Constants.CSV_SEPARATOR;
- csv += 'tagID' + Constants.CSV_SEPARATOR;
- csv += 'tagDescription' + Constants.CSV_SEPARATOR;
- csv += 'timezone' + Constants.CSV_SEPARATOR;
- csv += 'startDate' + Constants.CSV_SEPARATOR;
- csv += 'startTime' + Constants.CSV_SEPARATOR;
- csv += 'endDate' + Constants.CSV_SEPARATOR;
- csv += 'endTime' + Constants.CSV_SEPARATOR;
- csv += 'totalConsumption' + Constants.CSV_SEPARATOR;
- csv += 'totalDuration' + Constants.CSV_SEPARATOR;
- csv += 'totalInactivity' + Constants.CSV_SEPARATOR;
- csv += 'price' + Constants.CSV_SEPARATOR;
- csv += 'priceUnit' + Constants.CR_LF;
+ const headerArray = [
+ 'id',
+ 'chargingStationID',
+ 'connector',
+ 'userID',
+ 'user',
+ 'tagID',
+ 'tagDescription',
+ 'timezone',
+ 'startDate',
+ 'startTime',
+ 'endDate',
+ 'endTime',
+ 'totalConsumption',
+ 'totalDuration',
+ 'totalInactivity',
+ 'price',
+ 'priceUnit'
+ ];
+ headers = headerArray.join(Constants.CSV_SEPARATOR);
}
// Content
- for (const transaction of transactions) {
- csv += transaction.id + Constants.CSV_SEPARATOR;
- csv += transaction.chargeBoxID + Constants.CSV_SEPARATOR;
- csv += transaction.connectorId + Constants.CSV_SEPARATOR;
- csv += (transaction.user ? Cypher.hash(transaction.user.id) : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.user ? Utils.buildUserFullName(transaction.user, false) : '') + Constants.CSV_SEPARATOR;
- csv += transaction.tagID + Constants.CSV_SEPARATOR;
- csv += (transaction.tag?.description || '') + Constants.CSV_SEPARATOR;
- csv += (transaction.timezone || 'N/A (UTC by default)') + Constants.CSV_SEPARATOR;
- csv += (transaction.timezone ? moment(transaction.timestamp).tz(transaction.timezone) : moment.utc(transaction.timestamp)).format('YYYY-MM-DD') + Constants.CSV_SEPARATOR;
- csv += (transaction.timezone ? moment(transaction.timestamp).tz(transaction.timezone) : moment.utc(transaction.timestamp)).format('HH:mm:ss') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? (transaction.timezone ? moment(transaction.stop.timestamp).tz(transaction.timezone) : moment.utc(transaction.stop.timestamp)).format('YYYY-MM-DD') : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? (transaction.timezone ? moment(transaction.stop.timestamp).tz(transaction.timezone) : moment.utc(transaction.stop.timestamp)).format('HH:mm:ss') : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? Math.round(transaction.stop.totalConsumptionWh ? transaction.stop.totalConsumptionWh / 1000 : 0) : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? Math.round(transaction.stop.totalDurationSecs ? transaction.stop.totalDurationSecs / 60 : 0) : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? Math.round(transaction.stop.totalInactivitySecs ? transaction.stop.totalInactivitySecs / 60 : 0) : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? transaction.stop.roundedPrice : '') + Constants.CSV_SEPARATOR;
- csv += (transaction.stop ? transaction.stop.priceUnit : '') + Constants.CR_LF;
- }
- return csv;
+ const rows = transactions.map((transaction) => {
+ const row = [
+ transaction.id,
+ transaction.chargeBoxID,
+ transaction.connectorId,
+ transaction.user ? Cypher.hash(transaction.user.id) : '',
+ transaction.user ? Utils.buildUserFullName(transaction.user, false) : '',
+ transaction.tagID,
+ transaction.tag?.description || '',
+ transaction.timezone || 'N/A (UTC by default)',
+ (transaction.timezone ? moment(transaction.timestamp).tz(transaction.timezone) : moment.utc(transaction.timestamp)).format('YYYY-MM-DD'),
+ (transaction.timezone ? moment(transaction.timestamp).tz(transaction.timezone) : moment.utc(transaction.timestamp)).format('HH:mm:ss'),
+ (transaction.stop ? (transaction.timezone ? moment(transaction.stop.timestamp).tz(transaction.timezone) : moment.utc(transaction.stop.timestamp)).format('YYYY-MM-DD') : ''),
+ (transaction.stop ? (transaction.timezone ? moment(transaction.stop.timestamp).tz(transaction.timezone) : moment.utc(transaction.stop.timestamp)).format('HH:mm:ss') : ''),
+ transaction.stop ? Math.round(transaction.stop.totalConsumptionWh ? transaction.stop.totalConsumptionWh / 1000 : 0) : '',
+ transaction.stop ? Math.round(transaction.stop.totalDurationSecs ? transaction.stop.totalDurationSecs / 60 : 0) : '',
+ transaction.stop ? Math.round(transaction.stop.totalInactivitySecs ? transaction.stop.totalInactivitySecs / 60 : 0) : '',
+ transaction.stop ? transaction.stop.roundedPrice : '',
+ transaction.stop ? transaction.stop.priceUnit : ''
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
}
private static async deleteTransactions(action: ServerAction, loggedUser: UserToken, transactionsIDs: number[]): Promise<ActionsResponse> {

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -1338,36 +1338,44 @@ export default class UserService {
}
private static convertToCSV(req: Request, users: User[], writeHeader = true): string {
- let csv = '';
+ let headers = null;
// Header
if (writeHeader) {
- csv = 'id' + Constants.CSV_SEPARATOR;
- csv += 'name' + Constants.CSV_SEPARATOR;
- csv += 'firstName' + Constants.CSV_SEPARATOR;
- csv += 'locale' + Constants.CSV_SEPARATOR;
- csv += 'role' + Constants.CSV_SEPARATOR;
- csv += 'status' + Constants.CSV_SEPARATOR;
- csv += 'email' + Constants.CSV_SEPARATOR;
- csv += 'eulaAcceptedOn' + Constants.CSV_SEPARATOR;
- csv += 'createdOn' + Constants.CSV_SEPARATOR;
- csv += 'changedOn' + Constants.CSV_SEPARATOR;
- csv += 'changedBy' + Constants.CR_LF;
+ const headerArray = [
+ 'id',
+ 'name',
+ 'firstName',
+ 'locale',
+ 'role',
+ 'status',
+ 'email',
+ 'eulaAcceptedOn',
+ 'createdOn',
+ 'changedOn',
+ 'changedBy'
+ ];
+ headers = headerArray.join(Constants.CSV_SEPARATOR);
}
// Content
- for (const user of users) {
- csv += Cypher.hash(user.id) + Constants.CSV_SEPARATOR;
- csv += user.name + Constants.CSV_SEPARATOR;
- csv += user.firstName + Constants.CSV_SEPARATOR;
- csv += user.locale + Constants.CSV_SEPARATOR;
- csv += user.role + Constants.CSV_SEPARATOR;
- csv += user.status + Constants.CSV_SEPARATOR;
- csv += user.email + Constants.CSV_SEPARATOR;
- csv += moment(user.eulaAcceptedOn).format('YYYY-MM-DD') + Constants.CSV_SEPARATOR;
- csv += moment(user.createdOn).format('YYYY-MM-DD') + Constants.CSV_SEPARATOR;
- csv += moment(user.lastChangedOn).format('YYYY-MM-DD') + Constants.CSV_SEPARATOR;
- csv += (user.lastChangedBy ? Utils.buildUserFullName(user.lastChangedBy as User, false) : '') + Constants.CR_LF;
- }
- return csv;
+ const rows = users.map((user) => {
+ const row = [
+ Cypher.hash(user.id),
+ user.name,
+ user.firstName,
+ user.locale,
+ user.role,
+ user.status,
+ user.email,
+ moment(user.eulaAcceptedOn).format('YYYY-MM-DD'),
+ moment(user.createdOn).format('YYYY-MM-DD'),
+ moment(user.lastChangedOn).format('YYYY-MM-DD'),
+ (user.lastChangedBy ? Utils.buildUserFullName(user.lastChangedBy as User, false) : '')
+ ].map((value) => {
+ return typeof value === 'string' ? '"' + value.replace('"', '""') + '"': value;
+ });
+ return row;
+ }).join(Constants.CR_LF);
+ return Utils.isNullOrUndefined(headers) ? Constants.CR_LF + rows : [headers, rows].join(Constants.CR_LF);
}
private static async getUsers(req: Request, res: Response, next: NextFunction): Promise<DataResult<User>> {

modified export csv handling
- used .join() + .map()
- put double quote and escaped double quotes in text to keep commas inside text
- handled null headers to still have CRLF
- for charging stations, logs, transactions, statistics and users