Compare commits

..

10 Commits

30 changed files with 265 additions and 96 deletions

View File

@@ -215,6 +215,31 @@ module.exports = {
message:
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
},
{
selector:
"MemberExpression[object.name='window'][property.name='localStorage']",
message:
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
},
{
selector:
"MemberExpression[object.name='window'][property.name='sessionStorage']",
message:
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
},
],
'no-restricted-globals': [
'error',
{
name: 'localStorage',
message:
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
},
{
name: 'sessionStorage',
message:
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
},
],
},
overrides: [
@@ -250,6 +275,9 @@ module.exports = {
'sonarjs/no-small-switch': 'off', // Small switches are OK in tests
// Test assertions intentionally reference window.location.origin for expected-value checks
'rulesdir/no-raw-absolute-path': 'off',
// Tests may access storage directly for setup/assertion/spy purposes
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
},
},
{

View File

@@ -1,6 +1,9 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
return localStorage.getItem(key);
return localStorage.getItem(getScopedKey(key));
} catch (e) {
return '';
}

View File

@@ -1,6 +1,9 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const remove = (key: string): boolean => {
try {
window.localStorage.removeItem(key);
localStorage.removeItem(getScopedKey(key));
return true;
} catch (e) {
return false;

View File

@@ -1,6 +1,9 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const set = (key: string, value: string): boolean => {
try {
localStorage.setItem(key, value);
localStorage.setItem(getScopedKey(key), value);
return true;
} catch (e) {
return false;

View File

@@ -0,0 +1,12 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
return sessionStorage.getItem(getScopedKey(key));
} catch (e) {
return '';
}
};
export default get;

View File

@@ -0,0 +1,13 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const remove = (key: string): boolean => {
try {
sessionStorage.removeItem(getScopedKey(key));
return true;
} catch (e) {
return false;
}
};
export default remove;

View File

@@ -0,0 +1,13 @@
/* eslint-disable no-restricted-globals */
import { getScopedKey } from 'utils/storage';
const set = (key: string, value: string): boolean => {
try {
sessionStorage.setItem(getScopedKey(key), value);
return true;
} catch (e) {
return false;
}
};
export default set;

View File

@@ -3,13 +3,16 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { withBasePath } from 'utils/basePath';
// 10 min in ms
const TIMEOUT_IN_MS = 10 * 60 * 1000;
export const LiveTail = (queryParams: string): EventSourcePolyfill =>
new EventSourcePolyfill(
`${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
ENVIRONMENT.baseURL
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
{
headers: {
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,

View File

@@ -1,10 +1,13 @@
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import removeSessionStorageApi from 'api/browser/sessionstorage/remove';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export const PREVIOUS_QUERY_KEY = 'previousQuery';
function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
try {
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
const raw = getSessionStorageApi(PREVIOUS_QUERY_KEY);
if (!raw) {
return {};
}
@@ -17,7 +20,7 @@ function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
try {
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
setSessionStorageApi(PREVIOUS_QUERY_KEY, JSON.stringify(store));
} catch {
// ignore quota or serialization errors
}
@@ -63,7 +66,7 @@ export const removeKeyFromPreviousQuery = (key: string): void => {
export const clearPreviousQuery = (): void => {
try {
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
removeSessionStorageApi(PREVIOUS_QUERY_KEY);
} catch {
// no-op
}

View File

@@ -108,8 +108,7 @@ function DynamicColumnTable({
// Update URL with new page number while preserving other params
urlQuery.set('page', page.toString());
const newUrl = `${window.location.pathname}?${urlQuery.toString()}`;
safeNavigate(newUrl);
safeNavigate({ search: `?${urlQuery.toString()}` });
// Call original pagination handler if provided
if (pagination?.onChange && !!pageSize) {

View File

@@ -1,3 +1,6 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { DynamicColumnsKey } from './contants';
import {
GetNewColumnDataFunction,
@@ -12,7 +15,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
}) => {
let columnVisibilityData: { [key: string]: boolean };
try {
const storedData = localStorage.getItem(tablesource);
const storedData = getLocalStorageKey(tablesource);
if (typeof storedData === 'string' && dynamicColumns) {
columnVisibilityData = JSON.parse(storedData);
return dynamicColumns.filter((column) => {
@@ -28,7 +31,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
initialColumnVisibility[key] = false;
});
localStorage.setItem(tablesource, JSON.stringify(initialColumnVisibility));
setLocalStorageKey(tablesource, JSON.stringify(initialColumnVisibility));
} catch (error) {
console.error(error);
}
@@ -42,14 +45,14 @@ export const setVisibleColumns = ({
dynamicColumns,
}: SetVisibleColumnsProps): void => {
try {
const storedData = localStorage.getItem(tablesource);
const storedData = getLocalStorageKey(tablesource);
if (typeof storedData === 'string' && dynamicColumns) {
const columnVisibilityData = JSON.parse(storedData);
const { key } = dynamicColumns[index];
if (key) {
columnVisibilityData[key] = checked;
}
localStorage.setItem(tablesource, JSON.stringify(columnVisibilityData));
setLocalStorageKey(tablesource, JSON.stringify(columnVisibilityData));
}
} catch (error) {
console.error(error);

View File

@@ -1,5 +1,6 @@
import { useCallback, useRef } from 'react';
import { Button } from 'antd';
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import ROUTES from 'constants/routes';
import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -26,7 +27,7 @@ function DashboardBreadcrumbs(): JSX.Element {
const { title = '', image = Base64Icons[0] } = selectedData || {};
const goToListPage = useCallback(() => {
const dashboardsListQueryParamsString = sessionStorage.getItem(
const dashboardsListQueryParamsString = getSessionStorageApi(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
);

View File

@@ -1,3 +1,6 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import removeLocalStorageKey from 'api/browser/localstorage/remove';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { GraphVisibilityState, SeriesVisibilityItem } from '../types';
@@ -12,7 +15,7 @@ export function getStoredSeriesVisibility(
widgetId: string,
): SeriesVisibilityItem[] | null {
try {
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
if (!storedData) {
return null;
@@ -29,7 +32,7 @@ export function getStoredSeriesVisibility(
} catch (error) {
if (error instanceof SyntaxError) {
// If the stored data is malformed, remove it
localStorage.removeItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
removeLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
}
// Silently handle parsing errors - fall back to default visibility
return null;
@@ -42,7 +45,7 @@ export function updateSeriesVisibilityToLocalStorage(
): void {
let visibilityStates: GraphVisibilityState[] = [];
try {
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
visibilityStates = JSON.parse(storedData || '[]');
} catch (error) {
if (error instanceof SyntaxError) {
@@ -63,7 +66,7 @@ export function updateSeriesVisibilityToLocalStorage(
];
}
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
JSON.stringify(visibilityStates),
);

View File

@@ -22,6 +22,8 @@ import {
Tooltip,
Typography,
} from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import { TelemetryFieldKey } from 'api/v5/v5';
import axios from 'axios';
@@ -472,7 +474,7 @@ function ExplorerOptions({
value: string;
}): void => {
// Retrieve stored views from local storage
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
// Initialize or parse the stored views
const updatedViews: PreservedViewsInLocalStorage = storedViews
@@ -486,7 +488,7 @@ function ExplorerOptions({
};
// Save the updated views back to local storage
localStorage.setItem(
setLocalStorageKey(
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
JSON.stringify(updatedViews),
);
@@ -537,7 +539,7 @@ function ExplorerOptions({
const removeCurrentViewFromLocalStorage = (): void => {
// Retrieve stored views from local storage
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
if (storedViews) {
// Parse the stored views
@@ -547,7 +549,7 @@ function ExplorerOptions({
delete parsedViews[PRESERVED_VIEW_TYPE];
// Update local storage with the modified views
localStorage.setItem(
setLocalStorageKey(
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
JSON.stringify(parsedViews),
);
@@ -672,7 +674,7 @@ function ExplorerOptions({
}
const parsedPreservedView = JSON.parse(
localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
);
const preservedView = parsedPreservedView[PRESERVED_VIEW_TYPE] || {};

View File

@@ -1,4 +1,6 @@
import { Color } from '@signozhq/design-tokens';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { showErrorNotification } from 'components/ExplorerCard/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
@@ -71,7 +73,7 @@ export const generateRGBAFromHex = (hex: string, opacity: number): string =>
export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
try {
const showExplorerToolbar = localStorage.getItem(
const showExplorerToolbar = getLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar === null) {
@@ -84,7 +86,7 @@ export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
[DataSource.TRACES]: true,
[DataSource.LOGS]: true,
};
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);
@@ -103,13 +105,13 @@ export const setExplorerToolBarVisibility = (
dataSource: string,
): void => {
try {
const showExplorerToolbar = localStorage.getItem(
const showExplorerToolbar = getLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar) {
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar);
parsedShowExplorerToolbar[dataSource] = value;
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);

View File

@@ -1,3 +1,5 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import getLabelName from 'lib/getLabelName';
import { QueryData } from 'types/api/widgets/getQuery';
@@ -100,7 +102,7 @@ export const saveLegendEntriesToLocalStorage = ({
try {
existingEntries = JSON.parse(
localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
);
} catch (error) {
console.error('Error parsing LEGEND_GRAPH from local storage', error);
@@ -115,7 +117,7 @@ export const saveLegendEntriesToLocalStorage = ({
}
try {
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
JSON.stringify(existingEntries),
);

View File

@@ -1,5 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import type { NotificationInstance } from 'antd/es/notification/interface';
import getLocalStorageKey from 'api/browser/localstorage/get';
import { NavigateToExplorerProps } from 'components/CeleryTask/useNavigateToExplorer';
import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -44,8 +45,8 @@ export const getLocalStorageGraphVisibilityState = ({
],
};
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {
@@ -94,8 +95,8 @@ export const getGraphVisibilityStateOnDataChange = ({
graphVisibilityStates: Array(options.series.length).fill(true),
legendEntry: showAllDataSet(options),
};
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {

View File

@@ -28,6 +28,8 @@ import {
Typography,
} from 'antd';
import type { TableProps } from 'antd/lib';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import createDashboard from 'api/v1/dashboards/create';
import { AxiosError } from 'axios';
@@ -147,7 +149,7 @@ function DashboardsList(): JSX.Element {
);
const getLocalStorageDynamicColumns = (): DashboardDynamicColumns => {
const dashboardDynamicColumnsString = localStorage.getItem('dashboard');
const dashboardDynamicColumnsString = getLocalStorageKey('dashboard');
let dashboardDynamicColumns: DashboardDynamicColumns = {
createdAt: true,
createdBy: true,
@@ -161,7 +163,7 @@ function DashboardsList(): JSX.Element {
);
if (isEmpty(tempDashboardDynamicColumns)) {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
} else {
dashboardDynamicColumns = { ...tempDashboardDynamicColumns };
}
@@ -169,7 +171,7 @@ function DashboardsList(): JSX.Element {
console.error(error);
}
} else {
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
}
return dashboardDynamicColumns;
@@ -183,7 +185,7 @@ function DashboardsList(): JSX.Element {
visibleColumns: DashboardDynamicColumns,
): void {
try {
localStorage.setItem('dashboard', JSON.stringify(visibleColumns));
setLocalStorageKey('dashboard', JSON.stringify(visibleColumns));
} catch (error) {
console.error(error);
}

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import isEqual from 'lodash-es/isEqual';
@@ -61,7 +62,7 @@ function useDashboardsListQueryParams(): {
const queryParamsString = params.toString();
sessionStorage.setItem(
setSessionStorageApi(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
queryParamsString,
);

View File

@@ -1,34 +1,20 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import getLocalStorageApi from 'api/browser/localstorage/get';
import removeLocalStorageApi from 'api/browser/localstorage/remove';
import setLocalStorageApi from 'api/browser/localstorage/set';
/**
* A React hook for interacting with localStorage.
* It allows getting, setting, and removing items from localStorage.
*
* @template T The type of the value to be stored.
* @param {string} key The localStorage key.
* @param {T | (() => T)} defaultValue The default value to use if no value is found in localStorage,
* @returns {[T, (value: T | ((prevState: T) => T)) => void, () => void]}
* A tuple containing:
* - The current value from state (and localStorage).
* - A function to set the value (updates state and localStorage).
* - A function to remove the value from localStorage and reset state to defaultValue.
*/
export function useLocalStorage<T>(
key: string,
defaultValue: T | (() => T),
): [T, (value: T | ((prevState: T) => T)) => void, () => void] {
// Stabilize the defaultValue to prevent unnecessary re-renders
const defaultValueRef = useRef<T | (() => T)>(defaultValue);
// Update the ref if defaultValue changes (for cases where it's intentionally dynamic)
useEffect(() => {
if (defaultValueRef.current !== defaultValue) {
defaultValueRef.current = defaultValue;
}
}, [defaultValue]);
// This function resolves the defaultValue if it's a function,
// and handles potential errors during localStorage access or JSON parsing.
const readValueFromStorage = useCallback((): T => {
const resolveddefaultValue =
defaultValueRef.current instanceof Function
@@ -36,33 +22,25 @@ export function useLocalStorage<T>(
: defaultValueRef.current;
try {
const item = window.localStorage.getItem(key);
// If item exists, parse it, otherwise return the resolved default value.
const item = getLocalStorageApi(key);
if (item) {
return JSON.parse(item) as T;
}
} catch (error) {
// Log error and fall back to default value if reading/parsing fails.
console.warn(`Error reading localStorage key "${key}":`, error);
}
return resolveddefaultValue;
}, [key]);
// Initialize state by reading from localStorage.
const [storedValue, setStoredValue] = useState<T>(readValueFromStorage);
// This function updates both localStorage and the React state.
const setValue = useCallback(
(value: T | ((prevState: T) => T)) => {
try {
// If a function is passed to setValue, it receives the latest value from storage.
const latestValueFromStorage = readValueFromStorage();
const valueToStore =
value instanceof Function ? value(latestValueFromStorage) : value;
// Save to localStorage.
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// Update React state.
setLocalStorageApi(key, JSON.stringify(valueToStore));
setStoredValue(valueToStore);
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
@@ -71,11 +49,9 @@ export function useLocalStorage<T>(
[key, readValueFromStorage],
);
// This function removes the item from localStorage and resets the React state.
const removeValue = useCallback(() => {
try {
window.localStorage.removeItem(key);
// Reset state to the (potentially resolved) defaultValue.
removeLocalStorageApi(key);
setStoredValue(
defaultValueRef.current instanceof Function
? (defaultValueRef.current as () => T)()
@@ -86,12 +62,9 @@ export function useLocalStorage<T>(
}
}, [key]);
// useEffect to update the storedValue if the key changes,
// or if the defaultValue prop changes causing readValueFromStorage to change.
// This ensures the hook reflects the correct localStorage item if its key prop dynamically changes.
useEffect(() => {
setStoredValue(readValueFromStorage());
}, [key, readValueFromStorage]); // Re-run if key or the read function changes.
}, [key, readValueFromStorage]);
return [storedValue, setValue, removeValue];
}

View File

@@ -1,3 +1,8 @@
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import removeSessionStorageApi from 'api/browser/sessionstorage/remove';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { getScopedKey } from 'utils/storage';
const PREFIX = 'dashboard_row_widget_';
function getKey(dashboardId: string): string {
@@ -8,21 +13,25 @@ export function setSelectedRowWidgetId(
dashboardId: string,
widgetId: string,
): void {
const key = getKey(dashboardId);
const unscopedKey = getKey(dashboardId);
const scopedPrefix = getScopedKey(PREFIX);
const scopedKey = getScopedKey(unscopedKey);
// remove all other selected widget ids for the dashboard before setting the new one
// to ensure only one widget is selected at a time. Helps out in weird navigate and refresh scenarios
// Object.keys returns the raw/already-scoped keys from the browser.
// Direct sessionStorage.removeItem is intentional here — k is already fully scoped.
// eslint-disable-next-line no-restricted-globals
Object.keys(sessionStorage)
.filter((k) => k.startsWith(PREFIX) && k !== key)
.filter((k) => k.startsWith(scopedPrefix) && k !== scopedKey)
// eslint-disable-next-line no-restricted-globals
.forEach((k) => sessionStorage.removeItem(k));
sessionStorage.setItem(key, widgetId);
setSessionStorageApi(unscopedKey, widgetId);
}
export function getSelectedRowWidgetId(dashboardId: string): string | null {
return sessionStorage.getItem(getKey(dashboardId));
return getSessionStorageApi(getKey(dashboardId));
}
export function clearSelectedRowWidgetId(dashboardId: string): void {
sessionStorage.removeItem(getKey(dashboardId));
removeSessionStorageApi(getKey(dashboardId));
}

View File

@@ -9,6 +9,8 @@ import React, {
useMemo,
useState,
} from 'react';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import {
getBrowserTimezone,
getTimezoneObjectByTimezoneString,
@@ -43,7 +45,7 @@ function TimezoneProvider({
}): JSX.Element {
const getStoredTimezoneValue = (): Timezone | null => {
try {
const timezoneValue = localStorage.getItem(LOCALSTORAGE.PREFERRED_TIMEZONE);
const timezoneValue = getLocalStorageKey(LOCALSTORAGE.PREFERRED_TIMEZONE);
if (timezoneValue) {
return getTimezoneObjectByTimezoneString(timezoneValue);
}
@@ -55,7 +57,7 @@ function TimezoneProvider({
const setStoredTimezoneValue = (value: string): void => {
try {
localStorage.setItem(LOCALSTORAGE.PREFERRED_TIMEZONE, value);
setLocalStorageKey(LOCALSTORAGE.PREFERRED_TIMEZONE, value);
} catch (error) {
console.error('Error saving timezone to localStorage:', error);
}

View File

@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { TelemetryFieldKey } from 'api/v5/v5';
import { LOCALSTORAGE } from 'constants/localStorage';
@@ -48,7 +49,7 @@ const getLogsUpdaterConfig = (
// Also update local storage
const local = JSON.parse(
localStorage.getItem(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
getLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
);
local.selectColumns = newColumns;
setLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS, JSON.stringify(local));
@@ -76,7 +77,7 @@ const getLogsUpdaterConfig = (
// Also update local storage
const local = JSON.parse(
localStorage.getItem(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
getLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
);
Object.assign(local, newFormatting);
setLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS, JSON.stringify(local));

View File

@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { TelemetryFieldKey } from 'api/v5/v5';
import { LOCALSTORAGE } from 'constants/localStorage';
@@ -37,7 +38,7 @@ const getTracesUpdaterConfig = (
});
const local = JSON.parse(
localStorage.getItem(LOCALSTORAGE.TRACES_LIST_OPTIONS) || '{}',
getLocalStorageKey(LOCALSTORAGE.TRACES_LIST_OPTIONS) || '{}',
);
local.selectColumns = newColumns;
setLocalStorageKey(LOCALSTORAGE.TRACES_LIST_OPTIONS, JSON.stringify(local));

View File

@@ -70,7 +70,7 @@ export const updateURL = (
userSelectedFilter: JSON.stringify(Object.fromEntries(userSelectedFilter)),
};
history.replace(
`${window.location.pathname}?${createQueryParams(queryParams)}`,
`${history.location.pathname}?${createQueryParams(queryParams)}`,
);
};

View File

@@ -29,6 +29,7 @@ import {
UPDATE_SELECTED_FIELDS,
} from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs';
import { withBasePath } from 'utils/basePath';
const supportedLogsOrder = [
OrderPreferenceItems.ASC,
@@ -37,7 +38,7 @@ const supportedLogsOrder = [
function getLogsOrder(): OrderPreferenceItems {
// set the value of order from the URL only when order query param is present and the user is landing on the old logs explorer page
if (window.location.pathname === ROUTES.OLD_LOGS_EXPLORER) {
if (window.location.pathname === withBasePath(ROUTES.OLD_LOGS_EXPLORER)) {
const orderParam = new URLSearchParams(window.location.search).get('order');
if (orderParam) {

View File

@@ -0,0 +1,72 @@
/**
* storage.ts memoizes basePath at module init (via basePath.ts IIFE).
* Use jest.isolateModules to re-import storage with a fresh DOM state each time.
*/
type StorageModule = typeof import('../storage');
function loadStorageModule(href?: string): StorageModule {
if (href !== undefined) {
const base = document.createElement('base');
base.setAttribute('href', href);
document.head.appendChild(base);
}
let mod!: StorageModule;
jest.isolateModules(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
mod = require('../storage');
});
return mod;
}
afterEach(() => {
document.head.querySelectorAll('base').forEach((el) => el.remove());
localStorage.clear();
});
describe('getScopedKey — root path "/"', () => {
it('returns the bare key unchanged', () => {
const { getScopedKey } = loadStorageModule('/');
expect(getScopedKey('AUTH_TOKEN')).toBe('AUTH_TOKEN');
});
it('backward compat: scoped key equals direct localStorage key', () => {
const { getScopedKey } = loadStorageModule('/');
localStorage.setItem('AUTH_TOKEN', 'tok');
expect(localStorage.getItem(getScopedKey('AUTH_TOKEN'))).toBe('tok');
});
});
describe('getScopedKey — prefixed path "/signoz/"', () => {
it('prefixes the key with the base path', () => {
const { getScopedKey } = loadStorageModule('/signoz/');
expect(getScopedKey('AUTH_TOKEN')).toBe('/signoz/AUTH_TOKEN');
});
it('isolates from root namespace', () => {
const { getScopedKey } = loadStorageModule('/signoz/');
localStorage.setItem('AUTH_TOKEN', 'root-tok');
expect(localStorage.getItem(getScopedKey('AUTH_TOKEN'))).toBeNull();
});
});
describe('getScopedKey — prefixed path "/testing/"', () => {
it('prefixes the key with /testing/', () => {
const { getScopedKey } = loadStorageModule('/testing/');
expect(getScopedKey('THEME')).toBe('/testing/THEME');
});
});
describe('getScopedKey — prefixed path "/playwright/"', () => {
it('prefixes the key with /playwright/', () => {
const { getScopedKey } = loadStorageModule('/playwright/');
expect(getScopedKey('THEME')).toBe('/playwright/THEME');
});
});
describe('getScopedKey — no <base> tag', () => {
it('falls back to bare key (basePath defaults to "/")', () => {
const { getScopedKey } = loadStorageModule();
expect(getScopedKey('THEME')).toBe('THEME');
});
});

View File

@@ -1,3 +1,6 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import removeLocalStorageKey from 'api/browser/localstorage/remove';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import dayjs from 'dayjs';
@@ -16,9 +19,7 @@ const MAX_STORED_RANGES = 3;
*/
export const getCustomTimeRanges = (): CustomTimeRange[] => {
try {
const stored = localStorage.getItem(
LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES,
);
const stored = getLocalStorageKey(LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES);
if (!stored) {
return [];
}
@@ -78,7 +79,7 @@ export const addCustomTimeRange = (
// Store in localStorage
try {
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES,
JSON.stringify(updatedRanges),
);
@@ -94,7 +95,7 @@ export const addCustomTimeRange = (
*/
export const clearCustomTimeRanges = (): void => {
try {
localStorage.removeItem(LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES);
removeLocalStorageKey(LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES);
} catch (error) {
console.warn('Failed to clear custom time ranges from localStorage:', error);
}
@@ -112,7 +113,7 @@ export const removeCustomTimeRange = (timestamp: number): CustomTimeRange[] => {
);
try {
localStorage.setItem(
setLocalStorageKey(
LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES,
JSON.stringify(updatedRanges),
);

View File

@@ -1,3 +1,5 @@
import getSessionStorageApi from 'api/browser/sessionstorage/get';
import setSessionStorageApi from 'api/browser/sessionstorage/set';
import { SESSIONSTORAGE } from 'constants/sessionStorage';
type ComponentImport = () => Promise<any>;
@@ -5,18 +7,17 @@ type ComponentImport = () => Promise<any>;
export const lazyRetry = (componentImport: ComponentImport): Promise<any> =>
new Promise((resolve, reject) => {
const hasRefreshed: boolean = JSON.parse(
window.sessionStorage.getItem(SESSIONSTORAGE.RETRY_LAZY_REFRESHED) ||
'false',
getSessionStorageApi(SESSIONSTORAGE.RETRY_LAZY_REFRESHED) || 'false',
);
componentImport()
.then((component: any) => {
window.sessionStorage.setItem(SESSIONSTORAGE.RETRY_LAZY_REFRESHED, 'false');
setSessionStorageApi(SESSIONSTORAGE.RETRY_LAZY_REFRESHED, 'false');
resolve(component);
})
.catch((error: Error) => {
if (!hasRefreshed) {
window.sessionStorage.setItem(SESSIONSTORAGE.RETRY_LAZY_REFRESHED, 'true');
setSessionStorageApi(SESSIONSTORAGE.RETRY_LAZY_REFRESHED, 'true');
window.location.reload();
}

View File

@@ -0,0 +1,11 @@
import { getBasePath } from 'utils/basePath';
/**
* Returns a storage key scoped to the runtime base path.
* At root ("/") the bare key is returned unchanged — backward compatible.
* At any other prefix the key is prefixed: "/signoz/AUTH_TOKEN".
*/
export function getScopedKey(key: string): string {
const basePath = getBasePath();
return basePath === '/' ? key : `${basePath}${key}`;
}