From f78c8c25f4a67681f0dd2bb354b8caa1bce65c09 Mon Sep 17 00:00:00 2001 From: Daniel Spasojevic Date: Tue, 5 Dec 2023 16:49:00 +1100 Subject: [PATCH] IE-356 + Added shorthand for specifying a 'since' based on duration --- package-lock.json | 24 ++++++++++++++++++++++++ package.json | 2 ++ src/scripts/logs.ts | 44 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 886ff75c..cc0943fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,11 @@ "@okta/okta-sdk-nodejs": "^6.5.0", "@types/node-fetch": "^2.6.2", "chalk-table": "^1.0.2", + "date-fns": "^2.30.0", "fp-ts": "^2.16.1", "generate-password": "^1.7.0", "table": "^6.8.0", + "tinyduration": "^3.3.0", "yargs": "^17.5.1", "zod": "^3.22.4" }, @@ -4228,6 +4230,22 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://nexus.bdc.agiledigital.co/repository/npmjs-org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -11124,6 +11142,12 @@ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-1.1.0.tgz", "integrity": "sha512-HFhr+OKGIHRO6krgzEt9MqbMO98wPDzDPr1BOpM/nZCChkK40UYn8b70nSjcan4jTzDSQecy1KRVVQRohIRWrw==" }, + "node_modules/tinyduration": { + "version": "3.3.0", + "resolved": "https://nexus.bdc.agiledigital.co/repository/npmjs-org/tinyduration/-/tinyduration-3.3.0.tgz", + "integrity": "sha512-sLR0iVUnnnyGEX/a3jhTA0QMK7UvakBqQJFLiibiuEYL6U1L85W+qApTZj6DcL1uoWQntYuL0gExoe9NU5B3PA==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 7bf62aee..8973a720 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ "@okta/okta-sdk-nodejs": "^6.5.0", "@types/node-fetch": "^2.6.2", "chalk-table": "^1.0.2", + "date-fns": "^2.30.0", "fp-ts": "^2.16.1", "generate-password": "^1.7.0", "table": "^6.8.0", + "tinyduration": "^3.3.0", "yargs": "^17.5.1", "zod": "^3.22.4" }, diff --git a/src/scripts/logs.ts b/src/scripts/logs.ts index a64106d5..6297623a 100644 --- a/src/scripts/logs.ts +++ b/src/scripts/logs.ts @@ -9,6 +9,8 @@ import { retrieveLogs } from './services/okta-service'; import * as okta from '@okta/okta-sdk-nodejs'; import { ReadonlyDate, ReadonlyURL, readonlyDate } from 'readonly-types'; import { table } from 'table'; +import * as duration from 'tinyduration'; +import { sub } from 'date-fns'; // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types const logsTable = (logs: readonly okta.LogEvent[]): string => { @@ -55,6 +57,16 @@ const displayLogs = ( TE.tapIO(Console.info) ); +const coercedDate = (s: string) => { + // eslint-disable-next-line functional/no-try-statement + try { + return readonlyDate(s); + } catch (error: unknown) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error(`Invalid date [${s}].`, { cause: error }); + } +}; + export default ( // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types rootCommand: RootCommand @@ -103,14 +115,27 @@ export default ( 'The filter by which the logs will be filtered (e.g. eventType eq "user.session.start").', }) .option('since', { - type: 'string', description: 'The start date of the logs to retrieve.', - coerce: (date: string) => readonlyDate(date), + coerce: (date: string) => coercedDate(date), }) .option('until', { - type: 'string', description: 'The end date of the logs to retrieve.', - coerce: (date: string) => readonlyDate(date), + coerce: (date: string) => coercedDate(date), + }) + .option('within', { + description: + 'The start date of the logs to retrieve, expressed as a duration to be applied to now (e.g. 1d/P1D for 1 day, 2w/P2W for two weeks).', + conflicts: ['since', 'until'], + coerce: (s: string) => { + const durationWithP = s.startsWith('P') ? s : `P${s}`; + // eslint-disable-next-line functional/no-try-statement + try { + return duration.parse(durationWithP.toUpperCase()); + } catch (error: unknown) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error(`Invalid duration [${s}].`, { cause: error }); + } + }, }); }; @@ -129,6 +154,7 @@ export default ( readonly filter?: string; readonly since?: ReadonlyDate; readonly until?: ReadonlyDate; + readonly within?: duration.Duration; }) => { const { clientId, privateKey } = args; const client = oktaReadOnlyClient( @@ -140,13 +166,21 @@ export default ( ['logs'] ); + const effectiveSince = + args.since !== undefined + ? args.since + : args.within !== undefined + ? // eslint-disable-next-line no-restricted-globals + sub(new Date(), args.within) + : undefined; + const result = await displayLogs( client, args.outputFormat, args.limit, args.query, args.filter, - args.since, + effectiveSince, args.until )(); // eslint-disable-next-line functional/no-conditional-statement