diff --git a/package.json b/package.json index 190c9255..4f46e812 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "jsmin": "1.0.1", "json-interpolate": "^1.0.3", "lru-cache": "^7.14.1", - "ms": "^2.1.3", "node-clipboardy": "^1.0.3", "node-fetch": "2.6.1", "pino": "^9.5.0", diff --git a/src/commands/api-mesh/__tests__/log-get-bulk.test.js b/src/commands/api-mesh/__tests__/log-get-bulk.test.js index 02308a4e..936a85c6 100644 --- a/src/commands/api-mesh/__tests__/log-get-bulk.test.js +++ b/src/commands/api-mesh/__tests__/log-get-bulk.test.js @@ -295,186 +295,6 @@ describe('GetBulkLogCommand startTime and endTime validation', () => { ); }); -describe('GetBulkLogCommand with --past and --from flags', () => { - let parseSpy; - - let now; - let fromDate; - beforeEach(() => { - now = new Date(); - fromDate = new Date(now); - fromDate.setDate(fromDate.getDate() - 29); // Set fromDate to 29 days ago - parseSpy = jest.spyOn(GetBulkLogCommand.prototype, 'parse').mockResolvedValue({ - flags: { - past: '20mins', - from: fromDate.toISOString().slice(0, 10) + ':12:00:00', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - initSdk.mockResolvedValue({ - imsOrgId: 'orgId', - imsOrgCode: 'orgCode', - projectId: 'projectId', - workspaceId: 'workspaceId', - workspaceName: 'workspaceName', - }); - getMeshId.mockResolvedValue('meshId'); - getPresignedUrls.mockResolvedValue({ - presignedUrls: [{ key: 'log1.csv', url: 'http://example.com/someHash' }], - totalSize: 2048, - }); - promptConfirm.mockResolvedValue(true); - global.requestId = 'dummy_request_id'; - }); - - afterEach(() => { - jest.clearAllMocks(); - // clear the date objects - now = null; - fromDate = null; - }); - - test('runs with valid --past and --from flags', async () => { - fs.existsSync.mockReturnValue(true); - fs.statSync.mockReturnValue({ size: 0 }); - - const mockWriteStream = { - write: jest.fn(), - end: jest.fn(), - on: jest.fn((event, callback) => { - if (event === 'finish') { - callback(); - } - }), - }; - fs.createWriteStream.mockReturnValue(mockWriteStream); - - const command = new GetBulkLogCommand([], {}); - await command.run(); - - expect(initSdk).toHaveBeenCalled(); - expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName'); - expect(getPresignedUrls).toHaveBeenCalledWith( - 'orgCode', - 'projectId', - 'workspaceId', - 'meshId', - expect.any(String), - expect.any(String), - ); - expect(fs.createWriteStream).toHaveBeenCalledWith(path.resolve(process.cwd(), 'test.csv'), { - flags: 'a', - }); - expect(mockWriteStream.write).toHaveBeenCalled(); - expect(mockWriteStream.end).toHaveBeenCalled(); - }); - - test('throws an error with invalid --from date components', async () => { - parseSpy.mockResolvedValueOnce({ - flags: { - past: '20mins', - from: fromDate.toISOString().slice(0, 10) + ':25:61:61', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - const command = new GetBulkLogCommand([], {}); - await expect(command.run()).rejects.toThrow( - 'Invalid date components passed in --from. Correct the date.', - ); - }); - - test('throws an error with invalid --from date format', async () => { - parseSpy.mockResolvedValueOnce({ - flags: { - past: '15mins', - from: fromDate.toISOString().slice(0, 10).replace(/-/g, ':') + ':15:00:00', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - const command = new GetBulkLogCommand([], {}); - await expect(command.run()).rejects.toThrow( - 'Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.', - ); - }); - - test('runs with valid --past flag without --from', async () => { - parseSpy.mockResolvedValueOnce({ - flags: { - past: '15mins', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - fs.existsSync.mockReturnValue(true); - fs.statSync.mockReturnValue({ size: 0 }); - - const command = new GetBulkLogCommand([], {}); - await command.run(); - - expect(initSdk).toHaveBeenCalled(); - expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName'); - expect(getPresignedUrls).toHaveBeenCalledWith( - 'orgCode', - 'projectId', - 'workspaceId', - 'meshId', - expect.any(String), - expect.any(String), - ); - }); - - test('throws an error with edge case for --past duration', async () => { - parseSpy.mockResolvedValueOnce({ - flags: { - past: '0s', - from: fromDate.toISOString().slice(0, 10) + ':12:00:00', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - const command = new GetBulkLogCommand([], {}); - await expect(command.run()).rejects.toThrow( - 'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".', - ); - }); - - test('runs with edge case for --from date', async () => { - parseSpy.mockResolvedValueOnce({ - flags: { - past: '15mins', - from: fromDate.toISOString().slice(0, 10) + ':00:00:00', - filename: 'test.csv', - ignoreCache: false, - }, - }); - - fs.existsSync.mockReturnValue(true); - fs.statSync.mockReturnValue({ size: 0 }); - - const command = new GetBulkLogCommand([], {}); - await command.run(); - - expect(initSdk).toHaveBeenCalled(); - expect(getMeshId).toHaveBeenCalledWith('orgCode', 'projectId', 'workspaceId', 'workspaceName'); - expect(getPresignedUrls).toHaveBeenCalledWith( - 'orgCode', - 'projectId', - 'workspaceId', - 'meshId', - expect.any(String), - expect.any(String), - ); - }); -}); - describe('validateDateTimeRange', () => { const testCases = [ { @@ -524,16 +344,9 @@ describe('validateDateTimeRange', () => { describe('parsePastDuration', () => { const validDurations = [ - ['20m', 20 * 60 * 1000], - ['20 m', 20 * 60 * 1000], - ['20min', 20 * 60 * 1000], - ['20 min', 20 * 60 * 1000], - ['20mins', 20 * 60 * 1000], - ['20 mins', 20 * 60 * 1000], - ['20minute', 20 * 60 * 1000], - ['20 minute', 20 * 60 * 1000], - ['20minutes', 20 * 60 * 1000], - ['20 minutes', 20 * 60 * 1000], + ['20', 20 * 60 * 1000], + ['30', 30 * 60 * 1000], + ['15', 15 * 60 * 1000], ]; test.each(validDurations)( @@ -544,14 +357,11 @@ describe('parsePastDuration', () => { }, ); - const invalidDurations = ['20h', '20 hours', '20s', '20 seconds']; + const invalidDurations = ['20h', '20 hours', '20s', '20 seconds', 'minutes', 'NaN', 'abc', '']; - test.each(invalidDurations)( - 'throws an error for invalid past duration format "%s"', - invalidPastDuration => { - expect(() => parsePastDuration(invalidPastDuration)).toThrow( - 'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".', - ); - }, - ); + test.each(invalidDurations)('throws an error for non-numeric input "%s"', invalidPastDuration => { + expect(() => parsePastDuration(invalidPastDuration)).toThrow( + 'Invalid format. The time window must be an integer, for example "20" or "15".', + ); + }); }); diff --git a/src/commands/api-mesh/log-get-bulk.js b/src/commands/api-mesh/log-get-bulk.js index 997f1c14..dbc61e9b 100644 --- a/src/commands/api-mesh/log-get-bulk.js +++ b/src/commands/api-mesh/log-get-bulk.js @@ -11,12 +11,10 @@ const { endTimeFlag, logFilenameFlag, pastFlag, - fromFlag, suggestCorrectedDateFormat, parsePastDuration, validateDateTimeRange, validateDateTimeFormat, - localToUTCTime, } = require('../../utils'); require('dotenv').config(); @@ -28,7 +26,6 @@ class GetBulkLogCommand extends Command { endTime: endTimeFlag, filename: logFilenameFlag, past: pastFlag, - from: fromFlag, }; async run() { @@ -89,26 +86,9 @@ class GetBulkLogCommand extends Command { formattedEndTime = flags.endTime.replace(/-|:|Z/g, '').replace('T', 'T'); } else if (flags.past) { const pastTimeWindow = parsePastDuration(flags.past); - if (flags.from) { - let convertedTime; - const dateTimeRegex = /^\d{4}-\d{2}-\d{2}:\d{2}:\d{2}:\d{2}$/; - if (!dateTimeRegex.test(flags.from)) { - this.error('Invalid format. Use the format YYYY-MM-DD:HH:MM:SS for --from.'); - } else { - try { - convertedTime = await localToUTCTime(flags.from.toString()); - } catch (error) { - this.error(`Invalid date components passed in --from. Correct the date.`); - } - } - // add the past window to the converted time to get the end time to fetch logs from the past - calculatedStartTime = new Date(convertedTime); - calculatedEndTime = new Date(calculatedStartTime.getTime() + pastTimeWindow); - } else { - // subtract the past window from the current time to get the start time to fetch recent logs from now - calculatedEndTime = new Date(); - calculatedStartTime = new Date(calculatedEndTime.getTime() - pastTimeWindow); - } + // Subtract the past window from the current time to get the start time to fetch recent logs from now + calculatedEndTime = new Date(); + calculatedStartTime = new Date(calculatedEndTime.getTime() - pastTimeWindow); // Validate the calculated start and end times range validateDateTimeRange(calculatedStartTime, calculatedEndTime); @@ -120,7 +100,7 @@ class GetBulkLogCommand extends Command { return; } else { this.error( - 'Missing required flags. Provide at least one flag --startTime, --endTime, or --past --from or type `mesh log:get-bulk --help` for more information.', + 'Missing required flags. Provide a time range with --startTime and --endTime flags, or use the --past flag for more recent logs. Use the `mesh log:get-bulk --help` command for more information.', ); return; } diff --git a/src/utils.js b/src/utils.js index 65034170..0946777f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,7 +6,6 @@ const { readFile } = require('fs/promises'); const { interpolateMesh } = require('./helpers'); const dotenv = require('dotenv'); const YAML = require('yaml'); -const ms = require('ms'); const parseEnv = require('envsub/js/envsub-parser'); const os = require('os'); const chalk = require('chalk'); @@ -94,11 +93,7 @@ const endTimeFlag = Flags.string({ }); const pastFlag = Flags.string({ - description: 'Past time window in mins', -}); - -const fromFlag = Flags.string({ - description: `The from time in YYYY-MM-DD:HH:MM:SS format based on your system's time zone. It is used to fetch logs from the past and is the starting time for the past time duration.`, + description: 'Past time window in minutes', }); const logFilenameFlag = Flags.string({ @@ -665,23 +660,21 @@ function suggestCorrectedDateFormat(inputDate) { /** * Parses a duration string representing a past time window and converts it to milliseconds. * - * @param {string} pastTimeWindow - The past time duration to parse, e.g., "20 mins", "15 minutes". + * @param {string} pastTimeWindow - The past time duration in minutes, e.g., "20", "15". * @returns {number} The duration in milliseconds. */ function parsePastDuration(pastTimeWindow) { - // Regular expression to match various formats of minute abbreviations - const pastDurationRegex = /^(\d+)\s*(m|mins?|minutes?)$/i; - const match = pastTimeWindow.match(pastDurationRegex); + // Check if pastTimeWindow contains non-numeric characters + const match = pastTimeWindow.match(/^(\d+)$/); - if (!match) { + const durationInMs = Number(pastTimeWindow) * 60 * 1000; + + if (isNaN(durationInMs) || !match) { throw new Error( - 'Invalid format. The past time window should be in minutes, for example, "20 mins", "15 minutes".', + 'Invalid format. The time window must be an integer, for example "20" or "15".', ); } - // Convert the matched duration to milliseconds - const durationInMs = ms(pastTimeWindow); - return durationInMs; } @@ -770,6 +763,7 @@ function validateDateTimeFormat(time) { return timeString.replace(/-|:|Z/g, '').replace('T', 'T'); } +// can be used later if we want to take --startTime and --endTime in local time /** * Convert a given local time string to UTC time string * @param {string} timeString - The time string in the format YYYY-MM-DD:HH:MM:SS @@ -823,7 +817,6 @@ module.exports = { endTimeFlag, logFilenameFlag, pastFlag, - fromFlag, suggestCorrectedDateFormat, parsePastDuration, validateDateTimeRange,