mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-23 12:20:31 +01:00
Compare commits
15 Commits
feat/dropd
...
v0.120.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3080c3c493 | ||
|
|
b0e9fbe24f | ||
|
|
aeb71469d9 | ||
|
|
1ff9a748ee | ||
|
|
dfa8625e3d | ||
|
|
a60f8551dd | ||
|
|
e607908b29 | ||
|
|
210ac2e74b | ||
|
|
5971a9efbf | ||
|
|
d43f3de049 | ||
|
|
7917540662 | ||
|
|
addb234c8c | ||
|
|
be6a663e4b | ||
|
|
ed17003329 | ||
|
|
50b452080f |
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.120.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -241,7 +241,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.120.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
@@ -139,7 +139,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.2
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.120.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.120.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -4360,146 +4360,6 @@ components:
|
||||
TimeDuration:
|
||||
format: int64
|
||||
type: integer
|
||||
TracedetailtypesEvent:
|
||||
properties:
|
||||
attributeMap:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
isError:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
timeUnixNano:
|
||||
minimum: 0
|
||||
type: integer
|
||||
type: object
|
||||
TracedetailtypesWaterfallRequest:
|
||||
properties:
|
||||
limit:
|
||||
minimum: 0
|
||||
type: integer
|
||||
selectedSpanId:
|
||||
type: string
|
||||
uncollapsedSpans:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
TracedetailtypesWaterfallResponse:
|
||||
properties:
|
||||
endTimestampMillis:
|
||||
minimum: 0
|
||||
type: integer
|
||||
hasMissingSpans:
|
||||
type: boolean
|
||||
hasMore:
|
||||
type: boolean
|
||||
rootServiceEntryPoint:
|
||||
type: string
|
||||
rootServiceName:
|
||||
type: string
|
||||
serviceNameToTotalDurationMap:
|
||||
additionalProperties:
|
||||
minimum: 0
|
||||
type: integer
|
||||
nullable: true
|
||||
type: object
|
||||
spans:
|
||||
items:
|
||||
$ref: '#/components/schemas/TracedetailtypesWaterfallSpan'
|
||||
nullable: true
|
||||
type: array
|
||||
startTimestampMillis:
|
||||
minimum: 0
|
||||
type: integer
|
||||
totalErrorSpansCount:
|
||||
minimum: 0
|
||||
type: integer
|
||||
totalSpansCount:
|
||||
minimum: 0
|
||||
type: integer
|
||||
uncollapsedSpans:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
TracedetailtypesWaterfallSpan:
|
||||
properties:
|
||||
attributes:
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
type: object
|
||||
db_name:
|
||||
type: string
|
||||
db_operation:
|
||||
type: string
|
||||
duration_nano:
|
||||
minimum: 0
|
||||
type: integer
|
||||
events:
|
||||
items:
|
||||
$ref: '#/components/schemas/TracedetailtypesEvent'
|
||||
nullable: true
|
||||
type: array
|
||||
external_http_method:
|
||||
type: string
|
||||
external_http_url:
|
||||
type: string
|
||||
flags:
|
||||
minimum: 0
|
||||
type: integer
|
||||
has_children:
|
||||
type: boolean
|
||||
has_error:
|
||||
type: boolean
|
||||
http_host:
|
||||
type: string
|
||||
http_method:
|
||||
type: string
|
||||
http_url:
|
||||
type: string
|
||||
is_remote:
|
||||
type: string
|
||||
kind:
|
||||
format: int32
|
||||
type: integer
|
||||
kind_string:
|
||||
type: string
|
||||
level:
|
||||
minimum: 0
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
parent_span_id:
|
||||
type: string
|
||||
resource:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
response_status_code:
|
||||
type: string
|
||||
span_id:
|
||||
type: string
|
||||
status_code:
|
||||
type: integer
|
||||
status_code_string:
|
||||
type: string
|
||||
status_message:
|
||||
type: string
|
||||
sub_tree_node_count:
|
||||
minimum: 0
|
||||
type: integer
|
||||
timestamp:
|
||||
minimum: 0
|
||||
type: integer
|
||||
trace_id:
|
||||
type: string
|
||||
trace_state:
|
||||
type: string
|
||||
type: object
|
||||
TypesAlertStatus:
|
||||
properties:
|
||||
inhibitedBy:
|
||||
@@ -12712,76 +12572,6 @@ paths:
|
||||
summary: Put profile in Zeus for a deployment.
|
||||
tags:
|
||||
- zeus
|
||||
/api/v3/traces/{traceID}/waterfall:
|
||||
post:
|
||||
deprecated: false
|
||||
description: Returns the waterfall view of spans for a given trace ID with tree
|
||||
structure, metadata, and windowed pagination
|
||||
operationId: GetWaterfall
|
||||
parameters:
|
||||
- in: path
|
||||
name: traceID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TracedetailtypesWaterfallRequest'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TracedetailtypesWaterfallResponse'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get waterfall view for a trace
|
||||
tags:
|
||||
- tracedetail
|
||||
/api/v5/query_range:
|
||||
post:
|
||||
deprecated: false
|
||||
|
||||
@@ -45,7 +45,8 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -24,6 +24,10 @@ window.matchMedia =
|
||||
};
|
||||
};
|
||||
|
||||
if (!HTMLElement.prototype.scrollIntoView) {
|
||||
HTMLElement.prototype.scrollIntoView = function (): void {};
|
||||
}
|
||||
|
||||
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
|
||||
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
|
||||
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
|
||||
|
||||
@@ -48,25 +48,10 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
"@sentry/vite-plugin": "2.22.6",
|
||||
"@signozhq/button": "0.0.5",
|
||||
"@signozhq/calendar": "0.1.1",
|
||||
"@signozhq/callout": "0.0.4",
|
||||
"@signozhq/checkbox": "0.0.4",
|
||||
"@signozhq/combobox": "0.0.4",
|
||||
"@signozhq/command": "0.0.2",
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/dialog": "0.0.4",
|
||||
"@signozhq/drawer": "0.0.6",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.4",
|
||||
"@signozhq/popover": "0.1.2",
|
||||
"@signozhq/radio-group": "0.0.4",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/tooltip": "0.0.2",
|
||||
"@signozhq/ui": "0.0.6",
|
||||
"@signozhq/tabs": "0.0.11",
|
||||
"@signozhq/table": "0.3.8",
|
||||
"@signozhq/toggle-group": "0.0.3",
|
||||
"@signozhq/ui": "0.0.9",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
@@ -138,12 +123,10 @@
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-hook-form": "7.71.2",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-lottie": "1.2.10",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-rnd": "^10.5.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom-v5-compat": "6.27.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
|
||||
@@ -65,13 +65,6 @@ export const TraceDetail = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailV3 = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "TraceDetailV3 Page" */ 'pages/TraceDetailV3Page/index'
|
||||
),
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
() => import(/* webpackChunkName: "UsageExplorerPage" */ 'modules/Usage'),
|
||||
);
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
StatusPage,
|
||||
SupportPage,
|
||||
TraceDetail,
|
||||
TraceDetailV3,
|
||||
TraceFilter,
|
||||
TracesExplorer,
|
||||
TracesFunnelDetails,
|
||||
@@ -142,16 +141,9 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceDetailV3,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL_OLD,
|
||||
exact: true,
|
||||
component: TraceDetail,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL_OLD',
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
|
||||
@@ -5332,248 +5332,6 @@ export interface TelemetrytypesTelemetryFieldValuesDTO {
|
||||
|
||||
export type TimeDurationDTO = number;
|
||||
|
||||
export type TracedetailtypesEventDTOAttributeMap = { [key: string]: unknown };
|
||||
|
||||
export interface TracedetailtypesEventDTO {
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
attributeMap?: TracedetailtypesEventDTOAttributeMap;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isError?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
timeUnixNano?: number;
|
||||
}
|
||||
|
||||
export interface TracedetailtypesWaterfallRequestDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
selectedSpanId?: string;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
uncollapsedSpans?: string[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TracedetailtypesWaterfallResponseDTOServiceNameToTotalDurationMap = {
|
||||
[key: string]: number;
|
||||
} | null;
|
||||
|
||||
export interface TracedetailtypesWaterfallResponseDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
endTimestampMillis?: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
hasMissingSpans?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
hasMore?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
rootServiceEntryPoint?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
rootServiceName?: string;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
serviceNameToTotalDurationMap?: TracedetailtypesWaterfallResponseDTOServiceNameToTotalDurationMap;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
spans?: TracedetailtypesWaterfallSpanDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
startTimestampMillis?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalErrorSpansCount?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalSpansCount?: number;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
uncollapsedSpans?: string[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TracedetailtypesWaterfallSpanDTOAttributes = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TracedetailtypesWaterfallSpanDTOResource = {
|
||||
[key: string]: string;
|
||||
} | null;
|
||||
|
||||
export interface TracedetailtypesWaterfallSpanDTO {
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
attributes?: TracedetailtypesWaterfallSpanDTOAttributes;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
db_name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
db_operation?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
duration_nano?: number;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
events?: TracedetailtypesEventDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
external_http_method?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
external_http_url?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
flags?: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
has_children?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
has_error?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
http_host?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
http_method?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
http_url?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
is_remote?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int32
|
||||
*/
|
||||
kind?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
kind_string?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
level?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
parent_span_id?: string;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
resource?: TracedetailtypesWaterfallSpanDTOResource;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
response_status_code?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
span_id?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
status_code?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status_code_string?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status_message?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
sub_tree_node_count?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
timestamp?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
trace_id?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
trace_state?: string;
|
||||
}
|
||||
|
||||
export interface TypesAlertStatusDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -7500,17 +7258,6 @@ export type GetHosts200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetWaterfallPathParameters = {
|
||||
traceID: string;
|
||||
};
|
||||
export type GetWaterfall200 = {
|
||||
data: TracedetailtypesWaterfallResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type QueryRangeV5200 = {
|
||||
data: Querybuildertypesv5QueryRangeResponseDTO;
|
||||
/**
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
MutationFunction,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
} from 'react-query';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
GetWaterfall200,
|
||||
GetWaterfallPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
TracedetailtypesWaterfallRequestDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* Returns the waterfall view of spans for a given trace ID with tree structure, metadata, and windowed pagination
|
||||
* @summary Get waterfall view for a trace
|
||||
*/
|
||||
export const getWaterfall = (
|
||||
{ traceID }: GetWaterfallPathParameters,
|
||||
tracedetailtypesWaterfallRequestDTO: BodyType<TracedetailtypesWaterfallRequestDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetWaterfall200>({
|
||||
url: `/api/v3/traces/${traceID}/waterfall`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: tracedetailtypesWaterfallRequestDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetWaterfallMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['getWaterfall'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return getWaterfall(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type GetWaterfallMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getWaterfall>>
|
||||
>;
|
||||
export type GetWaterfallMutationBody = BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
export type GetWaterfallMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get waterfall view for a trace
|
||||
*/
|
||||
export const useGetWaterfall = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof getWaterfall>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallPathParameters;
|
||||
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getGetWaterfallMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { omit } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
GetTraceV3PayloadProps,
|
||||
GetTraceV3SuccessResponse,
|
||||
SpanV3,
|
||||
} from 'types/api/trace/getTraceV3';
|
||||
|
||||
const getTraceV3 = async (
|
||||
props: GetTraceV3PayloadProps,
|
||||
): Promise<SuccessResponse<GetTraceV3SuccessResponse> | ErrorResponse> => {
|
||||
let uncollapsedSpans = [...props.uncollapsedSpans];
|
||||
if (!props.isSelectedSpanIDUnCollapsed) {
|
||||
uncollapsedSpans = uncollapsedSpans.filter(
|
||||
(node) => node !== props.selectedSpanId,
|
||||
);
|
||||
} else if (
|
||||
props.selectedSpanId &&
|
||||
!uncollapsedSpans.includes(props.selectedSpanId)
|
||||
) {
|
||||
// V3 backend only uses uncollapsedSpans list (unlike V2 which also interprets
|
||||
// isSelectedSpanIDUnCollapsed server-side), so explicitly add the selected span
|
||||
uncollapsedSpans.push(props.selectedSpanId);
|
||||
}
|
||||
const postData: GetTraceV3PayloadProps = {
|
||||
...props,
|
||||
uncollapsedSpans,
|
||||
limit: 10000,
|
||||
};
|
||||
const response = await axios.post<GetTraceV3SuccessResponse>(
|
||||
`/traces/${props.traceId}/waterfall`,
|
||||
omit(postData, 'traceId'),
|
||||
);
|
||||
|
||||
// V3 API wraps response in { status, data }
|
||||
const rawPayload = (response.data as any).data || response.data;
|
||||
|
||||
// Derive 'service.name' from resource for convenience — only derived field
|
||||
const spans: SpanV3[] = (rawPayload.spans || []).map((span: any) => ({
|
||||
...span,
|
||||
'service.name': span.resource?.['service.name'] || '',
|
||||
}));
|
||||
|
||||
// V3 API returns startTimestampMillis/endTimestampMillis as relative durations (ms from epoch offset),
|
||||
// not absolute unix millis like V2. The span timestamps are absolute unix millis.
|
||||
// Convert by using the first span's timestamp as the base if there's a mismatch.
|
||||
let { startTimestampMillis, endTimestampMillis } = rawPayload;
|
||||
if (
|
||||
spans.length > 0 &&
|
||||
spans[0].timestamp > 0 &&
|
||||
startTimestampMillis < spans[0].timestamp / 10
|
||||
) {
|
||||
const durationMillis = endTimestampMillis - startTimestampMillis;
|
||||
startTimestampMillis = spans[0].timestamp;
|
||||
endTimestampMillis = startTimestampMillis + durationMillis;
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: {
|
||||
...rawPayload,
|
||||
spans,
|
||||
startTimestampMillis,
|
||||
endTimestampMillis,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default getTraceV3;
|
||||
15
frontend/src/auto-import-registry.d.ts
vendored
15
frontend/src/auto-import-registry.d.ts
vendored
@@ -10,22 +10,7 @@
|
||||
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
import '@signozhq/button';
|
||||
import '@signozhq/calendar';
|
||||
import '@signozhq/callout';
|
||||
import '@signozhq/checkbox';
|
||||
import '@signozhq/combobox';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/dialog';
|
||||
import '@signozhq/drawer';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/radio-group';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/tooltip';
|
||||
import '@signozhq/ui';
|
||||
import '@signozhq/tabs';
|
||||
import '@signozhq/table';
|
||||
import '@signozhq/toggle-group';
|
||||
|
||||
@@ -80,12 +80,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
animation: horizontal-shaking 300ms ease-out;
|
||||
|
||||
.error-content {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
border-radius: 4px;
|
||||
|
||||
&__summary-section {
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
@@ -58,7 +59,7 @@
|
||||
&__message-badge-line {
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
color-mix(in srgb, var(--bg-cherry-500) 30%, transparent) 1px,
|
||||
color-mix(in srgb, var(--danger-background) 30%, transparent) 1px,
|
||||
transparent 2px
|
||||
);
|
||||
}
|
||||
@@ -84,7 +85,7 @@
|
||||
}
|
||||
|
||||
&__scroll-hint {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
}
|
||||
|
||||
&__scroll-hint-text {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { LifeBuoy } from 'lucide-react';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
@@ -23,8 +23,10 @@ function AuthHeader(): JSX.Element {
|
||||
</div>
|
||||
<Button
|
||||
className="auth-header-help-button"
|
||||
prefixIcon={<LifeBuoy size={12} />}
|
||||
prefix={<LifeBuoy size={12} />}
|
||||
onClick={handleGetHelp}
|
||||
variant="solid"
|
||||
color="none"
|
||||
>
|
||||
Get Help
|
||||
</Button>
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
.masked-dots {
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function useNavigateToExplorer(): (
|
||||
);
|
||||
|
||||
const { getUpdatedQuery } = useUpdatedQuery();
|
||||
const { selectedDashboard } = useDashboardStore();
|
||||
const { dashboardData } = useDashboardStore();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
return useCallback(
|
||||
@@ -111,7 +111,7 @@ export function useNavigateToExplorer(): (
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
},
|
||||
selectedDashboard,
|
||||
dashboardData,
|
||||
})
|
||||
.then((query) => {
|
||||
preparedQuery = query;
|
||||
@@ -140,7 +140,7 @@ export function useNavigateToExplorer(): (
|
||||
minTime,
|
||||
maxTime,
|
||||
getUpdatedQuery,
|
||||
selectedDashboard,
|
||||
dashboardData,
|
||||
notifications,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
}
|
||||
}
|
||||
&--negative {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
|
||||
.change-percentage-pill {
|
||||
&__icon {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
&__label {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import {
|
||||
Button,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccounts,
|
||||
@@ -50,9 +53,7 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
} = useCreateServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account created successfully', {
|
||||
richColors: true,
|
||||
});
|
||||
toast.success('Service account created successfully');
|
||||
reset();
|
||||
await setIsOpen(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
@@ -128,7 +129,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
type="button"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X size={12} />
|
||||
@@ -137,10 +137,10 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
@@ -69,7 +75,6 @@ describe('CreateServiceAccountModal', () => {
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Service account created successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -121,12 +126,12 @@ describe('CreateServiceAccountModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
await screen.findByRole('dialog', { name: /New Service Account/i });
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
name: /New Service Account/i,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /New Service Account/i }),
|
||||
).not.toBeInTheDocument();
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
});
|
||||
|
||||
it('shows "Name is required" after clearing the name field', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Calendar } from '@signozhq/calendar';
|
||||
import { Calendar } from '@signozhq/ui';
|
||||
import { Button } from 'antd';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
@@ -661,7 +661,9 @@ function CustomTimePicker({
|
||||
onClick={handleZoomOut}
|
||||
disabled={zoomOutDisabled}
|
||||
data-testid="zoom-out-btn"
|
||||
prefixIcon={<ZoomOut size={14} />}
|
||||
prefix={<ZoomOut size={14} />}
|
||||
variant="solid"
|
||||
color="none"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
.details-header {
|
||||
// ghost + secondary missing hover bg token in @signozhq/button
|
||||
--button-ghost-hover-background: var(--l3-background);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
height: 36px;
|
||||
background: var(--l2-background);
|
||||
|
||||
&__icon-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--l1-foreground);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { X } from '@signozhq/icons';
|
||||
|
||||
import './DetailsHeader.styles.scss';
|
||||
|
||||
export interface HeaderAction {
|
||||
key: string;
|
||||
component: ReactNode; // check later if we can use direct btn itself or not.
|
||||
}
|
||||
|
||||
export interface DetailsHeaderProps {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
actions?: HeaderAction[];
|
||||
closePosition?: 'left' | 'right';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function DetailsHeader({
|
||||
title,
|
||||
onClose,
|
||||
actions,
|
||||
closePosition = 'right',
|
||||
className,
|
||||
}: DetailsHeaderProps): JSX.Element {
|
||||
const closeButton = (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="secondary"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="details-header__icon-btn"
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`details-header ${className || ''}`}>
|
||||
{closePosition === 'left' && closeButton}
|
||||
|
||||
<span className="details-header__title">{title}</span>
|
||||
|
||||
{actions && (
|
||||
<div className="details-header__actions">
|
||||
{actions.map((action) => (
|
||||
<div key={action.key}>{action.component}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{closePosition === 'right' && closeButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailsHeader;
|
||||
@@ -1,7 +0,0 @@
|
||||
.details-panel-drawer {
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
|
||||
import './DetailsPanelDrawer.styles.scss';
|
||||
|
||||
interface DetailsPanelDrawerProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function DetailsPanelDrawer({
|
||||
isOpen,
|
||||
onClose,
|
||||
children,
|
||||
className,
|
||||
}: DetailsPanelDrawerProps): JSX.Element {
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={isOpen}
|
||||
onOpenChange={(open): void => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showOverlay={false}
|
||||
allowOutsideClick
|
||||
className={`details-panel-drawer ${className || ''}`}
|
||||
content={<div className="details-panel-drawer__body">{children}</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailsPanelDrawer;
|
||||
@@ -1,8 +0,0 @@
|
||||
export type {
|
||||
DetailsHeaderProps,
|
||||
HeaderAction,
|
||||
} from './DetailsHeader/DetailsHeader';
|
||||
export { default as DetailsHeader } from './DetailsHeader/DetailsHeader';
|
||||
export { default as DetailsPanelDrawer } from './DetailsPanelDrawer';
|
||||
export type { DetailsPanelState, UseDetailsPanelOptions } from './types';
|
||||
export { default as useDetailsPanel } from './useDetailsPanel';
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface DetailsPanelState {
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export interface UseDetailsPanelOptions {
|
||||
entityId: string | undefined;
|
||||
onClose?: () => void;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { DetailsPanelState, UseDetailsPanelOptions } from './types';
|
||||
|
||||
function useDetailsPanel({
|
||||
entityId,
|
||||
onClose,
|
||||
}: UseDetailsPanelOptions): DetailsPanelState {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const prevEntityIdRef = useRef<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const currentId = entityId || '';
|
||||
if (currentId && currentId !== prevEntityIdRef.current) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
prevEntityIdRef.current = currentId;
|
||||
}, [entityId]);
|
||||
|
||||
const open = useCallback(() => setIsOpen(true), []);
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
return { isOpen, open, close };
|
||||
}
|
||||
|
||||
export default useDetailsPanel;
|
||||
@@ -1,14 +1,8 @@
|
||||
.download-popover {
|
||||
.ant-popover-inner {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
var(--l2-background) 0%,
|
||||
var(--l3-background) 98.68%
|
||||
);
|
||||
background-color: var(--l2-background);
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 0 8px 12px 8px;
|
||||
margin: 6px 0;
|
||||
}
|
||||
@@ -19,7 +13,7 @@
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
color: var(--l3-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -38,7 +32,7 @@
|
||||
flex-direction: column;
|
||||
|
||||
:global(.ant-radio-wrapper) {
|
||||
color: var(--foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button, DialogWrapper } from '@signozhq/ui';
|
||||
import { MemberRow } from 'components/MembersTable/MembersTable';
|
||||
|
||||
interface DeleteMemberDialogProps {
|
||||
@@ -36,6 +35,24 @@ function DeleteMemberDialog({
|
||||
</>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<>
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
disabled={isDeleting}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isDeleting ? 'Processing...' : title}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
open={open}
|
||||
@@ -49,25 +66,9 @@ function DeleteMemberDialog({
|
||||
className="alert-dialog delete-dialog"
|
||||
showCloseButton={false}
|
||||
disableOutsideClick={false}
|
||||
footer={footer}
|
||||
>
|
||||
<p className="delete-dialog__body">{body}</p>
|
||||
|
||||
<DialogFooter className="delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
size="sm"
|
||||
disabled={isDeleting}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isDeleting ? 'Processing...' : title}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
{body}
|
||||
</DialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -11,7 +11,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--padding-5) var(--padding-4);
|
||||
}
|
||||
|
||||
&__field {
|
||||
@@ -50,6 +49,7 @@
|
||||
border-radius: 2px;
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
box-sizing: border-box;
|
||||
|
||||
&--disabled {
|
||||
cursor: not-allowed;
|
||||
@@ -120,17 +120,11 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
padding: 0 var(--padding-4);
|
||||
border-top: 1px solid var(--l1-border);
|
||||
flex-shrink: 0;
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
&__footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
&__footer-right {
|
||||
@@ -223,10 +217,6 @@
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
[data-slot='dialog-description'] {
|
||||
width: 510px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Badge, toast } from '@signozhq/ui';
|
||||
import { Badge, Button, DrawerWrapper, Input, toast } from '@signozhq/ui';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -207,7 +204,7 @@ function EditMemberDrawer({
|
||||
onSuccess: (): void => {
|
||||
toast.success(
|
||||
isInvited ? 'Invite revoked successfully' : 'Member deleted successfully',
|
||||
{ richColors: true, position: 'top-right' },
|
||||
{ position: 'top-right' },
|
||||
);
|
||||
setShowDeleteConfirm(false);
|
||||
onComplete();
|
||||
@@ -342,10 +339,7 @@ function EditMemberDrawer({
|
||||
if (errors.length > 0) {
|
||||
setSaveErrors(errors);
|
||||
} else {
|
||||
toast.success('Member details updated successfully', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.success('Member details updated successfully');
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@@ -403,7 +397,6 @@ function EditMemberDrawer({
|
||||
onClose();
|
||||
} else {
|
||||
toast.error('Failed to generate password reset link', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
@@ -427,15 +420,12 @@ function EditMemberDrawer({
|
||||
linkType === 'invite'
|
||||
? 'Invite link copied to clipboard'
|
||||
: 'Reset link copied to clipboard';
|
||||
toast.success(message, { richColors: true, position: 'top-right' });
|
||||
toast.success(message);
|
||||
}, [resetLink, copyToClipboard, linkType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyState.error) {
|
||||
toast.error('Failed to copy link', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.error('Failed to copy link');
|
||||
}
|
||||
}, [copyState.error]);
|
||||
|
||||
@@ -596,16 +586,21 @@ function EditMemberDrawer({
|
||||
const drawerContent = (
|
||||
<div className="edit-member-drawer__layout">
|
||||
<div className="edit-member-drawer__body">{drawerBody}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="edit-member-drawer__footer">
|
||||
{!isDeleted && (
|
||||
<div className="edit-member-drawer__footer">
|
||||
<>
|
||||
<div className="edit-member-drawer__footer-left">
|
||||
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="link"
|
||||
color="destructive"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
@@ -617,9 +612,10 @@ function EditMemberDrawer({
|
||||
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
variant="link"
|
||||
color="warning"
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
@@ -638,7 +634,7 @@ function EditMemberDrawer({
|
||||
</div>
|
||||
|
||||
<div className="edit-member-drawer__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
|
||||
<Button variant="solid" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -646,14 +642,13 @@ function EditMemberDrawer({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
disabled={!isDirty || isSaving || isRootUser}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Member Details'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -668,14 +663,14 @@ function EditMemberDrawer({
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
allowOutsideClick
|
||||
header={{ title: 'Member Details' }}
|
||||
content={drawerContent}
|
||||
className="edit-member-drawer"
|
||||
/>
|
||||
title="Member Details"
|
||||
footer={footer}
|
||||
width="wide"
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
|
||||
<ResetLinkDialog
|
||||
open={showResetLinkDialog}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Button, DialogWrapper } from '@signozhq/ui';
|
||||
|
||||
interface ResetLinkDialogProps {
|
||||
open: boolean;
|
||||
@@ -49,7 +48,7 @@ function ResetLinkDialog({
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={onCopy}
|
||||
prefixIcon={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
className="reset-link-dialog__copy-btn"
|
||||
>
|
||||
{hasCopied ? 'Copied!' : 'Copy'}
|
||||
|
||||
@@ -20,36 +20,6 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
|
||||
jest.mock('@signozhq/drawer', () => ({
|
||||
DrawerWrapper: ({
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
content?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null => (open ? <div>{content}</div> : null),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/dialog', () => ({
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
</div>
|
||||
) : null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
@@ -66,6 +36,41 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
@@ -160,6 +165,8 @@ function renderDrawer(
|
||||
describe('EditMemberDrawer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockCopyState.value = undefined;
|
||||
mockCopyState.error = undefined;
|
||||
showErrorModal.mockClear();
|
||||
server.use(
|
||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||
@@ -726,16 +733,16 @@ describe('EditMemberDrawer', () => {
|
||||
await user.click(screen.getByRole('button', { name: /^copy$/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
expect.stringContaining('reset-tok-abc'),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('button', { name: /copied!/i }),
|
||||
).toBeInTheDocument();
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Reset link copied to clipboard',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith(
|
||||
expect.stringContaining('reset-tok-abc'),
|
||||
);
|
||||
expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 20px;
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
padding-left: 3px;
|
||||
padding-right: 8px;
|
||||
cursor: pointer;
|
||||
span {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 200% */
|
||||
@@ -21,7 +21,7 @@
|
||||
&__wrap {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--l1-background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
|
||||
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
|
||||
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
|
||||
@@ -40,15 +40,17 @@
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 0;
|
||||
background: var(--l2-background);
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
background: none !important;
|
||||
|
||||
.ant-modal-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -80,6 +82,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
padding: 3px 7px;
|
||||
background: var(--l2-background);
|
||||
@@ -90,15 +93,15 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin: 0 !important;
|
||||
height: 6px;
|
||||
background: var(--bg-sakura-500);
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 !important;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
|
||||
.label {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
@@ -21,8 +20,9 @@
|
||||
padding: 0px 8px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
@@ -46,6 +47,7 @@
|
||||
border-bottom-left-radius: 0px;
|
||||
font-size: 12px !important;
|
||||
line-height: 27px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
@@ -61,8 +63,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
}
|
||||
@@ -71,7 +73,7 @@
|
||||
.input {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
@@ -196,12 +196,16 @@
|
||||
}
|
||||
|
||||
.invite-team-members-error-callout {
|
||||
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
border-radius: 4px;
|
||||
animation: horizontal-shaking 300ms ease-out;
|
||||
}
|
||||
|
||||
.invite-members-modal__error-callout {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes horizontal-shaking {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { Select } from 'antd';
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
@@ -200,10 +203,7 @@ function InviteMembersModal({
|
||||
})),
|
||||
});
|
||||
}
|
||||
toast.success('Invites sent successfully', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
toast.success('Invites sent successfully', { position: 'top-right' });
|
||||
resetAndClose();
|
||||
onComplete?.();
|
||||
} catch (err) {
|
||||
@@ -274,7 +274,6 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
className="remove-team-member-button"
|
||||
onClick={(): void => removeRow(row.id)}
|
||||
aria-label="Remove row"
|
||||
>
|
||||
@@ -289,14 +288,16 @@ function InviteMembersModal({
|
||||
</div>
|
||||
|
||||
{(hasInvalidEmails || hasInvalidRoles) && (
|
||||
<Callout
|
||||
type="error"
|
||||
size="small"
|
||||
showIcon
|
||||
icon={<CircleAlert size={12} />}
|
||||
className="invite-team-members-error-callout"
|
||||
description={getValidationErrorMessage()}
|
||||
/>
|
||||
<div className="invite-members-modal__error-callout">
|
||||
<Callout
|
||||
type="error"
|
||||
size="small"
|
||||
showIcon
|
||||
icon={<CircleAlert size={12} />}
|
||||
>
|
||||
{getValidationErrorMessage()}
|
||||
</Callout>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -304,9 +305,8 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="dashed"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
className="add-another-member-button"
|
||||
prefixIcon={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
prefix={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
onClick={addRow}
|
||||
>
|
||||
Add another
|
||||
@@ -317,7 +317,6 @@ function InviteMembersModal({
|
||||
type="button"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={resetAndClose}
|
||||
>
|
||||
<X size={12} />
|
||||
@@ -327,7 +326,6 @@ function InviteMembersModal({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitDisabled}
|
||||
loading={isSubmitting}
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
}
|
||||
|
||||
&.ERROR {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
|
||||
import { useCopyToClipboard, useLocation } from 'react-use';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
@@ -15,6 +16,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||
import InfraMetrics from 'container/LogDetailedView/InfraMetrics/InfraMetrics';
|
||||
import JSONView from 'container/LogDetailedView/JsonView';
|
||||
import Overview from 'container/LogDetailedView/Overview';
|
||||
import {
|
||||
aggregateAttributesResourcesToString,
|
||||
@@ -44,7 +46,6 @@ import {
|
||||
TextSelect,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { JsonView } from 'periscope/components/JsonView';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
@@ -363,7 +364,9 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
icon={<ChevronUp size={14} />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-up"
|
||||
disabled={isPrevDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'previous' })}
|
||||
@@ -375,7 +378,9 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
icon={<ChevronDown size={14} />}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-down"
|
||||
disabled={isNextDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'next' })}
|
||||
@@ -385,8 +390,10 @@ function LogDetailInner({
|
||||
{showOpenInExplorerBtn && (
|
||||
<div>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
className="open-in-explorer-btn"
|
||||
icon={<Compass size={16} />}
|
||||
onClick={handleOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
@@ -482,8 +489,10 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Filter size={16} />}
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Filter size={12} />}
|
||||
onClick={handleFilterVisible}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -498,8 +507,10 @@ function LogDetailInner({
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Copy size={16} />}
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Copy size={12} />}
|
||||
onClick={selectedView === VIEW_TYPES.JSON ? handleJSONCopy : onLogCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -527,9 +538,7 @@ function LogDetailInner({
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && (
|
||||
<JsonView data={LogJsonData} height="68vh" />
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||
|
||||
{selectedView === VIEW_TYPES.CONTEXT && (
|
||||
<ContextView
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
background-color: var(--bg-cherry-600);
|
||||
}
|
||||
&.severity-error-1 {
|
||||
background-color: var(--bg-cherry-500);
|
||||
background-color: var(--danger-background);
|
||||
}
|
||||
&.severity-error-2 {
|
||||
background-color: var(--bg-cherry-400);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { TableProps } from 'antd';
|
||||
|
||||
export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
@@ -7,7 +8,7 @@ export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
|
||||
paddingBottom: 6,
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
color: isDarkMode ? 'var(--bg-vanilla-400)' : 'var(--bg-slate-400)',
|
||||
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400,
|
||||
fontSize: '14px',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -10,7 +11,7 @@ interface TableBodyContentProps {
|
||||
export const TableBodyContent = styled.div<TableBodyContentProps>`
|
||||
margin-bottom: 0;
|
||||
color: ${(props): string =>
|
||||
props.isDarkMode ? 'var(--bg-vanilla-400, #c0c1c3)' : 'var(--bg-slate-400)'};
|
||||
props.isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400};
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
.timestamp-text {
|
||||
color: var(--l1-foreground);
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
<p className={cx('text', fontSize)}>{date}</p>
|
||||
<p className={cx('timestamp-text text', fontSize)}>{date}</p>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -93,7 +93,7 @@
|
||||
gap: 12px;
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -139,7 +139,8 @@
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: left;
|
||||
color: var(--muted-foreground);
|
||||
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
@@ -177,7 +178,7 @@
|
||||
padding: 12px;
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -330,7 +331,7 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--muted-foreground);
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
@@ -486,169 +487,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.format-options-popover {
|
||||
.ant-popover-inner {
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
|
||||
.nested-menu-container {
|
||||
.font-size-dropdown {
|
||||
.back-btn {
|
||||
.text {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.option-btn {
|
||||
.text {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
|
||||
.text:hover {
|
||||
color: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-column-container {
|
||||
.add-new-column-header {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-column-content {
|
||||
.column-format-new-options {
|
||||
.column-name {
|
||||
color: var(--l2-background);
|
||||
|
||||
&.selected {
|
||||
background-color: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.font-size-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.value {
|
||||
.font-value {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.column-divider {
|
||||
border-top: 2px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.lucide {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--l2-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.lucide {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-line {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.max-lines-per-row-input {
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
.periscope-btn {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
.column-format,
|
||||
.column-format-new-options {
|
||||
.column-name {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.nested-menu-container {
|
||||
backdrop-filter: blur(18px);
|
||||
|
||||
.item {
|
||||
.item-label {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-item-content-container {
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(255, 255, 255, 0.9) 98.68%
|
||||
);
|
||||
|
||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.ant-table-thead {
|
||||
> tr > th,
|
||||
> tr > td {
|
||||
background: var(--background);
|
||||
background: var(--l1-background);
|
||||
font-size: var(--paragraph-small-600-font-size);
|
||||
font-weight: var(--paragraph-small-600-font-weight);
|
||||
line-height: var(--paragraph-small-600-line-height);
|
||||
|
||||
@@ -1445,11 +1445,22 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
|
||||
// Custom dropdown render with sections support
|
||||
const customDropdownRender = useCallback((): React.ReactElement => {
|
||||
// Process options based on current search
|
||||
const processedOptions =
|
||||
selectedValues.length > 0 && isEmpty(searchText)
|
||||
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
|
||||
: filteredOptions;
|
||||
// When ALL is selected and the options contain sections (groups),
|
||||
// skip prioritization so section headers (e.g. "Related values" /
|
||||
// "All values") remain visible instead of being collapsed away by
|
||||
// every option getting hoisted to the top. For flat option lists we
|
||||
// still prioritize so selected/synthesized values stay rendered.
|
||||
const hasSections = filteredOptions.some(
|
||||
(opt) => 'options' in opt && Array.isArray(opt.options),
|
||||
);
|
||||
const shouldPrioritize =
|
||||
selectedValues.length > 0 &&
|
||||
isEmpty(searchText) &&
|
||||
!(hasSections && (allOptionShown || isAllSelected));
|
||||
|
||||
const processedOptions = shouldPrioritize
|
||||
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
|
||||
: filteredOptions;
|
||||
|
||||
const { sectionOptions, nonSectionOptions } = splitOptions(processedOptions);
|
||||
|
||||
@@ -1747,6 +1758,8 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}, [
|
||||
selectedValues,
|
||||
searchText,
|
||||
allOptionShown,
|
||||
isAllSelected,
|
||||
filteredOptions,
|
||||
splitOptions,
|
||||
isLabelPresent,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
@@ -283,4 +284,68 @@ describe('CustomMultiSelect Component', () => {
|
||||
// When all options are selected, component shows ALL tag instead
|
||||
expect(screen.getByText('ALL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('section visibility when ALL is selected', () => {
|
||||
it('keeps group headers visible when every grouped value is selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Group 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps every grouped option visible within its section when all are selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
const group1Region = screen.getByRole('group', {
|
||||
name: 'Group 1 options',
|
||||
});
|
||||
const group2Region = screen.getByRole('group', {
|
||||
name: 'Group 2 options',
|
||||
});
|
||||
|
||||
// Each option stays inside its original section rather than being
|
||||
// hoisted into a flat selected-first list.
|
||||
expect(group1Region).toHaveTextContent('Group 1 - Option 1');
|
||||
expect(group1Region).toHaveTextContent('Group 1 - Option 2');
|
||||
expect(group2Region).toHaveTextContent('Group 2 - Option 1');
|
||||
expect(group2Region).toHaveTextContent('Group 2 - Option 2');
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps group headers visible when value is the ALL sentinel', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockGroupedOptions}
|
||||
value={('__ALL__' as unknown) as string[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Group 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -355,7 +355,7 @@ $custom-border-color: #2c3044;
|
||||
.navigation-error {
|
||||
.navigation-text,
|
||||
.navigation-icons {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
color: var(--danger-background) !important;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-builder-v2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -274,7 +276,7 @@
|
||||
.ant-input-group-addon {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -50,8 +50,8 @@ const havingOperators = [
|
||||
value: 'IN',
|
||||
},
|
||||
{
|
||||
label: 'NOT_IN',
|
||||
value: 'NOT_IN',
|
||||
label: 'NOT IN',
|
||||
value: 'NOT IN',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -129,7 +129,7 @@ function HavingFilter({
|
||||
const operator = havingOperators[j];
|
||||
newOptions.push({
|
||||
label: `${opt.func}(${opt.arg}) ${operator.label}`,
|
||||
value: `${opt.func}(${opt.arg}) ${operator.label} `,
|
||||
value: `${opt.func}(${opt.arg}) ${operator.value} `,
|
||||
apply: (
|
||||
view: EditorView,
|
||||
completion: { label: string; value: string },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-add-ons {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -102,7 +104,7 @@
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
padding: 0px !important;
|
||||
background-color: var(--card) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border);
|
||||
@@ -211,7 +213,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--card) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -249,8 +251,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
@@ -284,108 +286,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.add-ons-list {
|
||||
.add-ons-tabs {
|
||||
.add-on-tab-title {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.selected-view {
|
||||
color: var(--primary-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.selected-view::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.compass-button {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.having-filter-container {
|
||||
.having-filter-select-container {
|
||||
.having-filter-select-editor {
|
||||
.cm-editor {
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
|
||||
ul {
|
||||
li {
|
||||
color: var(--l1-foreground) !important;
|
||||
&:hover {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background: var(--l3-background) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.chip-decorator {
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-aggregation-container {
|
||||
display: block;
|
||||
|
||||
@@ -26,7 +28,7 @@
|
||||
&.error {
|
||||
.cm-editor {
|
||||
.cm-content {
|
||||
border-color: var(--bg-cherry-500) !important;
|
||||
border-color: var(--danger-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +142,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -184,7 +186,7 @@
|
||||
max-width: 300px;
|
||||
|
||||
.query-aggregation-error-message {
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
@@ -196,6 +198,7 @@
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
@@ -270,7 +273,7 @@
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--l1-background) !important;
|
||||
background: var(--l2-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.code-mirror-where-clause {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -30,7 +32,7 @@
|
||||
border-left: none !important;
|
||||
|
||||
&.hasErrors {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +41,7 @@
|
||||
&.hasErrors {
|
||||
.cm-editor {
|
||||
.cm-content {
|
||||
border-color: var(--bg-cherry-500);
|
||||
border-color: var(--danger-background);
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
@@ -156,7 +158,7 @@
|
||||
.cm-line {
|
||||
line-height: 34px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -211,7 +213,7 @@
|
||||
|
||||
.invalid {
|
||||
background-color: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
|
||||
.query-validation-status {
|
||||
@@ -232,7 +234,7 @@
|
||||
|
||||
font-size: 12px;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
@@ -454,30 +456,3 @@
|
||||
margin-top: -6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.code-mirror-where-clause {
|
||||
.cm-editor {
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ jest.mock('hooks/useDarkMode', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
|
||||
useDashboardStore: (): { selectedDashboard: undefined } => ({
|
||||
selectedDashboard: undefined,
|
||||
useDashboardStore: (): { dashboardData: undefined } => ({
|
||||
dashboardData: undefined,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
width: 2px;
|
||||
height: 11px;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-cherry-500);
|
||||
background: var(--danger-background);
|
||||
}
|
||||
|
||||
.label-true {
|
||||
|
||||
@@ -158,12 +158,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxTrigger,
|
||||
} from '@signozhq/combobox';
|
||||
} from '@signozhq/ui';
|
||||
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
@@ -200,7 +200,6 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
setOpen(false);
|
||||
}}
|
||||
isSelected={validQueryIndex === option.value}
|
||||
showCheck={false}
|
||||
>
|
||||
{option.label}
|
||||
</ComboboxItem>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
&__title {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__container {
|
||||
@@ -19,7 +21,7 @@
|
||||
background-color: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
}
|
||||
@@ -43,14 +45,23 @@
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__button {
|
||||
background: var(--secondary-background);
|
||||
color: var(--secondary-foreground);
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
// TODO: Need to override the button styles for this component due to container styles.
|
||||
// Fix - @aks07
|
||||
|
||||
&__button {
|
||||
margin-top: 12px;
|
||||
color: var(--base-black);
|
||||
background-color: var(--base-white);
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--base-white);
|
||||
color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import classNames from 'classnames';
|
||||
import { X } from 'lucide-react';
|
||||
import { Check, X } from 'lucide-react';
|
||||
|
||||
import './AnnouncementTooltip.styles.scss';
|
||||
|
||||
@@ -46,13 +46,12 @@ function AnnouncementTooltip({
|
||||
className={classNames('announcement-tooltip__container', className)}
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left + 30,
|
||||
left: position.left + 20,
|
||||
}}
|
||||
>
|
||||
<div className="announcement-tooltip__header">
|
||||
<Typography.Text className="announcement-tooltip__title">
|
||||
{title}
|
||||
</Typography.Text>
|
||||
<p className="announcement-tooltip__title">{title}</p>
|
||||
|
||||
<X
|
||||
size={18}
|
||||
onClick={closeTooltip}
|
||||
@@ -61,7 +60,13 @@ function AnnouncementTooltip({
|
||||
</div>
|
||||
<p className="announcement-tooltip__message">{message}</p>
|
||||
<div className="announcement-tooltip__footer">
|
||||
<Button onClick={closeTooltip} className="announcement-tooltip__button">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={closeTooltip}
|
||||
prefix={<Check size={16} />}
|
||||
className="announcement-tooltip__footer__button"
|
||||
>
|
||||
Okay
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--foreground);
|
||||
line-height: var(--line-height-20);
|
||||
@@ -142,6 +142,10 @@
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
&__callout-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__expiry-label {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import { Badge, Button, Callout } from '@signozhq/ui';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export interface KeyCreatedPhaseProps {
|
||||
@@ -40,11 +38,13 @@ function KeyCreatedPhase({
|
||||
<Badge color="vanilla">{expiryLabel}</Badge>
|
||||
</div>
|
||||
|
||||
<Callout
|
||||
type="info"
|
||||
showIcon
|
||||
message="Store the key securely. This is the only time it will be displayed."
|
||||
/>
|
||||
<div className="add-key-modal__callout-wrapper">
|
||||
<Callout
|
||||
type="info"
|
||||
showIcon
|
||||
title="Store the key securely. This is the only time it will be displayed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Button, Input, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
|
||||
import { DatePicker } from 'antd';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
@@ -56,11 +54,12 @@ function KeyFormPhase({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onValueChange={(val): void => {
|
||||
onChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
className="add-key-modal__expiry-toggle"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
@@ -112,6 +111,7 @@ function KeyFormPhase({
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
@@ -118,12 +117,12 @@ function AddKeyModal(): JSX.Element {
|
||||
copyToClipboard(createdKey.key);
|
||||
setHasCopied(true);
|
||||
setTimeout(() => setHasCopied(false), 2000);
|
||||
toast.success('Key copied to clipboard', { richColors: true });
|
||||
toast.success('Key copied to clipboard');
|
||||
}, [copyToClipboard, createdKey?.key]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyState.error) {
|
||||
toast.error('Failed to copy key', { richColors: true });
|
||||
toast.error('Failed to copy key');
|
||||
}
|
||||
}, [copyState.error]);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Button, DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getGetServiceAccountQueryKey,
|
||||
@@ -42,7 +40,7 @@ function DeleteAccountModal(): JSX.Element {
|
||||
} = useDeleteServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Service account deleted', { richColors: true });
|
||||
toast.success('Service account deleted');
|
||||
await setIsDeleteOpen(null);
|
||||
await setAccountId(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
@@ -70,6 +68,32 @@ function DeleteAccountModal(): JSX.Element {
|
||||
setIsDeleteOpen(null);
|
||||
}
|
||||
|
||||
const content = (
|
||||
<p className="sa-delete-dialog__body">
|
||||
Are you sure you want to delete <strong>{accountName}</strong>? This action
|
||||
cannot be undone. All keys associated with this service account will be
|
||||
permanently removed.
|
||||
</p>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="sa-delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" onClick={handleCancel}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
open={open}
|
||||
@@ -83,28 +107,9 @@ function DeleteAccountModal(): JSX.Element {
|
||||
className="alert-dialog sa-delete-dialog"
|
||||
showCloseButton={false}
|
||||
disableOutsideClick={isErrorModalVisible}
|
||||
footer={footer}
|
||||
>
|
||||
<p className="sa-delete-dialog__body">
|
||||
Are you sure you want to delete <strong>{accountName}</strong>? This action
|
||||
cannot be undone. All keys associated with this service account will be
|
||||
permanently removed.
|
||||
</p>
|
||||
<DialogFooter className="sa-delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
size="sm"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
{content}
|
||||
</DialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { LockKeyhole, Trash2, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Input,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@signozhq/ui';
|
||||
import { DatePicker } from 'antd';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
@@ -72,11 +75,12 @@ function EditKeyForm({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onValueChange={(val): void => {
|
||||
onChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
className="edit-key-modal__expiry-toggle"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
@@ -132,25 +136,21 @@ function EditKeyForm({
|
||||
</form>
|
||||
|
||||
<div className="edit-key-modal__footer">
|
||||
<Button
|
||||
type="button"
|
||||
className="edit-key-modal__footer-danger"
|
||||
onClick={onRevokeClick}
|
||||
>
|
||||
<Button variant="ghost" color="destructive" onClick={onRevokeClick}>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</Button>
|
||||
<div className="edit-key-modal__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
&__meta-label {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
@@ -72,7 +71,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
||||
const { mutate: updateKey, isLoading: isSaving } = useUpdateServiceAccountKey({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Key updated successfully', { richColors: true });
|
||||
toast.success('Key updated successfully');
|
||||
await setEditKeyId(null);
|
||||
if (selectedAccountId) {
|
||||
await invalidateListServiceAccountKeys(queryClient, {
|
||||
@@ -96,7 +95,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
||||
} = useRevokeServiceAccountKey({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Key revoked successfully', { richColors: true });
|
||||
toast.success('Key revoked successfully');
|
||||
setIsRevokeConfirmOpen(false);
|
||||
await setEditKeyId(null);
|
||||
if (selectedAccountId) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { KeyRound, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Skeleton, Table, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -96,7 +96,7 @@ function buildColumns({
|
||||
<Tooltip title={isDisabled ? 'Service account disabled' : 'Revoke Key'}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
size="sm"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(e): void => {
|
||||
@@ -177,8 +177,8 @@ function KeysTab({
|
||||
</a>
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
className="keys-tab__learn-more"
|
||||
variant="link"
|
||||
color="primary"
|
||||
onClick={async (): Promise<void> => {
|
||||
await setIsAddKeyOpen(true);
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { LockKeyhole } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import { Badge, Input } from '@signozhq/ui';
|
||||
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import RolesSelect from 'components/RolesSelect';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Button, DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getListServiceAccountKeysQueryKey,
|
||||
@@ -36,7 +34,7 @@ export function RevokeKeyContent({
|
||||
Revoking this key will permanently invalidate it. Any systems using this key
|
||||
will lose access immediately.
|
||||
</p>
|
||||
<DialogFooter className="delete-dialog__footer">
|
||||
<div className="delete-dialog__footer">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={onCancel}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
@@ -51,7 +49,7 @@ export function RevokeKeyContent({
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -79,7 +77,7 @@ function RevokeKeyModal(): JSX.Element {
|
||||
} = useRevokeServiceAccountKey({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
toast.success('Key revoked successfully', { richColors: true });
|
||||
toast.success('Key revoked successfully');
|
||||
await setRevokeKeyId(null);
|
||||
if (accountId) {
|
||||
await invalidateListServiceAccountKeys(queryClient, { id: accountId });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { ChevronDown, ChevronUp, CircleAlert, RotateCw } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
@@ -40,9 +40,9 @@ function SaveErrorItem({
|
||||
</span>
|
||||
{onRetry && !isRetrying && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
color="none"
|
||||
aria-label="Retry"
|
||||
size="xs"
|
||||
onClick={async (e): Promise<void> => {
|
||||
e.stopPropagation();
|
||||
setIsRetrying(true);
|
||||
|
||||
@@ -5,31 +5,21 @@
|
||||
margin-left: var(--margin-2);
|
||||
}
|
||||
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--padding-3) var(--padding-4) var(--padding-2) var(--padding-4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__tab-group {
|
||||
[data-slot='toggle-group'] {
|
||||
height: 32px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
[data-slot='toggle-group-item'] {
|
||||
height: 32px;
|
||||
border-radius: 0;
|
||||
border-left: 1px solid var(--l1-border);
|
||||
background: transparent;
|
||||
@@ -40,6 +30,7 @@
|
||||
padding: 0 var(--padding-7);
|
||||
gap: var(--spacing-3);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
@@ -88,7 +79,7 @@
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--padding-5) var(--padding-4);
|
||||
padding-top: var(--padding-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
@@ -112,14 +103,11 @@
|
||||
}
|
||||
|
||||
&__footer {
|
||||
height: 56px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--padding-4);
|
||||
border-top: 1px solid var(--secondary);
|
||||
background: var(--card);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__keys-pagination {
|
||||
@@ -302,7 +290,7 @@
|
||||
|
||||
&__icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
}
|
||||
|
||||
&__title {
|
||||
@@ -310,7 +298,7 @@
|
||||
min-width: 0;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
line-height: var(--line-height-18);
|
||||
letter-spacing: -0.06px;
|
||||
text-align: left;
|
||||
@@ -587,6 +575,5 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-4);
|
||||
margin-top: var(--margin-6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import {
|
||||
Button,
|
||||
DrawerWrapper,
|
||||
toast,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@signozhq/ui';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
@@ -331,7 +334,6 @@ function ServiceAccountDrawer({
|
||||
setSaveErrors(errors);
|
||||
} else {
|
||||
toast.success('Service account updated successfully', {
|
||||
richColors: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
onSuccess({ closeDrawer: false });
|
||||
@@ -379,7 +381,7 @@ function ServiceAccountDrawer({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={activeTab}
|
||||
onValueChange={(val): void => {
|
||||
onChange={(val): void => {
|
||||
if (val) {
|
||||
setActiveTab(val as ServiceAccountDrawerTab);
|
||||
if (val !== ServiceAccountDrawerTab.Keys) {
|
||||
@@ -471,69 +473,64 @@ function ServiceAccountDrawer({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!isDeleted && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
className="sa-drawer__footer-btn"
|
||||
onClick={(): void => {
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
const footer = (
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!isDeleted && (
|
||||
<Button
|
||||
variant="link"
|
||||
color="destructive"
|
||||
onClick={(): void => {
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
</Button>
|
||||
)}
|
||||
{!isDeleted && (
|
||||
<div className="sa-drawer__footer-right">
|
||||
<Button variant="solid" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{!isDeleted && (
|
||||
<div className="sa-drawer__footer-right">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -547,14 +544,15 @@ function ServiceAccountDrawer({
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
allowOutsideClick
|
||||
header={{ title: 'Service Account Details' }}
|
||||
content={drawerContent}
|
||||
title="Service Account Details"
|
||||
className="sa-drawer"
|
||||
/>
|
||||
width="wide"
|
||||
footer={footer}
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
|
||||
<DeleteAccountModal />
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
|
||||
import AddKeyModal from '../AddKeyModal';
|
||||
|
||||
@@ -117,10 +123,7 @@ describe('AddKeyModal', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCopyToClipboard).toHaveBeenCalledWith('snz_abc123xyz456secret');
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Key copied to clipboard',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockToast.success).toHaveBeenCalledWith('Key copied to clipboard');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,11 +131,9 @@ describe('AddKeyModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
await screen.findByRole('dialog', { name: /Add a New Key/i });
|
||||
const dialog = await screen.findByRole('dialog', { name: /Add a New Key/i });
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Add a New Key/i }),
|
||||
).not.toBeInTheDocument();
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,9 +29,14 @@ function renderModal(
|
||||
account: 'sa-1',
|
||||
'edit-key': 'key-1',
|
||||
},
|
||||
onUrlUpdate?: jest.Mock,
|
||||
): ReturnType<typeof render> {
|
||||
return render(
|
||||
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={searchParams}
|
||||
hasMemory
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<EditKeyModal keyItem={keyItem} />
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
@@ -82,10 +87,7 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
await user.click(screen.getByRole('button', { name: /Save Changes/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Key updated successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockToast.success).toHaveBeenCalledWith('Key updated successfully');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -97,14 +99,31 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
|
||||
it('cancel clears edit-key param and closes modal', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
const onUrlUpdate = jest.fn();
|
||||
renderModal(mockKey, undefined, onUrlUpdate);
|
||||
|
||||
await screen.findByDisplayValue('Original Key Name');
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
|
||||
).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(onUrlUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const latestUrlUpdate =
|
||||
onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]?.[0];
|
||||
expect(latestUrlUpdate).toEqual(
|
||||
expect.objectContaining({
|
||||
queryString: expect.any(String),
|
||||
}),
|
||||
);
|
||||
expect(latestUrlUpdate.queryString).toContain('account=sa-1');
|
||||
expect(latestUrlUpdate.queryString).not.toContain('edit-key=');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('revoke flow: clicking Revoke Key shows confirmation inside same dialog', async () => {
|
||||
@@ -136,10 +155,7 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
await user.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Key revoked successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -164,10 +164,7 @@ describe('KeysTab', () => {
|
||||
await user.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToast.success).toHaveBeenCalledWith(
|
||||
'Key revoked successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,18 +6,23 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
jest.mock('@signozhq/drawer', () => ({
|
||||
DrawerWrapper: ({
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
content?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null => (open ? <div>{content}</div> : null),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
}));
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.ant-table-thead {
|
||||
> tr > th,
|
||||
> tr > td {
|
||||
background: var(--background);
|
||||
background: var(--l1-background);
|
||||
font-size: var(--paragraph-small-600-font-size);
|
||||
font-weight: var(--paragraph-small-600-font-weight);
|
||||
line-height: var(--paragraph-small-600-line-height);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
gap: 20px;
|
||||
padding: 8px 12px;
|
||||
|
||||
background: var(--background);
|
||||
background: var(--l1-background);
|
||||
color: var(--l2-foreground);
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
&__error {
|
||||
font-size: 0.75rem;
|
||||
color: var(--bg-cherry-500);
|
||||
color: var(--danger-background);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-cherry-500);
|
||||
background: var(--danger-background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.timeline-v3-container {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline-v3-cursor-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--l1-foreground);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
import { resolveTimeFromInterval } from 'components/TimelineV2/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
import {
|
||||
getIntervals,
|
||||
getIntervalUnit,
|
||||
getMinimumIntervalsBasedOnWidth,
|
||||
Interval,
|
||||
} from './utils';
|
||||
|
||||
import './TimelineV3.styles.scss';
|
||||
|
||||
interface ITimelineV3Props {
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
timelineHeight: number;
|
||||
offsetTimestamp: number;
|
||||
/** Cursor X as a fraction of the timeline width (0–1). null = no cursor. */
|
||||
cursorXPercent?: number | null;
|
||||
}
|
||||
|
||||
function TimelineV3(props: ITimelineV3Props): JSX.Element {
|
||||
const {
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
timelineHeight,
|
||||
offsetTimestamp,
|
||||
cursorXPercent,
|
||||
} = props;
|
||||
const [intervals, setIntervals] = useState<Interval[]>([]);
|
||||
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const spread = endTimestamp - startTimestamp;
|
||||
|
||||
useEffect(() => {
|
||||
if (spread < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minIntervals = getMinimumIntervalsBasedOnWidth(width);
|
||||
const intervalisedSpread = (spread / minIntervals) * 1.0;
|
||||
const newIntervals = getIntervals(
|
||||
intervalisedSpread,
|
||||
spread,
|
||||
offsetTimestamp,
|
||||
);
|
||||
|
||||
setIntervals(newIntervals);
|
||||
}, [startTimestamp, endTimestamp, width, offsetTimestamp, spread]);
|
||||
|
||||
// Compute cursor time label using the same unit as timeline ticks
|
||||
const cursorLabel = useMemo(() => {
|
||||
if (cursorXPercent == null || spread <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeAtCursor = offsetTimestamp + cursorXPercent * spread;
|
||||
const unit = getIntervalUnit(spread, offsetTimestamp);
|
||||
const formatted = toFixed(resolveTimeFromInterval(timeAtCursor, unit), 2);
|
||||
return `${formatted}${unit.name}`;
|
||||
}, [cursorXPercent, spread, offsetTimestamp]);
|
||||
|
||||
if (endTimestamp < startTimestamp) {
|
||||
console.error(
|
||||
'endTimestamp cannot be less than startTimestamp',
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
);
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const strokeColor = isDarkMode ? ' rgb(192,193,195,0.8)' : 'black';
|
||||
const svgHeight = timelineHeight * 2.5;
|
||||
const cursorX = cursorXPercent != null ? cursorXPercent * width : null;
|
||||
|
||||
return (
|
||||
<div ref={ref as never} className="timeline-v3-container">
|
||||
<svg
|
||||
width={width}
|
||||
height={svgHeight}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
overflow="visible"
|
||||
>
|
||||
{intervals &&
|
||||
intervals.length > 0 &&
|
||||
intervals.map((interval, index) => (
|
||||
<g
|
||||
transform={`translate(${(interval.percentage * width) / 100},0)`}
|
||||
key={`${interval.percentage + interval.label + index}`}
|
||||
textAnchor="middle"
|
||||
fontSize="0.6rem"
|
||||
>
|
||||
<text
|
||||
x={index === intervals.length - 1 ? -10 : 0}
|
||||
y={timelineHeight * 2}
|
||||
fill={strokeColor}
|
||||
>
|
||||
{interval.label}
|
||||
</text>
|
||||
<line y1={0} y2={timelineHeight} stroke={strokeColor} strokeWidth="1" />
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
{/* Cursor time badge — DOM element for easy CSS styling */}
|
||||
{cursorX !== null && cursorLabel && (
|
||||
<div className="timeline-v3-cursor-badge" style={{ left: cursorX }}>
|
||||
{cursorLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimelineV3;
|
||||
@@ -1,109 +0,0 @@
|
||||
import {
|
||||
IIntervalUnit,
|
||||
Interval,
|
||||
INTERVAL_UNITS,
|
||||
resolveTimeFromInterval,
|
||||
} from 'components/TimelineV2/utils';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
export type { Interval };
|
||||
|
||||
/**
|
||||
* Select the interval unit matching the timeline's logic.
|
||||
* Exported so crosshair labels use the same unit as timeline ticks.
|
||||
*/
|
||||
export function getIntervalUnit(
|
||||
spread: number,
|
||||
offsetTimestamp: number,
|
||||
): IIntervalUnit {
|
||||
const minIntervals = 6;
|
||||
const intervalSpread = spread / minIntervals;
|
||||
const valueForUnitSelection = Math.max(offsetTimestamp, intervalSpread);
|
||||
let unit: IIntervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||
if (valueForUnitSelection * INTERVAL_UNITS[idx].multiplier >= 1) {
|
||||
unit = INTERVAL_UNITS[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Fewer intervals than TimelineV2 for a cleaner flamegraph ruler. */
|
||||
export function getMinimumIntervalsBasedOnWidth(width: number): number {
|
||||
if (width < 640) {
|
||||
return 3;
|
||||
}
|
||||
if (width < 768) {
|
||||
return 4;
|
||||
}
|
||||
if (width < 1024) {
|
||||
return 5;
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes timeline intervals with offset-aware labels.
|
||||
* Labels reflect absolute time from trace start (offsetTimestamp + elapsed),
|
||||
* so when zoomed into a window, the first tick shows e.g. "50ms" not "0ms".
|
||||
*/
|
||||
export function getIntervals(
|
||||
intervalSpread: number,
|
||||
baseSpread: number,
|
||||
offsetTimestamp: number,
|
||||
): Interval[] {
|
||||
const integerPartString = intervalSpread.toString().split('.')[0];
|
||||
const integerPartLength = integerPartString.length;
|
||||
|
||||
const intervalSpreadNormalized =
|
||||
intervalSpread < 1.0
|
||||
? intervalSpread
|
||||
: Math.floor(Number(integerPartString) / 10 ** (integerPartLength - 1)) *
|
||||
10 ** (integerPartLength - 1);
|
||||
|
||||
// Unit must suit both: (1) tick granularity (intervalSpread) and (2) label magnitude
|
||||
// (offsetTimestamp). When zoomed deep into a trace, labels show offsetTimestamp + elapsed,
|
||||
// so we must pick a unit where that value is readable (e.g. "500.00s" not "500000.00ms").
|
||||
const valueForUnitSelection = Math.max(offsetTimestamp, intervalSpread);
|
||||
let intervalUnit: IIntervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||
const standardInterval = INTERVAL_UNITS[idx];
|
||||
if (valueForUnitSelection * standardInterval.multiplier >= 1) {
|
||||
intervalUnit = INTERVAL_UNITS[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const intervals: Interval[] = [
|
||||
{
|
||||
label: `${toFixed(
|
||||
resolveTimeFromInterval(offsetTimestamp, intervalUnit),
|
||||
2,
|
||||
)}${intervalUnit.name}`,
|
||||
percentage: 0,
|
||||
},
|
||||
];
|
||||
|
||||
// Only show even-interval ticks — skip the trailing partial tick at the edge.
|
||||
// The last even tick sits before the full width, so it doesn't conflict with
|
||||
// span duration labels that may have sub-millisecond precision.
|
||||
let elapsedIntervals = 0;
|
||||
|
||||
while (
|
||||
elapsedIntervals + intervalSpreadNormalized <= baseSpread &&
|
||||
intervals.length < 20
|
||||
) {
|
||||
elapsedIntervals += intervalSpreadNormalized;
|
||||
const labelTime = offsetTimestamp + elapsedIntervals;
|
||||
|
||||
intervals.push({
|
||||
label: `${toFixed(resolveTimeFromInterval(labelTime, intervalUnit), 2)}${
|
||||
intervalUnit.name
|
||||
}`,
|
||||
percentage: (elapsedIntervals / baseSpread) * 100,
|
||||
});
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
.warning-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// === SECTION: Summary (Top)
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
|
||||
@@ -161,10 +161,11 @@ describe('CmdKPalette', () => {
|
||||
});
|
||||
|
||||
test('clicking a navigation item calls history.push with correct route', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const homeItem = screen.getByText(HOME_LABEL);
|
||||
await userEvent.click(homeItem);
|
||||
await user.click(homeItem);
|
||||
|
||||
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
});
|
||||
@@ -194,10 +195,11 @@ describe('CmdKPalette', () => {
|
||||
});
|
||||
|
||||
test('closing the palette via handleInvoke sets open to false', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const dashItem = screen.getByText('Go to Dashboards');
|
||||
await userEvent.click(dashItem);
|
||||
await user.click(dashItem);
|
||||
|
||||
// last call from handleInvoke should set open to false
|
||||
expect(mockSetOpen).toHaveBeenCalledWith(false);
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
.cmdk-item-light:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--background) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
}
|
||||
|
||||
.cmdk-item-light[data-selected='true'] {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandShortcut,
|
||||
} from '@signozhq/command';
|
||||
} from '@signozhq/ui';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
|
||||
@@ -37,6 +37,5 @@ export enum LOCALSTORAGE {
|
||||
SHOW_FREQUENCY_CHART = 'SHOW_FREQUENCY_CHART',
|
||||
DISSMISSED_COST_METER_INFO = 'DISMISSED_COST_METER_INFO',
|
||||
DISMISSED_API_KEYS_DEPRECATION_BANNER = 'DISMISSED_API_KEYS_DEPRECATION_BANNER',
|
||||
TRACE_DETAILS_SPAN_DETAILS_POSITION = 'TRACE_DETAILS_SPAN_DETAILS_POSITION',
|
||||
LICENSE_KEY_CALLOUT_DISMISSED = 'LICENSE_KEY_CALLOUT_DISMISSED',
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ export const REACT_QUERY_KEY = {
|
||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
||||
GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL',
|
||||
GET_TRACE_V3_WATERFALL: 'GET_TRACE_V3_WATERFALL',
|
||||
GET_TRACE_V2_FLAMEGRAPH: 'GET_TRACE_V2_FLAMEGRAPH',
|
||||
GET_POD_LIST: 'GET_POD_LIST',
|
||||
GET_NODE_LIST: 'GET_NODE_LIST',
|
||||
|
||||
@@ -8,7 +8,6 @@ const ROUTES = {
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
TRACE_DETAIL_OLD: '/trace-old/:id',
|
||||
TRACES_EXPLORER: '/traces-explorer',
|
||||
ONBOARDING: '/onboarding',
|
||||
GET_STARTED: '/get-started',
|
||||
|
||||
@@ -33,102 +33,6 @@ const themeColors = {
|
||||
purple: '#800080',
|
||||
cyan: '#00FFFF',
|
||||
},
|
||||
traceDetailColorsV3: {
|
||||
// Blues
|
||||
blue1: '#2F80ED',
|
||||
blue2: '#3366E6',
|
||||
blue3: '#4682B4',
|
||||
blue4: '#1F63E0',
|
||||
blue5: '#3A7AED',
|
||||
blue6: '#5A9DF5',
|
||||
blue7: '#2874A6',
|
||||
blue8: '#2E86C1',
|
||||
blue9: '#3498DB',
|
||||
blue10: '#1E90FF',
|
||||
blue11: '#4169E1',
|
||||
|
||||
// Cyans / Teals
|
||||
cyan1: '#00CEC9',
|
||||
cyan2: '#22A6F2',
|
||||
cyan3: '#00B0AA',
|
||||
cyan4: '#33D6C2',
|
||||
cyan5: '#66E9DA',
|
||||
cyan6: '#48DBFB',
|
||||
cyan7: '#00BFFF',
|
||||
cyan8: '#63B8FF',
|
||||
teal1: '#009688',
|
||||
teal2: '#1ABC9C',
|
||||
teal3: '#48C9B0',
|
||||
teal4: '#76D7C4',
|
||||
teal5: '#20B2AA',
|
||||
|
||||
// Greens
|
||||
green1: '#27AE60',
|
||||
green2: '#3CB371',
|
||||
green3: '#1E8449',
|
||||
green4: '#2ECC71',
|
||||
green5: '#58D68D',
|
||||
green6: '#229954',
|
||||
green7: '#52BE80',
|
||||
green8: '#82E0AA',
|
||||
green9: '#73C6B6',
|
||||
|
||||
// Limes
|
||||
lime1: '#A3E635',
|
||||
lime2: '#B9F18D',
|
||||
lime3: '#84CC16',
|
||||
lime4: '#65A30D',
|
||||
|
||||
// Yellows
|
||||
yellow1: '#F1C40F',
|
||||
yellow2: '#F7DC6F',
|
||||
yellow3: '#F9E79F',
|
||||
yellow4: '#F4D03F',
|
||||
yellow5: '#D4AC0D',
|
||||
|
||||
// Golds / Ambers
|
||||
gold1: '#F2C94C',
|
||||
gold2: '#FFD93D',
|
||||
gold3: '#FFCA28',
|
||||
gold4: '#B7950B',
|
||||
gold5: '#D4A017',
|
||||
|
||||
// Oranges (non-red)
|
||||
orange1: '#F39C12',
|
||||
orange2: '#E67E22',
|
||||
orange3: '#F5B041',
|
||||
orange4: '#D35400',
|
||||
orange5: '#EB984E',
|
||||
orange6: '#FAD7A0',
|
||||
|
||||
// Purples / Violets
|
||||
purple1: '#BB6BD9',
|
||||
purple2: '#9B51E0',
|
||||
purple3: '#DA77F2',
|
||||
purple4: '#C77DFF',
|
||||
purple5: '#6C5CE7',
|
||||
purple6: '#8E44AD',
|
||||
purple7: '#9B59B6',
|
||||
purple8: '#BB8FCE',
|
||||
purple9: '#7D3C98',
|
||||
purple10: '#A569BD',
|
||||
|
||||
// Lavenders
|
||||
lavender1: '#AF7AC5',
|
||||
lavender2: '#C39BD3',
|
||||
lavender3: '#D2B4DE',
|
||||
|
||||
// Pinks / Magentas
|
||||
pink1: '#E91E8C',
|
||||
pink2: '#FF6FD8',
|
||||
pink3: '#F06292',
|
||||
pink4: '#CE93D8',
|
||||
|
||||
// Salmons / Corals (distinct from error red)
|
||||
salmon1: '#FF8A65',
|
||||
salmon2: '#FFAB91',
|
||||
salmon3: '#E0876A',
|
||||
},
|
||||
chartcolors: {
|
||||
// Blues (3)
|
||||
dodgerBlue: '#2F80ED',
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
.domain-detail-drawer {
|
||||
border-left: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
background: var(--l2-background) !important;
|
||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.ant-drawer-header {
|
||||
@@ -309,16 +309,12 @@
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
|
||||
background-color: var(--l2-background);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td:has(.endpoint-name-value) {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--l1-foreground) 4%,
|
||||
color-mix(in srgb, var(--l3-background) 60%, transparent)
|
||||
);
|
||||
background-color: var(--l2-background);
|
||||
}
|
||||
|
||||
.ant-table-cell:first-child {
|
||||
@@ -359,10 +355,6 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.endpoint-name-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -536,7 +528,7 @@
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
|
||||
background: color-mix(in srgb, var(--l1-background) 4%, transparent);
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td:has(.status-code-value) {
|
||||
@@ -586,7 +578,7 @@
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.endpoint-name-value {
|
||||
@@ -768,6 +760,7 @@
|
||||
.dependent-services-container {
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
.ant-table {
|
||||
.ant-table-thead > tr > th {
|
||||
padding: 12px;
|
||||
@@ -797,7 +790,7 @@
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
padding: 12px;
|
||||
padding: 0px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: var(--l1-foreground);
|
||||
@@ -812,13 +805,11 @@
|
||||
.ant-table-cell:has(.top-services-item-latency) {
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
|
||||
}
|
||||
|
||||
.ant-table-cell:has(.top-services-item-latency-title) {
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
@@ -853,7 +844,7 @@
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.ant-table-content {
|
||||
@@ -911,7 +902,6 @@
|
||||
}
|
||||
|
||||
.top-services-item-progress-bar {
|
||||
background-color: var(--l1-border);
|
||||
border-radius: 2px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
@@ -1027,213 +1017,3 @@
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.domain-navigate-cta {
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.domain-detail-drawer {
|
||||
.endpoint-details-card,
|
||||
.status-code-table-container,
|
||||
.endpoint-details-filters-container,
|
||||
.endpoint-details-filters-container-dropdown,
|
||||
.ant-radio-button-wrapper,
|
||||
.views-tabs-container,
|
||||
.ant-btn-default.tab,
|
||||
.tab::before,
|
||||
.endpoint-meta-data-pill,
|
||||
.endpoint-meta-data-label,
|
||||
.endpoints-table-container,
|
||||
.group-by-label,
|
||||
.ant-select-selector,
|
||||
.ant-drawer-header {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
.views-tabs .tab::before {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.domain-detail-drawer__endpoint {
|
||||
.ant-typography {
|
||||
color: var(--l2-foreground);
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.views-tabs {
|
||||
.tab {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.selected_view {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
|
||||
.selected_view::before {
|
||||
background: var(--l3-background);
|
||||
border-left: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.round-metric-tag {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.group-by-container {
|
||||
.group-by-label {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.endpoints-table-container {
|
||||
.ant-table {
|
||||
.ant-table-cell {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.table-row-light {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.round-metric-tag {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.endpoint-meta-data {
|
||||
.endpoint-meta-data-pill {
|
||||
.endpoint-meta-data-label {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.endpoint-meta-data-value {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-code-table-container {
|
||||
.ant-table {
|
||||
.ant-table-cell {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.table-row-light {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.round-metric-tag {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-services-content {
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
.dependent-services-container {
|
||||
border: none;
|
||||
padding: 10px 12px;
|
||||
.top-services-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.top-services-item-progress {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
color: var(--l1-foreground);
|
||||
padding: 0 12px;
|
||||
margin-right: 12px;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.top-services-item-key {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.top-services-item-count {
|
||||
background-color: var(--l3-background);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.top-services-item-progress-bar {
|
||||
background-color: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-table {
|
||||
.ant-table-thead > tr > th {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: var(--l2-background);
|
||||
}
|
||||
.table-row-dark {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
.top-services-item-percentage {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.top-services-load-more {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.error-state-container {
|
||||
.error-state-content-wrapper {
|
||||
.refresh-cta {
|
||||
color: var(--l2-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.end-point-details-zero-state-wrapper {
|
||||
.end-point-details-zero-state-content-wrapper {
|
||||
.end-point-details-zero-state-text-content {
|
||||
.title {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add border-bottom to table cells when pagination is not present
|
||||
.ant-spin-container:not(:has(.ant-pagination)) .ant-table-cell {
|
||||
border-bottom: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
}
|
||||
|
||||
.ant-table-thead > tr > th:has(.domain-list-name-col-header) {
|
||||
background: color-mix(in srgb, var(--l3-background) 60%, transparent);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
@@ -83,7 +83,7 @@
|
||||
}
|
||||
|
||||
.ant-table-cell:has(.domain-list-name-col-value) {
|
||||
background: color-mix(in srgb, var(--l3-background) 60%, transparent);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.round-metric-tag {
|
||||
@@ -107,7 +107,7 @@
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--l1-foreground) 4%,
|
||||
color-mix(in srgb, var(--l3-background) 60%, transparent)
|
||||
var(--l2-background)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: var(--l3-background);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
|
||||
.expanded-clickable-row {
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
.app-content {
|
||||
width: calc(100% - 54px); // width of the sidebar
|
||||
z-index: 0;
|
||||
background: var(--l1-background);
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -139,22 +140,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.isDarkMode {
|
||||
.app-layout {
|
||||
.app-content {
|
||||
background: var(--background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isLightMode {
|
||||
.app-layout {
|
||||
.app-content {
|
||||
background: var(--card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trial-expiry-banner,
|
||||
.slow-api-warning-banner,
|
||||
.workspace-restricted-banner {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user