Skip to content

Commit 160f482

Browse files
author
Corentin Mors
authored
Export team commands as CSV (#145)
Fix #144
1 parent ea9665c commit 160f482

File tree

9 files changed

+802
-409
lines changed

9 files changed

+802
-409
lines changed

documentation/pages/business/audit-logs.mdx

+43
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,49 @@ With the following options you can filter the logs by start and end date, log ty
3838
--category <category> log category
3939
```
4040

41+
### Filtering by date
42+
43+
We use epoch timestamps in milliseconds, so you can use the `date` command to get the timestamp of a specific date:
44+
45+
```sh
46+
# On Linux and Windows
47+
date -d "2021-09-01" +%s000
48+
49+
# On macOS
50+
date -j -f "%Y-%m-%d" "2021-09-01" +%s000
51+
```
52+
53+
The final command would look like this using `date`:
54+
55+
```sh copy
56+
# On Linux and Windows
57+
dcli t logs --start $(date -d "2021-09-01" +%s000) --end $(date -d "2021-09-02" +%s000)
58+
59+
# On macOS
60+
dcli t logs --start $(date -j -f "%Y-%m-%d" "2021-09-01" +%s000) --end $(date -j -f "%Y-%m-%d" "2021-09-02" +%s000)
61+
```
62+
63+
In the output logs timestamps are in milliseconds, so you can use the `date` command to convert them to a human readable format:
64+
65+
```sh
66+
# On Linux and Windows
67+
date -d @1688629046919
68+
69+
# On macOS
70+
date -r 1688629046919
71+
```
72+
73+
## Export the logs as CSV
74+
75+
You can export the logs as CSV using the `--csv` option.
76+
77+
```sh copy
78+
dcli t logs --csv --start 0 --end now > logs.csv
79+
```
80+
81+
This allows you to open the logs in a spreadsheet editor like Excel or Google Sheets.
82+
Note that the `properties` field is kept as a JSON string in the CSV file because its content varies depending on the log type.
83+
4184
## Logs types (default)
4285

4386
| Type | Event message |

documentation/pages/business/members.mdx

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ You can pipe the output to `jq` to filter the results:
2020
dcli t members | jq '.members[] | select(.isTeamCaptain == true)'
2121
```
2222

23+
## Exporting the list as a CSV
24+
25+
You can use the `--csv` flag to export the list as a CSV file:
26+
27+
```sh copy
28+
dcli t members --csv > members.csv
29+
```
30+
31+
This allows you to open the file in a spreadsheet editor such as Excel or Google Sheets.
32+
2333
## Members interface
2434

2535
| Property | Type | Description |

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
"typescript": "^4.9.5"
6868
},
6969
"dependencies": {
70+
"@json2csv/plainjs": "^7.0.1",
71+
"@json2csv/transforms": "^7.0.1",
7072
"@napi-rs/clipboard": "^1.1.1",
7173
"@napi-rs/keyring": "^1.1.3",
7274
"@node-rs/argon2": "^1.5.0",

src/command-handlers/teamDevices.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,21 @@ export async function listAllTeamDevices(options: { json: boolean }) {
2222
});
2323

2424
console.log(JSON.stringify(result));
25-
} else {
26-
const result = listTeamDevicesResponse.teamDevices
27-
.sort((a, b) => a.creationDateUnix - b.creationDateUnix)
28-
.map((device) => {
29-
return {
30-
accessKey: device.accessKey,
31-
name: device.deviceName,
32-
platform: device.platform,
33-
creationDate: unixTimestampToHumanReadable(device.creationDateUnix),
34-
updateDate: unixTimestampToHumanReadable(device.updateDateUnix),
35-
lastActivityDate: unixTimestampToHumanReadable(device.lastActivityDateUnix),
36-
};
37-
});
38-
39-
console.table(result);
25+
return;
4026
}
27+
28+
const result = listTeamDevicesResponse.teamDevices
29+
.sort((a, b) => a.creationDateUnix - b.creationDateUnix)
30+
.map((device) => {
31+
return {
32+
accessKey: device.accessKey,
33+
name: device.deviceName,
34+
platform: device.platform,
35+
creationDate: unixTimestampToHumanReadable(device.creationDateUnix),
36+
updateDate: unixTimestampToHumanReadable(device.updateDateUnix),
37+
lastActivityDate: unixTimestampToHumanReadable(device.lastActivityDateUnix),
38+
};
39+
});
40+
41+
console.table(result);
4142
}

src/command-handlers/teamLogs.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
11
import winston from 'winston';
22
import { connectAndPrepare } from '../modules/database';
33
import { StartAuditLogsQueryParams, startAuditLogsQuery, getAuditLogQueryResults } from '../endpoints';
4-
import { getTeamDeviceCredentials } from '../utils';
4+
import { getTeamDeviceCredentials, jsonToCsv } from '../utils';
55

6-
export const runTeamLogs = async (options: { start: string; end: string; type: string; category: string }) => {
6+
export const runTeamLogs = async (options: {
7+
start: string;
8+
end: string;
9+
type: string;
10+
category: string;
11+
csv: boolean;
12+
}) => {
713
const teamDeviceCredentials = getTeamDeviceCredentials();
814

915
const { start, type, category } = options;
1016
const end = options.end === 'now' ? Date.now().toString() : options.end;
1117

1218
const { db } = await connectAndPrepare({ autoSync: false });
13-
await getAuditLogs({
19+
const logs = await getAuditLogs({
1420
teamDeviceCredentials,
1521
startDateRangeUnix: parseInt(start),
1622
endDateRangeUnix: parseInt(end),
1723
logType: type,
1824
category,
1925
});
2026
db.close();
27+
28+
if (options.csv) {
29+
console.log(jsonToCsv(logs.map((log) => JSON.parse(log) as object)));
30+
return;
31+
}
32+
33+
logs.forEach((log) => console.log(log));
2134
};
2235

2336
const MAX_RESULT = 1000;
2437

25-
export const getAuditLogs = async (params: StartAuditLogsQueryParams) => {
38+
export const getAuditLogs = async (params: StartAuditLogsQueryParams): Promise<string[]> => {
2639
const { teamDeviceCredentials } = params;
2740

2841
const { queryExecutionId } = await startAuditLogsQuery(params);
@@ -52,5 +65,5 @@ export const getAuditLogs = async (params: StartAuditLogsQueryParams) => {
5265
logs = logs.concat(result.results);
5366
}
5467

55-
logs.forEach((log) => console.log(log));
68+
return logs;
5669
};

src/command-handlers/teamMembers.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { getTeamMembers as getTeamMembersRequest } from '../endpoints';
2-
import { getTeamDeviceCredentials } from '../utils';
2+
import { getTeamDeviceCredentials, flattenJsonArrayOfObject, jsonToCsv } from '../utils';
33

44
interface GetTeamMembersParams {
55
page: number;
66
limit: number;
7+
csv: boolean;
78
}
89

910
export const runTeamMembers = async (params: GetTeamMembersParams) => {
@@ -16,5 +17,13 @@ export const runTeamMembers = async (params: GetTeamMembersParams) => {
1617
limit,
1718
});
1819

20+
if (params.csv) {
21+
if (response.pages) {
22+
console.log(`Page ${response.page + 1} of ${response.pages}`);
23+
}
24+
console.log(jsonToCsv(flattenJsonArrayOfObject(response.members)));
25+
return;
26+
}
27+
1928
console.log(JSON.stringify(response));
2029
};

src/commands/team/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const teamCommands = (params: { program: Command }) => {
1616
teamGroup.addHelpText(
1717
'before',
1818
'/!\\ Commands in this section (except credentials) require team credentials to be set in the environment.\n' +
19-
'Use generate-credentials to generate some team credentials (requires to be a team administrator).\n'
19+
'Use `dcli team credentials generate` to generate some team credentials (requires to be a team administrator).\n'
2020
);
2121
}
2222
}
@@ -29,10 +29,12 @@ export const teamCommands = (params: { program: Command }) => {
2929
.description('List team members')
3030
.argument('[page]', 'Page number', '0')
3131
.argument('[limit]', 'Limit of members per page', '0')
32-
.action(async (page: string, limit: string) => {
32+
.option('--csv', 'Output in CSV format')
33+
.action(async (page: string, limit: string, options: { csv: boolean }) => {
3334
await runTeamMembers({
3435
page: parseInt(page),
3536
limit: parseInt(limit),
37+
csv: options.csv,
3638
});
3739
});
3840

@@ -44,6 +46,7 @@ export const teamCommands = (params: { program: Command }) => {
4446
.option('--end <end>', 'end timestamp in ms (use "now" to get the current timestamp)', 'now')
4547
.option('--type <type>', 'log type')
4648
.option('--category <category>', 'log category')
49+
.option('--csv', 'Output in CSV format')
4750
.action(runTeamLogs);
4851

4952
teamGroup

src/utils/strings.ts

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { Parser } from '@json2csv/plainjs';
2+
import { flatten } from '@json2csv/transforms';
3+
14
export const notEmpty = <TValue>(value: TValue | null | undefined): value is TValue => {
25
return value !== null && value !== undefined;
36
};
@@ -23,3 +26,15 @@ export const removeUnderscoresAndCapitalize = (string: string): string => {
2326
export const unixTimestampToHumanReadable = (timestamp: number | null): string => {
2427
return timestamp ? new Date(timestamp * 1000).toLocaleString() : 'N/A';
2528
};
29+
30+
export const jsonToCsv = (json: Record<string, any>): string => {
31+
const parser = new Parser();
32+
const csv = parser.parse(json);
33+
return csv;
34+
};
35+
36+
export const flattenJsonArrayOfObject = (json: Record<string, any>[]): Record<string, any> => {
37+
const flattenTransform = flatten();
38+
const flattenedJson = json.map((entry) => flattenTransform(entry));
39+
return flattenedJson;
40+
};

0 commit comments

Comments
 (0)