Skip to content

Commit

Permalink
Export team commands as CSV (#145)
Browse files Browse the repository at this point in the history
Fix #144
  • Loading branch information
Mikescops authored Aug 3, 2023
1 parent ea9665c commit 160f482
Show file tree
Hide file tree
Showing 9 changed files with 802 additions and 409 deletions.
43 changes: 43 additions & 0 deletions documentation/pages/business/audit-logs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,49 @@ With the following options you can filter the logs by start and end date, log ty
--category <category> log category
```

### Filtering by date

We use epoch timestamps in milliseconds, so you can use the `date` command to get the timestamp of a specific date:

```sh
# On Linux and Windows
date -d "2021-09-01" +%s000

# On macOS
date -j -f "%Y-%m-%d" "2021-09-01" +%s000
```

The final command would look like this using `date`:

```sh copy
# On Linux and Windows
dcli t logs --start $(date -d "2021-09-01" +%s000) --end $(date -d "2021-09-02" +%s000)

# On macOS
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)
```

In the output logs timestamps are in milliseconds, so you can use the `date` command to convert them to a human readable format:

```sh
# On Linux and Windows
date -d @1688629046919

# On macOS
date -r 1688629046919
```

## Export the logs as CSV

You can export the logs as CSV using the `--csv` option.

```sh copy
dcli t logs --csv --start 0 --end now > logs.csv
```

This allows you to open the logs in a spreadsheet editor like Excel or Google Sheets.
Note that the `properties` field is kept as a JSON string in the CSV file because its content varies depending on the log type.

## Logs types (default)

| Type | Event message |
Expand Down
10 changes: 10 additions & 0 deletions documentation/pages/business/members.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ You can pipe the output to `jq` to filter the results:
dcli t members | jq '.members[] | select(.isTeamCaptain == true)'
```

## Exporting the list as a CSV

You can use the `--csv` flag to export the list as a CSV file:

```sh copy
dcli t members --csv > members.csv
```

This allows you to open the file in a spreadsheet editor such as Excel or Google Sheets.

## Members interface

| Property | Type | Description |
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@json2csv/plainjs": "^7.0.1",
"@json2csv/transforms": "^7.0.1",
"@napi-rs/clipboard": "^1.1.1",
"@napi-rs/keyring": "^1.1.3",
"@node-rs/argon2": "^1.5.0",
Expand Down
31 changes: 16 additions & 15 deletions src/command-handlers/teamDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,21 @@ export async function listAllTeamDevices(options: { json: boolean }) {
});

console.log(JSON.stringify(result));
} else {
const result = listTeamDevicesResponse.teamDevices
.sort((a, b) => a.creationDateUnix - b.creationDateUnix)
.map((device) => {
return {
accessKey: device.accessKey,
name: device.deviceName,
platform: device.platform,
creationDate: unixTimestampToHumanReadable(device.creationDateUnix),
updateDate: unixTimestampToHumanReadable(device.updateDateUnix),
lastActivityDate: unixTimestampToHumanReadable(device.lastActivityDateUnix),
};
});

console.table(result);
return;
}

const result = listTeamDevicesResponse.teamDevices
.sort((a, b) => a.creationDateUnix - b.creationDateUnix)
.map((device) => {
return {
accessKey: device.accessKey,
name: device.deviceName,
platform: device.platform,
creationDate: unixTimestampToHumanReadable(device.creationDateUnix),
updateDate: unixTimestampToHumanReadable(device.updateDateUnix),
lastActivityDate: unixTimestampToHumanReadable(device.lastActivityDateUnix),
};
});

console.table(result);
}
23 changes: 18 additions & 5 deletions src/command-handlers/teamLogs.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import winston from 'winston';
import { connectAndPrepare } from '../modules/database';
import { StartAuditLogsQueryParams, startAuditLogsQuery, getAuditLogQueryResults } from '../endpoints';
import { getTeamDeviceCredentials } from '../utils';
import { getTeamDeviceCredentials, jsonToCsv } from '../utils';

export const runTeamLogs = async (options: { start: string; end: string; type: string; category: string }) => {
export const runTeamLogs = async (options: {
start: string;
end: string;
type: string;
category: string;
csv: boolean;
}) => {
const teamDeviceCredentials = getTeamDeviceCredentials();

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

const { db } = await connectAndPrepare({ autoSync: false });
await getAuditLogs({
const logs = await getAuditLogs({
teamDeviceCredentials,
startDateRangeUnix: parseInt(start),
endDateRangeUnix: parseInt(end),
logType: type,
category,
});
db.close();

if (options.csv) {
console.log(jsonToCsv(logs.map((log) => JSON.parse(log) as object)));
return;
}

logs.forEach((log) => console.log(log));
};

const MAX_RESULT = 1000;

export const getAuditLogs = async (params: StartAuditLogsQueryParams) => {
export const getAuditLogs = async (params: StartAuditLogsQueryParams): Promise<string[]> => {
const { teamDeviceCredentials } = params;

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

logs.forEach((log) => console.log(log));
return logs;
};
11 changes: 10 additions & 1 deletion src/command-handlers/teamMembers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { getTeamMembers as getTeamMembersRequest } from '../endpoints';
import { getTeamDeviceCredentials } from '../utils';
import { getTeamDeviceCredentials, flattenJsonArrayOfObject, jsonToCsv } from '../utils';

interface GetTeamMembersParams {
page: number;
limit: number;
csv: boolean;
}

export const runTeamMembers = async (params: GetTeamMembersParams) => {
Expand All @@ -16,5 +17,13 @@ export const runTeamMembers = async (params: GetTeamMembersParams) => {
limit,
});

if (params.csv) {
if (response.pages) {
console.log(`Page ${response.page + 1} of ${response.pages}`);
}
console.log(jsonToCsv(flattenJsonArrayOfObject(response.members)));
return;
}

console.log(JSON.stringify(response));
};
7 changes: 5 additions & 2 deletions src/commands/team/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const teamCommands = (params: { program: Command }) => {
teamGroup.addHelpText(
'before',
'/!\\ Commands in this section (except credentials) require team credentials to be set in the environment.\n' +
'Use generate-credentials to generate some team credentials (requires to be a team administrator).\n'
'Use `dcli team credentials generate` to generate some team credentials (requires to be a team administrator).\n'
);
}
}
Expand All @@ -29,10 +29,12 @@ export const teamCommands = (params: { program: Command }) => {
.description('List team members')
.argument('[page]', 'Page number', '0')
.argument('[limit]', 'Limit of members per page', '0')
.action(async (page: string, limit: string) => {
.option('--csv', 'Output in CSV format')
.action(async (page: string, limit: string, options: { csv: boolean }) => {
await runTeamMembers({
page: parseInt(page),
limit: parseInt(limit),
csv: options.csv,
});
});

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

teamGroup
Expand Down
15 changes: 15 additions & 0 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Parser } from '@json2csv/plainjs';
import { flatten } from '@json2csv/transforms';

export const notEmpty = <TValue>(value: TValue | null | undefined): value is TValue => {
return value !== null && value !== undefined;
};
Expand All @@ -23,3 +26,15 @@ export const removeUnderscoresAndCapitalize = (string: string): string => {
export const unixTimestampToHumanReadable = (timestamp: number | null): string => {
return timestamp ? new Date(timestamp * 1000).toLocaleString() : 'N/A';
};

export const jsonToCsv = (json: Record<string, any>): string => {
const parser = new Parser();
const csv = parser.parse(json);
return csv;
};

export const flattenJsonArrayOfObject = (json: Record<string, any>[]): Record<string, any> => {
const flattenTransform = flatten();
const flattenedJson = json.map((entry) => flattenTransform(entry));
return flattenedJson;
};
Loading

0 comments on commit 160f482

Please sign in to comment.