Compare commits

..

46 Commits

Author SHA1 Message Date
swapnil-signoz
c7f656bf5b Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-24 00:31:20 +05:30
swapnil-signoz
8533a85dcf refactor: adding missing case for azure service update 2026-04-23 23:36:24 +05:30
swapnil-signoz
bc49a19f15 Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-23 23:02:01 +05:30
swapnil-signoz
bb16d92176 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-23 23:01:14 +05:30
swapnil-signoz
2efa776de2 Merge branch 'main' into feat/cloudintegration-azure-service-def 2026-04-23 20:39:17 +05:30
swapnil-signoz
97d4f29d3a Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-23 01:34:51 +05:30
swapnil-signoz
7aa720a6e1 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-23 01:34:32 +05:30
swapnil-signoz
1296ae627d refactor: updating types 2026-04-23 01:34:10 +05:30
swapnil-signoz
4771391b9d Merge branch 'feat/cloudintegration-azure-impl' of https://github.com/SigNoz/signoz into feat/cloudintegration-azure-impl 2026-04-22 23:56:55 +05:30
swapnil-signoz
79e6485379 refactor: updating deny settings mode 2026-04-22 23:56:39 +05:30
swapnil-signoz
69f0508b15 Merge branch 'main' into feat/cloudintegration-azure-impl 2026-04-22 22:57:19 +05:30
swapnil-signoz
481e94e5d2 fix: update integration account ID and add agent version to Azure CLI and PowerShell commands 2026-04-22 19:33:20 +05:30
swapnil-signoz
4eadc17fd1 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 17:48:18 +05:30
swapnil-signoz
2acee1a8eb refactor: updating service defs 2026-04-22 17:47:34 +05:30
swapnil-signoz
f3c9129fa6 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 17:43:16 +05:30
swapnil-signoz
3ab42a9012 refactor: update Azure service identifiers 2026-04-22 17:42:23 +05:30
swapnil-signoz
f870efbdac Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 14:43:08 +05:30
swapnil-signoz
5068f412e6 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 14:42:10 +05:30
swapnil-signoz
7f5d1d8ddb refactor: updating azure blob storage service name 2026-04-22 14:41:02 +05:30
swapnil-signoz
a81501bd87 refactor: updating blob storage service name 2026-04-22 14:39:37 +05:30
swapnil-signoz
06cc2b71c6 refactor: updating connection artifact struct 2026-04-22 14:28:05 +05:30
swapnil-signoz
2a23522510 Merge branch 'feat/cloudintegration-azure-service-def' into feat/cloudintegration-azure-impl 2026-04-22 13:58:58 +05:30
swapnil-signoz
f3dfff6de1 refactor: updating telemetry strategy 2026-04-22 13:58:16 +05:30
swapnil-signoz
841c928b90 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-22 13:53:10 +05:30
swapnil-signoz
e3374d0bf3 refactor: updating strategy struct 2026-04-22 13:52:30 +05:30
swapnil-signoz
a7cd98dd8f feat: wip 2026-04-22 13:46:16 +05:30
swapnil-signoz
2714ded0b5 fix: handle optional connection URL in AWS integration 2026-04-22 01:45:56 +05:30
swapnil-signoz
c54513c327 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-22 01:44:25 +05:30
swapnil-signoz
7050a9a841 refactor: updating command key 2026-04-20 22:05:42 +05:30
swapnil-signoz
aea5447f55 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-20 18:48:05 +05:30
swapnil-signoz
d65628d989 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-20 18:47:53 +05:30
swapnil-signoz
2d96ea84e5 Merge branch 'feat/cloudintegration-azure-types' into feat/cloudintegration-azure-service-def 2026-04-20 18:43:04 +05:30
swapnil-signoz
ff38502517 Merge branch 'main' into feat/cloudintegration-azure-types 2026-04-20 16:32:34 +05:30
swapnil-signoz
e50d0684b3 refactor: updating definitions with metrics and strategy 2026-04-20 15:01:39 +05:30
swapnil-signoz
6463029786 refactor: update service names for Azure Blob Storage telemetry 2026-04-20 15:01:39 +05:30
swapnil-signoz
806108d7b6 feat: adding service definitions for azure 2026-04-20 15:01:39 +05:30
swapnil-signoz
617afeb64b refactor: lint issues 2026-04-20 15:01:26 +05:30
swapnil-signoz
3737905670 feat: completing azure types 2026-04-20 14:37:49 +05:30
swapnil-signoz
54c79642f5 refactor: updating azure integration config 2026-04-20 11:19:41 +05:30
swapnil-signoz
0682c528da refactor: updating omitempty tags 2026-04-20 10:26:08 +05:30
swapnil-signoz
3377bc8a2b feat: adding azure services 2026-04-20 09:51:43 +05:30
swapnil-signoz
3b0d5dcf0e feat: adding cloud integration azure types 2026-04-19 19:20:06 +05:30
swapnil-signoz
aa8c4471dc refactor: using upper case key for AWS 2026-04-19 00:49:02 +05:30
swapnil-signoz
04b8ef4d86 refactor: separating cloud provider types 2026-04-19 00:24:13 +05:30
swapnil-signoz
9aee83607f Merge branch 'main' into refactor/cloudprovider-types-separation 2026-04-19 00:17:15 +05:30
swapnil-signoz
d8abbce47e refactor: moving types to cloud provider specific namespace/pkg 2026-04-17 11:57:41 +05:30
42 changed files with 300 additions and 543 deletions

View File

@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,

View File

@@ -2,27 +2,47 @@ package implcloudprovider
import (
"context"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
type azurecloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
return &azurecloudprovider{
serviceDefinitions: defStore,
}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
cliCommand := cloudintegrationtypes.NewAzureConnectionCLICommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
psCommand := cloudintegrationtypes.NewAzureConnectionPowerShellCommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
return &cloudintegrationtypes.ConnectionArtifact{
Azure: cloudintegrationtypes.NewAzureConnectionArtifact(cliCommand, psCommand),
}, nil
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
if err != nil {
return nil, err
}
// override cloud integration dashboard id.
for index, dashboard := range serviceDef.Assets.Dashboards {
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
}
return serviceDef, nil
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
@@ -30,5 +50,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.Azure
if strategy == nil {
continue
}
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
if !logsEnabled && !metricsEnabled {
continue
}
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
ResourceProvider: strategy.ResourceProvider,
ResourceType: strategy.ResourceType,
}
if metricsEnabled && strategy.Metrics != nil {
entry.Metrics = strategy.Metrics
}
if logsEnabled && strategy.Logs != nil {
entry.Logs = strategy.Logs
}
strategies = append(strategies, entry)
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
account.Config.Azure.DeploymentRegion,
account.Config.Azure.ResourceGroups,
strategies,
),
}, nil
}

View File

@@ -429,9 +429,13 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// NOTE: not adding stats for services for now.
// get connected accounts for Azure
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
if err == nil {
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
}
// TODO: add more cloud providers when supported
// NOTE: not adding stats for services for now.
return stats, nil
}

View File

@@ -288,18 +288,6 @@
// Prevents navigator.clipboard - use useCopyToClipboard hook instead (disabled in tests via override)
"signoz/no-raw-absolute-path": "error",
// Prevents window.open(path), window.location.origin + path, window.location.href = path
"no-restricted-globals": [
"error",
{
"name": "localStorage",
"message": "Use scoped wrappers from api/browser/localstorage/ instead (ensures keys are prefixed when served under a URL base path)."
},
{
"name": "sessionStorage",
"message": "Use scoped wrappers from api/browser/sessionstorage/ instead (ensures keys are prefixed when served under a URL base path)."
}
],
// Prevents direct localStorage/sessionStorage access — use scoped wrappers
"no-restricted-imports": [
"error",
{
@@ -613,9 +601,7 @@
// Should ignore due to mocks
"signoz/no-navigator-clipboard": "off",
// Tests can use navigator.clipboard directly,
"signoz/no-raw-absolute-path":"off",
"no-restricted-globals": "off"
// Tests need raw localStorage/sessionStorage to seed DOM state for isolation
"signoz/no-raw-absolute-path":"off"
}
},
{

View File

@@ -68,14 +68,8 @@
// Mirrors the logic in ThemeProvider (hooks/useDarkMode/index.tsx).
(function () {
try {
// When served under a URL prefix (e.g. /signoz/), storage keys are scoped
// to that prefix by the React app (see utils/storage.ts getScopedKey).
// Read the <base> tag — already populated by the Go template — to derive
// the same prefix here, before any JS module has loaded.
var basePath = (document.querySelector('base') || {}).getAttribute('href') || '/';
var prefix = basePath === '/' ? '' : basePath;
var theme = localStorage.getItem(prefix + 'THEME');
var autoSwitch = localStorage.getItem(prefix + 'THEME_AUTO_SWITCH') === 'true';
var theme = localStorage.getItem('THEME');
var autoSwitch = localStorage.getItem('THEME_AUTO_SWITCH') === 'true';
if (autoSwitch) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'

View File

@@ -1,130 +0,0 @@
/**
* localstorage/get — lazy migration tests.
*
* basePath is memoized at module init, so each describe block re-imports the
* module with a fresh DOM state via jest.isolateModules.
*/
type GetModule = typeof import('../get');
function loadGetModule(href: string): GetModule {
const base = document.createElement('base');
base.setAttribute('href', href);
document.head.append(base);
let mod!: GetModule;
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
mod = require('../get');
});
return mod;
}
afterEach(() => {
for (const el of document.head.querySelectorAll('base')) {
el.remove();
}
localStorage.clear();
});
describe('get — root path "/"', () => {
it('reads the bare key', () => {
const { default: get } = loadGetModule('/');
localStorage.setItem('AUTH_TOKEN', 'tok');
expect(get('AUTH_TOKEN')).toBe('tok');
});
it('returns null when key is absent', () => {
const { default: get } = loadGetModule('/');
expect(get('MISSING')).toBeNull();
});
it('does NOT promote bare keys (no-op at root)', () => {
const { default: get } = loadGetModule('/');
localStorage.setItem('THEME', 'light');
get('THEME');
// bare key must still be present — no migration at root
expect(localStorage.getItem('THEME')).toBe('light');
});
});
describe('get — prefixed path "/signoz/"', () => {
it('reads an already-scoped key directly', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
expect(get('AUTH_TOKEN')).toBe('scoped-tok');
});
it('returns null when neither scoped nor bare key exists', () => {
const { default: get } = loadGetModule('/signoz/');
expect(get('MISSING')).toBeNull();
});
it('lazy-migrates bare key to scoped key on first read', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('AUTH_TOKEN', 'old-tok');
const result = get('AUTH_TOKEN');
expect(result).toBe('old-tok');
expect(localStorage.getItem('/signoz/AUTH_TOKEN')).toBe('old-tok');
expect(localStorage.getItem('AUTH_TOKEN')).toBeNull();
});
it('scoped key takes precedence over bare key', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('AUTH_TOKEN', 'bare-tok');
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
expect(get('AUTH_TOKEN')).toBe('scoped-tok');
// bare key left untouched — scoped already existed
expect(localStorage.getItem('AUTH_TOKEN')).toBe('bare-tok');
});
it('subsequent reads after migration use scoped key (no double-write)', () => {
const { default: get } = loadGetModule('/signoz/');
localStorage.setItem('THEME', 'dark');
get('THEME'); // triggers migration
localStorage.removeItem('THEME'); // simulate bare key gone
// second read still finds the scoped key
expect(get('THEME')).toBe('dark');
});
});
describe('get — two-prefix isolation', () => {
it('/signoz/ and /testing/ do not share migrated values', () => {
localStorage.setItem('THEME', 'light');
const base1 = document.createElement('base');
base1.setAttribute('href', '/signoz/');
document.head.append(base1);
let getSignoz!: GetModule['default'];
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
getSignoz = require('../get').default;
});
base1.remove();
// migrate bare → /signoz/THEME
getSignoz('THEME');
const base2 = document.createElement('base');
base2.setAttribute('href', '/testing/');
document.head.append(base2);
let getTesting!: GetModule['default'];
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
getTesting = require('../get').default;
});
base2.remove();
// /testing/ prefix: bare key already gone, scoped key does not exist
expect(getTesting('THEME')).toBeNull();
expect(localStorage.getItem('/signoz/THEME')).toBe('light');
expect(localStorage.getItem('/testing/THEME')).toBeNull();
});
});
export {};

View File

@@ -1,26 +1,7 @@
/* oxlint-disable no-restricted-globals */
import { getBasePath } from 'utils/basePath';
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
const scopedKey = getScopedKey(key);
const value = localStorage.getItem(scopedKey);
// Lazy migration: if running under a URL prefix and the scoped key doesn't
// exist yet, fall back to the bare key (written by a previous root deployment).
// Promote it to the scoped key and remove the bare key so future reads are fast.
if (value === null && getBasePath() !== '/') {
const bare = localStorage.getItem(key);
if (bare !== null) {
localStorage.setItem(scopedKey, bare);
localStorage.removeItem(key);
return bare;
}
}
return value;
} catch {
return localStorage.getItem(key);
} catch (e) {
return '';
}
};

View File

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

View File

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

View File

@@ -1,81 +0,0 @@
/**
* sessionstorage/get — lazy migration tests.
* Mirrors the localStorage get tests; same logic, different storage.
*/
type GetModule = typeof import('../get');
function loadGetModule(href: string): GetModule {
const base = document.createElement('base');
base.setAttribute('href', href);
document.head.append(base);
let mod!: GetModule;
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
mod = require('../get');
});
return mod;
}
afterEach(() => {
for (const el of document.head.querySelectorAll('base')) {
el.remove();
}
sessionStorage.clear();
});
describe('get — root path "/"', () => {
it('reads the bare key', () => {
const { default: get } = loadGetModule('/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
expect(get('retry-lazy-refreshed')).toBe('true');
});
it('returns null when key is absent', () => {
const { default: get } = loadGetModule('/');
expect(get('MISSING')).toBeNull();
});
it('does NOT promote bare keys at root', () => {
const { default: get } = loadGetModule('/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
get('retry-lazy-refreshed');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('true');
});
});
describe('get — prefixed path "/signoz/"', () => {
it('reads an already-scoped key directly', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'true');
expect(get('retry-lazy-refreshed')).toBe('true');
});
it('returns null when neither scoped nor bare key exists', () => {
const { default: get } = loadGetModule('/signoz/');
expect(get('MISSING')).toBeNull();
});
it('lazy-migrates bare key to scoped key on first read', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('retry-lazy-refreshed', 'true');
const result = get('retry-lazy-refreshed');
expect(result).toBe('true');
expect(sessionStorage.getItem('/signoz/retry-lazy-refreshed')).toBe('true');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBeNull();
});
it('scoped key takes precedence over bare key', () => {
const { default: get } = loadGetModule('/signoz/');
sessionStorage.setItem('retry-lazy-refreshed', 'bare');
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'scoped');
expect(get('retry-lazy-refreshed')).toBe('scoped');
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('bare');
});
});
export {};

View File

@@ -1,27 +0,0 @@
/* oxlint-disable no-restricted-globals */
import { getBasePath } from 'utils/basePath';
import { getScopedKey } from 'utils/storage';
const get = (key: string): string | null => {
try {
const scopedKey = getScopedKey(key);
const value = sessionStorage.getItem(scopedKey);
// Lazy migration: same pattern as localStorage — promote bare keys written
// by a previous root deployment to the scoped key on first read.
if (value === null && getBasePath() !== '/') {
const bare = sessionStorage.getItem(key);
if (bare !== null) {
sessionStorage.setItem(scopedKey, bare);
sessionStorage.removeItem(key);
return bare;
}
}
return value;
} catch {
return '';
}
};
export default get;

View File

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

View File

@@ -1,13 +0,0 @@
/* oxlint-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 {
return false;
}
};
export default set;

View File

@@ -20,7 +20,6 @@ export const GeneratedAPIInstance = <T>(
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,

View File

@@ -3,16 +3,13 @@ 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
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
`${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
{
headers: {
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,

View File

@@ -1,13 +1,10 @@
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 = getSessionStorageApi(PREVIOUS_QUERY_KEY);
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
if (!raw) {
return {};
}
@@ -20,7 +17,7 @@ function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
try {
setSessionStorageApi(PREVIOUS_QUERY_KEY, JSON.stringify(store));
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
} catch {
// ignore quota or serialization errors
}
@@ -66,7 +63,7 @@ export const removeKeyFromPreviousQuery = (key: string): void => {
export const clearPreviousQuery = (): void => {
try {
removeSessionStorageApi(PREVIOUS_QUERY_KEY);
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
} catch {
// no-op
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
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';
@@ -27,7 +26,7 @@ function DashboardBreadcrumbs(): JSX.Element {
const { title = '', image = Base64Icons[0] } = selectedData || {};
const goToListPage = useCallback(() => {
const dashboardsListQueryParamsString = getSessionStorageApi(
const dashboardsListQueryParamsString = sessionStorage.getItem(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
);

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
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';
@@ -73,7 +71,7 @@ export const generateRGBAFromHex = (hex: string, opacity: number): string =>
export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
try {
const showExplorerToolbar = getLocalStorageKey(
const showExplorerToolbar = localStorage.getItem(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar === null) {
@@ -86,7 +84,7 @@ export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
[DataSource.TRACES]: true,
[DataSource.LOGS]: true,
};
setLocalStorageKey(
localStorage.setItem(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);
@@ -105,13 +103,13 @@ export const setExplorerToolBarVisibility = (
dataSource: string,
): void => {
try {
const showExplorerToolbar = getLocalStorageKey(
const showExplorerToolbar = localStorage.getItem(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
);
if (showExplorerToolbar) {
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar);
parsedShowExplorerToolbar[dataSource] = value;
setLocalStorageKey(
localStorage.setItem(
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
JSON.stringify(parsedShowExplorerToolbar),
);

View File

@@ -1,5 +1,3 @@
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';
@@ -102,7 +100,7 @@ export const saveLegendEntriesToLocalStorage = ({
try {
existingEntries = JSON.parse(
getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
);
} catch (error) {
console.error('Error parsing LEGEND_GRAPH from local storage', error);
@@ -117,7 +115,7 @@ export const saveLegendEntriesToLocalStorage = ({
}
try {
setLocalStorageKey(
localStorage.setItem(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
JSON.stringify(existingEntries),
);

View File

@@ -1,6 +1,5 @@
/* 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';
@@ -45,8 +44,8 @@ export const getLocalStorageGraphVisibilityState = ({
],
};
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {
@@ -95,8 +94,8 @@ export const getGraphVisibilityStateOnDataChange = ({
graphVisibilityStates: Array(options.series.length).fill(true),
legendEntry: showAllDataSet(options),
};
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = getLocalStorageKey(
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
const legendGraphFromLocalStore = localStorage.getItem(
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
);
let legendFromLocalStore: {

View File

@@ -28,8 +28,6 @@ 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';
@@ -149,7 +147,7 @@ function DashboardsList(): JSX.Element {
);
const getLocalStorageDynamicColumns = (): DashboardDynamicColumns => {
const dashboardDynamicColumnsString = getLocalStorageKey('dashboard');
const dashboardDynamicColumnsString = localStorage.getItem('dashboard');
let dashboardDynamicColumns: DashboardDynamicColumns = {
createdAt: true,
createdBy: true,
@@ -163,7 +161,7 @@ function DashboardsList(): JSX.Element {
);
if (isEmpty(tempDashboardDynamicColumns)) {
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
} else {
dashboardDynamicColumns = { ...tempDashboardDynamicColumns };
}
@@ -171,7 +169,7 @@ function DashboardsList(): JSX.Element {
console.error(error);
}
} else {
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
}
return dashboardDynamicColumns;
@@ -185,7 +183,7 @@ function DashboardsList(): JSX.Element {
visibleColumns: DashboardDynamicColumns,
): void {
try {
setLocalStorageKey('dashboard', JSON.stringify(visibleColumns));
localStorage.setItem('dashboard', JSON.stringify(visibleColumns));
} catch (error) {
console.error(error);
}

View File

@@ -1,5 +1,4 @@
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';
@@ -62,7 +61,7 @@ function useDashboardsListQueryParams(): {
const queryParamsString = params.toString();
setSessionStorageApi(
sessionStorage.setItem(
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
queryParamsString,
);

View File

@@ -1,20 +1,34 @@
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
@@ -22,25 +36,33 @@ export function useLocalStorage<T>(
: defaultValueRef.current;
try {
const item = getLocalStorageApi(key);
const item = window.localStorage.getItem(key);
// If item exists, parse it, otherwise return the resolved default value.
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;
setLocalStorageApi(key, JSON.stringify(valueToStore));
// Save to localStorage.
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// Update React state.
setStoredValue(valueToStore);
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
@@ -49,9 +71,11 @@ export function useLocalStorage<T>(
[key, readValueFromStorage],
);
// This function removes the item from localStorage and resets the React state.
const removeValue = useCallback(() => {
try {
removeLocalStorageApi(key);
window.localStorage.removeItem(key);
// Reset state to the (potentially resolved) defaultValue.
setStoredValue(
defaultValueRef.current instanceof Function
? (defaultValueRef.current as () => T)()
@@ -62,9 +86,12 @@ 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]);
}, [key, readValueFromStorage]); // Re-run if key or the read function changes.
return [storedValue, setValue, removeValue];
}

View File

@@ -1,8 +1,3 @@
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 {
@@ -13,25 +8,21 @@ export function setSelectedRowWidgetId(
dashboardId: string,
widgetId: string,
): void {
const unscopedKey = getKey(dashboardId);
const scopedPrefix = getScopedKey(PREFIX);
const scopedKey = getScopedKey(unscopedKey);
const key = getKey(dashboardId);
// Object.keys returns the raw/already-scoped keys from the browser.
// Direct sessionStorage.removeItem is intentional here — k is already fully scoped.
// oxlint-disable-next-line no-restricted-globals
// 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(sessionStorage)
.filter((k) => k.startsWith(scopedPrefix) && k !== scopedKey)
// oxlint-disable-next-line no-restricted-globals
.filter((k) => k.startsWith(PREFIX) && k !== key)
.forEach((k) => sessionStorage.removeItem(k));
setSessionStorageApi(unscopedKey, widgetId);
sessionStorage.setItem(key, widgetId);
}
export function getSelectedRowWidgetId(dashboardId: string): string | null {
return getSessionStorageApi(getKey(dashboardId));
return sessionStorage.getItem(getKey(dashboardId));
}
export function clearSelectedRowWidgetId(dashboardId: string): void {
removeSessionStorageApi(getKey(dashboardId));
sessionStorage.removeItem(getKey(dashboardId));
}

View File

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

View File

@@ -1,5 +1,4 @@
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';
@@ -49,7 +48,7 @@ const getLogsUpdaterConfig = (
// Also update local storage
const local = JSON.parse(
getLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
localStorage.getItem(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
);
local.selectColumns = newColumns;
setLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS, JSON.stringify(local));
@@ -77,7 +76,7 @@ const getLogsUpdaterConfig = (
// Also update local storage
const local = JSON.parse(
getLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
localStorage.getItem(LOCALSTORAGE.LOGS_LIST_OPTIONS) || '{}',
);
Object.assign(local, newFormatting);
setLocalStorageKey(LOCALSTORAGE.LOGS_LIST_OPTIONS, JSON.stringify(local));

View File

@@ -1,5 +1,4 @@
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';
@@ -38,7 +37,7 @@ const getTracesUpdaterConfig = (
});
const local = JSON.parse(
getLocalStorageKey(LOCALSTORAGE.TRACES_LIST_OPTIONS) || '{}',
localStorage.getItem(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(
`${history.location.pathname}?${createQueryParams(queryParams)}`,
`${window.location.pathname}?${createQueryParams(queryParams)}`,
);
};

View File

@@ -29,7 +29,6 @@ import {
UPDATE_SELECTED_FIELDS,
} from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs';
import { withBasePath } from 'utils/basePath';
const supportedLogsOrder = [
OrderPreferenceItems.ASC,
@@ -38,7 +37,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 === withBasePath(ROUTES.OLD_LOGS_EXPLORER)) {
if (window.location.pathname === ROUTES.OLD_LOGS_EXPLORER) {
const orderParam = new URLSearchParams(window.location.search).get('order');
if (orderParam) {

View File

@@ -1,23 +1,23 @@
import { getLocation } from 'utils/getLocation';
import { buildAbsolutePath } from '../app';
// buildAbsolutePath reads history.location.pathname (basename-relative) rather than
// window.location.pathname, so we mock lib/history instead of utils/getLocation.
jest.mock('lib/history', () => ({
__esModule: true,
default: {
location: { pathname: '/' },
},
}));
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
const mockHistory = require('lib/history').default as {
location: { pathname: string };
};
jest.mock('utils/getLocation');
const BASE_PATH = '/some-base-path';
const mockLocation = (pathname: string): void => {
mockHistory.location.pathname = pathname;
(getLocation as jest.Mock).mockReturnValue({
pathname,
href: `http://localhost:8080${pathname}`,
origin: 'http://localhost:8080',
protocol: 'http:',
host: 'localhost',
hostname: 'localhost',
port: '',
search: '',
hash: '',
});
};
describe('buildAbsolutePath', () => {

View File

@@ -1,72 +0,0 @@
/**
* 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.append(base);
}
let mod!: StorageModule;
jest.isolateModules(() => {
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
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

@@ -2,8 +2,8 @@ import getLocalStorage from 'api/browser/localstorage/get';
import { FeatureKeys } from 'constants/features';
import { SKIP_ONBOARDING } from 'constants/onboarding';
import dayjs from 'dayjs';
import history from 'lib/history';
import { get } from 'lodash-es';
import { getLocation } from 'utils/getLocation';
export const isOnboardingSkipped = (): boolean =>
getLocalStorage(SKIP_ONBOARDING) === 'true';
@@ -40,15 +40,16 @@ export function isIngestionActive(data: any): boolean {
const key = get(table, 'columns[0].id');
const value = get(table, `rows[0].data["${key}"]`) || '0';
return Number.parseInt(value, 10) > 0;
return parseInt(value, 10) > 0;
}
/**
* Builds a path by combining the current page's pathname with a relative path.
* Builds an absolute path by combining the current page's pathname with a relative path.
*
* @param {Object} params - The parameters for building the absolute path
* @param {string} params.relativePath - The relative path to append to the current pathname
* @param {string} [params.urlQueryString] - Optional query string to append to the final path (without leading '?')
*
* @param {Object} params
* @param {string} params.relativePath - Relative path to append to the current pathname
* @param {string} [params.urlQueryString] - Query string without leading '?'
* @returns {string} The constructed absolute path, optionally with query string
*/
export function buildAbsolutePath({
@@ -58,18 +59,14 @@ export function buildAbsolutePath({
relativePath: string;
urlQueryString?: string;
}): string {
const currentPathname = history.location.pathname;
const { pathname } = getLocation();
if (!relativePath) {
return urlQueryString
? `${currentPathname}?${urlQueryString}`
: currentPathname;
return urlQueryString ? `${pathname}?${urlQueryString}` : pathname;
}
// ensure base path always ends with a forward slash
const basePath = currentPathname.endsWith('/')
? currentPathname
: `${currentPathname}/`;
const basePath = pathname.endsWith('/') ? pathname : `${pathname}/`;
// handle relative path starting with a forward slash
const normalizedRelativePath = relativePath.startsWith('/')

View File

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

View File

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

View File

@@ -1,11 +0,0 @@
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}`;
}

View File

@@ -440,3 +440,4 @@ func (handler *handler) AgentCheckIn(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, cloudintegrationtypes.NewGettableAgentCheckIn(provider, resp))
}

View File

@@ -1,5 +1,17 @@
package cloudintegrationtypes
import (
"fmt"
"strings"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
AgentArmTemplateS3Path = valuer.NewString("https://signoz-integrations.s3.us-east-1.amazonaws.com/azure-arm-template-%s.json")
AgentDeploymentStackName = valuer.NewString("signoz-integration")
)
type AzureAccountConfig struct {
DeploymentRegion string `json:"deploymentRegion" required:"true"`
ResourceGroups []string `json:"resourceGroups" required:"true" nullable:"false"`
@@ -62,3 +74,75 @@ func NewAzureIntegrationConfig(
TelemetryCollectionStrategy: strategies,
}
}
func NewAzureConnectionArtifact(cliCommand, cloudPowerShellCommand string) *AzureConnectionArtifact {
return &AzureConnectionArtifact{
CLICommand: cliCommand,
CloudPowerShellCommand: cloudPowerShellCommand,
}
}
func NewAzureConnectionCLICommand(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) string {
templateURL := fmt.Sprintf(AgentArmTemplateS3Path.StringValue(), agentVersion)
lines := []string{
"az stack sub create",
fmt.Sprintf(" --name %s", AgentDeploymentStackName.StringValue()),
fmt.Sprintf(" --location %s", cfg.DeploymentRegion),
fmt.Sprintf(" --template-uri %s", templateURL),
" --parameters",
fmt.Sprintf(" location='%s'", cfg.DeploymentRegion),
fmt.Sprintf(" signozApiKey='%s'", creds.SigNozAPIKey),
fmt.Sprintf(" signozApiUrl='%s'", creds.SigNozAPIURL),
fmt.Sprintf(" signozIngestionUrl='%s'", creds.IngestionURL),
fmt.Sprintf(" signozIngestionKey='%s'", creds.IngestionKey),
fmt.Sprintf(" signozIntegrationAccountId='%s'", accountID.StringValue()),
fmt.Sprintf(" signozIntegrationAgentVersion='%s'", agentVersion),
" --action-on-unmanage deleteAll",
" --deny-settings-mode denyDelete",
}
return strings.Join(lines, " \\\n")
}
func NewAzureConnectionPowerShellCommand(
accountID valuer.UUID,
agentVersion string,
creds *Credentials,
cfg *AzurePostableAccountConfig,
) string {
params := []struct{ k, v string }{
{"location", cfg.DeploymentRegion},
{"signozApiKey", creds.SigNozAPIKey},
{"signozApiUrl", creds.SigNozAPIURL},
{"signozIngestionUrl", creds.IngestionURL},
{"signozIngestionKey", creds.IngestionKey},
{"signozIntegrationAccountId", accountID.StringValue()},
{"signozIntegrationAgentVersion", agentVersion},
{"rgName", "signoz-integration-rg"},
{"containerEnvName", "signoz-integration-agent-env"},
{"deploymentEnv", "production"},
}
const keyWidth = 36
var paramLines []string
for _, p := range params {
paramLines = append(paramLines, fmt.Sprintf(" %-*s= \"%s\"", keyWidth, p.k, p.v))
}
templateURL := fmt.Sprintf(AgentArmTemplateS3Path.StringValue(), agentVersion)
return strings.Join([]string{
"New-AzSubscriptionDeploymentStack `",
fmt.Sprintf(" -Name \"%s\" `", AgentDeploymentStackName.StringValue()),
fmt.Sprintf(" -Location \"%s\" `", cfg.DeploymentRegion),
fmt.Sprintf(" -TemplateUri \"%s\" `", templateURL),
" -TemplateParameterObject @{",
strings.Join(paramLines, "\n"),
" } `",
" -ActionOnUnmanage \"deleteAll\" `",
" -DenySettingsMode \"denyDelete\"",
}, "\n")
}

View File

@@ -239,6 +239,10 @@ func (service *CloudIntegrationService) Update(provider CloudProviderType, servi
}
// other validations happen in newStorableServiceConfig
case CloudProviderTypeAzure:
if config.Azure == nil {
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "Azure config is required for Azure service")
}
default:
return errors.NewInvalidInputf(ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}