From 707ed63be6aaefb97d9e9a3e6a3f1871365d550b Mon Sep 17 00:00:00 2001 From: Pooja Date: Wed, 14 Jan 2026 17:13:54 +0530 Subject: [PATCH] fix: timestamp tooltip message (#6688) --- .../src/utils/codemirror/brunoVarInfo.js | 18 ++++++----- .../src/utils/codemirror/brunoVarInfo.spec.js | 30 +++++++++++++++++-- packages/bruno-common/src/index.ts | 2 +- .../bruno-common/src/utils/faker-functions.ts | 2 ++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index d26a1eb5e..36cb33977 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -6,7 +6,7 @@ * LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3 */ -import { interpolate, mockDataFunctions } from '@usebruno/common'; +import { interpolate, mockDataFunctions, timeBasedDynamicVars } from '@usebruno/common'; import { getVariableScope, isVariableSecret, getAllVariables } from 'utils/collections'; import { updateVariableInScope } from 'providers/ReduxStore/slices/collections/actions'; import store from 'providers/ReduxStore'; @@ -194,11 +194,13 @@ export const renderVarInfo = (token, options) => { } else if (variableName.startsWith('$')) { const fakerKeyword = variableName.substring(1); // Remove the $ prefix const fakerFunction = mockDataFunctions[fakerKeyword]; + const isTimeBased = timeBasedDynamicVars.has(fakerKeyword); scopeInfo = { type: 'dynamic', value: '', data: null, - isValidFakerVariable: !!fakerFunction + isValidDynamicVariable: !!fakerFunction, + isTimeBased }; } else if (variableName.startsWith('process.env.')) { // Check if this is a process.env variable (starts with "process.env.") @@ -300,8 +302,8 @@ export const renderVarInfo = (token, options) => { return into; } - // Show warning for invalid faker variable (starts with $ but not a valid faker function) - if (scopeInfo.type === 'dynamic' && !scopeInfo.isValidFakerVariable) { + // Show warning for invalid dynamic variable (starts with $ but not a valid dynamic function) + if (scopeInfo.type === 'dynamic' && !scopeInfo.isValidDynamicVariable) { const warningNote = document.createElement('div'); warningNote.className = 'var-warning-note'; warningNote.textContent = `Unknown dynamic variable "${variableName}". Check the variable name.`; @@ -309,11 +311,13 @@ export const renderVarInfo = (token, options) => { return into; } - // For valid dynamic variables, just show the read-only note (no value display since it's generated at runtime) - if (scopeInfo.type === 'dynamic' && scopeInfo.isValidFakerVariable) { + // For valid dynamic variables, show appropriate read-only note based on type + if (scopeInfo.type === 'dynamic' && scopeInfo.isValidDynamicVariable) { const readOnlyNote = document.createElement('div'); readOnlyNote.className = 'var-readonly-note'; - readOnlyNote.textContent = 'Generates random value on each request'; + readOnlyNote.textContent = scopeInfo.isTimeBased + ? 'Generates current timestamp on each request' + : 'Generates random value on each request'; into.appendChild(readOnlyNote); return into; } diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js index 5595b826d..412bd715d 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.spec.js @@ -8,8 +8,11 @@ jest.mock('@usebruno/common', () => ({ randomFirstName: jest.fn(() => 'John'), randomLastName: jest.fn(() => 'Doe'), randomEmail: jest.fn(() => 'john.doe@example.com'), - randomUUID: jest.fn(() => '123e4567-e89b-12d3-a456-426614174000') - } + randomUUID: jest.fn(() => '123e4567-e89b-12d3-a456-426614174000'), + timestamp: jest.fn(() => '1704067200'), + isoTimestamp: jest.fn(() => '2024-01-01T00:00:00.000Z') + }, + timeBasedDynamicVars: new Set(['timestamp', 'isoTimestamp']) })); jest.mock('providers/ReduxStore', () => ({ @@ -467,6 +470,29 @@ describe('renderVarInfo', () => { expect(warningNote).not.toBeNull(); expect(warningNote.textContent).toContain('Unknown dynamic variable'); }); + + it('should show time-based note for $timestamp variable', () => { + const { readOnlyNote, scopeBadge } = setupDynamicRender('$timestamp'); + + expect(scopeBadge.textContent).toBe('Dynamic'); + expect(readOnlyNote).not.toBeNull(); + expect(readOnlyNote.textContent).toBe('Generates current timestamp on each request'); + }); + + it('should show time-based note for $isoTimestamp variable', () => { + const { readOnlyNote, scopeBadge } = setupDynamicRender('$isoTimestamp'); + + expect(scopeBadge.textContent).toBe('Dynamic'); + expect(readOnlyNote).not.toBeNull(); + expect(readOnlyNote.textContent).toBe('Generates current timestamp on each request'); + }); + + it('should show random note for non-time-based dynamic variables', () => { + const { readOnlyNote } = setupDynamicRender('$randomEmail'); + + expect(readOnlyNote).not.toBeNull(); + expect(readOnlyNote.textContent).toBe('Generates random value on each request'); + }); }); describe('OAuth2 variable rendering', () => { diff --git a/packages/bruno-common/src/index.ts b/packages/bruno-common/src/index.ts index 40d298e78..8ef73549d 100644 --- a/packages/bruno-common/src/index.ts +++ b/packages/bruno-common/src/index.ts @@ -1,4 +1,4 @@ -export { mockDataFunctions } from './utils/faker-functions'; +export { mockDataFunctions, timeBasedDynamicVars } from './utils/faker-functions'; export { default as interpolate } from './interpolate'; export { default as isRequestTagsIncluded } from './tags'; diff --git a/packages/bruno-common/src/utils/faker-functions.ts b/packages/bruno-common/src/utils/faker-functions.ts index a86a8bb89..4d08a9762 100644 --- a/packages/bruno-common/src/utils/faker-functions.ts +++ b/packages/bruno-common/src/utils/faker-functions.ts @@ -1,5 +1,7 @@ import { faker } from '@faker-js/faker'; +export const timeBasedDynamicVars = new Set(['timestamp', 'isoTimestamp']); + export const mockDataFunctions = { guid: () => faker.string.uuid(), timestamp: () => Math.floor(Date.now() / 1000).toString(),