Compare commits

..

2 Commits

Author SHA1 Message Date
SagarRajput-7
2e8aa0f42d fix(sso): strip conditional google auth and role mapping fields from save payload (#11613)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
2026-06-08 20:16:19 +00:00
Nikhil Soni
c688170a13 feat(trace-detail): add flamegraph v3 with optimized memory consumption (part 1) (#11462)
* feat: add types for flamegraph v3 in module structure

* chore: remove limit from request payload

It's a new api so doesn't need to be backward compatible

* feat: add config for flamegraph

* feat: add method to enrich selected spans

* feat: add api and module for flamegraph v3

* feat: query full spans for smaller traces

* chore: move exported methods to the top

* chore: ignore nil assigment lint error

* chore: extract out flamegraph building logic for easier review

* chore: update openapi specs

* chore: move flamegraph after aggregation to reduce diff

* chore: remove un related changes to keep diff minimum

* chore: avoid passing request type to module

* feat: return selected fields only in flamegraph

And reduce the no. of fields scanned from db

* chore: make array fields required and non nullable

* chore: update openapi specs

* chore: mark all fields in response as required

* refactor: switch to using group by instead of distinct on

Since group by is faster is enough no. of threads are used

* chore: remove service name root level field from flamegraph span

* chore: update openapi specs

* fix: update alias in order by of the query

* chore: remove unnecessary nil check

* fix: set empty array for missing data

* chore: use single line for error creation

* chore: mark response fields as required

* chore: update openapi specs

* chore: add test to verify the flamegraph query sql

* chore: update openapi specs

* chore: use orderbyasc instead of order by
2026-06-08 17:29:30 +00:00
182 changed files with 5183 additions and 4669 deletions

View File

@@ -440,6 +440,17 @@ traces:
max_depth_to_auto_expand: 5
# Threshold below which all spans are returned without windowing.
max_limit_to_select_all_spans: 10000
flamegraph:
# Maximum number of BFS depth levels included in a windowed response.
max_selected_levels: 50
# Maximum spans per level before sampling is applied.
max_spans_per_level: 100
# Number of highest-latency spans always included when sampling a level.
sampling_top_latency_count: 5
# Number of timestamp buckets used for uniform sampling within a level.
sampling_bucket_count: 50
# Threshold below which all spans are returned without windowing or sampling.
select_all_spans_limit: 100000
##################### Authz #################################
authz:

View File

@@ -6638,6 +6638,70 @@ components:
- attribute
- resource
type: string
SpantypesFlamegraphSpan:
properties:
attributes:
additionalProperties: {}
type: object
durationNano:
minimum: 0
type: integer
event:
items:
$ref: '#/components/schemas/SpantypesEvent'
type: array
hasError:
type: boolean
level:
format: int64
type: integer
name:
type: string
parentSpanId:
type: string
resource:
additionalProperties:
type: string
type: object
spanId:
type: string
timestamp:
minimum: 0
type: integer
required:
- spanId
- parentSpanId
- timestamp
- durationNano
- hasError
- name
- level
- event
- attributes
- resource
type: object
SpantypesGettableFlamegraphTrace:
properties:
endTimestampMillis:
format: int64
type: integer
hasMore:
type: boolean
spans:
items:
items:
$ref: '#/components/schemas/SpantypesFlamegraphSpan'
type: array
type: array
startTimestampMillis:
format: int64
type: integer
required:
- spans
- startTimestampMillis
- endTimestampMillis
- hasMore
type: object
SpantypesGettableSpanMapperGroups:
properties:
items:
@@ -6703,6 +6767,15 @@ components:
traceId:
type: string
type: object
SpantypesPostableFlamegraph:
properties:
selectFields:
items:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
type: array
selectedSpanId:
type: string
type: object
SpantypesPostableSpanMapper:
properties:
config:
@@ -20535,6 +20608,75 @@ paths:
summary: Put profile in Zeus for a deployment.
tags:
- zeus
/api/v3/traces/{traceID}/flamegraph:
post:
deprecated: false
description: Returns the flamegraph view of spans for a given trace ID.
operationId: GetFlamegraph
parameters:
- in: path
name: traceID
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableFlamegraph'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableFlamegraphTrace'
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 flamegraph view for a trace
tags:
- tracedetail
/api/v3/traces/{traceID}/waterfall:
post:
deprecated: false

View File

@@ -7769,6 +7769,77 @@ export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
}
export type SpantypesFlamegraphSpanDTOAttributes = { [key: string]: unknown };
export type SpantypesFlamegraphSpanDTOResource = { [key: string]: string };
export interface SpantypesFlamegraphSpanDTO {
/**
* @type object
*/
attributes: SpantypesFlamegraphSpanDTOAttributes;
/**
* @type integer
* @minimum 0
*/
durationNano: number;
/**
* @type array
*/
event: SpantypesEventDTO[];
/**
* @type boolean
*/
hasError: boolean;
/**
* @type integer
* @format int64
*/
level: number;
/**
* @type string
*/
name: string;
/**
* @type string
*/
parentSpanId: string;
/**
* @type object
*/
resource: SpantypesFlamegraphSpanDTOResource;
/**
* @type string
*/
spanId: string;
/**
* @type integer
* @minimum 0
*/
timestamp: number;
}
export interface SpantypesGettableFlamegraphTraceDTO {
/**
* @type integer
* @format int64
*/
endTimestampMillis: number;
/**
* @type boolean
*/
hasMore: boolean;
/**
* @type array
*/
spans: SpantypesFlamegraphSpanDTO[][];
/**
* @type integer
* @format int64
*/
startTimestampMillis: number;
}
export type SpantypesSpanMapperGroupConditionDTOAnyOf = {
/**
* @type array,null
@@ -8070,6 +8141,17 @@ export interface SpantypesGettableWaterfallTraceDTO {
uncollapsedSpans?: string[] | null;
}
export interface SpantypesPostableFlamegraphDTO {
/**
* @type array
*/
selectFields?: TelemetrytypesTelemetryFieldKeyDTO[];
/**
* @type string
*/
selectedSpanId?: string;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
@@ -10424,6 +10506,17 @@ export type GetHosts200 = {
status: string;
};
export type GetFlamegraphPathParameters = {
traceID: string;
};
export type GetFlamegraph200 = {
data: SpantypesGettableFlamegraphTraceDTO;
/**
* @type string
*/
status: string;
};
export type GetWaterfallPathParameters = {
traceID: string;
};

View File

@@ -12,6 +12,8 @@ import type {
} from 'react-query';
import type {
GetFlamegraph200,
GetFlamegraphPathParameters,
GetTraceAggregations200,
GetTraceAggregationsPathParameters,
GetWaterfall200,
@@ -19,6 +21,7 @@ import type {
GetWaterfallV4200,
GetWaterfallV4PathParameters,
RenderErrorResponseDTO,
SpantypesPostableFlamegraphDTO,
SpantypesPostableTraceAggregationsDTO,
SpantypesPostableWaterfallDTO,
} from '../sigNoz.schemas';
@@ -126,6 +129,105 @@ export const useGetTraceAggregations = <
> => {
return useMutation(getGetTraceAggregationsMutationOptions(options));
};
/**
* Returns the flamegraph view of spans for a given trace ID.
* @summary Get flamegraph view for a trace
*/
export const getFlamegraph = (
{ traceID }: GetFlamegraphPathParameters,
spantypesPostableFlamegraphDTO?: BodyType<SpantypesPostableFlamegraphDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetFlamegraph200>({
url: `/api/v3/traces/${traceID}/flamegraph`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableFlamegraphDTO,
signal,
});
};
export const getGetFlamegraphMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getFlamegraph>>,
TError,
{
pathParams: GetFlamegraphPathParameters;
data?: BodyType<SpantypesPostableFlamegraphDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof getFlamegraph>>,
TError,
{
pathParams: GetFlamegraphPathParameters;
data?: BodyType<SpantypesPostableFlamegraphDTO>;
},
TContext
> => {
const mutationKey = ['getFlamegraph'];
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 getFlamegraph>>,
{
pathParams: GetFlamegraphPathParameters;
data?: BodyType<SpantypesPostableFlamegraphDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return getFlamegraph(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type GetFlamegraphMutationResult = NonNullable<
Awaited<ReturnType<typeof getFlamegraph>>
>;
export type GetFlamegraphMutationBody =
| BodyType<SpantypesPostableFlamegraphDTO>
| undefined;
export type GetFlamegraphMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get flamegraph view for a trace
*/
export const useGetFlamegraph = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getFlamegraph>>,
TError,
{
pathParams: GetFlamegraphPathParameters;
data?: BodyType<SpantypesPostableFlamegraphDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof getFlamegraph>>,
TError,
{
pathParams: GetFlamegraphPathParameters;
data?: BodyType<SpantypesPostableFlamegraphDTO>;
},
TContext
> => {
return useMutation(getGetFlamegraphMutationOptions(options));
};
/**
* 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

View File

@@ -20,9 +20,9 @@
padding: 0px 8px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
color: var(--input-with-label-color, var(--l2-foreground));
border: 1px solid var(--l2-border);
background: var(--l2-background);
color: var(--l2-foreground);
display: flex;
justify-content: flex-start;
@@ -35,54 +35,21 @@
min-width: 150px;
font-family: 'Space Mono', monospace !important;
--input-border-radius: 0px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
color: var(--input-with-label-color, var(--l2-foreground));
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
border-right: none;
border-left: none;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
font-size: 12px !important;
line-height: 25px;
position: relative;
&:hover,
&:focus {
z-index: 1;
}
.ant-select-selector {
position: relative;
border-radius: inherit;
}
.ant-select:hover .ant-select-selector,
.ant-select-focused .ant-select-selector {
z-index: 1;
}
&.input__has-label-after {
margin-left: -1px;
.ant-select-selector {
margin-left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
&.input__has-close-button {
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
~ .close-btn {
margin-left: -1px;
}
}
line-height: 27px;
&::placeholder {
color: var(--input-with-label-color, var(--l3-foreground)) !important;
color: var(--l2-foreground) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
@@ -96,35 +63,25 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
height: 100%;
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
width: 38px;
position: relative;
&:hover,
&:focus {
z-index: 2;
}
}
&.labelAfter {
.input {
border-radius: 2px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.label {
border-left: none;
border-radius: 0px 2px 2px 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
}
}

View File

@@ -45,10 +45,7 @@ function InputWithLabel({
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
className="input"
placeholder={placeholder}
type={type}
value={inputValue}

View File

@@ -80,8 +80,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -101,8 +101,7 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 6px dotted var(--l1-border);
}
/* Horizontal line pointing from vertical to the item */
@@ -115,8 +114,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -242,8 +241,7 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 6px dotted var(--l1-border);
}
/* Horizontal line pointing from vertical to the item */
@@ -256,8 +254,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -275,16 +273,6 @@
line-height: 16px; /* 128.571% */
resize: none;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
&:placeholder {
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
}
}
.formula-legend {
@@ -294,42 +282,15 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(
--query-builder-v2-background-color,
var(--l2-background)
);
color: var(--query-builder-v2-color, var(--l2-foreground));
background: var(--l2-background);
color: var(--l2-foreground);
font-size: 12px;
font-weight: 300;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
position: relative;
}
.ant-input {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
height: 36px;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
position: relative;
margin-left: -1px;
&:hover,
&:focus {
z-index: 1;
border-color: var(--internal-ant-border-color-hover);
}
&:placeholder {
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
}
}
}
}
@@ -362,8 +323,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -434,8 +395,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -451,7 +412,7 @@
min-width: 120px;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
@@ -496,7 +457,7 @@
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
height: 34px !important;
box-sizing: border-box !important;

View File

@@ -92,11 +92,6 @@
.ant-select {
width: 100%;
.ant-select-selector {
min-height: 36px;
}
.ant-select-selection-search-input {
min-width: max-content !important;
max-width: 100% !important;
@@ -105,12 +100,9 @@
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground));
border: 1.005px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
@@ -131,7 +123,6 @@
.input {
flex: initial;
width: 100px !important;
min-height: 36px;
}
}
}

View File

@@ -8,9 +8,9 @@
.ant-select-selection-search-input {
font-size: 12px !important;
line-height: 25px;
line-height: 27px;
&::placeholder {
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
color: var(--l2-foreground) !important;
font-size: 12px !important;
}
}
@@ -22,12 +22,9 @@
.ant-select-selector {
width: 100%;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground));
border: 1px solid var(--l1-border) !important;
background: var(--l1-background);
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
@@ -36,49 +33,36 @@
min-height: 36px;
.ant-select-selection-placeholder {
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
color: var(--l2-foreground) !important;
font-size: 12px !important;
}
}
}
.qb-select-popover.ant-select-dropdown {
border-radius: 4px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
.ant-select-dropdown {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
.ant-select-item {
color: var(--query-builder-v2-color, var(--l2-foreground));
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
.ant-select-item {
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
&:not(:last-of-type) {
margin-bottom: 4px;
}
&:hover,
&.ant-select-item-option-active {
background: var(--l3-background) !important;
}
&:hover,
&.ant-select-item-option-active {
background: var(
--query-builder-v2-selected-background-color,
var(--l3-background)
) !important;
}
&.ant-select-item-option-selected {
background: var(
--query-builder-v2-selected-background-color,
var(--l3-background)
) !important;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
font-weight: 600;
&.ant-select-item-option-selected {
background: var(--l3-background) !important;
border: 1px solid var(--l1-border);
font-weight: 600;
}
}
}
}

View File

@@ -142,7 +142,6 @@ export const MetricsSelect = memo(function MetricsSelect({
{signalSourceChangeEnabled && (
<Select
className="source-selector"
popupClassName="qb-select-popover"
placeholder="Source"
options={SOURCE_OPTIONS}
value={source}

View File

@@ -1,23 +1,8 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-add-ons {
width: 100%;
--toggle-group-secondary-bg: var(
--query-builder-v2-toggle-group-background-color,
var(--l1-background-hover)
);
--toggle-group-secondary-border: var(
--query-builder-v2-toggle-group-border-color,
var(--l2-border)
);
--toggle-group-secondary-active-bg: var(
--query-builder-v2-toggle-group-active-background-color,
var(--l1-background)
);
--toggle-group-secondary-bg-hover: var(
--query-builder-v2-toggle-group-background-color-hover,
var(--l2-background)
);
.add-on-tab-title {
display: flex;
align-items: center;
@@ -44,33 +29,32 @@
font-style: normal;
font-weight: var(--font-weight-normal);
color: var(--query-builder-v2-color, var(--l2-foreground));
color: var(--l2-foreground);
}
> button {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
border-left: none;
min-width: 120px;
height: 36px;
line-height: 36px;
&:first-child {
border-left: 1px solid
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 1px solid var(--l1-border);
}
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l1-border);
}
&[data-state='on'] {
color: var(--text-robin-500);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
display: none;
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l1-border);
}
}
}
@@ -81,7 +65,7 @@
height: 30px;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
@@ -94,13 +78,10 @@
align-items: center;
.having-filter-select-container {
position: relative;
width: 100%;
display: flex;
flex-direction: row;
align-items: flex-start;
background: var(--query-builder-v2-background-color, var(--l2-background));
padding-right: 38px;
align-items: center;
.having-filter-select-editor {
border-radius: 2px;
@@ -125,17 +106,15 @@
}
.cm-content {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-left-width: 0px;
border-right-width: 0px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
padding: 0px !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
background-color: var(--l2-background) !important;
&:focus-within {
border-color: var(--query-builder-v2-border-color, var(--l2-border));
border-color: var(--l1-border);
}
}
@@ -239,32 +218,17 @@
}
.cm-line {
min-height: 34px;
line-height: 32px !important;
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&,
.ͼ1a {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
@@ -273,11 +237,8 @@
}
.chip-decorator {
background: var(
--query-builder-v2-chip-decorator-background-color,
var(--l3-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
border-radius: 4px;
padding: 2px 4px;
margin-right: 4px;
@@ -285,38 +246,34 @@
}
.cm-selectionBackground {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
.cm-activeLine > span {
font-size: 12px !important;
}
.cm-placeholder {
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
}
}
}
.close-btn {
position: absolute;
top: 0;
right: 0;
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
height: 100%;
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
width: 38px;
border-left: transparent;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
&:focus:not(:focus-visible),
&.ant-btn:focus:not(:focus-visible) {
border-color: var(--l2-border);
border-left-color: transparent;
outline: none;
box-shadow: none;
}
}
}
}
@@ -343,8 +300,20 @@
font-size: 12px !important;
}
input {
min-height: 36px;
$add-on-row-height: 38px;
.periscope-input-with-label {
.input {
.ant-select {
height: $add-on-row-height;
}
}
}
.input-with-label {
.input {
height: $add-on-row-height;
}
}
}
}

View File

@@ -23,7 +23,7 @@
flex: 1;
min-width: 0;
font-size: 12px;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
color: var(--l2-foreground) !important;
&.error {
.cm-editor {
@@ -51,15 +51,14 @@
.cm-content {
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
padding: 0px !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
background-color: var(--l1-background) !important;
&:focus-within {
border-color: var(--query-builder-v2-border-color, var(--l2-border));
border-color: var(--l1-border);
}
}
@@ -75,7 +74,7 @@
right: 0px !important;
border-radius: 4px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
@@ -119,7 +118,7 @@
box-sizing: border-box;
overflow: hidden;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
color: var(--l2-foreground) !important;
font-family: 'Space Mono', monospace !important;
.cm-completionIcon {
@@ -128,10 +127,7 @@
&:hover,
&[aria-selected='true'] {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
font-weight: 600 !important;
}
@@ -146,24 +142,15 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
@@ -172,11 +159,8 @@
}
.chip-decorator {
background: var(
--query-builder-v2-chip-decorator-background-color,
var(--l3-background)
) !important;
color: var(--query-builder-v2-color, var(--l1-foreground)) !important;
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
border-radius: 4px;
padding: 2px 4px;
margin-right: 4px;
@@ -184,10 +168,7 @@
}
.cm-selectionBackground {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
}
@@ -220,11 +201,12 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
height: 100%;
border: 1px solid var(--l1-border);
background: var(--l1-background);
height: 38px;
width: 38px;
border-left: transparent;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
@@ -235,13 +217,13 @@
height: 36px;
line-height: 36px;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
font-family: 'Space Mono', monospace !important;
&::placeholder {
color: var(--query-builder-v2-color, var(--l2-foreground));
color: var(--l1-foreground);
opacity: 0.5;
}
}
@@ -256,10 +238,9 @@
.query-aggregation-interval-input-container {
.query-aggregation-interval-input {
input {
min-height: 36px;
max-width: 120px;
&::placeholder {
color: var(--query-builder-v2-color, var(--l2-foreground));
color: var(--l2-foreground);
}
}
}
@@ -270,8 +251,8 @@
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background-color: var(--l1-border);
border: 1px solid var(--l1-border);
border-radius: 4px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}

View File

@@ -1,7 +1,7 @@
.add-trace-operator-button,
.add-new-query-button,
.add-formula-button {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}

View File

@@ -34,14 +34,11 @@
.query-status-container {
width: 32px;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
background-color: var(--l1-background) !important;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
border-radius: 2px;
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
@@ -80,16 +77,16 @@
.cm-content {
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
padding: 0px !important;
&:focus-within {
border-color: var(--query-builder-v2-border-color, var(--l2-border));
border-color: var(--l1-border);
}
}
&.cm-focused {
outline: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
outline: 1px solid var(--l1-border);
}
.cm-tooltip-autocomplete {
@@ -151,17 +148,11 @@
font-family: 'Space Mono', monospace !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
background-color: var(--l1-background) !important;
color: var(--l2-foreground) !important;
&:hover {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
}
.cm-completionIcon {
@@ -169,10 +160,7 @@
}
&[aria-selected='true'] {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
font-weight: 600 !important;
}
}
@@ -184,49 +172,25 @@
}
.cm-line {
line-height: 36px !important;
line-height: 34px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&,
.ͼ1a {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
}
.cm-selectionBackground {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
.cm-placeholder {
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
}
}
.cursor-position {

View File

@@ -65,14 +65,6 @@
display: flex;
flex-direction: column;
gap: 8px;
// Meant to fix the query builder colors
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
}
&-aggregation-container {

View File

@@ -1,5 +1,5 @@
.alertHistory {
.alert-history {
display: flex;
flex-direction: column;
gap: var(--spacing-10);
gap: 24px;
}

View File

@@ -3,13 +3,13 @@ import { useState } from 'react';
import Statistics from './Statistics/Statistics';
import Timeline from './Timeline/Timeline';
import styles from './AlertHistory.module.scss';
import './AlertHistory.styles.scss';
function AlertHistory(): JSX.Element {
const [totalCurrentTriggers, setTotalCurrentTriggers] = useState(0);
return (
<div className={styles.alertHistory}>
<div className="alert-history">
<Statistics
totalCurrentTriggers={totalCurrentTriggers}
setTotalCurrentTriggers={setTotalCurrentTriggers}

View File

@@ -1,40 +0,0 @@
.alertPopoverTriggerAction {
cursor: pointer;
}
.alertHistoryPopover {
:global(.ant-popover-inner) {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
padding: 0 !important;
}
:global(.ant-popover-arrow) {
&::before {
background: var(--l1-background);
}
}
}
.contributorRowPopoverButtons {
display: flex;
flex-direction: column;
}
.contributorRowPopoverButtonsButton {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-6) var(--spacing-7);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
letter-spacing: 0.14px;
width: 160px;
cursor: pointer;
background: var(--l1-background);
border-color: var(--l1-border);
&:hover {
background: var(--l2-background);
}
}

View File

@@ -0,0 +1,15 @@
.alert-popover-trigger-action {
cursor: pointer;
}
.alert-history-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
}
.ant-popover-arrow {
&::before {
background: var(--l1-background);
}
}
}

View File

@@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { DraftingCompass } from '@signozhq/icons';
import styles from './AlertPopover.module.scss';
import './AlertPopover.styles.scss';
type Props = {
children: React.ReactNode;
@@ -24,30 +24,30 @@ function PopoverContent({
}): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div className={styles.contributorRowPopoverButtons}>
<div className="contributor-row-popover-buttons">
{!!relatedLogsLink && (
<Link
to={`${ROUTES.LOGS_EXPLORER}?${relatedLogsLink}`}
className={styles.contributorRowPopoverButtonsButton}
className="contributor-row-popover-buttons__button"
>
<div>
<div className="icon">
<LogsIcon />
</div>
<div>View Logs</div>
<div className="text">View Logs</div>
</Link>
)}
{!!relatedTracesLink && (
<Link
to={`${ROUTES.TRACES_EXPLORER}?${relatedTracesLink}`}
className={styles.contributorRowPopoverButtonsButton}
className="contributor-row-popover-buttons__button"
>
<div>
<div className="icon">
<DraftingCompass
size={14}
color={isDarkMode ? Color.BG_VANILLA_400 : Color.TEXT_INK_400}
/>
</div>
<div>View Traces</div>
<div className="text">View Traces</div>
</Link>
)}
</div>
@@ -64,13 +64,13 @@ function AlertPopover({
relatedLogsLink,
}: Props): JSX.Element {
return (
<div className={styles.alertPopoverTriggerAction}>
<div className="alert-popover-trigger-action">
<Popover
showArrow={false}
placement="bottom"
color="linear-gradient(139deg, rgba(18, 19, 23, 1) 0%, rgba(18, 19, 23, 1) 98.68%)"
destroyTooltipOnHide
rootClassName={styles.alertHistoryPopover}
rootClassName="alert-history-popover"
content={
<PopoverContent
relatedTracesLink={relatedTracesLink}
@@ -112,3 +112,4 @@ export function ConditionalAlertPopover({
}
return <div>{children}</div>;
}
export default AlertPopover;

View File

@@ -4,5 +4,5 @@
height: 280px;
border: 1px solid var(--l1-border);
border-radius: 4px;
margin: 0 var(--spacing-8);
margin: 0 16px;
}

View File

@@ -3,7 +3,7 @@ import { AlertRuleStats } from 'types/api/alerts/def';
import StatsCardsRenderer from './StatsCardsRenderer/StatsCardsRenderer';
import TopContributorsRenderer from './TopContributorsRenderer/TopContributorsRenderer';
import styles from './Statistics.module.scss';
import './Statistics.styles.scss';
function Statistics({
setTotalCurrentTriggers,
@@ -13,7 +13,7 @@ function Statistics({
totalCurrentTriggers: AlertRuleStats['totalCurrentTriggers'];
}): JSX.Element {
return (
<div className={styles.statistics}>
<div className="statistics">
<StatsCardsRenderer setTotalCurrentTriggers={setTotalCurrentTriggers} />
<TopContributorsRenderer totalCurrentTriggers={totalCurrentTriggers} />
</div>

View File

@@ -1,102 +0,0 @@
.statsCard {
width: 21.7%;
border-right: 1px solid var(--l1-border);
padding: var(--spacing-4) var(--spacing-6) var(--spacing-6);
}
.statsCardEmpty {
justify-content: normal;
}
.statsCardTitleWrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-transform: uppercase;
font-size: var(--periscope-font-size-base);
line-height: 22px;
color: var(--l2-foreground);
font-weight: var(--font-weight-medium);
}
.durationIndicator {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
.icon {
display: flex;
align-self: center;
}
.text {
text-transform: uppercase;
color: var(--l3-foreground);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-semibold);
letter-spacing: 0.48px;
}
.statsCardStats {
margin-top: var(--spacing-10);
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}
.countLabel {
color: var(--l1-foreground);
font-family: var(--periscope-font-family-mono);
font-size: var(--font-size-2xl);
line-height: 36px;
}
.statsCardAlertHistoryGraph {
margin-top: var(--spacing-16);
}
.alertHistoryGraph {
width: 100%;
height: 72px;
}
.changePercentage {
width: max-content;
display: flex;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 20px;
align-items: center;
gap: var(--spacing-2);
}
// TODO(@signozhq/design-tokens): replace --text-forest-* with --success-foreground after release
.changePercentageSuccess {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
}
.changePercentageError {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
color: var(--danger-foreground);
}
.changePercentageNoPreviousData {
color: var(--primary-foreground);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
padding: var(--spacing-2) var(--spacing-8);
}
.changePercentageIcon {
display: flex;
align-self: center;
}
.changePercentageLabel {
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
line-height: 16px;
}

View File

@@ -0,0 +1,94 @@
.stats-card {
width: 21.7%;
border-right: 1px solid var(--l1-border);
padding: 9px 12px 13px;
&--empty {
justify-content: normal;
}
&__title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
.title {
text-transform: uppercase;
font-size: 13px;
line-height: 22px;
color: var(--l2-foreground);
font-weight: 500;
}
.duration-indicator {
display: flex;
align-items: center;
gap: 4px;
.icon {
display: flex;
align-self: center;
}
.text {
text-transform: uppercase;
color: var(--l3-foreground);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.48px;
}
}
}
&__stats {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 4px;
.count-label {
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 24px;
line-height: 36px;
}
}
&__alert-history-graph {
margin-top: 80px;
.alert-history-graph {
width: 100%;
height: 72px;
}
}
}
.change-percentage {
width: max-content;
display: flex;
padding: 4px 8px;
border-radius: 20px;
align-items: center;
gap: 4px;
&--success {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
}
&--error {
background: color-mix(in srgb, var(--error-background) 10%, transparent);
color: var(--error-foreground);
}
&--no-previous-data {
color: var(--primary-foreground);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
padding: 4px 16px;
}
&__icon {
display: flex;
align-self: center;
}
&__label {
font-size: 12px;
font-weight: 500;
line-height: 16px;
}
}

View File

@@ -1,4 +1,3 @@
import cx from 'classnames';
import { Color } from '@signozhq/design-tokens';
import { Tooltip } from 'antd';
import { QueryParams } from 'constants/query';
@@ -13,7 +12,7 @@ import {
extractDayFromTimestamp,
} from './utils';
import styles from './StatsCard.module.scss';
import './StatsCard.styles.scss';
type ChangePercentageProps = {
percentage: number;
@@ -27,11 +26,11 @@ function ChangePercentage({
}: ChangePercentageProps): JSX.Element {
if (direction > 0) {
return (
<div className={cx(styles.changePercentage, styles.changePercentageSuccess)}>
<div className={styles.changePercentageIcon}>
<div className="change-percentage change-percentage--success">
<div className="change-percentage__icon">
<ArrowDownLeft size={14} color={Color.BG_FOREST_500} />
</div>
<div className={styles.changePercentageLabel}>
<div className="change-percentage__label">
{percentage}% vs Last {duration}
</div>
</div>
@@ -39,11 +38,11 @@ function ChangePercentage({
}
if (direction < 0) {
return (
<div className={cx(styles.changePercentage, styles.changePercentageError)}>
<div className={styles.changePercentageIcon}>
<div className="change-percentage change-percentage--error">
<div className="change-percentage__icon">
<ArrowUpRight size={14} color={Color.BG_CHERRY_500} />
</div>
<div className={styles.changePercentageLabel}>
<div className="change-percentage__label">
{percentage}% vs Last {duration}
</div>
</div>
@@ -51,13 +50,8 @@ function ChangePercentage({
}
return (
<div
className={cx(
styles.changePercentage,
styles.changePercentageNoPreviousData,
)}
>
<div className={styles.changePercentageLabel}>no previous data</div>
<div className="change-percentage change-percentage--no-previous-data">
<div className="change-percentage__label">no previous data</div>
</div>
);
}
@@ -109,27 +103,27 @@ function StatsCard({
const formattedEndTimeForTooltip = convertTimestampToLocaleDateString(endTime);
return (
<div className={cx(styles.statsCard, { [styles.statsCardEmpty]: isEmpty })}>
<div className={styles.statsCardTitleWrapper}>
<div className={styles.title}>{title}</div>
<div className={styles.durationIndicator}>
<div className={styles.icon}>
<div className={`stats-card ${isEmpty ? 'stats-card--empty' : ''}`}>
<div className="stats-card__title-wrapper">
<div className="title">{title}</div>
<div className="duration-indicator">
<div className="icon">
<Calendar size={14} color={Color.BG_SLATE_200} />
</div>
{relativeTime ? (
<div className={styles.text}>{displayTime}</div>
<div className="text">{displayTime}</div>
) : (
<Tooltip
title={`From ${formattedStartTimeForTooltip} to ${formattedEndTimeForTooltip}`}
>
<div className={styles.text}>{displayTime}</div>
<div className="text">{displayTime}</div>
</Tooltip>
)}
</div>
</div>
<div className={styles.statsCardStats}>
<div className={styles.countLabel}>
<div className="stats-card__stats">
<div className="count-label">
{isEmpty ? emptyMessage : displayValue || totalCurrentCount}
</div>
@@ -140,8 +134,8 @@ function StatsCard({
/>
</div>
<div className={styles.statsCardAlertHistoryGraph}>
<div className={styles.alertHistoryGraph}>
<div className="stats-card__alert-history-graph">
<div className="alert-history-graph">
{!isEmpty && timeSeries.length > 1 && (
<StatsGraph timeSeries={timeSeries} changeDirection={changeDirection} />
)}

View File

@@ -1,45 +0,0 @@
.topContributorsCard {
width: 56.6%;
overflow: hidden;
}
.topContributorsCardHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-4) var(--spacing-6);
border-bottom: 1px solid var(--l1-border);
}
.title {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
line-height: 22px;
letter-spacing: 0.52px;
text-transform: uppercase;
}
.viewAll {
display: flex;
align-items: center;
gap: var(--spacing-2);
cursor: pointer;
padding: 0;
height: var(--line-height-20);
&:hover {
background-color: transparent !important;
}
}
.label {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: var(--line-height-20);
letter-spacing: -0.07px;
}
.icon {
display: flex;
}

View File

@@ -0,0 +1,163 @@
.top-contributors-card {
width: 56.6%;
overflow: hidden;
&--view-all {
width: auto;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--l1-border);
.title {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
line-height: 22px;
letter-spacing: 0.52px;
text-transform: uppercase;
}
.view-all {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 0;
height: 20px;
&:hover {
background-color: transparent !important;
}
.label {
color: var(--l2-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
}
.icon {
display: flex;
}
}
}
.contributors-row {
height: 80px;
}
.top-contributors-progress {
--progress-background: transparent;
}
&__content {
.ant-table {
&-cell {
padding: 12px !important;
}
}
.contributors-row {
background: var(--l1-background);
td {
border: none !important;
}
&:not(:last-of-type) td {
border-bottom: 1px solid var(--l1-border) !important;
}
}
.total-contribution {
color: var(--primary-foreground);
font-family: 'Geist Mono';
font-size: 12px;
font-weight: 500;
letter-spacing: -0.06px;
padding: 4px 8px;
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
border-radius: 50px;
width: max-content;
}
}
.empty-content {
margin: 16px 12px;
padding: 40px 45px;
display: flex;
flex-direction: column;
gap: 12px;
border: 1px dashed var(--l1-border);
border-radius: 6px;
&__icon {
font-family: Inter;
font-size: 20px;
line-height: 26px;
letter-spacing: -0.103px;
}
&__text {
color: var(--l2-foreground);
line-height: 18px;
.bold-text {
color: var(--l1-foreground);
font-weight: 500;
}
}
&__button-wrapper {
margin-top: 12px;
.configure-alert-rule-button {
padding: 8px 16px;
border-radius: 2px;
background: var(--l3-background);
border-width: 0;
color: var(--l1-foreground);
line-height: 24px;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
}
}
}
}
.ant-popover-inner:has(.contributor-row-popover-buttons) {
padding: 0 !important;
}
.contributor-row-popover-buttons {
display: flex;
flex-direction: column;
&__button {
display: flex;
align-items: center;
gap: 6px;
padding: 12px 15px;
color: var(--l2-foreground);
font-size: 14px;
letter-spacing: 0.14px;
width: 160px;
cursor: pointer;
background: var(--l1-background);
border-color: var(--l1-border);
.text,
.icon {
color: var(--l1-foreground);
}
&:hover {
background: var(--l2-background);
.text,
.icon {
color: var(--l1-foreground);
}
}
.icon {
display: flex;
}
}
}
.view-all-drawer {
border-radius: 4px;
}

View File

@@ -10,7 +10,7 @@ import TopContributorsContent from './TopContributorsContent';
import { TopContributorsCardProps } from './types';
import ViewAllDrawer from './ViewAllDrawer';
import styles from './TopContributorsCard.module.scss';
import './TopContributorsCard.styles.scss';
function TopContributorsCard({
topContributorsData,
@@ -48,17 +48,13 @@ function TopContributorsCard({
return (
<>
<div className={styles.topContributorsCard}>
<div className={styles.topContributorsCardHeader}>
<div className={styles.title}>top contributors</div>
<div className="top-contributors-card">
<div className="top-contributors-card__header">
<div className="title">top contributors</div>
{topContributorsData.length > 3 && (
<Button
type="text"
className={styles.viewAll}
onClick={toggleViewAllDrawer}
>
<div className={styles.label}>View all</div>
<div className={styles.icon}>
<Button type="text" className="view-all" onClick={toggleViewAllDrawer}>
<div className="label">View all</div>
<div className="icon">
<ArrowRight
size={14}
color={isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400}

View File

@@ -1,27 +0,0 @@
.emptyContent {
margin: var(--spacing-8) var(--spacing-6);
padding: var(--spacing-20) 45px;
display: flex;
flex-direction: column;
gap: var(--spacing-6);
border: 1px dashed var(--l1-border);
border-radius: 6px;
}
.emptyContentIcon {
font-family: var(--font-family-inter);
font-size: var(--font-size-xl);
line-height: 26px;
letter-spacing: -0.103px;
}
.emptyContentText {
color: var(--l2-foreground);
line-height: var(--line-height-18);
}
.topContributorsCardContent {
:global(.ant-table-cell) {
padding: var(--spacing-6) !important;
}
}

View File

@@ -1,4 +1,3 @@
import styles from './TopContributorsContent.module.scss';
import TopContributorsRows from './TopContributorsRows';
import { TopContributorsCardProps } from './types';
@@ -10,9 +9,9 @@ function TopContributorsContent({
if (isEmpty) {
return (
<div className={styles.emptyContent}>
<div className={styles.emptyContentIcon}></div>
<div className={styles.emptyContentText}>
<div className="empty-content">
<div className="empty-content__icon"></div>
<div className="empty-content__text">
Top contributors highlight the most frequently triggering group-by
attributes in multi-dimensional alerts
</div>
@@ -21,7 +20,7 @@ function TopContributorsContent({
}
return (
<div className={styles.topContributorsCardContent}>
<div className="top-contributors-card__content">
<TopContributorsRows
topContributors={topContributorsData.slice(0, 3)}
totalCurrentTriggers={totalCurrentTriggers}

View File

@@ -1,28 +0,0 @@
.contributorsRow {
height: 80px;
background: var(--l1-background);
td {
border: none !important;
}
&:not(:last-of-type) td {
border-bottom: 1px solid var(--l1-border) !important;
}
}
.topContributorsProgress {
--progress-background: transparent;
}
.totalContribution {
color: var(--primary-foreground);
font-family: var(--periscope-font-family-mono);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
letter-spacing: -0.06px;
padding: var(--spacing-2) var(--spacing-4);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
border-radius: 50px;
width: max-content;
}

View File

@@ -12,8 +12,6 @@ import {
AlertRuleTopContributors,
} from 'types/api/alerts/def';
import styles from './TopContributorsRows.module.scss';
function TopContributorsRows({
topContributors,
totalCurrentTriggers,
@@ -55,7 +53,7 @@ function TopContributorsRows({
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
strokeColor={Color.BG_ROBIN_500}
className={styles.topContributorsProgress}
className="top-contributors-progress"
/>
</ConditionalAlertPopover>
),
@@ -70,7 +68,7 @@ function TopContributorsRows({
relatedTracesLink={record.relatedTracesLink}
relatedLogsLink={record.relatedLogsLink}
>
<div className={styles.totalContribution}>
<div className="total-contribution">
{count}/{totalCurrentTriggers}
</div>
</ConditionalAlertPopover>
@@ -90,7 +88,7 @@ function TopContributorsRows({
return (
<Table
rowClassName={styles.contributorsRow}
rowClassName="contributors-row"
rowKey={(row): string => `top-contributor-${row.fingerprint}`}
onRow={handleRowClick}
columns={columns}

View File

@@ -1,13 +0,0 @@
.topContributorsCardViewAll {
width: auto;
}
.topContributorsCardContent {
:global(.ant-table-cell) {
padding: var(--spacing-6) !important;
}
}
.viewAllDrawer {
border-radius: 4px;
}

View File

@@ -3,7 +3,6 @@ import { Drawer } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { AlertRuleStats, AlertRuleTopContributors } from 'types/api/alerts/def';
import styles from './ViewAllDrawer.module.scss';
import TopContributorsRows from './TopContributorsRows';
function ViewAllDrawer({
@@ -25,15 +24,15 @@ function ViewAllDrawer({
onClose={toggleViewAllDrawer}
placement="right"
width="50%"
className={styles.viewAllDrawer}
className="view-all-drawer"
style={{
overscrollBehavior: 'contain',
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
}}
title="Viewing All Contributors"
>
<div className={styles.topContributorsCardViewAll}>
<div className={styles.topContributorsCardContent}>
<div className="top-contributors-card--view-all">
<div className="top-contributors-card__content">
<TopContributorsRows
topContributors={topContributorsData}
totalCurrentTriggers={totalCurrentTriggers}

View File

@@ -0,0 +1,35 @@
.timeline-graph {
display: flex;
flex-direction: column;
gap: 24px;
background: var(--l2-background);
padding: 12px;
border-radius: 4px;
border: 1px solid var(--l1-border);
height: 150px;
&__title {
width: max-content;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-size: 12px;
line-height: 18px;
letter-spacing: -0.06px;
}
&__chart {
.chart-placeholder {
width: 100%;
height: 52px;
background: color-mix(in srgb, var(--l1-foreground) 12%, transparent);
display: flex;
align-items: center;
justify-content: center;
.chart-icon {
font-size: 2rem;
}
}
}
}

View File

@@ -1,22 +0,0 @@
.timelineGraph {
display: flex;
flex-direction: column;
gap: var(--spacing-10);
background: var(--l2-background);
padding: var(--spacing-6);
border-radius: 4px;
border: 1px solid var(--l1-border);
height: 150px;
}
.timelineGraphTitle {
width: max-content;
padding: var(--spacing-1) var(--spacing-4);
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-size: var(--periscope-font-size-small);
line-height: var(--line-height-18);
letter-spacing: -0.06px;
}

View File

@@ -4,7 +4,7 @@ import DataStateRenderer from 'periscope/components/DataStateRenderer/DataStateR
import Graph from '../Graph/Graph';
import styles from './GraphWrapper.module.scss';
import '../Graph/Graph.styles.scss';
function GraphWrapper({
totalCurrentTriggers,
@@ -40,11 +40,11 @@ function GraphWrapper({
// }, [startTime]);
return (
<div className={styles.timelineGraph}>
<div className={styles.timelineGraphTitle}>
<div className="timeline-graph">
<div className="timeline-graph__title">
{totalCurrentTriggers} triggers in {relativeTime}
</div>
<div>
<div className="timeline-graph__chart">
<DataStateRenderer
isLoading={isLoading}
isError={isError || !isValidRuleId || !ruleId}

View File

@@ -1,70 +0,0 @@
.timelineTable {
border-top: 1px solid var(--l1-border);
border-radius: 6px;
overflow: hidden;
margin-top: var(--spacing-2);
min-height: 600px;
:global(.ant-table) {
background: var(--l1-background);
}
:global(.ant-table-cell) {
padding: var(--spacing-6) var(--spacing-8) !important;
vertical-align: baseline;
&::before {
display: none;
}
}
:global(.ant-table-thead) > tr > th {
border-color: var(--l1-border);
background: var(--l1-background);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
padding: var(--spacing-6) var(--spacing-8) var(--spacing-4) !important;
}
:global(.ant-table-tbody) > tr > td {
border: none;
}
:global(.ant-table.ant-table-middle) {
border-bottom: 1px solid var(--l1-border);
border-left: 1px solid var(--l1-border);
border-right: 1px solid var(--l1-border);
border-radius: 6px;
}
:global(.ant-pagination-item-active) {
display: flex;
width: var(--spacing-10);
height: var(--spacing-10);
align-items: center;
justify-content: center;
padding: var(--spacing-0) var(--spacing-4);
border-radius: 2px;
background: var(--primary-background);
& > a {
color: var(--primary-foreground);
line-height: var(--line-height-20);
font-weight: var(--font-weight-medium);
}
}
}
.alertRuleCreatedAt {
font-size: var(--periscope-font-size-base);
color: var(--l2-foreground);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
}
.alertHistoryLabelSearch {
:global(.ant-select-selector) {
border: none;
background: var(--l2-background);
}
}

View File

@@ -0,0 +1,89 @@
.timeline-table {
border-top: 1px solid var(--l1-border);
border-radius: 6px;
overflow: hidden;
margin-top: 4px;
min-height: 600px;
.ant-table {
background: var(--l1-background);
&-cell {
padding: 12px 16px !important;
vertical-align: baseline;
&::before {
display: none;
}
}
&-thead > tr > th {
border-color: var(--l1-border);
background: var(--l1-background);
font-size: 12px;
font-weight: 500;
padding: 12px 16px 8px !important;
}
&-tbody > tr > td {
border: none;
}
}
.label-filter {
padding: 6px 8px;
border-radius: 4px;
background: var(--l1-foreground);
border-width: 0;
line-height: 18px;
& ::placeholder {
color: var(--l2-foreground);
font-size: 12px;
letter-spacing: 0.6px;
text-transform: uppercase;
font-weight: 500;
}
}
.alert-rule {
&-value,
&__created-at {
font-size: 14px;
color: var(--l2-foreground);
}
&-value {
font-weight: 500;
line-height: 20px;
}
&__created-at {
line-height: 18px;
letter-spacing: -0.07px;
}
}
.ant-table.ant-table-middle {
border-bottom: 1px solid var(--l1-border);
border-left: 1px solid var(--l1-border);
border-right: 1px solid var(--l1-border);
border-radius: 6px;
}
.ant-pagination-item {
&-active {
display: flex;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
padding: 1px 8px;
border-radius: 2px;
background: var(--primary-background);
& > a {
color: var(--primary-foreground);
line-height: 20px;
font-weight: 500;
}
}
}
.alert-history-label-search {
.ant-select-selector {
border: none;
background: var(--l2-background);
}
}
}

View File

@@ -13,7 +13,7 @@ import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { timelineTableColumns } from './useTimelineTable';
import styles from './Table.module.scss';
import './Table.styles.scss';
function TimelineTable(): JSX.Element {
const [filters, setFilters] = useState<TagFilter>(initialFilters);
@@ -54,7 +54,7 @@ function TimelineTable(): JSX.Element {
});
return (
<div className={styles.timelineTable}>
<div className="timeline-table">
<Table
rowKey={(row): string => `${row.fingerprint}-${row.value}-${row.unixMilli}`}
columns={timelineTableColumns({

View File

@@ -17,8 +17,6 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import styles from './Table.module.scss';
const transformLabelsToQbKeys = (
labels: AlertRuleTimelineTableResponse['labels'],
): AttributeKey[] => Object.keys(labels).flatMap((key) => [{ key }]);
@@ -58,7 +56,7 @@ function LabelFilter({
<ClientSideQBSearch
onChange={handleSearch}
filters={filters}
className={styles.alertHistoryLabelSearch}
className="alert-history-label-search"
attributeKeys={transformedKeys}
attributeValuesMap={attributesMap}
suffixIcon={
@@ -90,21 +88,29 @@ export const timelineTableColumns = ({
dataIndex: 'state',
sorter: true,
width: 140,
render: (value): JSX.Element => <AlertState state={value} showLabel />,
render: (value): JSX.Element => (
<div className="alert-rule-state">
<AlertState state={value} showLabel />
</div>
),
},
{
title: (
<LabelFilter setFilters={setFilters} filters={filters} labels={labels} />
),
dataIndex: 'labels',
render: (labels): JSX.Element => <AlertLabels labels={labels} />,
render: (labels): JSX.Element => (
<div className="alert-rule-labels">
<AlertLabels labels={labels} />
</div>
),
},
{
title: 'CREATED AT',
dataIndex: 'unixMilli',
width: 200,
render: (value): JSX.Element => (
<div className={styles.alertRuleCreatedAt}>
<div className="alert-rule__created-at">
{formatTimezoneAdjustedTimestamp(value, DATE_TIME_FORMATS.DASH_DATETIME)}
</div>
),
@@ -119,7 +125,7 @@ export const timelineTableColumns = ({
relatedLogsLink={record.relatedLogsLink}
>
<Button type="text" ghost>
<Ellipsis size="md" />
<Ellipsis className="dropdown-icon" size="md" />
</Button>
</ConditionalAlertPopover>
),

View File

@@ -1,35 +0,0 @@
.timelineTabsAndFilters {
display: flex;
justify-content: space-between;
align-items: center;
}
.resetButton,
.top5Contributors {
display: flex;
align-items: center;
gap: var(--spacing-5);
}
.comingSoon {
display: inline-flex;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 20px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
justify-content: center;
align-items: center;
gap: var(--spacing-2);
}
.comingSoonText {
color: var(--text-sienna-400);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
letter-spacing: -0.05px;
line-height: normal;
}
.comingSoonIcon {
display: flex;
}

View File

@@ -0,0 +1,32 @@
.timeline-tabs-and-filters {
display: flex;
justify-content: space-between;
align-items: center;
.reset-button,
.top-5-contributors {
display: flex;
align-items: center;
gap: 10px;
}
.coming-soon {
display: inline-flex;
padding: 4px 8px;
border-radius: 20px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
justify-content: center;
align-items: center;
gap: 5px;
&__text {
color: var(--text-sienna-400);
font-size: 10px;
font-weight: 500;
letter-spacing: -0.05px;
line-height: normal;
}
&__icon {
display: flex;
}
}
}

View File

@@ -6,13 +6,13 @@ import history from 'lib/history';
import { Info } from '@signozhq/icons';
import Tabs2 from 'periscope/components/Tabs2';
import styles from './TabsAndFilters.module.scss';
import './TabsAndFilters.styles.scss';
function ComingSoon(): JSX.Element {
return (
<div className={styles.comingSoon}>
<div className={styles.comingSoonText}>Coming Soon</div>
<div className={styles.comingSoonIcon}>
<div className="coming-soon">
<div className="coming-soon__text">Coming Soon</div>
<div className="coming-soon__icon">
<Info size={10} color={Color.BG_SIENNA_400} />
</div>
</div>
@@ -27,7 +27,7 @@ function TimelineTabs(): JSX.Element {
{
value: TimelineTab.TOP_5_CONTRIBUTORS,
label: (
<div className={styles.top5Contributors}>
<div className="top-5-contributors">
Top 5 Contributors
<ComingSoon />
</div>
@@ -80,7 +80,7 @@ function TimelineFilters(): JSX.Element {
function TabsAndFilters(): JSX.Element {
return (
<div className={styles.timelineTabsAndFilters}>
<div className="timeline-tabs-and-filters">
<TimelineTabs />
<TimelineFilters />
</div>

View File

@@ -1,14 +0,0 @@
.timeline {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
margin: 0 var(--spacing-8);
}
.timelineTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-20);
letter-spacing: -0.07px;
}

View File

@@ -0,0 +1,14 @@
.timeline {
display: flex;
flex-direction: column;
gap: 8px;
margin: 0 16px;
&__title {
color: var(--l1-foreground);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
}

View File

@@ -2,7 +2,7 @@ import GraphWrapper from './GraphWrapper/GraphWrapper';
import TimelineTable from './Table/Table';
import TabsAndFilters from './TabsAndFilters/TabsAndFilters';
import styles from './Timeline.module.scss';
import './Timeline.styles.scss';
function TimelineTableRenderer(): JSX.Element {
return <TimelineTable />;
@@ -14,15 +14,15 @@ function Timeline({
totalCurrentTriggers: number;
}): JSX.Element {
return (
<div className={styles.timeline}>
<div className={styles.timelineTitle}>Timeline</div>
<div>
<div className="timeline">
<div className="timeline__title">Timeline</div>
<div className="timeline__tabs-and-filters">
<TabsAndFilters />
</div>
<div>
<div className="timeline__graph">
<GraphWrapper totalCurrentTriggers={totalCurrentTriggers} />
</div>
<div>
<div className="timeline__table">
<TimelineTableRenderer />
</div>
</div>

View File

@@ -1,183 +0,0 @@
.anomalyAlertEvaluationView {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: var(--spacing-4);
width: 100%;
height: 100%;
:global(.uplot-tooltip) {
background-color: rgb(0 0 0 / 90%);
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
color: #ddd;
font-size: var(--periscope-font-size-base);
line-height: 1.4;
padding: var(--spacing-4) var(--spacing-6);
pointer-events: none;
position: absolute;
z-index: 100;
max-height: 500px;
width: 280px;
overflow-y: auto;
display: none;
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136 136 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
:global(.uplot-tooltip-title) {
font-weight: var(--font-weight-semibold);
margin-bottom: var(--spacing-2);
}
:global(.uplot-tooltip-series) {
display: flex;
gap: var(--spacing-2);
padding: var(--spacing-2) 0;
align-items: center;
}
:global(.uplot-tooltip-series-name) {
margin-right: var(--spacing-2);
}
:global(.uplot-tooltip-band) {
font-style: italic;
color: #666;
}
:global(.uplot-tooltip-marker) {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: var(--spacing-4);
vertical-align: middle;
}
:global(.uplot) {
:global(.u-title) {
text-align: center;
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-normal);
display: flex;
height: 40px;
align-items: center;
}
:global(.u-legend) {
display: flex;
margin-top: var(--spacing-8);
tbody {
width: 100%;
:global(.u-series) {
display: inline-flex;
}
}
}
}
}
.chartSection {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.chartSectionMultiSeries {
composes: chartSection;
width: calc(100% - 240px);
}
.noDataContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--spacing-4);
}
.seriesSelection {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
width: 240px;
padding: 0 var(--spacing-4);
height: 100%;
}
.seriesList {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
}
.seriesListSearch {
margin-bottom: var(--spacing-8);
}
.seriesListTitle {
margin-top: var(--spacing-6);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-normal);
}
.seriesListItems {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
overflow-y: auto;
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136 136 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.seriesListItem {
display: flex;
flex-direction: row;
gap: var(--spacing-4);
cursor: pointer;
}
.seriesListItemColor {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-flex;
margin-right: var(--spacing-4);
vertical-align: middle;
}

View File

@@ -0,0 +1,180 @@
.anomaly-alert-evaluation-view {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 8px;
width: 100%;
height: 100%;
.anomaly-alert-evaluation-view-chart-section {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
&.has-multi-series-data {
width: calc(100% - 240px);
}
.anomaly-alert-evaluation-view-no-data-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 8px;
}
}
.anomaly-alert-evaluation-view-series-selection {
display: flex;
flex-direction: column;
gap: 8px;
width: 240px;
padding: 0px 8px;
height: 100%;
.anomaly-alert-evaluation-view-series-list {
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
.anomaly-alert-evaluation-view-series-list-search {
margin-bottom: 16px;
}
.anomaly-alert-evaluation-view-series-list-title {
margin-top: 12px;
font-size: 13px !important;
font-weight: 400;
}
.anomaly-alert-evaluation-view-series-list-items {
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
overflow-y: auto;
.anomaly-alert-evaluation-view-series-list-item {
display: flex;
flex-direction: row;
gap: 8px;
.anomaly-alert-evaluation-view-series-list-item-color {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-flex;
margin-right: 8px;
vertical-align: middle;
}
cursor: pointer;
}
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136, 136, 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
}
}
.uplot {
.u-title {
text-align: center;
font-size: 18px;
font-weight: 400;
display: flex;
height: 40px;
font-size: 13px;
align-items: center;
}
.u-legend {
display: flex;
margin-top: 16px;
tbody {
width: 100%;
.u-series {
display: inline-flex;
}
}
}
}
}
.uplot-tooltip {
background-color: rgba(0, 0, 0, 0.9);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #ddd;
font-size: 13px;
line-height: 1.4;
padding: 8px 12px;
pointer-events: none;
position: absolute;
z-index: 100;
max-height: 500px;
width: 280px;
overflow-y: auto;
display: none; /* Hide tooltip by default */
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136, 136, 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.uplot-tooltip-title {
font-weight: bold;
margin-bottom: 4px;
}
.uplot-tooltip-series {
display: flex;
gap: 4px;
padding: 4px 0px;
align-items: center;
}
.uplot-tooltip-series-name {
margin-right: 4px;
}
.uplot-tooltip-band {
font-style: italic;
color: #666;
}
.uplot-tooltip-marker {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
}

View File

@@ -15,7 +15,7 @@ import uPlot from 'uplot';
import tooltipPlugin from './tooltipPlugin';
import 'uplot/dist/uPlot.min.css';
import styles from './AnomalyAlertEvaluationView.module.scss';
import './AnomalyAlertEvaluationView.styles.scss';
const { Search } = Input;
@@ -284,11 +284,11 @@ function AnomalyAlertEvaluationView({
}, 300);
return (
<div className={styles.anomalyAlertEvaluationView}>
<div className="anomaly-alert-evaluation-view">
<div
className={
allSeries.length > 1 ? styles.chartSectionMultiSeries : styles.chartSection
}
className={`anomaly-alert-evaluation-view-chart-section ${
allSeries.length > 1 ? 'has-multi-series-data' : ''
}`}
ref={graphRef}
>
{allSeries.length > 0 ? (
@@ -298,7 +298,7 @@ function AnomalyAlertEvaluationView({
chartRef={chartRef}
/>
) : (
<div className={styles.noDataContainer}>
<div className="anomaly-alert-evaluation-view-no-data-container">
<ChartLine size={48} strokeWidth={0.5} />
<Typography>No Data</Typography>
@@ -307,20 +307,20 @@ function AnomalyAlertEvaluationView({
</div>
{allSeries.length > 1 && (
<div className={styles.seriesSelection}>
<div className="anomaly-alert-evaluation-view-series-selection">
{allSeries.length > 1 && (
<div className={styles.seriesList}>
<div className="anomaly-alert-evaluation-view-series-list">
<Search
className={styles.seriesListSearch}
className="anomaly-alert-evaluation-view-series-list-search"
placeholder="Search a series"
allowClear
onChange={handleSearchValueChange}
/>
<div className={styles.seriesListItems}>
<div className="anomaly-alert-evaluation-view-series-list-items">
{filteredSeriesKeys.length > 0 && (
<Checkbox
className={styles.seriesListItem}
className="anomaly-alert-evaluation-view-series-list-item"
name="series"
value={selectedSeries === null}
onChange={(): void => handleSeriesChange(null)}
@@ -332,14 +332,14 @@ function AnomalyAlertEvaluationView({
{filteredSeriesKeys.map((seriesKey) => (
<div key={seriesKey}>
<Checkbox
className={styles.seriesListItem}
className="anomaly-alert-evaluation-view-series-list-item"
key={seriesKey}
name="series"
value={selectedSeries === seriesKey}
onChange={(): void => handleSeriesChange(seriesKey)}
>
<div
className={styles.seriesListItemColor}
className="anomaly-alert-evaluation-view-series-list-item-color"
style={{ backgroundColor: seriesData[seriesKey].color }}
/>

View File

@@ -1,65 +0,0 @@
.createAlertTabsExtra {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
.createAlertWrapper {
margin-top: var(--spacing-5);
:global(.divider) {
border-color: var(--l1-border);
margin: var(--spacing-8) 0;
}
:global(.breadcrumb-divider) {
margin-top: var(--spacing-5);
}
}
.createAlertBreadcrumb {
padding-left: var(--spacing-8);
:global(.breadcrumb-item) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: var(--line-height-20);
letter-spacing: 0.25px;
padding: 0;
}
:global(.ant-breadcrumb-separator),
:global(.breadcrumb-item--last) {
color: var(--muted-foreground);
font-family: var(--periscope-font-family-mono);
}
}
.createAlertBreadcrumb ol {
align-items: center;
}
.alertsContainer {
:global(.top-level-tab.periscope-tab) {
padding: var(--spacing-1) 0;
}
:global(.ant-tabs-nav) {
padding: 0 var(--spacing-4);
margin-bottom: 0 !important;
&::before {
border-bottom: 1px solid var(--l1-border) !important;
}
}
:global(.ant-tabs-tab) {
&[data-node-key='TriggeredAlerts'] {
margin-left: var(--spacing-8);
}
}
:global(.ant-tabs-tab) [aria-selected='false'] :global(.periscope-tab) {
color: var(--l2-foreground);
}
}

View File

@@ -0,0 +1,75 @@
.create-alert-tabs {
&__extra {
display: flex;
align-items: center;
gap: 16px;
}
}
.create-alert-wrapper {
margin-top: 10px;
.divider {
border-color: var(--l1-border);
margin: 16px 0;
}
.breadcrumb-divider {
margin-top: 10px;
}
}
.create-alert__breadcrumb {
padding-left: 16px;
ol {
align-items: center;
}
.breadcrumb-item {
color: var(--l2-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
padding: 0;
}
.ant-breadcrumb-separator,
.breadcrumb-item--last {
color: var(--muted-foreground);
font-family: 'Geist Mono';
}
}
.alerts-container {
.top-level-tab.periscope-tab {
padding: 2px 0;
}
.ant-tabs {
&-nav {
padding: 0 8px;
margin-bottom: 0 !important;
&::before {
border-bottom: 1px solid var(--l1-border) !important;
}
}
&-tab {
&[data-node-key='TriggeredAlerts'] {
margin-left: 16px;
}
&:not(:first-of-type) {
margin-left: 24px !important;
}
[aria-selected='false'] {
.periscope-tab {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo } from 'react';
import cx from 'classnames';
import { Form, Tabs, TabsProps } from 'antd';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/AlertHistory/ConfigureIcon';
@@ -23,7 +22,7 @@ import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
import { ALERTS_VALUES_MAP, ALERT_TYPE_BREADCRUMB_TITLE } from './defaults';
import SelectAlertType from './SelectAlertType';
import styles from './CreateAlertRule.module.scss';
import './CreateAlertRule.styles.scss';
function CreateRules(): JSX.Element {
const [formInstance] = Form.useForm();
@@ -144,9 +143,9 @@ function CreateRules(): JSX.Element {
),
key: AlertListTabs.ALERT_RULES,
children: (
<div className={styles.createAlertWrapper}>
<div className="create-alert-wrapper">
<AlertBreadcrumb
className={styles.createAlertBreadcrumb}
className="create-alert__breadcrumb"
items={
isTypeSelectionMode
? [
@@ -191,9 +190,9 @@ function CreateRules(): JSX.Element {
items={items}
activeKey={AlertListTabs.ALERT_RULES}
onChange={handleTabChange}
className={cx(styles.alertsContainer, 'create-alert-tabs')}
className="alerts-container create-alert-tabs"
tabBarExtraContent={
<div className={styles.createAlertTabsExtra}>
<div className="create-alert-tabs__extra">
<DateTimeSelector showAutoRefresh />
<HeaderRightSection
enableAnnouncements={false}

View File

@@ -1,66 +0,0 @@
.alertConditionContainer {
margin: 0 var(--spacing-8);
margin-top: var(--spacing-12);
}
.alertCondition {
display: flex;
align-items: center;
margin-left: var(--spacing-6);
margin-top: var(--spacing-12);
}
.alertConditionTabs {
display: flex;
border-radius: 2px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
}
.explorerViewOption {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0;
border-left: 0.5px solid var(--l2-border);
border-bottom: 0.5px solid var(--l2-border);
width: 120px;
height: 36px;
gap: var(--spacing-4);
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l3-foreground);
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
}
.activeTab {
background-color: var(--l2-background);
border-bottom: none;
&:hover {
background-color: var(--l2-background-hover) !important;
}
}
.condensedAdvancedOptionsContainer {
margin-top: var(--spacing-8);
width: fit-parent;
}

View File

@@ -15,7 +15,7 @@ import AlertThreshold from './AlertThreshold';
import AnomalyThreshold from './AnomalyThreshold';
import { ANOMALY_TAB_TOOLTIP, THRESHOLD_TAB_TOOLTIP } from './constants';
import styles from './AlertCondition.module.scss';
import './styles.scss';
function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
@@ -67,15 +67,15 @@ function AlertCondition(): JSX.Element {
};
return (
<div className={styles.alertConditionContainer}>
<div className="alert-condition-container">
<Stepper stepNumber={2} label="Set alert conditions" />
<div className={styles.alertCondition}>
<div className={styles.alertConditionTabs}>
<div className="alert-condition">
<div className="alert-condition-tabs">
{tabs.map((tab) => (
<Tooltip key={tab.value} title={getTabTooltip(tab)}>
<Button
className={classNames(styles.explorerViewOption, {
[styles.activeTab]: alertType === tab.value,
className={classNames('list-view-tab', 'explorer-view-option', {
'active-tab': alertType === tab.value,
})}
onClick={(): void => {
if (alertType !== tab.value) {
@@ -106,7 +106,7 @@ function AlertCondition(): JSX.Element {
refreshChannels={refreshChannels}
/>
)}
<div className={styles.condensedAdvancedOptionsContainer}>
<div className="condensed-advanced-options-container">
<AdvancedOptions />
</div>
</div>

View File

@@ -1,85 +0,0 @@
.alertThresholdContainer {
padding: var(--spacing-12);
padding-right: 72px;
background-color: var(--l2-background);
border: 1px solid var(--l2-border);
width: 100%;
}
.alertConditionSentences {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
}
.alertConditionSentence {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
:global(.ant-select) {
width: 240px;
:global(.ant-select-selector) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.thresholdsSection {
margin-top: var(--spacing-8);
margin-left: var(--spacing-12);
}
.addThresholdBtn {
margin-top: var(--spacing-4);
border: 1px dashed var(--l3-border);
color: var(--l2-foreground);
background-color: transparent;
border-radius: 4px;
height: 32px;
padding: 0 var(--spacing-8);
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
&:hover {
border-color: var(--l2-border);
color: var(--l3-foreground);
}
:global(.anticon) {
margin-right: var(--spacing-4);
}
}

View File

@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import getRandomColor from 'lib/getRandomColor';
import { Plus } from '@signozhq/icons';
@@ -31,7 +32,8 @@ import {
RoutingPolicyBanner,
} from './utils';
import styles from './AlertThreshold.module.scss';
import './styles.scss';
import '../EvaluationSettings/styles.scss';
function AlertThreshold({
channels,
@@ -217,11 +219,16 @@ function AlertThreshold({
};
return (
<div className={styles.alertThresholdContainer}>
<div
className={classNames(
'alert-threshold-container',
'condensed-alert-threshold-container',
)}
>
{/* Main condition sentence */}
<div className={styles.alertConditionSentences}>
<div className={styles.alertConditionSentence}>
<Typography.Text className={styles.sentenceText}>
<div className="alert-condition-sentences">
<div className="alert-condition-sentence">
<Typography.Text className="sentence-text">
Send a notification when
</Typography.Text>
<Select
@@ -231,7 +238,7 @@ function AlertThreshold({
options={queryNames}
data-testid="alert-threshold-query-select"
/>
<Typography.Text className={styles.sentenceText}>is</Typography.Text>
<Typography.Text className="sentence-text">is</Typography.Text>
<Select
value={
(normalizeOperator(thresholdState.operator) ??
@@ -247,7 +254,7 @@ function AlertThreshold({
options={THRESHOLD_OPERATOR_OPTIONS}
data-testid="alert-threshold-operator-select"
/>
<Typography.Text className={styles.sentenceText}>
<Typography.Text className="sentence-text">
the threshold(s)
</Typography.Text>
<Select
@@ -265,13 +272,13 @@ function AlertThreshold({
options={matchTypeOptionsWithTooltips}
data-testid="alert-threshold-match-type-select"
/>
<Typography.Text className={styles.sentenceText}>
<Typography.Text className="sentence-text">
during the <EvaluationSettings />
</Typography.Text>
</div>
</div>
<div className={styles.thresholdsSection}>
<div className="thresholds-section">
{thresholdState.thresholds.map((threshold, index) => (
<ThresholdItem
key={threshold.id}
@@ -290,7 +297,7 @@ function AlertThreshold({
type="dashed"
icon={<Plus size={16} />}
onClick={addThreshold}
className={styles.addThresholdBtn}
className="add-threshold-btn"
data-testid="add-threshold-button"
>
Add Threshold

View File

@@ -1,63 +0,0 @@
.anomalyThresholdContainer {
padding: var(--spacing-12);
padding-right: 72px;
background-color: var(--l2-background);
border: 1px solid var(--l2-border);
width: 100%;
:global(.ant-select) {
:global(.ant-select-selector) {
min-width: 150px;
}
}
}
.alertConditionSentences {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
}
.alertConditionSentence {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
:global(.ant-select) {
width: 240px;
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}

View File

@@ -24,8 +24,6 @@ import {
RoutingPolicyBanner,
} from './utils';
import styles from './AnomalyThreshold.module.scss';
function AnomalyThreshold({
channels,
isLoadingChannels,
@@ -66,14 +64,11 @@ function AnomalyThreshold({
};
return (
<div className={styles.anomalyThresholdContainer}>
<div className={styles.alertConditionSentences}>
<div className="anomaly-threshold-container">
<div className="alert-condition-sentences">
{/* Sentence 1 */}
<div className={styles.alertConditionSentence}>
<Typography.Text
data-testid="notification-text"
className={styles.sentenceText}
>
<div className="alert-condition-sentence">
<Typography.Text data-testid="notification-text" className="sentence-text">
Send notification when the observed value for
</Typography.Text>
<Select
@@ -89,7 +84,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="evaluation-window-text"
className={styles.sentenceText}
className="sentence-text"
>
during the last
</Typography.Text>
@@ -105,12 +100,9 @@ function AnomalyThreshold({
options={ANOMALY_TIME_DURATION_OPTIONS}
/>
</div>
<div className={styles.alertConditionSentence}>
<div className="alert-condition-sentence">
{/* Sentence 2 */}
<Typography.Text
data-testid="threshold-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="threshold-text" className="sentence-text">
is
</Typography.Text>
<Select
@@ -125,10 +117,7 @@ function AnomalyThreshold({
}}
options={deviationOptions}
/>
<Typography.Text
data-testid="deviations-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="deviations-text" className="sentence-text">
deviations
</Typography.Text>
<Select
@@ -147,7 +136,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="predicted-data-text"
className={styles.sentenceText}
className="sentence-text"
>
the predicted data
</Typography.Text>
@@ -167,11 +156,8 @@ function AnomalyThreshold({
/>
</div>
{/* Sentence 3 */}
<div className={styles.alertConditionSentence}>
<Typography.Text
data-testid="using-the-text"
className={styles.sentenceText}
>
<div className="alert-condition-sentence">
<Typography.Text data-testid="using-the-text" className="sentence-text">
using the
</Typography.Text>
<Select
@@ -187,7 +173,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="algorithm-with-text"
className={styles.sentenceText}
className="sentence-text"
>
algorithm with
</Typography.Text>
@@ -206,7 +192,7 @@ function AnomalyThreshold({
<>
<Typography.Text
data-testid="seasonality-text"
className={styles.sentenceText}
className="sentence-text"
>
seasonality to
</Typography.Text>
@@ -242,10 +228,7 @@ function AnomalyThreshold({
/>
</>
) : (
<Typography.Text
data-testid="seasonality-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="seasonality-text" className="sentence-text">
seasonality
</Typography.Text>
)}

View File

@@ -1,108 +0,0 @@
.thresholdItem {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: var(--spacing-8);
}
.thresholdRow {
display: flex;
align-items: center;
gap: var(--spacing-8);
margin-bottom: 2px;
}
.thresholdIndicator {
display: flex;
}
.thresholdDot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.thresholdControls {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-wrap: wrap;
--input-background: var(--l3-background);
--input-hover-background: var(--l3-background);
--input-focus-background: var(--l3-background);
--input-border-color: var(--l3-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
:global(.ant-input),
:global(.ant-input-number) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
&:focus {
border-color: var(--internal-ant-border-color-focus);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--muted-foreground);
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
&:focus {
border-color: var(--internal-ant-border-color-focus);
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.iconBtn {
color: var(--l2-foreground);
border: 1px solid var(--l3-border);
background-color: var(--l3-background);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.highlightedText {
font-weight: bold;
color: var(--bg-robin-400);
margin: 0 4px;
}

View File

@@ -11,8 +11,6 @@ import { normalizeOperator } from '../utils';
import { ThresholdItemProps } from './types';
import { NotificationChannelsNotFoundContent } from './utils';
import styles from './ThresholdItem.module.scss';
function ThresholdItem({
threshold,
updateThreshold,
@@ -84,16 +82,15 @@ function ThresholdItem({
};
return (
<div key={threshold.id} className={styles.thresholdItem}>
<div className={styles.thresholdRow}>
<div className={styles.thresholdIndicator}>
<div key={threshold.id} className="threshold-item">
<div className="threshold-row">
<div className="threshold-indicator">
<div
className={styles.thresholdDot}
className="threshold-dot"
style={{ backgroundColor: threshold.color }}
data-testid="threshold-dot"
/>
</div>
<div className={styles.thresholdControls}>
<div className="threshold-controls">
<Input
placeholder="Enter threshold name"
value={threshold.label}
@@ -103,10 +100,8 @@ function ThresholdItem({
style={{ width: 200 }}
data-testid="threshold-name-input"
/>
<Typography.Text className={styles.sentenceText}>on value</Typography.Text>
<Typography.Text
className={`${styles.sentenceText} ${styles.highlightedText}`}
>
<Typography.Text className="sentence-text">on value</Typography.Text>
<Typography.Text className="sentence-text highlighted-text">
{getOperatorSymbol()}
</Typography.Text>
<Input
@@ -122,9 +117,7 @@ function ThresholdItem({
{yAxisUnitSelect}
{!notificationSettings.routingPolicies && (
<>
<Typography.Text className={styles.sentenceText}>
send to
</Typography.Text>
<Typography.Text className="sentence-text">send to</Typography.Text>
<Select
value={threshold.channels}
onChange={(value): void =>
@@ -161,9 +154,7 @@ function ThresholdItem({
)}
{showRecoveryThreshold && (
<>
<Typography.Text className={styles.sentenceText}>
recover on
</Typography.Text>
<Typography.Text className="sentence-text">recover on</Typography.Text>
<Input
placeholder="Enter recovery threshold value"
value={threshold.recoveryThresholdValue ?? ''}
@@ -179,7 +170,7 @@ function ThresholdItem({
type="default"
icon={<Trash size={16} />}
onClick={removeRecoveryThreshold}
className={styles.iconBtn}
className="icon-btn"
data-testid="remove-recovery-threshold-button"
/>
</Tooltip>
@@ -203,7 +194,7 @@ function ThresholdItem({
type="default"
icon={<CircleX size={16} />}
onClick={(): void => removeThreshold(threshold.id)}
className={styles.iconBtn}
className="icon-btn"
data-testid="remove-threshold-button"
/>
</Tooltip>

View File

@@ -25,6 +25,7 @@ const THRESHOLD_VIEW_TEST_ID = 'threshold-view';
const ANOMALY_VIEW_TEST_ID = 'anomaly-view';
const ANOMALY_TAB_TEXT = 'Anomaly';
const THRESHOLD_TAB_TEXT = 'Threshold';
const ACTIVE_TAB_CLASS = '.active-tab';
// Mock the Stepper component
jest.mock('../../Stepper', () => ({
@@ -129,9 +130,9 @@ describe('AlertCondition', () => {
// screen.queryByTestId(ANOMALY_THRESHOLD_TEST_ID),
// ).not.toBeInTheDocument();
// Verify threshold tab exists
// Verify threshold tab is active by default
const thresholdTab = screen.getByText(THRESHOLD_TAB_TEXT);
expect(thresholdTab).toBeInTheDocument();
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
// Verify both tabs are visible (METRICS_BASED_ALERT supports multiple tabs)
expect(screen.getByText(THRESHOLD_TAB_TEXT)).toBeInTheDocument();
@@ -205,24 +206,22 @@ describe('AlertCondition', () => {
});
// TODO: Unskip this when anomaly tab is implemented
// Note: Active tab styling is verified through component behavior (correct content shown)
// rather than CSS class checks since CSS modules classes are mocked in tests
it.skip('applies active tab styling correctly', () => {
renderAlertCondition();
// Threshold tab should be active by default - verify by checking content
expect(screen.getByTestId(ALERT_THRESHOLD_TEST_ID)).toBeInTheDocument();
expect(
screen.queryByTestId(ANOMALY_THRESHOLD_TEST_ID),
).not.toBeInTheDocument();
const thresholdTab = screen.getByText(THRESHOLD_TAB_TEXT);
const anomalyTab = screen.getByText(ANOMALY_TAB_TEXT);
// Threshold tab should be active by default
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
expect(anomalyTab.closest(ACTIVE_TAB_CLASS)).not.toBeInTheDocument();
// Click anomaly tab
const anomalyTab = screen.getByText(ANOMALY_TAB_TEXT);
fireEvent.click(anomalyTab);
// Anomaly tab should be active now - verify by checking content
expect(screen.getByTestId(ANOMALY_THRESHOLD_TEST_ID)).toBeInTheDocument();
expect(screen.queryByTestId(ALERT_THRESHOLD_TEST_ID)).not.toBeInTheDocument();
// Anomaly tab should be active now
expect(anomalyTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).not.toBeInTheDocument();
});
it('shows multiple tabs for METRICS_BASED_ALERT', () => {

View File

@@ -126,8 +126,8 @@ describe('ThresholdItem', () => {
it('renders threshold indicator with correct color', () => {
renderThresholdItem();
// Find the threshold dot by data-testid
const thresholdDot = screen.getByTestId('threshold-dot');
// Find the threshold dot by its class
const thresholdDot = document.querySelector('.threshold-dot');
expect(thresholdDot).toHaveStyle('background-color: #ff0000');
});

View File

@@ -0,0 +1,406 @@
.alert-condition-container {
margin: 0 16px;
margin-top: 24px;
.alert-condition {
display: flex;
align-items: center;
margin-left: 12px;
margin-top: 24px;
.alert-condition-tabs {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
.explorer-view-option {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0px;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
}
}
}
}
.alert-threshold-container,
.anomaly-threshold-container {
padding: 24px;
padding-right: 72px;
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
width: 100%;
.alert-condition-sentences {
display: flex;
flex-direction: column;
gap: 12px;
.alert-condition-sentence {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
.sentence-text {
color: var(--l2-foreground);
font-size: 13px;
line-height: 1.5;
display: flex;
align-items: center;
gap: 8px;
}
.ant-select {
width: 240px;
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
}
}
}
}
.thresholds-section {
margin-top: 16px;
margin-left: 24px;
.threshold-item {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: 16px;
.threshold-row {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 2px;
.threshold-indicator {
.threshold-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
}
.threshold-controls {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
}
}
.icon-btn {
color: var(--muted-foreground);
border: 1px solid var(--l1-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.recovery-threshold-input-group {
display: flex;
align-items: center;
gap: 0;
margin-left: 28px;
.recovery-threshold-label {
pointer-events: none;
cursor: default;
}
.recovery-threshold-btn {
pointer-events: none;
cursor: default;
color: var(--muted-foreground);
background-color: var(--card) !important;
border: 1px solid var(--l1-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
}
}
.add-threshold-btn {
margin-top: 8px;
border: 1px dashed var(--l1-border);
color: var(--l2-foreground);
background-color: transparent;
border-radius: 4px;
height: 32px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
.anticon {
margin-right: 8px;
}
}
}
.routing-policies-info-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 16px;
background-color: color-mix(
in srgb,
var(--primary-background) 10%,
transparent
);
border: 1px solid var(--primary-background);
padding: 8px 16px;
.routing-policies-info-banner-right {
display: flex;
align-items: center;
gap: 8px;
.view-routing-policies-button {
color: var(--accent-primary);
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
}
.ant-typography {
color: var(--accent-primary);
}
}
}
.anomaly-threshold-container {
.ant-select {
.ant-select-selector {
min-width: 150px;
}
}
}
.condensed-alert-threshold-container,
.condensed-anomaly-threshold-container {
width: 100%;
}
.condensed-advanced-options-container {
margin-top: 16px;
width: fit-parent;
}
.condensed-evaluation-settings-container {
.ant-btn {
display: flex;
align-items: center;
min-width: 240px;
width: auto;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.evaluate-alert-conditions-button-left {
color: var(--l2-foreground);
font-size: 12px;
flex-shrink: 0;
}
.evaluate-alert-conditions-button-right {
display: flex;
align-items: center;
color: var(--l2-foreground);
gap: 8px;
flex-shrink: 0;
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--l1-border);
padding: 1px 4px;
}
}
}
}
.highlighted-text {
font-weight: bold;
color: var(--bg-robin-400);
margin: 0 4px;
}
// Tooltip styles
.tooltip-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
.tooltip-description {
margin-bottom: 8px;
span {
font-weight: bold;
color: var(--bg-robin-400);
}
}
.tooltip-example {
margin-bottom: 8px;
color: var(--l2-foreground);
}
.tooltip-link {
.tooltip-link-text {
color: var(--accent-primary);
font-size: 11px;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}

View File

@@ -1,68 +0,0 @@
.routingPoliciesInfoBanner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-4);
margin-top: var(--spacing-8);
background-color: color-mix(
in srgb,
var(--primary-background) 10%,
transparent
);
border: 1px solid var(--primary-background);
padding: var(--spacing-4) var(--spacing-8);
:global(.ant-typography) {
color: var(--accent-primary);
}
}
.routingPoliciesInfoBannerRight {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
.viewRoutingPoliciesButton {
color: var(--accent-primary);
font-size: var(--periscope-font-size-small);
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
.tooltipContent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.tooltipExample {
margin-bottom: var(--spacing-4);
color: var(--l2-foreground);
}
.tooltipLink {
display: block;
}
.tooltipLinkText {
color: var(--accent-primary);
font-size: var(--periscope-font-size-small);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.tooltipDescription {
margin-bottom: var(--spacing-4);
span {
font-weight: bold;
color: var(--bg-robin-400);
}
}

View File

@@ -22,8 +22,6 @@ import { openInNewTab } from 'utils/navigation';
import { ROUTING_POLICIES_ROUTE } from './constants';
import { RoutingPolicyBannerProps } from './types';
import styles from './utils.module.scss';
export function getQueryNames(currentQuery: Query): BaseOptionType[] {
const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator(
currentQuery.builder.queryTraceOperator,
@@ -185,7 +183,7 @@ function TooltipContent({
handleTooltipClick(e);
}
}}
className={styles.tooltipContent}
className="tooltip-content"
>
{children}
</div>
@@ -206,7 +204,7 @@ function TooltipExample({
matchType: AlertThresholdMatchType;
}): JSX.Element {
return (
<div className={styles.tooltipExample}>
<div className="tooltip-example">
<strong>Example:</strong>
<br />
Say, For a 5-minute window (configured in Evaluation settings), 1 min
@@ -222,12 +220,12 @@ function TooltipExample({
function TooltipLink(): JSX.Element {
return (
<div className={styles.tooltipLink}>
<div className="tooltip-link">
<a
href="https://signoz.io/docs"
target="_blank"
rel="noopener noreferrer"
className={styles.tooltipLinkText}
className="tooltip-link-text"
>
Learn more
</a>
@@ -263,7 +261,7 @@ export const getMatchTypeTooltip = (
case AlertThresholdMatchType.AT_LEAST_ONCE:
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if <span>ANY</span> of
those aggregated data points crosses the threshold.
@@ -284,7 +282,7 @@ export const getMatchTypeTooltip = (
case AlertThresholdMatchType.ALL_THE_TIME:
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if <span>ALL</span>{' '}
aggregated data points cross the threshold.
@@ -308,7 +306,7 @@ export const getMatchTypeTooltip = (
).toFixed(1);
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if the{' '}
<span>AVERAGE</span> of all aggregated data points crosses the threshold.
@@ -330,7 +328,7 @@ export const getMatchTypeTooltip = (
const total = dataPoints.reduce((a, b) => a + b, 0);
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if the{' '}
<span>SUM</span> of all aggregated data points crosses the threshold.
@@ -352,7 +350,7 @@ export const getMatchTypeTooltip = (
const lastPoint = dataPoints[dataPoints.length - 1];
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers based on the{' '}
<span>MOST RECENT</span> aggregated data point only.
@@ -416,11 +414,11 @@ export function RoutingPolicyBanner({
}: RoutingPolicyBannerProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
return (
<div className={styles.routingPoliciesInfoBanner}>
<div className="routing-policies-info-banner">
<Typography.Text>
Use <strong>Routing Policies</strong> for dynamic routing
</Typography.Text>
<div className={styles.routingPoliciesInfoBannerRight}>
<div className="routing-policies-info-banner-right">
<Switch
value={notificationSettings.routingPolicies}
testId="routing-policies-switch"
@@ -433,7 +431,7 @@ export function RoutingPolicyBanner({
/>
<Button
type="link"
className={styles.viewRoutingPoliciesButton}
className="view-routing-policies-button"
data-testid="view-routing-policies-button"
onClick={(): void => safeNavigate(ROUTING_POLICIES_ROUTE)}
>

View File

@@ -1,134 +0,0 @@
.alertHeader {
background-color: var(--l1-background);
font-family: inherit;
color: var(--l1-foreground);
padding: var(--spacing-6) var(--spacing-8);
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
}
.editAlertHeader {
flex: 1;
}
.tabBar {
display: flex;
align-items: center;
justify-content: space-between;
}
.tab {
display: flex;
align-items: center;
background-color: var(--l1-background);
height: 32px;
font-size: var(--periscope-font-size-base);
color: var(--l1-foreground);
&::before {
content: '';
margin-right: var(--spacing-3);
font-size: var(--periscope-font-size-base);
color: var(--l3-foreground);
}
}
.content {
padding: var(--spacing-4) 0;
background: var(--l1-background);
display: flex;
flex-direction: column;
gap: var(--spacing-4);
min-width: 300px;
flex: 1;
}
.inputTitle {
background-color: transparent;
background: var(--l2-background);
color: var(--l2-foreground);
width: 100%;
min-width: 300px;
&:hover {
background: var(--l2-background);
}
}
.inputDescription {
font-size: var(--periscope-font-size-base);
background-color: transparent;
color: var(--l2-foreground);
}
.labelsInput {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
}
.labelsInputAddButton {
width: fit-content;
font-size: var(--periscope-font-size-base);
color: var(--l2-foreground);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
cursor: pointer;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
}
.labelsInputExistingLabels {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-4);
}
.labelsInputLabelPill {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
background-color: #ad7f581a;
color: var(--bg-sienna-400);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 16px;
font-size: var(--periscope-font-size-small);
border: 1px solid var(--bg-sienna-500);
font-family: 'Geist Mono';
}
.labelsInputRemoveButton {
background: none;
border: none;
color: var(--bg-sienna-400);
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
&:hover {
color: var(--l1-foreground);
}
}
.labelsInputInputContainer {
display: flex;
align-items: center;
background-color: transparent;
border: none;
}
.labelsInputInput {
flex: 1;
}

View File

@@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import classNames from 'classnames';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -14,7 +14,8 @@ import { Labels } from 'types/api/alerts/def';
import { useCreateAlertState } from '../context';
import LabelsInput from './LabelsInput';
import styles from './CreateAlertHeader.module.scss';
import './styles.scss';
function CreateAlertHeader(): JSX.Element {
const { alertState, setAlertState, isEditMode } = useCreateAlertState();
@@ -55,11 +56,11 @@ function CreateAlertHeader(): JSX.Element {
return (
<div
className={cx(styles.alertHeader, { [styles.editAlertHeader]: isEditMode })}
className={classNames('alert-header', { 'edit-alert-header': isEditMode })}
>
{!isEditMode && (
<div className={styles.tabBar}>
<div className={styles.tab}>New Alert Rule</div>
<div className="alert-header__tab-bar">
<div className="alert-header__tab">New Alert Rule</div>
<Button
prefix={<RotateCcw size={12} />}
onClick={handleSwitchToClassicExperience}
@@ -71,7 +72,7 @@ function CreateAlertHeader(): JSX.Element {
</Button>
</div>
)}
<div className={styles.content}>
<div className="alert-header__content">
<Input
type="text"
value={alertState.name}
@@ -82,7 +83,7 @@ function CreateAlertHeader(): JSX.Element {
alertRuleContext.setAlertRuleName(newName);
}
}}
className={styles.inputTitle}
className="alert-header__input title"
placeholder="Enter alert rule name"
data-testid="alert-name-input"
/>

View File

@@ -1,10 +1,8 @@
import React, { useCallback, useState } from 'react';
import { X } from '@signozhq/icons';
import { useNotifications } from 'hooks/useNotifications';
import { Input } from '@signozhq/ui/input';
import { LabelInputState, LabelsInputProps } from './types';
import styles from './CreateAlertHeader.module.scss';
function LabelsInput({
labels,
@@ -122,19 +120,19 @@ function LabelsInput({
}, [inputState]);
return (
<div className={styles.labelsInput}>
<div className="labels-input">
{Object.keys(labels).length > 0 && (
<div className={styles.labelsInputExistingLabels}>
<div className="labels-input__existing-labels">
{Object.entries(labels).map(([key, value]) => (
<span
key={key}
className={styles.labelsInputLabelPill}
className="labels-input__label-pill"
data-testid={`label-pill-${key}-${value}`}
>
{key}: {value}
<button
type="button"
className={styles.labelsInputRemoveButton}
className="labels-input__remove-button"
aria-label={`Remove label ${key}`}
onClick={(): void => handleRemoveLabel(key)}
>
@@ -147,7 +145,7 @@ function LabelsInput({
{!isAdding ? (
<button
className={styles.labelsInputAddButton}
className="labels-input__add-button"
type="button"
onClick={handleAddLabelsClick}
data-testid="alert-add-label-button"
@@ -155,15 +153,15 @@ function LabelsInput({
+ Add labels
</button>
) : (
<div className={styles.labelsInputInputContainer}>
<Input
<div className="labels-input__input-container">
<input
autoFocus
type="text"
value={inputState.isKeyInput ? inputState.key : inputState.value}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
className={styles.labelsInputInput}
className="labels-input__input"
placeholder={inputState.isKeyInput ? 'Enter key' : 'Enter value'}
data-testid="alert-add-label-input"
/>

View File

@@ -0,0 +1,145 @@
.alert-header {
background-color: var(--l1-background);
font-family: inherit;
color: var(--l1-foreground);
padding: 12px 16px;
&__tab-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Tab block visuals */
&__tab {
display: flex;
align-items: center;
background-color: var(--l1-background);
height: 32px;
font-size: 13px;
color: var(--l1-foreground);
}
&__tab::before {
content: '';
margin-right: 6px;
font-size: 13px;
color: var(--l3-foreground);
}
&__content {
padding: 8px 0;
background: var(--l1-background);
display: flex;
flex-direction: column;
gap: 8px;
min-width: 300px;
flex: 1;
}
&__input.title {
background-color: transparent;
color: var(--l1-foreground);
width: 100%;
min-width: 300px;
}
&__input.description {
font-size: 13px;
background-color: transparent;
color: var(--l2-foreground);
}
.ant-btn {
display: flex;
gap: 4px;
align-items: center;
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
margin-right: 16px;
}
}
.labels-input {
display: flex;
flex-direction: column;
gap: 8px;
&__add-button {
width: fit-content;
font-size: 13px;
color: var(--l2-foreground);
border: 1px solid var(--l1-border);
background-color: transparent;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
}
&__existing-labels {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
&__label-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background-color: #ad7f581a;
color: var(--bg-sienna-400);
padding: 4px 8px;
border-radius: 16px;
font-size: 12px;
border: 1px solid var(--bg-sienna-500);
font-family: 'Geist Mono';
}
&__remove-button {
background: none;
border: none;
color: var(--bg-sienna-400);
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
&:hover {
color: var(--l1-foreground);
}
}
&__input-container {
display: flex;
align-items: center;
background-color: transparent;
border: none;
}
&__input {
flex: 1;
background-color: transparent;
border: none;
outline: none;
padding: 6px 8px;
color: var(--l1-foreground);
font-size: 13px;
&::placeholder {
color: var(--l2-foreground);
}
&:focus,
&:active {
border: none;
outline: none;
}
}
}

View File

@@ -1,14 +1,14 @@
.createAlertV2Container {
.create-alert-v2-container {
background-color: var(--l1-background);
padding-bottom: 100px;
}
.stickyPageSpinner {
.sticky-page-spinner {
position: fixed;
inset: 0;
display: grid;
place-items: center;
background: rgb(0 0 0 / 35%);
background: rgba(0, 0, 0, 0.35);
z-index: 10000;
pointer-events: auto;
}

View File

@@ -12,7 +12,7 @@ import QuerySection from './QuerySection';
import { CreateAlertV2Props } from './types';
import { Spinner } from './utils';
import styles from './CreateAlertV2.module.scss';
import './CreateAlertV2.styles.scss';
function CreateAlertV2({ alertType }: CreateAlertV2Props): JSX.Element {
const queryToRedirect = buildInitialAlertDef(alertType);
@@ -25,7 +25,7 @@ function CreateAlertV2({ alertType }: CreateAlertV2Props): JSX.Element {
return (
<CreateAlertProvider initialAlertType={alertType}>
<Spinner />
<div className={styles.createAlertV2Container}>
<div className="create-alert-v2-container">
<CreateAlertHeader />
<QuerySection />
<AlertCondition />

View File

@@ -5,7 +5,8 @@ import { Typography } from '@signozhq/ui/typography';
import { Info } from '@signozhq/icons';
import { IAdvancedOptionItemProps } from '../types';
import styles from './styles.module.scss';
import './styles.scss';
function AdvancedOptionItem({
title,
@@ -28,9 +29,9 @@ function AdvancedOptionItem({
};
return (
<div className={styles.advancedOptionItem} data-testid={dataTestId}>
<div className={styles.advancedOptionItemLeftContent}>
<Typography.Text className={styles.advancedOptionItemTitle}>
<div className="advanced-option-item" data-testid={dataTestId}>
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
{title}
{tooltipText && (
<Tooltip title={tooltipText}>
@@ -38,13 +39,13 @@ function AdvancedOptionItem({
</Tooltip>
)}
</Typography.Text>
<Typography.Text className={styles.advancedOptionItemDescription}>
<Typography.Text className="advanced-option-item-description">
{description}
</Typography.Text>
</div>
<div className={styles.advancedOptionItemRightContent}>
<div className="advanced-option-item-right-content">
<div
className={styles.advancedOptionItemInput}
className="advanced-option-item-input"
style={{ display: showInput ? 'block' : 'none' }}
>
{input}

View File

@@ -1,154 +0,0 @@
.advancedOptionItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: var(--spacing-8);
border-bottom: 1px solid var(--l1-border);
}
.advancedOptionItemLeftContent {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.advancedOptionItemTitle {
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.advancedOptionItemDescription {
color: var(--muted-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.advancedOptionItemInput {
margin-top: var(--spacing-8);
:global(.ant-input) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l3-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
&:focus {
border-color: var(--internal-ant-border-color-focus);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
&:focus {
border-color: var(--internal-ant-border-color-focus);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--l2-foreground);
}
}
}
.advancedOptionItemRightContent {
display: flex;
align-items: flex-start;
gap: var(--spacing-8);
}
.advancedOptionItemInputGroup {
display: flex;
align-items: center;
gap: var(--spacing-4);
--input-background: var(--l3-background);
--input-hover-background: var(--l3-background);
--input-focus-background: var(--l3-background);
--input-border-color: var(--l3-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
:global(.ant-input) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
&:hover {
border-color: var(--internal-ant-border-color-hover);
}
&:focus {
border-color: var(--internal-ant-border-color-focus);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--l3-background);
color: var(--l3-foreground);
height: 32px;
border: 1px solid var(--l3-border);
&:hover {
border-color: var(--l3-border);
}
&:focus {
border-color: var(--l3-border);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--l2-foreground);
}
}
}
.advancedOptionItemButton {
display: flex;
align-items: center;
gap: var(--spacing-4);
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
border-radius: 4px;
}

View File

@@ -0,0 +1,150 @@
.advanced-option-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 16px;
border-bottom: 1px solid var(--l1-border);
.advanced-option-item-left-content {
display: flex;
flex-direction: column;
gap: 6px;
.advanced-option-item-title {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.advanced-option-item-description {
color: var(--muted-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 400;
}
.advanced-option-item-input {
margin-top: 16px;
.ant-input {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--l2-foreground);
}
}
}
}
.advanced-option-item-right-content {
display: flex;
align-items: flex-start;
gap: 16px;
.advanced-option-item-input-group {
display: flex;
align-items: center;
gap: 8px;
.ant-input {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--l2-background);
color: var(--l1-foreground);
height: 32px;
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--l2-foreground);
}
}
}
.advanced-option-item-button {
display: flex;
align-items: center;
gap: 8px;
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
border-radius: 4px;
}
}
}

View File

@@ -4,15 +4,13 @@ import { Typography } from '@signozhq/ui/typography';
import { useCreateAlertState } from '../context';
import AdvancedOptionItem from './AdvancedOptionItem';
import advancedOptionStyles from './AdvancedOptionItem/styles.module.scss';
import EvaluationCadence from './EvaluationCadence';
import styles from './styles.module.scss';
function AdvancedOptions(): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
return (
<div className={styles.advancedOptionsContainer}>
<div className="advanced-options-container">
<Collapse bordered={false}>
<Collapse.Panel header="ADVANCED OPTIONS" key="1">
<EvaluationCadence />
@@ -21,7 +19,7 @@ function AdvancedOptions(): JSX.Element {
description="Send notification if no data is received for a specified time period."
tooltipText="Useful for monitoring data pipelines or services that should continuously send data. For example, alert if no logs are received for 10 minutes"
input={
<div className={advancedOptionStyles.advancedOptionItemInputGroup}>
<div className="advanced-option-item-input-group">
<Input
placeholder="Enter tolerance limit..."
type="number"
@@ -54,7 +52,7 @@ function AdvancedOptions(): JSX.Element {
description="Only trigger alert when there are enough data points to make a reliable decision."
tooltipText="Prevents false alarms when there's insufficient data. For example, require at least 5 data points before checking if CPU usage is above 80%."
input={
<div className={advancedOptionStyles.advancedOptionItemInputGroup}>
<div className="advanced-option-item-input-group">
<Input
placeholder="Enter minimum datapoints..."
style={{ width: 100 }}

View File

@@ -6,8 +6,6 @@ import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/
import { IEditCustomScheduleProps } from 'container/CreateAlertV2/EvaluationSettings/types';
import { Calendar1, Pencil, Trash } from '@signozhq/icons';
import styles from './styles.module.scss';
function EditCustomSchedule({
setIsEvaluationCadenceDetailsVisible,
setIsPreviewVisible,
@@ -19,7 +17,7 @@ function EditCustomSchedule({
return (
<Typography.Text>
<Typography.Text>Every</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.repeatEvery
.charAt(0)
.toUpperCase() +
@@ -28,7 +26,7 @@ function EditCustomSchedule({
{advancedOptions.evaluationCadence.custom.repeatEvery !== 'day' && (
<>
<Typography.Text>on</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.occurence
.map(
(occurence) => occurence.charAt(0).toUpperCase() + occurence.slice(1),
@@ -38,7 +36,7 @@ function EditCustomSchedule({
</>
)}
<Typography.Text>at</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.startAt}
</Typography.Text>
</Typography.Text>
@@ -47,11 +45,11 @@ function EditCustomSchedule({
return (
<Typography.Text>
<Typography.Text>Starting on</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.date?.format('DD/MM/YYYY')}
</Typography.Text>
<Typography.Text>at</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.startAt}
</Typography.Text>
</Typography.Text>
@@ -79,9 +77,9 @@ function EditCustomSchedule({
};
return (
<div className={styles.editCustomSchedule} data-testid="edit-custom-schedule">
<div className="edit-custom-schedule">
{displayText}
<div>
<div className="button-row">
<Button.Group>
<Button type="default" onClick={handleEdit}>
<Pencil size={12} />

View File

@@ -8,8 +8,9 @@ import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
import EditCustomSchedule from './EditCustomSchedule';
import EvaluationCadenceDetails from './EvaluationCadenceDetails';
import EvaluationCadencePreview from './EvaluationCadencePreview';
import advancedOptionStyles from '../AdvancedOptionItem/styles.module.scss';
import styles from './styles.module.scss';
import './styles.scss';
import '../AdvancedOptionItem/styles.scss';
function EvaluationCadence(): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
@@ -40,31 +41,25 @@ function EvaluationCadence(): JSX.Element {
// };
return (
<div className={styles.evaluationCadenceContainer}>
<div
className={`${advancedOptionStyles.advancedOptionItem} ${styles.evaluationCadenceItem}`}
>
<div className={advancedOptionStyles.advancedOptionItemLeftContent}>
<Typography.Text className={advancedOptionStyles.advancedOptionItemTitle}>
<div className="evaluation-cadence-container">
<div className="advanced-option-item evaluation-cadence-item">
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
How often to check
<Tooltip title="Controls how frequently the alert evaluates your conditions. For most alerts, 1-5 minutes is sufficient.">
<Info data-testid="evaluation-cadence-tooltip-icon" size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text
className={advancedOptionStyles.advancedOptionItemDescription}
>
<Typography.Text className="advanced-option-item-description">
How frequently this alert checks your data. Default: Every 1 minute
</Typography.Text>
</div>
{isCustomScheduleButtonVisible && (
<div
className={advancedOptionStyles.advancedOptionItemRightContent}
className="advanced-option-item-right-content"
data-testid="evaluation-cadence-input-group"
>
<Input.Group
className={advancedOptionStyles.advancedOptionItemInputGroup}
>
<Input.Group className="advanced-option-item-input-group">
<Input
type="number"
placeholder="Enter time"

View File

@@ -21,7 +21,6 @@ import {
isValidRRule,
} from '../utils';
import { ScheduleList } from './EvaluationCadencePreview';
import styles from './styles.module.scss';
function EvaluationCadenceDetails({
setIsOpen,
@@ -91,8 +90,8 @@ function EvaluationCadenceDetails({
}, [evaluationCadence.custom.repeatEvery]);
const EditorView = (
<div className={styles.editorView} data-testid="editor-view">
<div className={styles.selectGroup}>
<div className="editor-view" data-testid="editor-view">
<div className="select-group">
<Typography.Text>REPEAT EVERY</Typography.Text>
<Select
options={EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS}
@@ -114,7 +113,7 @@ function EvaluationCadenceDetails({
/>
</div>
{evaluationCadence.custom.repeatEvery !== 'day' && (
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>ON DAY(S)</Typography.Text>
<Select
options={occurenceOptions}
@@ -136,7 +135,7 @@ function EvaluationCadenceDetails({
/>
</div>
)}
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.custom.startAt}
@@ -151,7 +150,7 @@ function EvaluationCadenceDetails({
}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -175,8 +174,8 @@ function EvaluationCadenceDetails({
);
const RRuleView = (
<div className={styles.rruleView} data-testid="rrule-view">
<div className={styles.selectGroup}>
<div className="rrule-view" data-testid="rrule-view">
<div className="select-group">
<Typography.Text>STARTING ON</Typography.Text>
<DatePicker
value={evaluationCadence.rrule.date}
@@ -192,7 +191,7 @@ function EvaluationCadenceDetails({
placeholder="Select date"
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.rrule.startAt}
@@ -295,19 +294,19 @@ function EvaluationCadenceDetails({
};
return (
<div className={styles.evaluationCadenceDetails}>
<Typography.Text className={styles.evaluationCadenceDetailsTitle}>
<div className="evaluation-cadence-details">
<Typography.Text className="evaluation-cadence-details-title">
Add Custom Schedule
</Typography.Text>
<div className={styles.evaluationCadenceDetailsContent}>
<div className={styles.evaluationCadenceDetailsContentRow}>
<div className={styles.querySectionTabs}>
<div className={styles.querySectionQueryActions}>
<div className="evaluation-cadence-details-content">
<div className="evaluation-cadence-details-content-row">
<div className="query-section-tabs">
<div className="query-section-query-actions">
{tabs.map((tab) => (
<Button
key={tab.value}
className={classNames(styles.explorerViewOption, {
[styles.activeTab]: activeTab === tab.value,
className={classNames('list-view-tab', 'explorer-view-option', {
'active-tab': activeTab === tab.value,
})}
onClick={(): void => {
handleChangeTab(tab.value as 'editor' | 'rrule');
@@ -321,7 +320,7 @@ function EvaluationCadenceDetails({
</div>
{activeTab === 'editor' && EditorView}
{activeTab === 'rrule' && RRuleView}
<div className={styles.buttonsRow}>
<div className="buttons-row">
<Button type="default" onClick={handleDiscard}>
Discard
</Button>
@@ -334,7 +333,7 @@ function EvaluationCadenceDetails({
</Button>
</div>
</div>
<div className={styles.evaluationCadenceDetailsContentRow}>
<div className="evaluation-cadence-details-content-row">
<ScheduleList
schedule={schedule}
currentTimezone={evaluationCadence.custom.timezone}

View File

@@ -10,7 +10,6 @@ import {
buildAlertScheduleFromCustomSchedule,
buildAlertScheduleFromRRule,
} from '../utils';
import styles from './styles.module.scss';
export function ScheduleList({
schedule,
@@ -18,21 +17,21 @@ export function ScheduleList({
}: IScheduleListProps): JSX.Element {
if (schedule && schedule.length > 0) {
return (
<div className={styles.schedulePreview} data-testid="schedule-preview">
<div className={styles.schedulePreviewHeader}>
<div className="schedule-preview" data-testid="schedule-preview">
<div className="schedule-preview-header">
<Calendar size={16} />
<Typography.Text className={styles.schedulePreviewTitle}>
<Typography.Text className="schedule-preview-title">
Schedule Preview
</Typography.Text>
</div>
<div className={styles.schedulePreviewList}>
<div className="schedule-preview-list">
{schedule.map((date) => (
<div key={date.toISOString()} className={styles.schedulePreviewItem}>
<div className={styles.schedulePreviewTimeline}>
<div className={styles.schedulePreviewTimelineLine} />
<div key={date.toISOString()} className="schedule-preview-item">
<div className="schedule-preview-timeline">
<div className="schedule-preview-timeline-line" />
</div>
<div className={styles.schedulePreviewContent}>
<div className={styles.schedulePreviewDate}>
<div className="schedule-preview-content">
<div className="schedule-preview-date">
{date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
@@ -46,8 +45,8 @@ export function ScheduleList({
second: '2-digit',
})}
</div>
<div className={styles.schedulePreviewSeparator} />
<div className={styles.schedulePreviewTimezone}>
<div className="schedule-preview-separator" />
<div className="schedule-preview-timezone">
{
TIMEZONE_DATA.find((timezone) => timezone.value === currentTimezone)
?.label
@@ -62,7 +61,7 @@ export function ScheduleList({
}
return (
<div className={styles.noSchedule} data-testid="no-schedule">
<div className="no-schedule" data-testid="no-schedule">
<Info size={32} />
<Typography.Text>
Please fill the relevant information to generate a schedule
@@ -99,19 +98,13 @@ function EvaluationCadencePreview({
open={isOpen}
onCancel={(): void => setIsOpen(false)}
footer={null}
className={styles.evaluationCadencePreviewModal}
className="evaluation-cadence-preview-modal"
width={800}
centered
>
<div
className={`${styles.evaluationCadenceDetails} ${styles.evaluationCadencePreview}`}
>
<div
className={`${styles.evaluationCadenceDetailsContent} ${styles.evaluationCadencePreviewContent}`}
>
<div
className={`${styles.evaluationCadenceDetailsContentRow} ${styles.evaluationCadencePreviewContentRow}`}
>
<div className="evaluation-cadence-details evaluation-cadence-preview">
<div className="evaluation-cadence-details-content">
<div className="evaluation-cadence-details-content-row">
<ScheduleList
schedule={schedule}
currentTimezone={advancedOptions.evaluationCadence.custom.timezone}

View File

@@ -1,3 +1,5 @@
import EvaluationCadence from './EvaluationCadence';
import './styles.scss';
export default EvaluationCadence;

View File

@@ -1,450 +0,0 @@
.evaluationCadenceContainer {
border-bottom: 1px solid var(--l1-border);
}
.evaluationCadenceItem {
border-bottom: none !important;
}
.editCustomSchedule {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
padding: var(--spacing-8);
:global(.ant-typography) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
}
:global(.ant-btn-group) {
:global(.ant-btn) {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
display: flex;
align-items: center;
gap: var(--spacing-4);
}
}
}
.highlight {
background-color: var(--l1-background);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
color: var(--l2-foreground);
font-weight: var(--font-weight-medium);
margin: 0 var(--spacing-2);
font-size: var(--periscope-font-size-base);
}
.evaluationCadenceDetails {
margin: var(--spacing-8);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
border: 1px solid var(--l1-border);
}
.evaluationCadenceDetailsTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
padding-left: var(--spacing-8);
padding-top: var(--spacing-8);
}
.querySectionTabs {
display: flex;
align-items: center;
}
.querySectionQueryActions {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
}
.explorerViewOption {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: var(--spacing-4);
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
}
.activeTab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
.evaluationCadenceDetailsContent {
display: flex;
gap: var(--spacing-8);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-8);
}
.evaluationCadenceDetailsContentRow {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
flex: 1;
height: 500px;
overflow-y: scroll;
padding-right: var(--spacing-8);
}
.editorView {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
.rruleView {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
textarea {
height: 200px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: 'Space Mono';
font-size: var(--periscope-font-size-base);
&::placeholder {
font-family: 'Space Mono';
color: var(--muted-foreground) !important;
}
}
}
.selectGroup {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
:global(.ant-typography) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
:global(.ant-select) {
border: 1px solid var(--l1-border);
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
:global(.ant-picker) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
:global(.ant-picker-input) {
background-color: var(--l2-background);
color: var(--l1-foreground);
}
}
}
.buttonsRow {
display: flex;
align-items: center;
gap: var(--spacing-8);
margin-top: var(--spacing-8);
}
.noSchedule {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
}
.schedulePreview {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
min-height: 0;
}
.schedulePreviewHeader {
display: flex;
align-items: center;
gap: var(--spacing-4);
padding: var(--spacing-4) 0;
background-color: var(--card);
position: sticky;
top: 0;
z-index: 1;
border-bottom: 1px solid var(--l1-border);
}
.schedulePreviewTitle {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.schedulePreviewList {
display: flex;
flex-direction: column;
gap: 0;
flex: 1;
overflow-y: auto;
padding-top: var(--spacing-4);
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l1-border);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
.schedulePreviewItem {
display: flex;
align-items: center;
gap: var(--spacing-6);
padding: var(--spacing-4) 0;
}
.schedulePreviewTimeline {
display: flex;
flex-direction: column;
align-items: center;
min-width: 20px;
}
.schedulePreviewTimelineLine {
width: 1px;
height: 20px;
background-color: var(--l2-background);
}
.schedulePreviewContent {
display: flex;
align-items: center;
gap: var(--spacing-6);
flex: 1;
}
.schedulePreviewDate {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: 400;
white-space: nowrap;
}
.schedulePreviewSeparator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.schedulePreviewTimezone {
color: var(--muted-foreground);
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
/* Global styles for ant-picker date panel */
:global(.ant-picker-date-panel) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
:global(.ant-picker-date-panel-layout) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
:global(.ant-picker-date-panel-header) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
/* Custom modal styles for preview */
.evaluationCadencePreviewModal {
:global(.ant-modal-content) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: var(--spacing-4);
}
:global(.ant-modal-header) {
background-color: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
padding: var(--spacing-8) 20px;
:global(.ant-modal-title) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-medium);
font-weight: var(--font-weight-semibold);
}
}
:global(.ant-modal-close) {
color: var(--l2-foreground);
top: var(--spacing-8);
right: 20px;
&:hover {
color: var(--l1-foreground);
}
}
:global(.ant-modal-body) {
padding: 0;
background-color: var(--l2-background);
}
}
.evaluationCadencePreview {
border: none;
margin: 0;
}
.evaluationCadencePreviewContent {
border-top: none;
padding: 0;
}
.evaluationCadencePreviewContentRow {
height: auto;
max-height: 60vh;
overflow-y: auto;
padding: var(--spacing-6);
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l2-background);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
.previewScheduleHeader {
background-color: var(--card);
border-bottom: 1px solid var(--l1-border);
padding: var(--spacing-6) var(--spacing-8);
margin: calc(-1 * var(--spacing-6)) calc(-1 * var(--spacing-6))
var(--spacing-8) calc(-1 * var(--spacing-6));
}
.previewScheduleTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.previewScheduleItem {
padding: var(--spacing-6) 0;
border-bottom: 1px solid var(--l1-border);
&:last-child {
border-bottom: none;
}
}
.previewScheduleTimelineLine {
width: 2px;
height: 24px;
background-color: var(--primary-background);
border-radius: 1px;
}
.previewScheduleDate {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.previewScheduleTimezone {
background-color: var(--l1-background);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
font-size: 12px;
}
.previewNoSchedule {
min-height: 300px;
padding: 40px var(--spacing-6);
svg {
color: var(--muted-foreground);
}
}

View File

@@ -0,0 +1,453 @@
.evaluation-cadence-container {
border-bottom: 1px solid var(--l1-border);
.evaluation-cadence-item {
border-bottom: none !important;
}
.edit-custom-schedule {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px;
.ant-typography {
color: var(--l1-foreground);
font-size: 13px;
.highlight {
background-color: var(--l1-background);
padding: 4px 8px;
border-radius: 4px;
color: var(--l2-foreground);
font-weight: 500;
margin: 0 4px;
font-size: 13px;
}
}
.ant-btn-group {
.ant-btn {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
}
}
}
}
.evaluation-cadence-details {
margin: 16px;
display: flex;
flex-direction: column;
gap: 16px;
border: 1px solid var(--l1-border);
.evaluation-cadence-details-title {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
padding-left: 16px;
padding-top: 16px;
}
.query-section-tabs {
display: flex;
align-items: center;
.query-section-query-actions {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
.explorer-view-option {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0px;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
}
}
}
.evaluation-cadence-details-content {
display: flex;
gap: 16px;
border-top: 1px solid var(--l1-border);
padding: 16px;
.evaluation-cadence-details-content-row {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
height: 500px;
overflow-y: scroll;
padding-right: 16px;
.editor-view,
.rrule-view {
display: flex;
flex-direction: column;
gap: 16px;
textarea {
height: 200px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: 'Space Mono';
font-size: 13px;
&::placeholder {
font-family: 'Space Mono';
color: var(--muted-foreground) !important;
}
}
.select-group {
display: flex;
flex-direction: column;
gap: 4px;
.ant-typography {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
.ant-select {
border: 1px solid var(--l1-border);
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
.ant-picker {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.ant-picker-input {
background-color: var(--l2-background);
color: var(--l1-foreground);
}
}
}
}
.buttons-row {
display: flex;
align-items: center;
gap: 16px;
margin-top: 16px;
}
.no-schedule {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 8px;
height: 100%;
color: var(--l1-foreground);
font-size: 13px;
}
.schedule-preview {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
min-height: 0;
.schedule-preview-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
background-color: var(--card);
position: sticky;
top: 0;
z-index: 1;
border-bottom: 1px solid var(--l1-border);
.schedule-preview-title {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
}
}
.schedule-preview-list {
display: flex;
flex-direction: column;
gap: 0;
flex: 1;
overflow-y: auto;
padding-top: 8px;
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l1-border);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
.schedule-preview-item {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
.schedule-preview-timeline {
display: flex;
flex-direction: column;
align-items: center;
min-width: 20px;
.schedule-preview-timeline-line {
width: 1px;
height: 20px;
background-color: var(--l2-background);
}
}
.schedule-preview-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
.schedule-preview-date {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 400;
white-space: nowrap;
}
.schedule-preview-separator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.schedule-preview-timezone {
color: var(--muted-foreground);
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
}
}
}
}
}
}
}
.ant-picker-date-panel {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
.ant-picker-date-panel-layout {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
.ant-picker-date-panel-header {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
// Custom modal styles for preview
.evaluation-cadence-preview-modal {
.ant-modal-content {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 8px;
}
.ant-modal-header {
background-color: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
padding: 16px 20px;
.ant-modal-title {
color: var(--l1-foreground);
font-size: 16px;
font-weight: 600;
}
}
.ant-modal-close {
color: var(--l2-foreground);
top: 16px;
right: 20px;
&:hover {
color: var(--l1-foreground);
}
}
.ant-modal-body {
padding: 0;
background-color: var(--l2-background);
}
.evaluation-cadence-details {
border: none;
margin: 0;
.evaluation-cadence-details-content {
border-top: none;
padding: 0;
.evaluation-cadence-details-content-row {
height: auto;
max-height: 60vh;
overflow-y: auto;
padding: 12px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l2-background);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
.schedule-preview {
.schedule-preview-header {
background-color: var(--card);
border-bottom: 1px solid var(--l1-border);
padding: 12px 16px;
margin: -12px -12px 16px -12px;
.schedule-preview-title {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
}
.schedule-preview-list {
.schedule-preview-item {
padding: 12px 0;
border-bottom: 1px solid var(--l1-border);
&:last-child {
border-bottom: none;
}
.schedule-preview-timeline {
.schedule-preview-timeline-line {
width: 2px;
height: 24px;
background-color: var(--primary-background);
border-radius: 1px;
}
}
.schedule-preview-content {
.schedule-preview-date {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
.schedule-preview-timezone {
background-color: var(--l1-background);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
}
}
}
}
.no-schedule {
min-height: 300px;
padding: 40px 12px;
svg {
color: var(--muted-foreground);
}
}
}
}
}
}
// Light mode styles

View File

@@ -5,7 +5,8 @@ import { ChevronDown, ChevronUp } from '@signozhq/icons';
import { useCreateAlertState } from '../context';
import EvaluationWindowPopover from './EvaluationWindowPopover';
import { getEvaluationWindowTypeText, getTimeframeText } from './utils';
import styles from './styles.module.scss';
import './styles.scss';
function EvaluationSettings(): JSX.Element {
const { evaluationWindow, setEvaluationWindow } = useCreateAlertState();
@@ -27,14 +28,13 @@ function EvaluationSettings(): JSX.Element {
}
trigger="click"
showArrow={false}
rootClassName="evaluation-window-popover-overlay"
>
<Button data-testid="evaluation-settings-button">
<div className={styles.evaluateAlertConditionsButtonLeft}>
<div className="evaluate-alert-conditions-button-left">
{getTimeframeText(evaluationWindow)}
</div>
<div className={styles.evaluateAlertConditionsButtonRight}>
<div className={styles.evaluateAlertConditionsButtonRightText}>
<div className="evaluate-alert-conditions-button-right">
<div className="evaluate-alert-conditions-button-right-text">
{getEvaluationWindowTypeText(evaluationWindow.windowType)}
</div>
{isEvaluationWindowPopoverOpen ? (
@@ -49,7 +49,7 @@ function EvaluationSettings(): JSX.Element {
return (
<div
className={styles.condensedEvaluationSettingsContainer}
className="condensed-evaluation-settings-container"
data-testid="condensed-evaluation-settings-container"
>
{popoverContent}

View File

@@ -12,7 +12,6 @@ import {
import TimeInput from '../TimeInput';
import { IEvaluationWindowDetailsProps } from '../types';
import { getCumulativeWindowTimeframeText } from '../utils';
import styles from '../styles.module.scss';
function EvaluationWindowDetails({
evaluationWindow,
@@ -118,12 +117,12 @@ function EvaluationWindowDetails({
if (isCurrentHour) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>STARTING AT MINUTE</Typography.Text>
<Select
options={currentHourOptions}
@@ -139,19 +138,19 @@ function EvaluationWindowDetails({
if (isCurrentDay) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>STARTING AT</Typography.Text>
<TimeInput
value={evaluationWindow.startingAt.time}
onChange={handleTimeChange}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -167,12 +166,12 @@ function EvaluationWindowDetails({
if (isCurrentMonth) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>STARTING ON DAY</Typography.Text>
<Select
options={currentMonthOptions}
@@ -182,14 +181,14 @@ function EvaluationWindowDetails({
data-testid="evaluation-window-details-starting-at-select"
/>
</div>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>STARTING AT</Typography.Text>
<TimeInput
value={evaluationWindow.startingAt.time}
onChange={handleTimeChange}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -204,13 +203,13 @@ function EvaluationWindowDetails({
}
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getRollingWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>Specify custom duration</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>VALUE</Typography.Text>
<Input
name="value"
@@ -221,7 +220,7 @@ function EvaluationWindowDetails({
data-testid="evaluation-window-details-custom-rolling-window-duration-input"
/>
</div>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>UNIT</Typography.Text>
<Select
options={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}

View File

@@ -16,7 +16,6 @@ import {
} from '../types';
import EvaluationWindowDetails from './EvaluationWindowDetails';
import { useKeyboardNavigationForEvaluationWindowPopover } from './useKeyboardNavigation';
import styles from '../styles.module.scss';
function EvaluationWindowPopover({
evaluationWindow,
@@ -52,42 +51,34 @@ function EvaluationWindowPopover({
onChange: (value: string) => void,
sectionId: string,
): JSX.Element => (
<div
className={styles.evaluationWindowContentItem}
data-section-id={sectionId}
>
<Typography.Text className={styles.evaluationWindowContentItemLabel}>
<div className="evaluation-window-content-item" data-section-id={sectionId}>
<Typography.Text className="evaluation-window-content-item-label">
{label}
</Typography.Text>
<div className={styles.evaluationWindowContentList}>
{contentOptions.map((option, index) => {
const isActive = currentValue === option.value;
return (
<div
className={classNames(styles.evaluationWindowContentListItem, {
[styles.evaluationWindowContentListItemActive]: isActive,
})}
key={option.value}
role="button"
tabIndex={0}
data-value={option.value}
data-section-id={sectionId}
data-testid={`${sectionId}-option-${option.value}`}
data-active={isActive}
onClick={(): void => onChange(option.value)}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(option.value);
}
}}
ref={index === 0 ? firstItemRef : undefined}
>
<Typography.Text>{option.label}</Typography.Text>
{isActive && <Check size={12} />}
</div>
);
})}
<div className="evaluation-window-content-list">
{contentOptions.map((option, index) => (
<div
className={classNames('evaluation-window-content-list-item', {
active: currentValue === option.value,
})}
key={option.value}
role="button"
tabIndex={0}
data-value={option.value}
data-section-id={sectionId}
onClick={(): void => onChange(option.value)}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(option.value);
}
}}
ref={index === 0 ? firstItemRef : undefined}
>
<Typography.Text>{option.label}</Typography.Text>
{currentValue === option.value && <Check size={12} />}
</div>
))}
</div>
</div>
);
@@ -103,7 +94,7 @@ function EvaluationWindowPopover({
);
}
return (
<div className={styles.selectionContent}>
<div className="selection-content">
<Typography.Text>
{getRollingWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
@@ -117,7 +108,7 @@ function EvaluationWindowPopover({
!evaluationWindow.timeframe
) {
return (
<div className={styles.selectionContent}>
<div className="selection-content">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
@@ -136,12 +127,12 @@ function EvaluationWindowPopover({
return (
<div
className={styles.evaluationWindowPopover}
className="evaluation-window-popover"
ref={containerRef}
role="menu"
aria-label="Evaluation window options"
>
<div className={styles.evaluationWindowContent}>
<div className="evaluation-window-content">
{renderEvaluationWindowContent(
'EVALUATION WINDOW',
EVALUATION_WINDOW_TYPE,

View File

@@ -1,54 +0,0 @@
.timeInputContainer {
display: flex;
align-items: center;
gap: 0;
}
// Compound + descendant selector keeps specificity above
// parent `.selectGroup :global(.ant-input)` override so the
// 40px field width is not clobbered to 60%.
.timeInputContainer :global(.ant-input).timeInputField {
width: 40px;
height: 32px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 600;
text-align: center;
border-radius: 4px;
&::placeholder {
color: var(--l2-foreground);
font-family: 'Space Mono', monospace;
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
outline: none;
}
&:disabled {
background-color: var(--l2-background);
color: var(--l2-foreground);
cursor: not-allowed;
&:hover {
border-color: var(--l1-border);
}
}
}
.timeInputSeparator {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 600;
margin: 0 4px;
user-select: none;
}

View File

@@ -0,0 +1,51 @@
.time-input-container {
display: flex;
align-items: center;
gap: 0;
.time-input-field {
width: 40px;
height: 32px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 600;
text-align: center;
border-radius: 4px;
&::placeholder {
color: var(--l2-foreground);
font-family: 'Space Mono', monospace;
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
outline: none;
}
&:disabled {
background-color: var(--l2-background);
color: var(--l2-foreground);
cursor: not-allowed;
&:hover {
border-color: var(--l1-border);
}
}
}
.time-input-separator {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 600;
margin: 0 4px;
user-select: none;
}
}

View File

@@ -1,7 +1,6 @@
import { Input } from '@signozhq/ui/input';
import React, { useEffect, useState } from 'react';
import styles from './TimeInput.module.scss';
import { Input } from '@signozhq/ui/input';
import './TimeInput.scss';
export interface TimeInputProps {
value?: string; // Format: "HH:MM:SS"
@@ -145,10 +144,7 @@ function TimeInput({
};
return (
<div
data-testid="time-input"
className={`${styles.timeInputContainer} ${className}`.trim()}
>
<div data-testid="time-input" className={`time-input-container ${className}`}>
<Input
data-field="hours"
value={hours}
@@ -157,11 +153,11 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'hours')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-hours"
/>
<span className={styles.timeInputSeparator}>:</span>
<span className="time-input-separator">:</span>
<Input
data-field="minutes"
value={minutes}
@@ -170,11 +166,11 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'minutes')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-minutes"
/>
<span className={styles.timeInputSeparator}>:</span>
<span className="time-input-separator">:</span>
<Input
data-field="seconds"
value={seconds}
@@ -183,7 +179,7 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'seconds')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-seconds"
/>

View File

@@ -13,12 +13,9 @@ jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
const ALERT_WHEN_DATA_STOPS_COMING_TEXT = 'Alert when data stops coming';
const MINIMUM_DATA_REQUIRED_TEXT = 'Minimum data required';
// const ACCOUNT_FOR_DATA_DELAY_TEXT = 'Account for data delay';
const ACCOUNT_FOR_DATA_DELAY_TEXT = 'Account for data delay';
const ADVANCED_OPTION_ITEM_CLASS = '.advanced-option-item';
const SWITCH_ROLE_SELECTOR = '[role="switch"]';
const SEND_NOTIFICATION_TEST_ID =
'send-notification-if-data-is-missing-container';
const ENFORCE_MINIMUM_DATAPOINTS_TEST_ID =
'enforce-minimum-datapoints-container';
describe('AdvancedOptions', () => {
it('should render evaluation cadence and the advanced options minimized by default', () => {
@@ -67,9 +64,9 @@ describe('AdvancedOptions', () => {
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
const alertWhenDataStopsComingContainer = screen.getByTestId(
SEND_NOTIFICATION_TEST_ID,
);
const alertWhenDataStopsComingContainer = screen
.getByText(ALERT_WHEN_DATA_STOPS_COMING_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const alertWhenDataStopsComingSwitch =
alertWhenDataStopsComingContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
@@ -97,9 +94,9 @@ describe('AdvancedOptions', () => {
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
const minimumDataRequiredContainer = screen.getByTestId(
ENFORCE_MINIMUM_DATAPOINTS_TEST_ID,
);
const minimumDataRequiredContainer = screen
.getByText(MINIMUM_DATA_REQUIRED_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const minimumDataRequiredSwitch = minimumDataRequiredContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
) as HTMLElement;
@@ -119,17 +116,15 @@ describe('AdvancedOptions', () => {
});
});
// TODO: Update when account for data delay is implemented - will need a data-testid
it.skip('"Account for data delay" works as expected', () => {
render(<AdvancedOptions />);
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
// This test needs a data-testid on the account for data delay component
const accountForDataDelayContainer = screen.getByTestId(
'account-for-data-delay-container',
);
const accountForDataDelayContainer = screen
.getByText(ACCOUNT_FOR_DATA_DELAY_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const accountForDataDelaySwitch = accountForDataDelayContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
) as HTMLElement;

View File

@@ -16,7 +16,7 @@ jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
const mockSetIsEvaluationCadenceDetailsVisible = jest.fn();
const mockSetIsPreviewVisible = jest.fn();
const EDIT_CUSTOM_SCHEDULE_TEST_ID = 'edit-custom-schedule';
const EDIT_CUSTOM_SCHEDULE_TEST_ID = '.edit-custom-schedule';
describe('EditCustomSchedule', () => {
it('should render the correct display text for custom mode with daily occurrence', () => {
@@ -47,7 +47,9 @@ describe('EditCustomSchedule', () => {
);
// Use textContent to verify the complete text across multiple Typography components
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent('EveryDayat00:00:00');
});
@@ -79,7 +81,9 @@ describe('EditCustomSchedule', () => {
/>,
);
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent(
'EveryWeekonMonday, Tuesday, Wednesday, Thursday, Fridayat00:00:00',
);
@@ -113,7 +117,9 @@ describe('EditCustomSchedule', () => {
/>,
);
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent('EveryMonthon1at00:00:00');
});

View File

@@ -12,15 +12,12 @@ const mockEvaluationWindow: EvaluationWindowState =
createMockEvaluationWindowState();
const mockSetEvaluationWindow = jest.fn();
const EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS =
'.evaluation-window-content-list-item';
const EVALUATION_WINDOW_DETAILS_TEST_ID = 'evaluation-window-details';
const ENTER_VALUE_PLACEHOLDER = 'Enter value';
const EVALUATION_WINDOW_TEXT = 'EVALUATION WINDOW';
// Test IDs for window type and timeframe options
const WINDOW_TYPE_ROLLING_TEST_ID = 'window-type-option-rolling';
const WINDOW_TYPE_CUMULATIVE_TEST_ID = 'window-type-option-cumulative';
const TIMEFRAME_LAST_5_MINUTES_TEST_ID = 'timeframe-option-5m0s';
const TIMEFRAME_CURRENT_HOUR_TEST_ID = 'timeframe-option-currentHour';
const LAST_5_MINUTES_TEXT = 'Last 5 minutes';
jest.mock('../EvaluationWindowPopover/EvaluationWindowDetails', () => ({
__esModule: true,
@@ -52,11 +49,15 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TYPE.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
expect(rollingItem).toHaveAttribute('data-active', 'true');
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(rollingItem).toHaveClass('active');
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
expect(cumulativeItem).toHaveAttribute('data-active', 'false');
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(cumulativeItem).not.toHaveClass('active');
});
it('should render all window type options with cumulative selected', () => {
@@ -72,10 +73,14 @@ describe('EvaluationWindowPopover', () => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
expect(cumulativeItem).toHaveAttribute('data-active', 'true');
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
expect(rollingItem).toHaveAttribute('data-active', 'false');
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(cumulativeItem).toHaveClass('active');
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(rollingItem).not.toHaveClass('active');
});
it('should render all timeframe options in rolling mode with last 5 minutes selected by default', () => {
@@ -88,8 +93,10 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TIMEFRAME.rolling.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const last5MinutesItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
expect(last5MinutesItem).toHaveAttribute('data-active', 'true');
const last5MinutesItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(last5MinutesItem).toHaveClass('active');
});
it('should render all timeframe options in cumulative mode with current hour selected by default', () => {
@@ -105,8 +112,10 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TIMEFRAME.cumulative.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const currentHourItem = screen.getByTestId(TIMEFRAME_CURRENT_HOUR_TEST_ID);
expect(currentHourItem).toHaveAttribute('data-active', 'true');
const currentHourItem = screen
.getByText('Current hour')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(currentHourItem).toHaveClass('active');
});
it('renders help text in details section for rolling mode with non-custom timeframe', () => {
@@ -178,11 +187,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
rollingItem?.focus();
fireEvent.keyDown(rollingItem, { key: 'ArrowDown' });
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(cumulativeItem).toHaveFocus();
});
@@ -194,11 +207,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: 'ArrowUp' });
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(rollingItem).toHaveFocus();
});
@@ -210,11 +227,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
rollingItem?.focus();
fireEvent.keyDown(rollingItem, { key: 'ArrowRight' });
const timeframeItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
const timeframeItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(timeframeItem).toHaveFocus();
});
@@ -226,11 +247,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const timeframeItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
const timeframeItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
timeframeItem?.focus();
fireEvent.keyDown(timeframeItem, { key: 'ArrowLeft' });
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(rollingItem).toHaveFocus();
});
@@ -242,7 +267,9 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: 'Enter' });
@@ -260,7 +287,9 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: ' ' });

View File

@@ -1,303 +0,0 @@
.evaluationSettingsContainer {
margin: var(--spacing-8);
}
.evaluateAlertConditionsContainer {
display: flex;
align-items: center;
gap: var(--spacing-8);
background-color: var(--l2-background);
padding: var(--spacing-8);
border-radius: 4px;
border: 1px solid var(--l1-border);
margin-bottom: var(--spacing-8);
:global(.ant-typography) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
}
:global(.ant-btn) {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
}
.evaluateAlertConditionsSeparator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.evaluateAlertConditionsButtonLeft {
color: var(--l2-foreground);
font-size: 12px;
padding-right: var(--spacing-8);
}
.evaluateAlertConditionsButtonRight {
display: flex;
align-items: center;
color: var(--muted-foreground);
gap: var(--spacing-4);
}
.evaluateAlertConditionsButtonRightText {
font-size: 12px;
font-weight: var(--font-weight-medium);
background-color: var(--l2-background);
padding: 1px var(--spacing-2);
}
.advancedOptionsContainer {
:global(.ant-collapse) {
:global(.ant-collapse-item) {
:global(.ant-collapse-header) {
background-color: var(--card);
border: 1px solid var(--l1-border);
:global(.ant-collapse-header-text) {
color: var(--muted-foreground);
font-family: Inter;
}
}
:global(.ant-collapse-content) {
:global(.ant-collapse-content-box) {
background-color: var(--card);
}
}
}
}
}
.condensedEvaluationSettingsContainer {
:global(.ant-btn) {
display: flex;
align-items: center;
min-width: 240px;
width: auto;
justify-content: space-between;
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
box-shadow: none;
}
}
.evaluateAlertConditionsButtonLeft {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-small);
flex-shrink: 0;
}
.evaluateAlertConditionsButtonRight {
display: flex;
align-items: center;
color: var(--l2-foreground);
gap: var(--spacing-4);
flex-shrink: 0;
}
.evaluateAlertConditionsButtonRightText {
font-size: var(--periscope-font-size-small);
font-weight: 500;
background-color: var(--l3-border);
padding: 1px 4px;
}
:global(.evaluation-window-popover-overlay) {
:global(.ant-popover-arrow) {
display: none !important;
}
:global(.ant-popover-content) {
background-color: var(--card);
border: 1px solid var(--l1-border);
border-radius: 4px;
padding: 0;
margin: 10px;
}
:global(.ant-popover-inner) {
background-color: var(--l2-background);
border: none;
padding: 0;
}
}
.evaluationWindowPopover {
min-width: 500px;
}
.evaluationWindowContent {
display: flex;
}
.evaluationWindowContentItem {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
border-right: 1px solid var(--l1-border);
padding: var(--spacing-6) var(--spacing-8);
min-width: 250px;
min-height: 300px;
}
.evaluationWindowContentItemLabel {
color: var(--muted-foreground);
font-size: var(--periscope-font-size-small);
line-height: 18px;
font-weight: var(--font-weight-medium);
}
.evaluationWindowContentList {
display: flex;
flex-direction: column;
}
.evaluationWindowContentListItem {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 calc(-1 * var(--spacing-8));
padding: var(--spacing-2) var(--spacing-8);
:global(.ant-typography) {
color: var(--muted-foreground);
font-weight: 400;
}
&:hover {
cursor: pointer;
background-color: var(--l1-background);
}
}
.evaluationWindowContentListItemActive {
background-color: var(--l1-background);
border-left: 2px solid var(--bg-robin-500);
:global(.ant-typography) {
font-weight: var(--font-weight-medium);
color: var(--l1-foreground);
}
}
.selectionContent {
padding: var(--spacing-8);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: 400px;
:global(.ant-typography) {
color: var(--muted-foreground);
}
:global(.ant-btn) {
width: fit-content;
}
}
.evaluationWindowFooter {
display: flex;
justify-content: flex-end;
gap: var(--spacing-4);
background-color: var(--l2-background);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-8);
:global(.ant-btn) {
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
}
}
.evaluationWindowDetails {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: 400px;
min-height: 300px;
padding: var(--spacing-8);
:global(.ant-typography) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
:global(.ant-select) {
width: 60%;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
&:hover {
border-color: var(--l1-border);
}
}
}
.selectGroup {
display: flex;
flex-direction: column;
gap: 2px;
:global(.ant-typography) {
color: var(--l3-foreground);
font-size: var(--periscope-font-size-small);
line-height: 18px;
font-weight: var(--font-weight-medium);
}
:global(.ant-input) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
width: 60%;
}
}
.timeSelectGroup {
:global(.ant-input-group) {
flex-direction: row;
gap: var(--spacing-4);
:global(.ant-select) {
width: 40px;
:global(.ant-select-selector) {
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
}

View File

@@ -0,0 +1,266 @@
.evaluation-settings-container {
margin: 16px;
.evaluate-alert-conditions-container {
display: flex;
align-items: center;
gap: 16px;
background-color: var(--l2-background);
padding: 16px;
border-radius: 4px;
border: 1px solid var(--l1-border);
margin-bottom: 16px;
.ant-typography {
color: var(--l2-foreground);
font-size: 13px;
}
.evaluate-alert-conditions-separator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.ant-btn {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.evaluate-alert-conditions-button-left {
color: var(--l2-foreground);
font-size: 12px;
padding-right: 16px;
}
.evaluate-alert-conditions-button-right {
display: flex;
align-items: center;
color: var(--muted-foreground);
gap: 8px;
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--l2-background);
padding: 1px 4px;
}
}
}
}
}
.advanced-options-container {
.ant-collapse {
.ant-collapse-item {
.ant-collapse-header {
background-color: var(--card);
border: 1px solid var(--l1-border);
.ant-collapse-header-text {
color: var(--muted-foreground);
font-family: Inter;
}
}
.ant-collapse-content {
.ant-collapse-content-box {
background-color: var(--card);
}
}
}
}
}
.ant-popover-arrow {
display: none !important;
}
.ant-popover-content {
background-color: var(--card);
border: 1px solid var(--l1-border);
border-radius: 4px;
padding: 0;
margin: 10px;
.ant-popover-inner {
background-color: var(--l2-background);
border: none;
padding: 0;
.evaluation-window-popover {
min-width: 500px;
.evaluation-window-content {
display: flex;
.evaluation-window-content-item {
display: flex;
flex-direction: column;
gap: 8px;
border-right: 1px solid var(--l1-border);
padding: 12px 16px;
min-width: 250px;
min-height: 300px;
.evaluation-window-content-item-label {
color: var(--muted-foreground);
font-size: 11px;
line-height: 18px;
font-weight: 500;
}
.evaluation-window-content-list {
display: flex;
flex-direction: column;
.evaluation-window-content-list-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 -16px;
padding: 4px 16px;
.ant-typography {
color: var(--muted-foreground);
font-weight: 400;
}
&.active {
background-color: var(--l1-background);
border-left: 2px solid var(--bg-robin-500);
.ant-typography {
font-weight: 500;
color: var(--l1-foreground);
}
}
&:hover {
cursor: pointer;
background-color: var(--l1-background);
}
}
}
}
.selection-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
width: 400px;
.ant-typography {
color: var(--muted-foreground);
}
.ant-btn {
width: fit-content;
}
}
}
.evaluation-window-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
background-color: var(--l2-background);
border-top: 1px solid var(--l1-border);
padding: 16px;
}
.ant-btn {
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
}
}
}
}
.evaluation-window-details {
display: flex;
flex-direction: column;
gap: 16px;
width: 400px;
min-height: 300px;
padding: 16px;
.select-group {
display: flex;
flex-direction: column;
gap: 2px;
.ant-typography {
color: var(--l3-foreground);
font-size: 11px;
line-height: 18px;
font-weight: 500;
}
}
.time-select-group {
.ant-input-group {
flex-direction: row;
gap: 8px;
.ant-select {
width: 40px;
.ant-select-selector {
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
}
.ant-typography {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
}
.ant-select {
width: 60%;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
&:hover {
border-color: var(--l1-border);
}
}
.select-group .ant-input:not(.time-input-field) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
width: 60%;
}
}

View File

@@ -1,19 +0,0 @@
.footer {
position: fixed;
bottom: 0;
left: 63px;
right: 0;
background-color: var(--l1-background);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-6);
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.buttonGroup {
display: flex;
gap: var(--spacing-6);
}

View File

@@ -19,7 +19,7 @@ import {
validateCreateAlertState,
} from './utils';
import styles from './Footer.module.scss';
import './styles.scss';
import {
invalidateGetRuleByID,
invalidateListRules,
@@ -243,7 +243,7 @@ function Footer(): JSX.Element {
]);
return (
<div className={styles.footer}>
<div className="create-alert-v2-footer">
<Button
variant="solid"
color="secondary"
@@ -252,7 +252,7 @@ function Footer(): JSX.Element {
>
<X size={14} /> Discard
</Button>
<div className={styles.buttonGroup}>
<div className="button-group">
{testAlertButton}
{saveAlertButton}
</div>

View File

@@ -0,0 +1,31 @@
.create-alert-v2-footer {
position: fixed;
bottom: 0;
left: 63px;
right: 0;
background-color: var(--l1-background);
border-top: 1px solid var(--l1-border);
padding: 12px;
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
.button-group {
display: flex;
gap: 12px;
}
.ant-btn {
display: flex;
align-items: center;
gap: 8px;
}
.ant-btn-default {
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
}
}

View File

@@ -7,8 +7,6 @@ import { Info } from '@signozhq/icons';
import { ALL_SELECTED_VALUE } from '../constants';
import { useCreateAlertState } from '../context';
import styles from './NotificationSettings.module.scss';
function MultipleNotifications(): JSX.Element {
const { notificationSettings, setNotificationSettings } =
useCreateAlertState();
@@ -101,7 +99,7 @@ function MultipleNotifications(): JSX.Element {
data-testid="multiple-notifications-select"
/>
{isMultipleNotificationsEnabled && (
<Typography.Text className={styles.multipleNotificationsSelectDescription}>
<Typography.Text className="multiple-notifications-select-description">
{groupByDescription}
</Typography.Text>
)}
@@ -124,15 +122,15 @@ function MultipleNotifications(): JSX.Element {
]);
return (
<div className={styles.multipleNotificationsContainer}>
<div className={styles.multipleNotificationsHeader}>
<Typography.Text className={styles.multipleNotificationsHeaderTitle}>
<div className="multiple-notifications-container">
<div className="multiple-notifications-header">
<Typography.Text className="multiple-notifications-header-title">
Group alerts by{' '}
<Tooltip title="Group similar alerts together to reduce notification volume. Leave empty to combine all matching alerts into one notification without grouping.">
<Info size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text className={styles.multipleNotificationsHeaderDescription}>
<Typography.Text className="multiple-notifications-header-description">
Combine alerts with the same field values into a single notification.
</Typography.Text>
</div>

View File

@@ -4,8 +4,6 @@ import { Info } from '@signozhq/icons';
import { useCreateAlertState } from '../context';
import styles from './NotificationSettings.module.scss';
function NotificationMessage(): JSX.Element {
const { notificationSettings, setNotificationSettings } =
useCreateAlertState();
@@ -52,21 +50,21 @@ function NotificationMessage(): JSX.Element {
// );
return (
<div className={styles.notificationMessageContainer}>
<div className={styles.notificationMessageHeader}>
<div className={styles.notificationMessageHeaderContent}>
<Typography.Text className={styles.notificationMessageHeaderTitle}>
<div className="notification-message-container">
<div className="notification-message-header">
<div className="notification-message-header-content">
<Typography.Text className="notification-message-header-title">
Notification Message
<Tooltip title="Customize the message content sent in alert notifications. Template variables like {{alertname}}, {{value}}, and {{threshold}} will be replaced with actual values when the alert fires.">
<Info size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text className={styles.notificationMessageHeaderDescription}>
<Typography.Text className="notification-message-header-description">
Custom message content for alert notifications. Use template variables to
include dynamic information.
</Typography.Text>
</div>
<div className={styles.notificationMessageHeaderActions}>
<div className="notification-message-header-actions">
{/* TODO: Add back when the functionality is implemented */}
{/* <Popover content={templateVariableContent}>
<Button type="text">

View File

@@ -1,292 +0,0 @@
.notificationSettingsContainer {
display: flex;
flex-direction: column;
margin: 0 var(--spacing-8);
}
.notificationMessageContainer {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
margin-top: -8px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
textarea {
height: 150px;
background: var(--l3-background);
border: 1px solid var(--l3-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
font-size: var(--periscope-font-size-base);
}
}
.notificationMessageHeader {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--spacing-8);
}
.notificationMessageHeaderContent {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
}
.notificationMessageHeaderTitle {
--typography-text-display: flex;
gap: var(--spacing-4);
align-items: center;
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
}
.notificationMessageHeaderDescription {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.notificationMessageHeaderActions {
:global(.ant-btn) {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
color: var(--bg-robin-400);
}
}
.notificationSettingsContent {
display: flex;
flex-direction: column;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
margin-top: var(--spacing-8);
}
.repeatNotificationsInput {
display: flex;
align-items: center;
gap: var(--spacing-4);
--input-background: var(--l3-background);
--input-hover-background: var(--l3-background);
--input-focus-background: var(--l3-background);
--input-border-color: var(--l3-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
:global(.ant-select) {
:global(.ant-select-selector) {
width: 120px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
:global(.ant-select-multiple) {
:global(.ant-select-selector) {
width: 200px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
.multipleNotificationsContainer {
display: flex;
padding: 4px var(--spacing-8) var(--spacing-8) var(--spacing-8);
border-bottom: 1px solid var(--l1-border);
justify-content: space-between;
:global(.ant-select) {
width: 300px;
:global(.ant-select-selector) {
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&:hover {
border-color: var(--l2-border);
}
&:focus {
border-color: var(--l2-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l2-foreground);
}
:global(.ant-select-arrow) {
color: var(--l2-foreground);
}
}
}
.multipleNotificationsHeader {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
:global(.ant-typography) {
--typography-text-display: flex;
gap: 4px;
align-items: center;
}
}
.multipleNotificationsHeaderTitle {
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.multipleNotificationsHeaderDescription {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.multipleNotificationsSelectDescription {
font-size: var(--periscope-font-size-small);
color: var(--l2-foreground);
margin-top: 4px;
--typography-text-display: block;
}
.reNotificationContainer {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
margin-top: var(--spacing-8);
}
.advancedOptionItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--spacing-8);
}
.advancedOptionItemLeftContent {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.advancedOptionItemTitle {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
}
.advancedOptionItemDescription {
color: var(--muted-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.borderBottom {
border-bottom: 1px solid var(--l1-border);
width: 100%;
margin-left: -16px;
margin-right: -32px;
}
.reNotificationCondition {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-wrap: nowrap;
:global(.ant-typography) {
font-size: var(--periscope-font-size-base);
font-weight: 400;
color: var(--l2-foreground);
white-space: nowrap;
}
:global(.ant-select) {
width: 200px;
height: 32px;
flex-shrink: 0;
:global(.ant-select-selector) {
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
}
:global(.ant-input) {
width: 200px;
flex-shrink: 0;
border: 1px solid var(--l1-border);
}
}
.templateVariableContent {
padding: var(--spacing-8);
display: flex;
flex-direction: column;
gap: 2px;
}
.templateVariableContentItem {
display: flex;
gap: var(--spacing-4);
align-items: center;
code {
background-color: var(--l1-background);
color: var(--l2-foreground);
padding: 2px 4px;
}
}

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