Compare commits

..

6 Commits

776 changed files with 11433 additions and 8001 deletions

View File

@@ -1,8 +1,6 @@
{
"oxc.typeAware": true,
"oxc.tsConfigPath": "./frontend/tsconfig.json",
"oxc.configPath": "./frontend/.oxlintrc.json",
"oxc.fmt.configPath": "./frontend/.oxfmtrc.json",
"editor.formatOnSave": true,
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.codeActionsOnSave": {
@@ -21,3 +19,4 @@
"python-envs.defaultEnvManager": "ms-python.python:system",
"python-envs.pythonProjects": []
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/SigNoz/signoz/cmd"
"github.com/SigNoz/signoz/ee/auditor/fileauditor"
"github.com/SigNoz/signoz/ee/auditor/otlphttpauditor"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
@@ -155,6 +156,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
if err := factories.Add(otlphttpauditor.NewFactory(licensing, version.Info)); err != nil {
panic(err)
}
if err := factories.Add(fileauditor.NewFactory(licensing, version.Info)); err != nil {
panic(err)
}
return factories
},
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {

View File

@@ -0,0 +1,37 @@
package fileauditor
import (
"context"
"log/slog"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/audittypes"
)
// export converts a batch of audit events to OTLP-JSON log records and appends
// the encoded payload to the configured file as one NDJSON line per batch.
// Mirrors the wire format that otlphttpauditor sends, so downstream tools can
// consume both transports interchangeably.
func (provider *provider) export(ctx context.Context, events []audittypes.AuditEvent) error {
logs := audittypes.NewPLogsFromAuditEvents(events, "signoz", provider.build.Version(), "signoz.audit")
payload, err := provider.marshaler.MarshalLogs(logs)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to marshal audit logs")
}
provider.mu.Lock()
defer provider.mu.Unlock()
if _, err := provider.file.Write(payload); err != nil {
provider.settings.Logger().ErrorContext(ctx, "audit export failed", errors.Attr(err), slog.Int("dropped_log_records", len(events)))
return errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to write audit logs")
}
if _, err := provider.file.Write([]byte("\n")); err != nil {
return errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to write audit log newline")
}
return provider.file.Sync()
}

View File

@@ -0,0 +1,99 @@
package fileauditor
import (
"context"
"os"
"sync"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/auditor/auditorserver"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/audittypes"
"github.com/SigNoz/signoz/pkg/version"
"go.opentelemetry.io/collector/pdata/plog"
)
var _ auditor.Auditor = (*provider)(nil)
type provider struct {
settings factory.ScopedProviderSettings
config auditor.Config
licensing licensing.Licensing
build version.Build
server *auditorserver.Server
marshaler plog.JSONMarshaler
file *os.File
mu sync.Mutex
}
func NewFactory(licensing licensing.Licensing, build version.Build) factory.ProviderFactory[auditor.Auditor, auditor.Config] {
return factory.NewProviderFactory(factory.MustNewName("file"), func(ctx context.Context, providerSettings factory.ProviderSettings, config auditor.Config) (auditor.Auditor, error) {
return newProvider(ctx, providerSettings, config, licensing, build)
})
}
func newProvider(_ context.Context, providerSettings factory.ProviderSettings, config auditor.Config, licensing licensing.Licensing, build version.Build) (auditor.Auditor, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/auditor/fileauditor")
file, err := os.OpenFile(config.File.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInvalidInput, auditor.ErrCodeAuditExportFailed, "failed to open audit file %q", config.File.Path)
}
provider := &provider{
settings: settings,
config: config,
licensing: licensing,
build: build,
marshaler: plog.JSONMarshaler{},
file: file,
}
server, err := auditorserver.New(settings,
auditorserver.Config{
BufferSize: config.BufferSize,
BatchSize: config.BatchSize,
FlushInterval: config.FlushInterval,
},
provider.export,
)
if err != nil {
_ = file.Close()
return nil, err
}
provider.server = server
return provider, nil
}
func (provider *provider) Start(ctx context.Context) error {
return provider.server.Start(ctx)
}
func (provider *provider) Audit(ctx context.Context, event audittypes.AuditEvent) {
if event.PrincipalAttributes.PrincipalOrgID.IsZero() {
provider.settings.Logger().WarnContext(ctx, "audit event dropped as org_id is zero")
return
}
if _, err := provider.licensing.GetActive(ctx, event.PrincipalAttributes.PrincipalOrgID); err != nil {
return
}
provider.server.Add(ctx, event)
}
func (provider *provider) Healthy() <-chan struct{} {
return provider.server.Healthy()
}
func (provider *provider) Stop(ctx context.Context) error {
serverErr := provider.server.Stop(ctx)
fileErr := provider.file.Close()
if serverErr != nil {
return serverErr
}
return fileErr
}

View File

@@ -9,6 +9,7 @@
"react",
"react-perf",
"typescript",
"unicorn",
"jsx-a11y",
"import",
"jest",
@@ -205,8 +206,6 @@
"@typescript-eslint/explicit-function-return-type": "error",
// Requires explicit return types on functions
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/no-useless-default-assignment": "off", // provide unsafe fixes in our codebase due to bad typing
"@typescript-eslint/no-duplicate-type-constituents": "off", // provide fixes that breaks some assumptions, eg: type A = L, B = L, C = A | B (removes B)
// Disallows require() in TypeScript (use import instead)
// Disabled - using TypeScript instead
"react/jsx-props-no-spreading": "off",
@@ -313,6 +312,14 @@
"name": "react-redux",
"message": "[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query."
},
{
"name": "xstate",
"message": "[State mgmt] xstate is deprecated. Migrate to Zustand or react-query."
},
{
"name": "@xstate/react",
"message": "[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query."
},
{
"name": "react",
"importNames": [
@@ -331,6 +338,82 @@
"react/no-array-index-key": "warn",
// TODO: Changed to warn during oxlint migration, should be changed to error,
"unicorn/error-message": "warn",
"unicorn/escape-case": "warn",
"unicorn/new-for-builtins": "warn",
"unicorn/no-abusive-eslint-disable": "warn",
"unicorn/no-console-spaces": "warn",
"unicorn/no-instanceof-array": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "warn",
"unicorn/no-new-buffer": "warn",
"unicorn/no-thenable": "warn",
"unicorn/no-unreadable-array-destructuring": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-promise-resolve-reject": "warn",
"unicorn/no-useless-spread": "warn",
"unicorn/no-zero-fractions": "warn",
"unicorn/number-literal-case": "warn",
"unicorn/prefer-array-find": "warn",
"unicorn/prefer-array-flat": "warn",
"unicorn/prefer-array-flat-map": "warn",
"unicorn/prefer-array-index-of": "warn",
"unicorn/prefer-array-some": "warn",
"unicorn/prefer-at": "warn",
"unicorn/prefer-code-point": "warn",
"unicorn/prefer-date-now": "warn",
"unicorn/prefer-default-parameters": "warn",
"unicorn/prefer-includes": "warn",
"unicorn/prefer-modern-math-apis": "warn",
"unicorn/prefer-native-coercion-functions": "warn",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-number-properties": "warn",
"unicorn/prefer-optional-catch-binding": "warn",
"unicorn/prefer-regexp-test": "warn",
"unicorn/prefer-set-has": "warn",
"unicorn/prefer-string-replace-all": "warn",
"unicorn/prefer-string-slice": "warn",
"unicorn/prefer-string-starts-ends-with": "warn",
"unicorn/prefer-string-trim-start-end": "warn",
"unicorn/prefer-type-error": "warn",
"unicorn/require-array-join-separator": "warn",
"unicorn/require-number-to-fixed-digits-argument": "warn",
"unicorn/throw-new-error": "warn",
"unicorn/consistent-function-scoping": "warn",
"unicorn/explicit-length-check": "warn",
"unicorn/filename-case": [
"warn",
{
"case": "kebabCase"
}
],
"unicorn/no-array-for-each": "warn",
"unicorn/no-lonely-if": "warn",
"unicorn/no-negated-condition": "warn",
"unicorn/no-null": "warn",
"unicorn/no-object-as-default-parameter": "warn",
"unicorn/no-static-only-class": "warn",
"unicorn/no-this-assignment": "warn",
"unicorn/no-unreadable-iife": "warn",
"unicorn/no-useless-switch-case": "warn",
"unicorn/no-useless-undefined": "warn",
"unicorn/prefer-add-event-listener": "warn",
"unicorn/prefer-dom-node-append": "warn",
"unicorn/prefer-dom-node-dataset": "warn",
"unicorn/prefer-dom-node-remove": "warn",
"unicorn/prefer-dom-node-text-content": "warn",
"unicorn/prefer-keyboard-event-key": "warn",
"unicorn/prefer-math-trunc": "warn",
"unicorn/prefer-modern-dom-apis": "warn",
"unicorn/prefer-negative-index": "warn",
"unicorn/prefer-prototype-methods": "warn",
"unicorn/prefer-query-selector": "warn",
"unicorn/prefer-reflect-apply": "warn",
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-spread": "warn",
"unicorn/prefer-ternary": "warn",
"unicorn/require-post-message-target-origin": "warn",
"oxc/bad-array-method-on-arguments": "error",
"oxc/bad-bitwise-operator": "error",
"oxc/bad-comparison-sequence": "error",
@@ -455,8 +538,8 @@
"sonarjs/no-nested-template-literals": "error",
// Avoids nested template literals
"sonarjs/no-redundant-boolean": "warn", // TODO: Change to error after migration
// Removes redundant boolean literals - turned off because it clashes with unicorn rules
"sonarjs/no-redundant-jump": "off",
// Removes redundant boolean literals
"sonarjs/no-redundant-jump": "error",
// Removes unnecessary returns/continues
"sonarjs/no-same-line-conditional": "error",
// Prevents same-line conditionals

View File

@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
@@ -72,12 +72,10 @@
// 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 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 autoSwitch = localStorage.getItem(prefix + 'THEME_AUTO_SWITCH') === 'true';
if (autoSwitch) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'

View File

@@ -42,13 +42,13 @@ window.getComputedStyle = function (
} catch {
// Return a minimal CSSStyleDeclaration so callers (testing-library, Radix UI)
// see the element as visible and without animations.
return {
return ({
display: '',
visibility: '',
opacity: '1',
animationName: 'none',
getPropertyValue: () => '',
} as unknown as CSSStyleDeclaration;
} as unknown) as CSSStyleDeclaration;
}
};

View File

@@ -9,7 +9,7 @@
"build": "vite build",
"preview": "vite preview",
"prettify": "oxfmt",
"fmt": "oxfmt --check",
"fmt": "echo 'Disabled due to migration' || oxfmt --check",
"lint": "oxlint ./src && stylelint \"src/**/*.scss\"",
"lint:js": "oxlint ./src",
"lint:generated": "oxlint ./src/api/generated --fix",
@@ -63,6 +63,7 @@
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
@@ -145,6 +146,7 @@
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4",
"xstate": "^4.31.0",
"zod": "4.3.6",
"zustand": "5.0.11"
},
@@ -212,9 +214,9 @@
"msw": "1.3.2",
"npm-run-all": "latest",
"orval": "7.18.0",
"oxfmt": "0.46.0",
"oxlint": "1.61.0",
"oxlint-tsgolint": "0.21.1",
"oxfmt": "0.41.0",
"oxlint": "1.59.0",
"oxlint-tsgolint": "0.20.0",
"portfinder-sync": "^0.0.2",
"postcss": "8.5.6",
"postcss-scss": "4.0.9",
@@ -238,8 +240,8 @@
},
"lint-staged": {
"*.(js|jsx|ts|tsx)": [
"oxfmt --check",
"oxlint --quiet",
"echo 'Disabled due to migration' || oxfmt --check",
"oxlint --fix",
"sh scripts/typecheck-staged.sh"
]
},

View File

@@ -56,6 +56,7 @@ function isExternalUrl(node) {
return false;
}
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
function isSafeHelperCall(node) {
return (
@@ -96,27 +97,18 @@ export default {
callee.object.type !== 'Identifier' ||
callee.object.name !== 'window' ||
callee.property.name !== 'open'
) {
return;
}
if (args.length === 0) {
return;
}
if (isExternalUrl(args[0])) {
return;
}
if (isSafeHelperCall(args[0])) {
return;
}
)
{return;}
if (args.length === 0) {return;}
if (isExternalUrl(args[0])) {return;}
if (isSafeHelperCall(args[0])) {return;}
context.report({ node, messageId: 'windowOpen' });
},
// window.location.origin + path
BinaryExpression(node) {
if (node.operator !== '+') {
return;
}
if (node.operator !== '+') {return;}
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
context.report({ node, messageId: 'originConcat' });
}
@@ -132,40 +124,26 @@ export default {
// window.location.origin used directly (not in concatenation)
// Catches: frontendBaseUrl: window.location.origin
MemberExpression(node) {
if (!isOriginAccess(node)) {
return;
}
if (!isOriginAccess(node)) {return;}
const parent = node.parent;
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
if (parent.type === 'BinaryExpression' && parent.operator === '+') {
return;
}
if (parent.type === 'BinaryExpression' && parent.operator === '+') {return;}
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
if (parent.type === 'TemplateLiteral') {
return;
}
if (parent.type === 'TemplateLiteral') {return;}
context.report({ node, messageId: 'originDirect' });
},
// window.location.href = path
AssignmentExpression(node) {
if (node.operator !== '=') {
return;
}
if (!isHrefAccess(node.left)) {
return;
}
if (node.operator !== '=') {return;}
if (!isHrefAccess(node.left)) {return;}
// Allow external URLs
if (isExternalUrl(node.right)) {
return;
}
if (isExternalUrl(node.right)) {return;}
// Allow safe helper calls
if (isSafeHelperCall(node.right)) {
return;
}
if (isSafeHelperCall(node.right)) {return;}
context.report({ node, messageId: 'hrefAssign' });
},

View File

@@ -16,6 +16,5 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
"service_accounts": "Service Accounts"
}

View File

@@ -53,6 +53,5 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
}

View File

@@ -16,6 +16,5 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
"service_accounts": "Service Accounts"
}

View File

@@ -76,6 +76,5 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
}

View File

@@ -0,0 +1,25 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GlobalConfigData,
GlobalConfigDataProps,
} from 'types/api/globalConfig/types';
const getGlobalConfig = async (): Promise<
SuccessResponseV2<GlobalConfigData>
> => {
try {
const response = await axios.get<GlobalConfigDataProps>(`/global/config`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getGlobalConfig;

View File

@@ -39,8 +39,8 @@ jest.mock('axios', () => {
describe('interceptorRejected', () => {
beforeEach(() => {
jest.clearAllMocks();
(axios as unknown as jest.Mock).mockResolvedValue({ data: 'success' });
(axios.isAxiosError as unknown as jest.Mock).mockReturnValue(true);
((axios as unknown) as jest.Mock).mockResolvedValue({ data: 'success' });
((axios.isAxiosError as unknown) as jest.Mock).mockReturnValue(true);
});
it('should preserve array payload structure when retrying a 401 request', async () => {
@@ -49,7 +49,7 @@ describe('interceptorRejected', () => {
{ relation: 'assignee', object: { resource: { name: 'editor' } } },
];
const error = {
const error = ({
response: {
status: 401,
config: {
@@ -67,7 +67,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: JSON.stringify(arrayPayload),
},
} as unknown as AxiosResponse;
} as unknown) as AxiosResponse;
try {
await interceptorRejected(error);
@@ -75,7 +75,7 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = axios as unknown as jest.Mock;
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
@@ -85,7 +85,7 @@ describe('interceptorRejected', () => {
it('should preserve object payload structure when retrying a 401 request', async () => {
const objectPayload = { key: 'value', nested: { data: 123 } };
const error = {
const error = ({
response: {
status: 401,
config: {
@@ -103,7 +103,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: JSON.stringify(objectPayload),
},
} as unknown as AxiosResponse;
} as unknown) as AxiosResponse;
try {
await interceptorRejected(error);
@@ -111,14 +111,14 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = axios as unknown as jest.Mock;
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(JSON.parse(retryCallConfig.data)).toEqual(objectPayload);
});
it('should handle undefined data gracefully when retrying', async () => {
const error = {
const error = ({
response: {
status: 401,
config: {
@@ -136,7 +136,7 @@ describe('interceptorRejected', () => {
headers: new AxiosHeaders(),
data: undefined,
},
} as unknown as AxiosResponse;
} as unknown) as AxiosResponse;
try {
await interceptorRejected(error);
@@ -144,7 +144,7 @@ describe('interceptorRejected', () => {
// Expected to reject after retry
}
const mockAxiosFn = axios as unknown as jest.Mock;
const mockAxiosFn = (axios as unknown) as jest.Mock;
expect(mockAxiosFn.mock.calls.length).toBe(1);
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
expect(retryCallConfig.data).toBeUndefined();

View File

@@ -9,8 +9,9 @@ const getRetentionV2 = async (): Promise<
SuccessResponseV2<PayloadProps<'logs'>>
> => {
try {
const response =
await ApiV2Instance.get<PayloadProps<'logs'>>(`/settings/ttl`);
const response = await ApiV2Instance.get<PayloadProps<'logs'>>(
`/settings/ttl`,
);
return {
httpStatusCode: response.status,

View File

@@ -52,18 +52,18 @@ describe('convertV5ResponseToLegacy', () => {
alias: '__result_0',
meta: {},
series: [
{
({
labels: [
{
key: { name: 'service.name' } as unknown as TelemetryFieldKey,
key: ({ name: 'service.name' } as unknown) as TelemetryFieldKey,
value: 'adservice',
},
],
values: [
{ timestamp: 1000, value: 10 } as unknown as TimeSeriesValue,
{ timestamp: 2000, value: 12 } as unknown as TimeSeriesValue,
({ timestamp: 1000, value: 10 } as unknown) as TimeSeriesValue,
({ timestamp: 2000, value: 12 } as unknown) as TimeSeriesValue,
],
} as unknown as TimeSeries,
} as unknown) as TimeSeries,
],
},
],
@@ -88,8 +88,10 @@ describe('convertV5ResponseToLegacy', () => {
},
]);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}' };
const result = convertV5ResponseToLegacy(input, legendMap, false);
@@ -119,33 +121,33 @@ describe('convertV5ResponseToLegacy', () => {
const scalar: ScalarData = {
columns: [
// group column
{
({
name: 'service.name',
queryName: 'A',
aggregationIndex: 0,
columnType: 'group',
} as unknown as ScalarData['columns'][number],
} as unknown) as ScalarData['columns'][number],
// aggregation 0
{
({
name: '__result_0',
queryName: 'A',
aggregationIndex: 0,
columnType: 'aggregation',
} as unknown as ScalarData['columns'][number],
} as unknown) as ScalarData['columns'][number],
// aggregation 1
{
({
name: '__result_1',
queryName: 'A',
aggregationIndex: 1,
columnType: 'aggregation',
} as unknown as ScalarData['columns'][number],
} as unknown) as ScalarData['columns'][number],
// formula F1
{
({
name: '__result',
queryName: 'F1',
aggregationIndex: 0,
columnType: 'aggregation',
} as unknown as ScalarData['columns'][number],
} as unknown) as ScalarData['columns'][number],
],
data: [['adservice', 606, 1.452, 151.5]],
};
@@ -172,15 +174,17 @@ describe('convertV5ResponseToLegacy', () => {
},
{
type: 'builder_formula',
spec: {
spec: ({
name: 'F1',
expression: 'A * 0.25',
} as unknown as QueryBuilderFormula,
} as unknown) as QueryBuilderFormula,
},
]);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}', F1: '' };
const result = convertV5ResponseToLegacy(input, legendMap, false);
@@ -250,8 +254,10 @@ describe('convertV5ResponseToLegacy', () => {
},
]);
const input: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5> =
makeBaseSuccess({ data: v5Data }, params);
const input: SuccessResponse<
MetricRangePayloadV5,
QueryRangeRequestV5
> = makeBaseSuccess({ data: v5Data }, params);
const legendMap = { A: '{{service.name}}' };
const result = convertV5ResponseToLegacy(input, legendMap, true);

View File

@@ -93,7 +93,7 @@ function convertTimeSeriesData(
labels: series.labels
? Object.fromEntries(
series.labels.map((label: any) => [label.key.name, label.value]),
)
)
: {},
labelsArray: series.labels
? series.labels.map((label: any) => ({ [label.key.name]: label.value }))
@@ -358,19 +358,16 @@ export function convertV5ResponseToLegacy(
const aggregationPerQuery =
(params as QueryRangeRequestV5)?.compositeQuery?.queries
?.filter((query) => query.type === 'builder_query')
.reduce(
(acc, query) => {
if (
query.type === 'builder_query' &&
'aggregations' in query.spec &&
query.spec.name
) {
acc[query.spec.name] = query.spec.aggregations;
}
return acc;
},
{} as Record<string, any>,
) || {};
.reduce((acc, query) => {
if (
query.type === 'builder_query' &&
'aggregations' in query.spec &&
query.spec.name
) {
acc[query.spec.name] = query.spec.aggregations;
}
return acc;
}, {} as Record<string, any>) || {};
// If formatForWeb is true, return as-is (like existing logic)
if (formatForWeb && v5Data?.type === 'scalar') {

View File

@@ -713,7 +713,7 @@ describe('prepareQueryRangePayloadV5', () => {
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: 'http.status_code >= 500' },
filters: undefined as unknown as IBuilderQuery['filters'],
filters: (undefined as unknown) as IBuilderQuery['filters'],
}),
],
queryFormulas: [],
@@ -746,7 +746,7 @@ describe('prepareQueryRangePayloadV5', () => {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: undefined as unknown as IBuilderQuery['filter'],
filter: (undefined as unknown) as IBuilderQuery['filter'],
filters: {
items: [
{
@@ -834,8 +834,8 @@ describe('prepareQueryRangePayloadV5', () => {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: undefined as unknown as IBuilderQuery['filter'],
filters: undefined as unknown as IBuilderQuery['filters'],
filter: (undefined as unknown) as IBuilderQuery['filter'],
filters: (undefined as unknown) as IBuilderQuery['filters'],
}),
],
queryFormulas: [],

View File

@@ -139,9 +139,10 @@ function createBaseSpec(
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
)?.filter((c) => ('key' in c ? c?.key : c?.name));
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
return {
stepInterval: queryData?.stepInterval || null,
@@ -159,7 +160,7 @@ function createBaseSpec(
signal: item?.signal,
materialized: item?.materialized,
}),
)
)
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -178,48 +179,52 @@ function createBaseSpec(
},
direction: order.order,
}),
)
)
: undefined,
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
having: isEmpty(queryData.having) ? undefined : (queryData?.having as Having),
functions: isEmpty(queryData.functions)
? undefined
: queryData.functions.map((func: QueryFunction): QueryFunction => {
// Normalize function name to handle case sensitivity
const normalizedName = normalizeFunctionName(func?.name);
return {
name: normalizedName as FunctionName,
args: isEmpty(func.namedArgs)
? func.args?.map((arg) => ({
value: arg?.value,
}))
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
name,
value,
})),
};
}),
: queryData.functions.map(
(func: QueryFunction): QueryFunction => {
// Normalize function name to handle case sensitivity
const normalizedName = normalizeFunctionName(func?.name);
return {
name: normalizedName as FunctionName,
args: isEmpty(func.namedArgs)
? func.args?.map((arg) => ({
value: arg?.value,
}))
: Object.entries(func?.namedArgs || {}).map(([name, value]) => ({
name,
value,
})),
};
},
),
selectFields: isEmpty(nonEmptySelectColumns)
? undefined
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => {
const fieldName = column.name ?? column.key;
const isDeprecated = isDeprecatedField(fieldName);
: nonEmptySelectColumns?.map(
(column: any): TelemetryFieldKey => {
const fieldName = column.name ?? column.key;
const isDeprecated = isDeprecatedField(fieldName);
const fieldObj: TelemetryFieldKey = {
name: fieldName,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
signal: column?.signal ?? undefined,
};
const fieldObj: TelemetryFieldKey = {
name: fieldName,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
signal: column?.signal ?? undefined,
};
// Only add fieldContext if the field is NOT deprecated
if (!isDeprecated && fieldName !== 'name') {
fieldObj.fieldContext =
column?.fieldContext ?? (column?.type as FieldContext);
}
// Only add fieldContext if the field is NOT deprecated
if (!isDeprecated && fieldName !== 'name') {
fieldObj.fieldContext =
column?.fieldContext ?? (column?.type as FieldContext);
}
return fieldObj;
}),
return fieldObj;
},
),
};
}
@@ -231,8 +236,7 @@ export function parseAggregations(
const result: { expression: string; alias?: string }[] = [];
// Matches function calls like "count()" or "sum(field)" with optional alias like "as 'alias'"
// Handles quoted ('alias'), dash-separated (field-name), and unquoted values after "as" keyword
const regex =
/([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
const regex = /([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+((?:'[^']*'|"[^"]*"|[a-zA-Z0-9_-]+)))?/g;
let match = regex.exec(expression);
while (match !== null) {
const expr = match[1];
@@ -361,9 +365,10 @@ function createTraceOperatorBaseSpec(
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (
queryData.selectColumns as (BaseAutocompleteData | TelemetryFieldKey)[]
)?.filter((c) => ('key' in c ? c?.key : c?.name));
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
const {
stepInterval,
@@ -390,7 +395,7 @@ function createTraceOperatorBaseSpec(
signal: item?.signal,
materialized: item?.materialized,
}),
)
)
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
@@ -406,7 +411,7 @@ function createTraceOperatorBaseSpec(
},
direction: order.order,
}),
)
)
: undefined,
legend: isEmpty(legend) ? undefined : legend,
having: isEmpty(having) ? undefined : (having as Having),
@@ -420,7 +425,7 @@ function createTraceOperatorBaseSpec(
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
}),
),
),
};
}
@@ -502,22 +507,18 @@ export function convertClickHouseQueriesToV5(
/**
* Helper function to reduce query arrays to objects
*/
function reduceQueriesToObject(queryArray: any[]): {
queries: Record<string, any>;
legends: Record<string, string>;
} {
function reduceQueriesToObject(
queryArray: any[],
): { queries: Record<string, any>; legends: Record<string, string> } {
const legends: Record<string, string> = {};
const queries = queryArray.reduce(
(acc, queryItem) => {
if (!queryItem.query) {
return acc;
}
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
const queries = queryArray.reduce((acc, queryItem) => {
if (!queryItem.query) {
return acc;
},
{} as Record<string, any>,
);
}
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
return acc;
}, {} as Record<string, any>);
return { queries, legends };
}
@@ -553,7 +554,7 @@ export const prepareQueryRangePayloadV5 = ({
queryTraceOperator && queryTraceOperator.length > 0
? queryTraceOperator.filter((traceOperator) =>
Boolean(traceOperator.expression.trim()),
)
)
: [];
const currentTraceOperator = mapQueryDataToApi(
@@ -647,18 +648,15 @@ export const prepareQueryRangePayloadV5 = ({
: graphType === PANEL_TYPES.TABLE),
fillGaps: fillGaps || false,
},
variables: Object.entries(variables).reduce(
(acc, [key, value]) => {
acc[key] = {
value,
type: dynamicVariables
?.find((v) => v.name === key)
?.type?.toLowerCase() as VariableType,
};
return acc;
},
{} as Record<string, VariableItem>,
),
variables: Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = {
value,
type: dynamicVariables
?.find((v) => v.name === key)
?.type?.toLowerCase() as VariableType,
};
return acc;
}, {} as Record<string, VariableItem>),
};
return { legendMap, queryPayload };

View File

@@ -74,7 +74,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background: radial-gradient(circle, #fff 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -9,7 +9,7 @@ jest.mock('../../../api/browser/localstorage/get', () => ({
}));
// Access the mocked function
const mockGet = getLocal as unknown as jest.Mock;
const mockGet = (getLocal as unknown) as jest.Mock;
describe('AppLoading', () => {
const SIGNOZ_TEXT = 'SigNoz';

View File

@@ -99,6 +99,36 @@
}
}
.lightMode {
.auth-error-container {
.error-content {
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -87,3 +87,23 @@
background: var(--l3-background);
flex-shrink: 0;
}
.lightMode {
.auth-footer-content {
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
}
.auth-footer-icon {
filter: brightness(0) saturate(100%) invert(25%) sepia(8%) saturate(518%)
hue-rotate(192deg) brightness(80%) contrast(95%);
opacity: 0.9;
}
.auth-footer-text {
color: var(--text-neutral-light-200);
}
.auth-footer-link-icon {
color: var(--text-neutral-light-100);
}
}

View File

@@ -143,3 +143,28 @@
}
}
}
.lightMode {
.bg-dot-pattern {
background: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 1px
);
background-size: 12px 12px;
}
.auth-page-gradient {
background: radial-gradient(
ellipse at center top,
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
transparent 60%
);
opacity: 0.8;
filter: blur(200px);
@media (min-width: 768px) {
filter: blur(300px);
}
}
}

View File

@@ -31,8 +31,9 @@ export function FilterSelect({
onChange,
isMultiple,
}: SelectOptionConfig): JSX.Element {
const { handleSearch, isFetching, options } =
useCeleryFilterOptions(filterType);
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
filterType,
);
const urlQuery = useUrlQuery();
const history = useHistory();

View File

@@ -280,28 +280,30 @@ function getTableData(data: QueueOverviewResponse['data']): RowData[] {
];
const tableData: RowData[] =
data?.map((row, index: number): RowData => {
const rowData: Record<string, string | number> = {};
columnOrder.forEach((key) => {
const value = row.data[key as keyof typeof row.data];
if (typeof value === 'string' || typeof value === 'number') {
rowData[key] = value;
}
});
Object.entries(row.data).forEach(([key, value]) => {
if (
!columnOrder.includes(key) &&
(typeof value === 'string' || typeof value === 'number')
) {
rowData[key] = value;
}
});
data?.map(
(row, index: number): RowData => {
const rowData: Record<string, string | number> = {};
columnOrder.forEach((key) => {
const value = row.data[key as keyof typeof row.data];
if (typeof value === 'string' || typeof value === 'number') {
rowData[key] = value;
}
});
Object.entries(row.data).forEach(([key, value]) => {
if (
!columnOrder.includes(key) &&
(typeof value === 'string' || typeof value === 'number')
) {
rowData[key] = value;
}
});
return {
...rowData,
key: index,
};
}) || [];
return {
...rowData,
key: index,
};
},
) || [];
return tableData;
}
@@ -478,10 +480,10 @@ export default function CeleryOverviewTable({
[searchText],
);
const filteredData = useMemo(
() => getFilteredData(tableData),
[getFilteredData, tableData],
);
const filteredData = useMemo(() => getFilteredData(tableData), [
getFilteredData,
tableData,
]);
const prevTableDataRef = useRef<string>();

View File

@@ -13,8 +13,9 @@ import { useCeleryFilterOptions } from '../useCeleryFilterOptions';
import './CeleryTaskConfigOptions.styles.scss';
function CeleryTaskConfigOptions(): JSX.Element {
const { handleSearch, isFetching, options } =
useCeleryFilterOptions('celery.task_name');
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
'celery.task_name',
);
const history = useHistory();
const location = useLocation();

View File

@@ -41,6 +41,7 @@
justify-content: center;
align-items: center;
border: 1px solid var(--l1-border);
background: linear-gradient(0deg, transparent 0%, transparent 100%), #0b0c0e;
.ant-card-body {
height: 100%;
@@ -237,7 +238,18 @@
height: 2px;
bottom: 0;
left: 0;
background-color: var(--l1-background);
color: var(--l1-foreground);
background-color: var(--l1-foreground);
}
}
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
background: unset;
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
}
}

View File

@@ -469,7 +469,8 @@ export const celeryActiveTasksWidgetData = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'flower_worker_number_of_currently_executing_tasks--float64--Gauge--true',
id:
'flower_worker_number_of_currently_executing_tasks--float64--Gauge--true',
key: 'flower_worker_number_of_currently_executing_tasks',
type: 'Gauge',
},

View File

@@ -127,17 +127,22 @@ function CeleryTaskLatencyGraph({
const onGraphClickHandler = useGraphClickHandler(handleSetTimeStamp);
const onGraphClick = useCallback(
(type: string): OnClickPluginOpts['onClick'] =>
(xValue, yValue, mouseX, mouseY, data): Promise<void> => {
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = firstDataPoint;
setEntityData({
entity,
value,
});
(type: string): OnClickPluginOpts['onClick'] => (
xValue,
yValue,
mouseX,
mouseY,
data,
): Promise<void> => {
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = firstDataPoint;
setEntityData({
entity,
value,
});
return onGraphClickHandler(xValue, yValue, mouseX, mouseY, type);
},
return onGraphClickHandler(xValue, yValue, mouseX, mouseY, type);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[handleSetTimeStamp],
);

View File

@@ -92,10 +92,10 @@ function CeleryTaskStateGraphConfig({
{isLoading
? '-'
: isError
? '-'
: Number.isNaN(values[index])
? '-'
: Math.round(Number(values[index]))}
? '-'
: Number.isNaN(values[index])
? '-'
: Math.round(Number(values[index]))}
</div>
</div>
{tab.key === barState && <div className="celery-task-states__indicator" />}

View File

@@ -65,7 +65,7 @@ export function applyCeleryFilterOnWidgetData(
items: [...(queryItem.filters?.items || []), ...filters],
op: queryItem.filters?.op || 'AND',
},
}
}
: queryItem,
),
},

View File

@@ -9,7 +9,7 @@ import { paths } from './CeleryUtils';
interface UseGetGraphCustomSeriesProps {
isDarkMode: boolean;
drawStyle?: (typeof drawStyles)[keyof typeof drawStyles];
drawStyle?: typeof drawStyles[keyof typeof drawStyles];
colorMapping?: Record<string, string>;
}

View File

@@ -37,8 +37,9 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
preference.name === USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
)?.value as string;
const { mutate: updateUserPreferenceMutation } =
useMutation(updateUserPreference);
const { mutate: updateUserPreferenceMutation } = useMutation(
updateUserPreference,
);
useEffect(() => {
// Update the seen version
@@ -59,8 +60,11 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
const checkScroll = useCallback((): void => {
if (changelogContentSectionRef.current) {
const { scrollHeight, clientHeight, scrollTop } =
changelogContentSectionRef.current;
const {
scrollHeight,
clientHeight,
scrollTop,
} = changelogContentSectionRef.current;
const isAtBottom = scrollHeight - clientHeight - scrollTop <= 8;
setHasScroll(scrollHeight > clientHeight + 24 && !isAtBottom); // 24px - buffer height to show show more
}

View File

@@ -14,8 +14,9 @@ import { getBaseUrl } from 'utils/basePath';
export default function ChatSupportGateway(): JSX.Element {
const { notifications } = useNotifications();
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
useState(false);
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
false,
);
const handleBillingOnSuccess = (
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,

View File

@@ -388,7 +388,7 @@ function ClientSideQBSearch(
({
label: key.key,
value: key,
}) as Option,
} as Option),
) || [],
);
}
@@ -462,7 +462,7 @@ function ClientSideQBSearch(
({
label: checkCommaInValue(String(val)),
value: val,
}) as Option,
} as Option),
),
);
} else {
@@ -490,7 +490,7 @@ function ClientSideQBSearch(
Array.isArray(tag.value) &&
tag.value[tag.value.length - 1] === ''
? tag.value?.slice(0, -1)
: (tag.value ?? '');
: tag.value ?? '';
filterTags.items.push({
id: tag.id || uuid().slice(0, 8),
key: tag.key,

View File

@@ -32,7 +32,6 @@ function CreateServiceAccountModal(): JSX.Element {
SA_QUERY_PARAMS.CREATE_SA,
parseAsBoolean.withDefault(false),
);
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
const { showErrorModal, isErrorModalVisible } = useErrorModal();
@@ -48,28 +47,29 @@ function CreateServiceAccountModal(): JSX.Element {
},
});
const { mutate: createServiceAccount, isLoading: isSubmitting } =
useCreateServiceAccount({
mutation: {
onSuccess: async (response) => {
toast.success('Service account created successfully');
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
await setSelectedAccountId(response.data.id);
},
onError: (err) => {
const errMessage = convertToApiError(
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
);
showErrorModal(errMessage as APIError);
},
const {
mutate: createServiceAccount,
isLoading: isSubmitting,
} = useCreateServiceAccount({
mutation: {
onSuccess: async () => {
toast.success('Service account created successfully');
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
},
});
onError: (err) => {
const errMessage = convertToApiError(
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
);
showErrorModal(errMessage as APIError);
},
},
});
function handleClose(): void {
reset();
void setIsOpen(null);
setIsOpen(null);
}
function handleCreate(values: FormValues): void {

View File

@@ -77,11 +77,11 @@
width: 280px;
&::placeholder {
color: var(--l1-foreground);
color: white;
}
&:focus::placeholder {
color: rgba($color: var(--l1-foreground), $alpha: 0.4);
color: rgba($color: #ffffff, $alpha: 0.4);
}
}
}
@@ -113,6 +113,42 @@
}
}
.lightMode {
.time-options-container {
.time-options-item {
&.active {
background-color: rgba($color: #ffffff, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
input::placeholder {
color: var(---bg-ink-300);
}
input:focus::placeholder {
color: rgba($color: #000000, $alpha: 0.4);
}
}
}
.date-time-popover__footer {
border-top: 1px solid var(--l1-border);
padding: 8px 14px;
@@ -264,3 +300,34 @@
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
.lightMode {
.timezone-container {
.timezone {
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--l1-foreground);
}
}
}
.custom-time-picker {
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
}
}
}
.timezone-badge {
background: rgb(179 179 179 / 15%);
}
.time-input-suffix-icon-badge {
background: rgb(179 179 179 / 15%);
&:hover {
background: rgb(179 179 179 / 20%);
}
}
}

View File

@@ -96,8 +96,10 @@ function CustomTimePicker({
maxTime,
isModalTimeSelection = false,
}: CustomTimePickerProps): JSX.Element {
const [selectedTimePlaceholderValue, setSelectedTimePlaceholderValue] =
useState('Select / Enter Time Range');
const [
selectedTimePlaceholderValue,
setSelectedTimePlaceholderValue,
] = useState('Select / Enter Time Range');
const [inputValue, setInputValue] = useState('');
const [inputStatus, setInputStatus] = useState<CustomTimePickerInputStatus>(

View File

@@ -116,10 +116,9 @@ function CustomTimePickerPopoverContent({
}: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation();
const isLogsExplorerPage = useMemo(
() => pathname === ROUTES.LOGS_EXPLORER,
[pathname],
);
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
pathname,
]);
const url = new URLSearchParams(window.location.search);
@@ -155,8 +154,8 @@ function CustomTimePickerPopoverContent({
if (!customDateTimeVisible) {
const customTimeRanges = getCustomTimeRanges();
const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] =
customTimeRanges.map((range) => ({
const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] = customTimeRanges.map(
(range) => ({
label: `${dayjs(range.from)
.tz(timezone.value)
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs(range.to)
@@ -166,7 +165,8 @@ function CustomTimePickerPopoverContent({
to: range.to,
value: range.timestamp,
timestamp: range.timestamp,
}));
}),
);
setRecentlyUsedTimeRanges(formattedCustomTimeRanges);
}

View File

@@ -129,3 +129,20 @@ $item-spacing: 8px;
width: 15px;
}
}
.lightMode {
.timezone-picker {
&__search {
.search-icon {
stroke: var(--l1-foreground);
}
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--l1-foreground);
}
}
}
}

View File

@@ -19,7 +19,7 @@ const TIMEZONE_TYPES = {
STANDARD: 'STANDARD',
} as const;
type TimezoneType = (typeof TIMEZONE_TYPES)[keyof typeof TIMEZONE_TYPES];
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
export const UTC_TIMEZONE: Timezone = {
name: 'Coordinated Universal Time — UTC, GMT',

View File

@@ -41,7 +41,8 @@ function DraggableTableRow({
);
}
interface DraggableTableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
interface DraggableTableRowProps
extends React.HTMLAttributes<HTMLTableRowElement> {
index: number;
moveRow: (dragIndex: number, hoverIndex: number) => void;
}

View File

@@ -6,9 +6,9 @@ export function dropHandler(monitor: DropTargetMonitor): { isOver: boolean } {
};
}
export function dragHandler(monitor: DragSourceMonitor): {
isDragging: boolean;
} {
export function dragHandler(
monitor: DragSourceMonitor,
): { isDragging: boolean } {
return {
isDragging: monitor.isDragging(),
};

View File

@@ -1,5 +1,9 @@
.dropdown-button {
color: var(--l1-foreground);
color: #fff;
}
.dropdown-button--dark {
color: #000;
}
.dropdown-icon {

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import './DropDown.styles.scss';
@@ -11,6 +12,8 @@ function DropDown({
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
@@ -32,7 +35,7 @@ function DropDown({
>
<Button
type="link"
className={`dropdown-button`}
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);

View File

@@ -158,8 +158,10 @@ function EditMemberDrawer({
new Date(String(existingToken.expiresAt)) < new Date();
// Create/regenerate token mutation
const { mutateAsync: createTokenMutation, isLoading: isGeneratingLink } =
useCreateResetPasswordToken();
const {
mutateAsync: createTokenMutation,
isLoading: isGeneratingLink,
} = useCreateResetPasswordToken();
const fetchedDisplayName =
fetchedUser?.data?.displayName ?? member?.name ?? '';
@@ -219,20 +221,22 @@ function EditMemberDrawer({
});
const makeRoleRetry = useCallback(
(context: string, rawRetry: () => Promise<void>) =>
async (): Promise<void> => {
try {
await rawRetry();
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
refetchUser();
} catch (err) {
setSaveErrors((prev) =>
prev.map((e) =>
e.context === context ? { ...e, apiError: toSaveApiError(err) } : e,
),
);
}
},
(
context: string,
rawRetry: () => Promise<void>,
) => async (): Promise<void> => {
try {
await rawRetry();
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
refetchUser();
} catch (err) {
setSaveErrors((prev) =>
prev.map((e) =>
e.context === context ? { ...e, apiError: toSaveApiError(err) } : e,
),
);
}
},
[refetchUser],
);
@@ -276,7 +280,7 @@ function EditMemberDrawer({
: updateUser({
pathParams: { id: member.id },
data: { displayName: localDisplayName },
})
})
: Promise.resolve();
const [nameResult, rolesResult] = await Promise.allSettled([
@@ -385,7 +389,7 @@ function EditMemberDrawer({
? formatTimezoneAdjustedTimestamp(
String(response.data.expiresAt),
DATE_TIME_FORMATS.DASH_DATETIME,
)
)
: null,
);
setHasCopiedResetLink(false);
@@ -494,8 +498,8 @@ function EditMemberDrawer({
isRootUser
? ROOT_USER_TOOLTIP
: isDeleted
? undefined
: 'You cannot modify your own role'
? undefined
: 'You cannot modify your own role'
}
>
<div className="edit-member-drawer__input-wrapper edit-member-drawer__input-wrapper--disabled">
@@ -618,13 +622,13 @@ function EditMemberDrawer({
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</span>
</Tooltip>

View File

@@ -168,12 +168,9 @@
gap: 3px;
background: var(--l1-border);
border-radius: 20px;
box-shadow:
0px 103px 12px 0px rgba(0, 0, 0, 0.01),
0px 66px 18px 0px rgba(0, 0, 0, 0.01),
0px 37px 22px 0px rgba(0, 0, 0, 0.03),
0px 17px 17px 0px rgba(0, 0, 0, 0.04),
0px 4px 9px 0px rgba(0, 0, 0, 0.04);
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
0px 66px 18px 0px rgba(0, 0, 0, 0.01), 0px 37px 22px 0px rgba(0, 0, 0, 0.03),
0px 17px 17px 0px rgba(0, 0, 0, 0.04), 0px 4px 9px 0px rgba(0, 0, 0, 0.04);
}
&__scroll-hint-text {

View File

@@ -25,14 +25,15 @@ function ErrorContent({ error, icon }: ErrorContentProps): JSX.Element {
errors: errorMessages,
code: errorCode,
message: errorMessage,
} = error && 'error' in error
? error?.error?.error || {}
: {
url: undefined,
errors: [],
code: error.code || 500,
message: error.message || 'Something went wrong',
};
} =
error && 'error' in error
? error?.error?.error || {}
: {
url: undefined,
errors: [],
code: error.code || 500,
message: error.message || 'Something went wrong',
};
return (
<section className="error-content">
{/* Summary Header */}

View File

@@ -23,8 +23,11 @@ function MenuItemGenerator({
refetchAllView,
sourcePage,
}: MenuItemLabelGeneratorProps): JSX.Element {
const { panelType, redirectWithQueryBuilderData, updateAllQueriesOperators } =
useQueryBuilder();
const {
panelType,
redirectWithQueryBuilderData,
updateAllQueriesOperators,
} = useQueryBuilder();
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const { notifications } = useNotifications();

View File

@@ -17,8 +17,11 @@ function SaveViewWithName({
}: SaveViewWithNameProps): JSX.Element {
const [form] = Form.useForm<SaveViewFormProps>();
const { t } = useTranslation(['explorer']);
const { currentQuery, panelType, redirectWithQueryBuilderData } =
useQueryBuilder();
const {
currentQuery,
panelType,
redirectWithQueryBuilderData,
} = useQueryBuilder();
const { notifications } = useNotifications();
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);

View File

@@ -1,7 +1,8 @@
import { QueryParams } from 'constants/query';
export const ExploreHeaderToolTip = {
url: 'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
url:
'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
text: 'More details on how to use query builder',
};

View File

@@ -55,8 +55,9 @@ function createMousedownHandler(
startDragPositionX = right;
}
const startValuePositionX =
chart.scales.x.getValueForPixel(startDragPositionX);
const startValuePositionX = chart.scales.x.getValueForPixel(
startDragPositionX,
);
dragData.onDragStart(startDragPositionX, startValuePositionX);
};
@@ -108,8 +109,9 @@ function createMouseupHandler(
endRelativePostionX = right;
}
const endValuePositionX =
chart.scales.x.getValueForPixel(endRelativePostionX);
const endValuePositionX = chart.scales.x.getValueForPixel(
endRelativePostionX,
);
dragData.onDragEnd(endRelativePostionX, endValuePositionX);

View File

@@ -12,12 +12,11 @@ export type IntersectionCursorPluginOptions = {
gapSize?: number;
};
export const defaultIntersectionCursorPluginOptions: Required<IntersectionCursorPluginOptions> =
{
color: 'white',
dashSize: 3,
gapSize: 3,
};
export const defaultIntersectionCursorPluginOptions: Required<IntersectionCursorPluginOptions> = {
color: 'white',
dashSize: 3,
gapSize: 3,
};
export function createIntersectionCursorPluginOptions(
isEnabled: boolean,

View File

@@ -52,7 +52,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => ({
])
? get(chart, ['options', 'plugins', 'legend', 'labels', 'generateLabels'])(
chart,
)
)
: null;
items?.forEach((item: Record<any, any>, index: number) => {

View File

@@ -95,7 +95,7 @@ export const getGraphOptions = (
},
],
},
}
}
: {}),
title: {
display: title !== undefined,

View File

@@ -177,9 +177,7 @@
color: var(--destructive);
opacity: 0.6;
padding: 0;
transition:
background-color 0.2s,
opacity 0.2s;
transition: background-color 0.2s, opacity 0.2s;
box-shadow: none;
&:hover {

View File

@@ -47,8 +47,9 @@ function LaunchChatSupport({
featureFlagsFetchError,
isLoggedIn,
} = useAppContext();
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
useState(false);
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
false,
);
const { pathname } = useLocation();

View File

@@ -5,7 +5,7 @@ export const VIEW_TYPES = {
INFRAMETRICS: 'INFRAMETRICS',
} as const;
export type VIEWS = (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES];
export type VIEWS = typeof VIEW_TYPES[keyof typeof VIEW_TYPES];
export const RESOURCE_KEYS = {
CLUSTER_NAME: 'k8s.cluster.name',

View File

@@ -261,7 +261,7 @@ function LogDetailInner({
...query.filter,
expression: value,
},
}
}
: query,
),
},

View File

@@ -20,10 +20,9 @@ function AddToQueryHOC({
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], dataType);
};
const popOverContent = useMemo(
() => <span>Add to query: {fieldKey}</span>,
[fieldKey],
);
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey,
]);
return (
<div className={cx('addToQueryContainer', fontSize)} onClick={handleQueryAdd}>

View File

@@ -104,7 +104,7 @@ type ListLogViewProps = {
selectedFields: IField[];
onSetActiveLog: (
log: ILog,
selectedTab?: (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES],
selectedTab?: typeof VIEW_TYPES[keyof typeof VIEW_TYPES],
) => void;
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
activeLog?: ILog | null;
@@ -166,11 +166,11 @@ function ListLogView({
? formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
)
)
: formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp / 1e6,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
),
),
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
);

View File

@@ -24,10 +24,10 @@ export const Container = styled(Card)<{
fontSize === FontSize.SMALL
? `margin-bottom:0.1rem;`
: fontSize === FontSize.MEDIUM
? `margin-bottom: 0.2rem;`
: fontSize === FontSize.LARGE
? `margin-bottom:0.3rem;`
: ``}
? `margin-bottom: 0.2rem;`
: fontSize === FontSize.LARGE
? `margin-bottom:0.3rem;`
: ``}
cursor: pointer;
&:not(:hover) .log-line-action-buttons {
@@ -41,10 +41,10 @@ export const Container = styled(Card)<{
fontSize === FontSize.SMALL
? `padding:0.1rem 0.6rem;`
: fontSize === FontSize.MEDIUM
? `padding: 0.2rem 0.6rem;`
: fontSize === FontSize.LARGE
? `padding:0.3rem 0.6rem;`
: ``}
? `padding: 0.2rem 0.6rem;`
: fontSize === FontSize.LARGE
? `padding:0.3rem 0.6rem;`
: ``}
${({ $isActiveLog, $isDarkMode, $logType }): string =>
getActiveLogBackground($isActiveLog, $isDarkMode, $logType)}
@@ -65,8 +65,8 @@ export const LogContainer = styled.div<LogContainerProps>`
fontSize === FontSize.SMALL
? `gap: 2px;`
: fontSize === FontSize.MEDIUM
? ` gap:4px;`
: `gap:6px;`}
? ` gap:4px;`
: `gap:6px;`}
`;
export const LogText = styled.div<LogTextProps>`

View File

@@ -88,11 +88,11 @@ function RawLogView({
? formatTimezoneAdjustedTimestamp(
data.timestamp,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
)
)
: formatTimezoneAdjustedTimestamp(
data.timestamp / 1e6,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
);
);
parts.push(date);
}

View File

@@ -39,22 +39,22 @@ export const RawLogViewContainer = styled(Row)<{
fontSize === FontSize.SMALL
? `margin: 1px 0;`
: fontSize === FontSize.MEDIUM
? `margin: 1px 0;`
: `margin: 2px 0;`}
? `margin: 1px 0;`
: `margin: 2px 0;`}
}
${({ $isReadOnly, $isActiveLog, $isDarkMode, $logType }): string =>
$isActiveLog
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
: !$isReadOnly
? `&:hover { ${getActiveLogBackground(true, $isDarkMode, $logType)} }`
: ''}
? `&:hover { ${getActiveLogBackground(true, $isDarkMode, $logType)} }`
: ''}
${({ $isHightlightedLog, $isDarkMode }): string =>
$isHightlightedLog
? `background-color: ${
$isDarkMode ? Color.BG_ROBIN_600 : Color.BG_VANILLA_400
};
};
transition: background-color 2s ease-in;`
: ''}
@@ -104,8 +104,8 @@ export const RawLogContent = styled.div<RawLogContentProps>`
fontSize === FontSize.SMALL
? `font-size:11px; line-height:16px; padding:1px;`
: fontSize === FontSize.MEDIUM
? `font-size:13px; line-height:20px; padding:1px;`
: `font-size:14px; line-height:24px; padding:2px;`}
? `font-size:13px; line-height:20px; padding:1px;`
: `font-size:14px; line-height:24px; padding:2px;`}
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};

View File

@@ -19,7 +19,7 @@ export interface RawLogViewProps {
handleChangeSelectedView?: ChangeViewFunctionType;
onSetActiveLog?: (
log: ILog,
selectedTab?: (typeof VIEW_TYPES)[keyof typeof VIEW_TYPES],
selectedTab?: typeof VIEW_TYPES[keyof typeof VIEW_TYPES],
) => void;
onClearActiveLog?: () => void;
}

View File

@@ -27,6 +27,6 @@ export const TableBodyContent = styled.div<TableBodyContentProps>`
fontSize === FontSize.SMALL
? `font-size:11px; line-height:16px;`
: fontSize === FontSize.MEDIUM
? `font-size:13px; line-height:20px;`
: `font-size:14px; line-height:24px;`}
? `font-size:13px; line-height:20px;`
: `font-size:14px; line-height:24px;`}
`;

View File

@@ -75,10 +75,10 @@ export function useLogsTableColumns({
: formatTimezoneAdjustedTimestamp(
ts / 1e6,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
);
);
return <TanStackTable.Text>{formatted}</TanStackTable.Text>;
},
}
}
: null;
const bodyCol: TableColumnDef<ILog> | null = fields.some(
@@ -100,7 +100,7 @@ export function useLogsTableColumns({
data-active={isActive}
/>
),
}
}
: null;
return [

View File

@@ -34,10 +34,9 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
const isDarkMode = useIsDarkMode();
const flattenLogData = useMemo(
() => logs.map((log) => FlatLogData(log)),
[logs],
);
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
logs,
]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
@@ -116,11 +115,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
? formatTimezoneAdjustedTimestamp(
field,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
)
)
: formatTimezoneAdjustedTimestamp(
field / 1e6,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
);
);
return {
children: (
<div className="table-timestamp">
@@ -130,7 +129,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
};
},
},
]
]
: []),
...(appendTo === 'center' ? fieldColumns : []),
...(fields.some((field) => field.name === 'body')
@@ -161,7 +160,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
),
}),
},
]
]
: []),
...(appendTo === 'end' ? fieldColumns : []),
];

View File

@@ -35,11 +35,13 @@ function OptionsMenu({
const [fontSizeValue, setFontSizeValue] = useState<FontSize>(
fontSize?.value || FontSize.SMALL,
);
const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] =
useState<boolean>(false);
const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] = useState<boolean>(
false,
);
const [showAddNewColumnContainer, setShowAddNewColumnContainer] =
useState(false);
const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState(
false,
);
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const listRef = useRef<HTMLDivElement>(null);

View File

@@ -196,3 +196,17 @@
opacity: 0.5;
}
}
.lightMode {
.members-table {
.ant-table-tbody {
> tr.members-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -167,3 +167,22 @@
padding-right: 8px;
}
}
.lightMode {
.config-btn {
&.missing-config-btn {
background: var(--bg-amber-100);
color: var(--bg-amber-500);
&:hover {
color: var(--bg-amber-600) !important;
}
}
.missing-config-btn {
.config-btn-content {
border-right: 1px solid var(--bg-amber-600);
}
}
}
}

View File

@@ -260,28 +260,23 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
/**
* Separates section and non-section options
*/
const splitOptions = useCallback(
(
options: OptionData[],
): {
sectionOptions: OptionData[];
nonSectionOptions: OptionData[];
} => {
const sectionOptions: OptionData[] = [];
const nonSectionOptions: OptionData[] = [];
const splitOptions = useCallback((options: OptionData[]): {
sectionOptions: OptionData[];
nonSectionOptions: OptionData[];
} => {
const sectionOptions: OptionData[] = [];
const nonSectionOptions: OptionData[] = [];
options.forEach((option) => {
if ('options' in option && Array.isArray(option.options)) {
sectionOptions.push(option);
} else {
nonSectionOptions.push(option);
}
});
options.forEach((option) => {
if ('options' in option && Array.isArray(option.options)) {
sectionOptions.push(option);
} else {
nonSectionOptions.push(option);
}
});
return { sectionOptions, nonSectionOptions };
},
[],
);
return { sectionOptions, nonSectionOptions };
}, []);
/**
* Apply search filtering to options
@@ -1634,7 +1629,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}}
data={enhancedNonSectionOptions}
itemContent={(index, item): React.ReactNode =>
mapOptions([item]) as unknown as React.ReactElement
(mapOptions([item]) as unknown) as React.ReactElement
}
totalCount={enhancedNonSectionOptions.length}
itemSize={(): number => 40}
@@ -1676,7 +1671,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}}
data={section.options || []}
itemContent={(index, item): React.ReactNode =>
mapOptions([item]) as unknown as React.ReactElement
(mapOptions([item]) as unknown) as React.ReactElement
}
totalCount={section.options?.length || 0}
itemSize={(): number => 40}
@@ -1941,7 +1936,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
? {
borderColor: Color.BG_ROBIN_500,
backgroundColor: Color.BG_SLATE_400,
}
}
: undefined
}
>

View File

@@ -112,28 +112,23 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
/**
* Separates section and non-section options
*/
const splitOptions = useCallback(
(
options: OptionData[],
): {
sectionOptions: OptionData[];
nonSectionOptions: OptionData[];
} => {
const sectionOptions: OptionData[] = [];
const nonSectionOptions: OptionData[] = [];
const splitOptions = useCallback((options: OptionData[]): {
sectionOptions: OptionData[];
nonSectionOptions: OptionData[];
} => {
const sectionOptions: OptionData[] = [];
const nonSectionOptions: OptionData[] = [];
options.forEach((option) => {
if ('options' in option && Array.isArray(option.options)) {
sectionOptions.push(option);
} else {
nonSectionOptions.push(option);
}
});
options.forEach((option) => {
if ('options' in option && Array.isArray(option.options)) {
sectionOptions.push(option);
} else {
nonSectionOptions.push(option);
}
});
return { sectionOptions, nonSectionOptions };
},
[],
);
return { sectionOptions, nonSectionOptions };
}, []);
/**
* Apply search filtering to options
@@ -327,8 +322,9 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
processedOptions = filterOptionsBySearch(processedOptions, searchText);
}
const { sectionOptions, nonSectionOptions } =
splitOptions(processedOptions);
const { sectionOptions, nonSectionOptions } = splitOptions(
processedOptions,
);
// Add custom option if needed
if (

View File

@@ -336,7 +336,7 @@ describe('CustomMultiSelect Component', () => {
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={'__ALL__' as unknown as string[]}
value={('__ALL__' as unknown) as string[]}
/>,
);

View File

@@ -16,7 +16,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
color: color-mix(in srgb, var(--border) 45%, transparent);
}
// Base styles are for dark mode
@@ -48,6 +48,10 @@ $custom-border-color: #2c3044;
visibility: visible !important;
pointer-events: none;
z-index: 2;
.lightMode & {
color: rgba(0, 0, 0, 0.85) !important;
}
}
&.ant-select-focused .ant-select-selection-placeholder {
@@ -63,6 +67,10 @@ $custom-border-color: #2c3044;
color: var(--l2-foreground);
z-index: 1;
pointer-events: none;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
.ant-select-selector {
@@ -106,7 +114,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
color: color-mix(in srgb, var(--border) 45%, transparent);
}
// Customize tags in multiselect (dark mode by default)
@@ -172,9 +180,7 @@ $custom-border-color: #2c3044;
.custom-multiselect-dropdown-container {
z-index: 1050 !important;
padding: 0;
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.5),
0 6px 16px 0 rgba(0, 0, 0, 0.4),
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.5), 0 6px 16px 0 rgba(0, 0, 0, 0.4),
0 9px 28px 8px rgba(0, 0, 0, 0.3);
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
@@ -209,7 +215,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
color: color-mix(in srgb, var(--border) 45%, transparent);
}
}
@@ -567,7 +573,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
color: color-mix(in srgb, var(--border) 45%, transparent);
}
.status-message {
@@ -798,10 +804,8 @@ $custom-border-color: #2c3044;
.custom-multiselect-dropdown-container {
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
.empty-message {
color: var(--l2-foreground);
@@ -972,9 +976,11 @@ $custom-border-color: #2c3044;
font-weight: 500;
z-index: 2;
pointer-events: none;
transition:
opacity 0.2s ease,
visibility 0.2s ease;
transition: opacity 0.2s ease, visibility 0.2s ease;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
&:focus-within .all-text {

View File

@@ -40,10 +40,8 @@ export interface CustomTagProps {
onClose: () => void;
}
export interface CustomMultiSelectProps extends Omit<
SelectProps<string[] | string>,
'options'
> {
export interface CustomMultiSelectProps
extends Omit<SelectProps<string[] | string>, 'options'> {
placeholder?: string;
className?: string;
loading?: boolean;

View File

@@ -26,7 +26,7 @@ function OverlayScrollbar({
theme: isDarkMode ? 'os-theme-light' : 'os-theme-dark',
},
...(customOptions || {}),
}) as PartialOptions,
} as PartialOptions),
[customOptions, isDarkMode],
);

View File

@@ -10,14 +10,8 @@
border-bottom: 1px solid var(--l1-border);
border-top: 1px solid var(--l1-border);
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
sans-serif;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
border-right: none;
border-left: none;

View File

@@ -76,34 +76,32 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
[showTraceOperator, isListViewPanel],
);
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] =
useMemo(() => {
const config: QueryBuilderProps['filterConfigs'] = {
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
const config: QueryBuilderProps['filterConfigs'] = {
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
return config;
}, []);
return config;
}, []);
const listViewTracesFilterConfigs: QueryBuilderProps['filterConfigs'] =
useMemo(() => {
const config: QueryBuilderProps['filterConfigs'] = {
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
limit: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
const listViewTracesFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
const config: QueryBuilderProps['filterConfigs'] = {
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
limit: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
return config;
}, []);
return config;
}, []);
const queryFilterConfigs = useMemo(() => {
if (isListViewPanel) {

View File

@@ -49,8 +49,11 @@ export const MetricsSelect = memo(function MetricsSelect({
entityVersion: version,
});
const { updateAllQueriesOperators, handleSetQueryData, panelType } =
useQueryBuilder();
const {
updateAllQueriesOperators,
handleSetQueryData,
panelType,
} = useQueryBuilder();
const source = useMemo(
() => (signalSource === 'meter' ? 'meter' : 'metrics'),
@@ -120,8 +123,9 @@ export const MetricsSelect = memo(function MetricsSelect({
signalSource: newSignalSource,
panelType: panelType || '',
});
const savedQuery: IBuilderQuery | null =
getPreviousQueryFromKey(newQueryKey);
const savedQuery: IBuilderQuery | null = getPreviousQueryFromKey(
newQueryKey,
);
// remove the new query key from session storage
removeKeyFromPreviousQuery(newQueryKey);

View File

@@ -464,37 +464,36 @@ function QueryAddOns({
</div>
)}
{selectedViews.find((view) => view.key === 'reduce_to') &&
showReduceTo && (
<div className="add-on-content" data-testid="reduce-to-content">
<div className="periscope-input-with-label">
<Tooltip
title={
<TooltipContent
label="Reduce to"
description="Apply mathematical operations like sum, average, min, max, or percentiles to reduce multiple time series into a single value."
docLink="https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations"
/>
}
placement="top"
mouseEnterDelay={0.5}
>
<div className="label" style={{ cursor: 'help' }}>
Reduce to
</div>
</Tooltip>
<div className="input">
<ReduceToFilter query={query} onChange={handleChangeReduceToV5} />
{selectedViews.find((view) => view.key === 'reduce_to') && showReduceTo && (
<div className="add-on-content" data-testid="reduce-to-content">
<div className="periscope-input-with-label">
<Tooltip
title={
<TooltipContent
label="Reduce to"
description="Apply mathematical operations like sum, average, min, max, or percentiles to reduce multiple time series into a single value."
docLink="https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations"
/>
}
placement="top"
mouseEnterDelay={0.5}
>
<div className="label" style={{ cursor: 'help' }}>
Reduce to
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
onClick={(): void => handleRemoveView('reduce_to')}
/>
</Tooltip>
<div className="input">
<ReduceToFilter query={query} onChange={handleChangeReduceToV5} />
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
onClick={(): void => handleRemoveView('reduce_to')}
/>
</div>
)}
</div>
)}
{selectedViews.find((view) => view.key === 'legend_format') && (
<div className="add-on-content" data-testid="legend-format-content">

View File

@@ -249,6 +249,57 @@
}
}
.lightMode {
.query-aggregation-container {
.aggregation-container {
.query-aggregation-select-container {
.query-aggregation-select-editor {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
backdrop-filter: none;
ul {
li {
&:hover,
&[aria-selected='true'] {
background: var(--l3-background) !important;
}
}
}
}
.cm-line {
::-moz-selection {
background: var(--l2-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l1-background) !important;
opacity: 0.5 !important;
}
.cm-function {
color: var(--primary-background) !important;
}
}
}
}
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-background);
border: none;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-border);

View File

@@ -290,12 +290,12 @@ function QueryAggregationSelect({
}
const regex = /([a-zA-Z_][\w]*)\s*\(([^)]*)\)/g;
const oldMatches = [...tr.startState.doc.toString().matchAll(regex)].filter(
(match) => validFunctions.includes(match[1].toLowerCase()),
);
const newMatches = [...tr.newDoc.toString().matchAll(regex)].filter(
(match) => validFunctions.includes(match[1].toLowerCase()),
);
const oldMatches = [
...tr.startState.doc.toString().matchAll(regex),
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
const newMatches = [
...tr.newDoc.toString().matchAll(regex),
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
if (
newMatches.length > oldMatches.length &&
@@ -558,7 +558,7 @@ function QueryAggregationSelect({
? availableSuggestions
: availableSuggestions.filter((suggestion) =>
suggestion.label.toLowerCase().includes(inputText.toLowerCase()),
);
);
return {
from: startOfArg,

View File

@@ -5,14 +5,8 @@
display: flex;
flex-direction: column;
gap: 8px;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
sans-serif;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
.query-where-clause-editor-container {
position: relative;

View File

@@ -194,8 +194,10 @@ function QuerySearch({
const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 });
const [isFetchingCompleteValuesList, setIsFetchingCompleteValuesList] =
useState<boolean>(false);
const [
isFetchingCompleteValuesList,
setIsFetchingCompleteValuesList,
] = useState<boolean>(false);
const lastPosRef = useRef<{ line: number; ch: number }>({ line: 0, ch: 0 });

View File

@@ -93,10 +93,9 @@ export const QueryV2 = forwardRef(function QueryV2(
[dataSource, panelType],
);
const showSpanScopeSelector = useMemo(
() => dataSource === DataSource.TRACES,
[dataSource],
);
const showSpanScopeSelector = useMemo(() => dataSource === DataSource.TRACES, [
dataSource,
]);
const showInlineQuerySearch = useMemo(() => {
if (!showTraceOperator) {
@@ -213,7 +212,7 @@ export const QueryV2 = forwardRef(function QueryV2(
icon: <Trash size={14} />,
onClick: handleDeleteQuery,
},
]
]
: []),
],
}}

View File

@@ -121,3 +121,44 @@
}
}
}
.lightMode {
.qb-trace-operator {
&-arrow {
&::before {
background: repeating-linear-gradient(
to right,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
&::after {
background-color: var(--l3-background);
}
}
&.non-list-view {
&::before {
background: repeating-linear-gradient(
to bottom,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
}
&-label-with-input {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
.label {
color: var(--l1-foreground) !important;
border-right: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -3,7 +3,7 @@ import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData';
import { getInvolvedQueriesInTraceOperator } from '../utils/utils';
const makeTraceOperator = (expression: string): IBuilderTraceOperator =>
({ expression }) as unknown as IBuilderTraceOperator;
(({ expression } as unknown) as IBuilderTraceOperator);
describe('getInvolvedQueriesInTraceOperator', () => {
it('returns empty array for empty input', () => {

View File

@@ -98,7 +98,9 @@ export function createTraceOperatorContext(
}
// Helper to determine token context
function determineTraceTokenContext(token: IToken): {
function determineTraceTokenContext(
token: IToken,
): {
isInAtom: boolean;
isInOperator: boolean;
isInParenthesis: boolean;

View File

@@ -36,14 +36,14 @@ beforeAll(() => {
const mockRange = {
// CodeMirror uses these for text measurement
getClientRects: (): DOMRectList =>
({
(({
length: 1,
item: (index: number): DOMRect | null => (index === 0 ? mockRect : null),
0: mockRect,
*[Symbol.iterator](): Generator<DOMRect> {
yield mockRect;
},
}) as unknown as DOMRectList,
} as unknown) as DOMRectList),
getBoundingClientRect: (): DOMRect => mockRect,
// CodeMirror calls these to set up text ranges
setStart: (node: Node, offset: number): void => {
@@ -72,7 +72,7 @@ beforeAll(() => {
},
commonAncestorContainer: document.body,
};
return mockRange as unknown as Range;
return (mockRange as unknown) as Range;
};
// Mock document.createRange to return a new Range instance each time

View File

@@ -94,12 +94,13 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
},
};
const updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'] =
(q) => q;
const updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'] = (
q,
) => q;
const updateQueriesData: QueryBuilderContextType['updateQueriesData'] = (q) =>
q;
const baseContext = {
const baseContext = ({
currentQuery: currentQueryObj,
stagedQuery: null,
lastUsedQuery: null,
@@ -132,7 +133,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
initQueryBuilderData: jest.fn(),
isStagedQueryUpdated: jest.fn(() => false),
isDefaultQuery: jest.fn(() => false),
} as unknown as QueryBuilderContextType;
} as unknown) as QueryBuilderContextType;
baseQBContext = baseContext;
mockedUseQueryBuilder.mockReturnValue(baseQBContext);
@@ -148,8 +149,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
handleChangeAggregatorAttribute: jest.fn(),
handleChangeDataSource: jest.fn(),
handleDeleteQuery: jest.fn(),
handleChangeQueryData:
jest.fn() as unknown as ReturnType<UseQueryOperations>['handleChangeQueryData'],
handleChangeQueryData: (jest.fn() as unknown) as ReturnType<UseQueryOperations>['handleChangeQueryData'],
handleChangeFormulaData: jest.fn(),
handleQueryFunctionsUpdates: handleQueryFunctionsUpdatesMock,
listOfAdditionalFormulaFilters: [],

View File

@@ -57,7 +57,7 @@ describe('MetricsSelect - signal source switching (standalone)', () => {
beforeEach(() => {
clearPreviousQuery();
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
(index: number, q: IBuilderQuery) => void
>;
@@ -222,7 +222,7 @@ describe('DataSource change - Logs to Traces', () => {
beforeEach(() => {
clearPreviousQuery();
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
(i: number, q: IBuilderQuery) => void
>;

View File

@@ -947,7 +947,7 @@ describe('convertAggregationToExpression', () => {
it('should handle undefined aggregateAttribute parameter with traces', () => {
const result = convertAggregationToExpression({
aggregateOperator: 'noop',
aggregateAttribute: undefined as unknown as BaseAutocompleteData,
aggregateAttribute: (undefined as unknown) as BaseAutocompleteData,
dataSource: DataSource.TRACES,
});
@@ -961,7 +961,7 @@ describe('convertAggregationToExpression', () => {
it('should handle undefined aggregateAttribute parameter with logs', () => {
const result = convertAggregationToExpression({
aggregateOperator: 'noop',
aggregateAttribute: undefined as unknown as BaseAutocompleteData,
aggregateAttribute: (undefined as unknown) as BaseAutocompleteData,
dataSource: DataSource.LOGS,
});

View File

@@ -210,8 +210,8 @@ export const convertExpressionToFilters = (
type: '',
},
value: pair.isMultiValue
? (formatValuesForFilter(pair.valueList as string[]) ?? [])
: (formatValuesForFilter(pair.value as string) ?? ''),
? formatValuesForFilter(pair.valueList as string[]) ?? []
: formatValuesForFilter(pair.value as string) ?? '',
});
});
@@ -469,8 +469,8 @@ export const convertFiltersToExpressionWithExistingQuery = (
type: '',
},
value: pair.isMultiValue
? (formatValuesForFilter(pair.valueList as string[]) ?? '')
: (formatValuesForFilter(pair.value as string) ?? ''),
? formatValuesForFilter(pair.valueList as string[]) ?? ''
: formatValuesForFilter(pair.value as string) ?? '',
});
}
});
@@ -554,7 +554,7 @@ export const removeKeysFromExpression = (
}
const value = pair.value?.toString().trim();
return value && value.includes('$');
})
})
: existingQueryPairs;
// Build a map for quick lookup of query pairs by their lowercase trimmed keys
@@ -744,18 +744,15 @@ export function getQueryLabelWithAggregation(
const labels: { label: string; value: string }[] = [];
const aggregationPerQuery =
queryData.reduce(
(acc, query) => {
if (query.queryName && query.aggregations?.length) {
acc[query.queryName] = createAggregation(query).map((a: any) => ({
alias: a.alias,
expression: a.expression,
}));
}
return acc;
},
{} as Record<string, any>,
) || {};
queryData.reduce((acc, query) => {
if (query.queryName && query.aggregations?.length) {
acc[query.queryName] = createAggregation(query).map((a: any) => ({
alias: a.alias,
expression: a.expression,
}));
}
return acc;
}, {} as Record<string, any>) || {};
Object.entries(aggregationPerQuery).forEach(([queryName, aggregations]) => {
const isMultipleAggregations = aggregations.length > 1;

View File

@@ -83,7 +83,7 @@ const createMockQueryBuilderData = (hasActiveFilters = false): any => ({
op: 'in',
value: [OTEL_DEMO, SAMPLE_FLASK],
},
]
]
: [],
},
},
@@ -99,7 +99,7 @@ describe('CheckboxFilter - User Flows', () => {
jest.clearAllMocks();
// Default mock implementations for useGetAggregateValues
mockUseGetAggregateValues.mockReturnValue({
mockUseGetAggregateValues.mockReturnValue(({
data: {
payload: {
stringAttributeValues: MOCK_SERVICE_NAMES,
@@ -107,7 +107,7 @@ describe('CheckboxFilter - User Flows', () => {
},
isLoading: false,
refetch: jest.fn(),
} as unknown as UseQueryResult<SuccessResponse<IAttributeValuesResponse>>);
} as unknown) as UseQueryResult<SuccessResponse<IAttributeValuesResponse>>);
// Default mock implementations for useGetQueryKeyValueSuggestions
// Returns data in the format expected by the hook

View File

@@ -82,8 +82,10 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
// Check if this filter has active filters in the query
const isSomeFilterPresentForCurrentAttribute = useMemo(
() =>
currentQuery.builder.queryData?.[activeQueryIndex]?.filters?.items?.some(
(item) => isKeyMatch(item.key?.key, filter.attributeKey.key),
currentQuery.builder.queryData?.[
activeQueryIndex
]?.filters?.items?.some((item) =>
isKeyMatch(item.key?.key, filter.attributeKey.key),
),
[currentQuery.builder.queryData, activeQueryIndex, filter.attributeKey.key],
);
@@ -124,16 +126,18 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
},
);
const { data: keyValueSuggestions, isLoading: isLoadingKeyValueSuggestions } =
useGetQueryKeyValueSuggestions({
key: filter.attributeKey.key,
signal: filter.dataSource || DataSource.LOGS,
signalSource: 'meter',
options: {
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
keepPreviousData: true,
},
});
const {
data: keyValueSuggestions,
isLoading: isLoadingKeyValueSuggestions,
} = useGetQueryKeyValueSuggestions({
key: filter.attributeKey.key,
signal: filter.dataSource || DataSource.LOGS,
signalSource: 'meter',
options: {
enabled: isOpen && source === QuickFiltersSource.METER_EXPLORER,
keepPreviousData: true,
},
});
const attributeValues: string[] = useMemo(() => {
const dataType = filter.attributeKey.dataType || DataTypes.String;
@@ -278,7 +282,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
idx === activeQueryIndex
? item.filters?.items?.filter(
(fil) => !isKeyMatch(fil.key?.key, filter.attributeKey.key),
) || []
) || []
: [...(item.filters?.items || [])],
op: item.filters?.op || 'AND',
},

View File

@@ -36,13 +36,12 @@ function Duration({
onFilterChange?: (query: Query) => void;
source?: QuickFiltersSource;
}): JSX.Element {
const [selectedFilters, setSelectedFilters] =
useState<
Record<
AllTraceFilterKeys,
{ values: string[] | string; keys: BaseAutocompleteData }
>
>();
const [selectedFilters, setSelectedFilters] = useState<
Record<
AllTraceFilterKeys,
{ values: string[] | string; keys: BaseAutocompleteData }
>
>();
const [activeKeys, setActiveKeys] = useState<string[]>([
filter.defaultOpen ? 'durationNano' : '',
]);

View File

@@ -152,7 +152,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background: radial-gradient(circle, #fff 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

Some files were not shown because too many files have changed in this diff Show More