Compare commits

..

3 Commits

Author SHA1 Message Date
Vinícius Lourenço
3fc181f84c chore(unused-files): remove all unused files 2026-04-30 11:29:35 -03:00
Vinicius Lourenço
cc3da72aa5 feat(global-time-store): add support to context, url persistence, store persistence, drift handle (#11081)
* feat(global-time-store): add support to context, url persistence, store persistence, drift handle

* chore(fmt): fix issue with format

* refactor(hooks): mark internal and public ones

* refactor(store): adapt to don't need round down

* refactor(global-time): scope queries via name for auto refresh to be isolated

* chore(use-query-cache): add little doc

* chore(global-time): update docs
2026-04-30 11:11:58 +00:00
Nityananda Gohain
755390c4b5 feat: types and handler for llm pricing rules (#10908)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: 1.Types for ai-o11y ricing rules

* fix: changes

* fix: minor changes

* fix: more changes

* fix: new updates

* fix: address comments

* fix: remove nullable

* fix: types

* fix: address comments

* fix: use mustnewuuid

* fix: correct table name

* fix: address comments and move pricing to a single struct

* fix: linting issues
2026-04-30 05:44:12 +00:00
267 changed files with 5716 additions and 10523 deletions

View File

@@ -2474,73 +2474,6 @@ components:
- requiredMetricsCheck
- endTimeBeforeRetention
type: object
InframonitoringtypesNodeCondition:
enum:
- ready
- not_ready
- ""
type: string
InframonitoringtypesNodeRecord:
properties:
condition:
$ref: '#/components/schemas/InframonitoringtypesNodeCondition'
meta:
additionalProperties: {}
nullable: true
type: object
nodeCPU:
format: double
type: number
nodeCPUAllocatable:
format: double
type: number
nodeMemory:
format: double
type: number
nodeMemoryAllocatable:
format: double
type: number
nodeName:
type: string
notReadyNodesCount:
type: integer
readyNodesCount:
type: integer
required:
- nodeName
- condition
- readyNodesCount
- notReadyNodesCount
- nodeCPU
- nodeCPUAllocatable
- nodeMemory
- nodeMemoryAllocatable
- meta
type: object
InframonitoringtypesNodes:
properties:
endTimeBeforeRetention:
type: boolean
records:
items:
$ref: '#/components/schemas/InframonitoringtypesNodeRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
total:
type: integer
type:
$ref: '#/components/schemas/InframonitoringtypesResponseType'
warning:
$ref: '#/components/schemas/Querybuildertypesv5QueryWarnData'
required:
- type
- records
- total
- requiredMetricsCheck
- endTimeBeforeRetention
type: object
InframonitoringtypesPodPhase:
enum:
- pending
@@ -2658,32 +2591,6 @@ components:
- end
- limit
type: object
InframonitoringtypesPostableNodes:
properties:
end:
format: int64
type: integer
filter:
$ref: '#/components/schemas/Querybuildertypesv5Filter'
groupBy:
items:
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
nullable: true
type: array
limit:
type: integer
offset:
type: integer
orderBy:
$ref: '#/components/schemas/Querybuildertypesv5OrderBy'
start:
format: int64
type: integer
required:
- start
- end
- limit
type: object
InframonitoringtypesPostablePods:
properties:
end:
@@ -2725,6 +2632,158 @@ components:
- list
- grouped_list
type: string
LlmpricingruletypesGettablePricingRules:
properties:
items:
items:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
nullable: true
type: array
limit:
type: integer
offset:
type: integer
total:
type: integer
required:
- items
- total
- offset
- limit
type: object
LlmpricingruletypesLLMPricingCacheCosts:
properties:
mode:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleCacheMode'
read:
format: double
type: number
write:
format: double
type: number
required:
- mode
type: object
LlmpricingruletypesLLMPricingRule:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
id:
type: string
isOverride:
type: boolean
modelName:
type: string
modelPattern:
$ref: '#/components/schemas/LlmpricingruletypesStringSlice'
orgId:
type: string
pricing:
$ref: '#/components/schemas/LlmpricingruletypesLLMRulePricing'
provider:
type: string
sourceId:
type: string
syncedAt:
format: date-time
nullable: true
type: string
unit:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- modelName
- provider
- modelPattern
- unit
- pricing
- isOverride
- enabled
type: object
LlmpricingruletypesLLMPricingRuleCacheMode:
enum:
- subtract
- additive
- unknown
type: string
LlmpricingruletypesLLMPricingRuleUnit:
enum:
- per_million_tokens
type: string
LlmpricingruletypesLLMRulePricing:
properties:
cache:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingCacheCosts'
input:
format: double
type: number
output:
format: double
type: number
required:
- input
- output
type: object
LlmpricingruletypesStringSlice:
items:
type: string
nullable: true
type: array
LlmpricingruletypesUpdatableLLMPricingRule:
properties:
enabled:
type: boolean
id:
nullable: true
type: string
isOverride:
nullable: true
type: boolean
modelName:
type: string
modelPattern:
items:
type: string
nullable: true
type: array
pricing:
$ref: '#/components/schemas/LlmpricingruletypesLLMRulePricing'
provider:
type: string
sourceId:
nullable: true
type: string
unit:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
required:
- modelName
- provider
- modelPattern
- unit
- pricing
- enabled
type: object
LlmpricingruletypesUpdatableLLMPricingRules:
properties:
rules:
items:
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRule'
nullable: true
type: array
required:
- rules
type: object
MetricsexplorertypesInspectMetricsRequest:
properties:
end:
@@ -7768,6 +7827,218 @@ paths:
summary: Create bulk invite
tags:
- users
/api/v1/llm_pricing_rules:
get:
deprecated: false
description: Returns all LLM pricing rules for the authenticated org, with pagination.
operationId: ListLLMPricingRules
parameters:
- in: query
name: offset
schema:
type: integer
- in: query
name: limit
schema:
type: integer
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/LlmpricingruletypesGettablePricingRules'
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
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List pricing rules
tags:
- llmpricingrules
put:
deprecated: false
description: Single write endpoint used by both the user and the Zeus sync job.
Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true)
are fully preserved when the request does not provide isOverride; only synced_at
is stamped.
operationId: CreateOrUpdateLLMPricingRules
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRules'
responses:
"204":
description: No Content
"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
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create or update pricing rules
tags:
- llmpricingrules
/api/v1/llm_pricing_rules/{id}:
delete:
deprecated: false
description: Hard-deletes a pricing rule. If auto-synced, it will be recreated
on the next sync cycle.
operationId: DeleteLLMPricingRule
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"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:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a pricing rule
tags:
- llmpricingrules
get:
deprecated: false
description: Returns a single LLM pricing rule by ID.
operationId: GetLLMPricingRule
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
status:
type: string
required:
- status
- data
type: object
description: OK
"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 a pricing rule
tags:
- llmpricingrules
/api/v1/logs/promote_paths:
get:
deprecated: false
@@ -11193,76 +11464,6 @@ paths:
summary: List Hosts for Infra Monitoring
tags:
- inframonitoring
/api/v2/infra_monitoring/nodes:
post:
deprecated: false
description: 'Returns a paginated list of Kubernetes nodes with key metrics:
CPU usage, CPU allocatable, memory working set, memory allocatable, and per-group
readyNodesCount / notReadyNodesCount derived from each node''s latest k8s.node.condition_ready
value in the window. Each node includes metadata attributes (k8s.node.uid,
k8s.cluster.name). The response type is ''list'' for the default k8s.node.name
grouping (each row is one node with its current condition string: ready /
not_ready / '''') or ''grouped_list'' for custom groupBy keys (each row aggregates
nodes in the group with readyNodesCount and notReadyNodesCount; condition
stays empty). Supports filtering via a filter expression, custom groupBy,
ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination
via offset/limit. Also reports missing required metrics and whether the requested
time range falls before the data retention boundary. Numeric metric fields
(nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1
as a sentinel when no data is available for that field.'
operationId: ListNodes
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InframonitoringtypesPostableNodes'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/InframonitoringtypesNodes'
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
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List Nodes for Infra Monitoring
tags:
- inframonitoring
/api/v2/infra_monitoring/pods:
post:
deprecated: false

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/api/generated/**/*.ts"]
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"]
}

View File

@@ -231,6 +231,7 @@
"ts-jest": "29.4.6",
"ts-node": "^10.2.1",
"typescript-plugin-css-modules": "5.2.0",
"use-sync-external-store": "1.6.0",
"vite-plugin-checker": "0.12.0",
"vite-plugin-compression": "0.5.1",
"vite-plugin-image-optimizer": "2.0.3",
@@ -266,4 +267,4 @@
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
}
}
}

View File

@@ -13,10 +13,8 @@ import type {
import type {
InframonitoringtypesPostableHostsDTO,
InframonitoringtypesPostableNodesDTO,
InframonitoringtypesPostablePodsDTO,
ListHosts200,
ListNodes200,
ListPods200,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
@@ -108,90 +106,6 @@ export const useListHosts = <
return useMutation(mutationOptions);
};
/**
* Returns a paginated list of Kubernetes nodes with key metrics: CPU usage, CPU allocatable, memory working set, memory allocatable, and per-group readyNodesCount / notReadyNodesCount derived from each node's latest k8s.node.condition_ready value in the window. Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is 'list' for the default k8s.node.name grouping (each row is one node with its current condition string: ready / not_ready / '') or 'grouped_list' for custom groupBy keys (each row aggregates nodes in the group with readyNodesCount and notReadyNodesCount; condition stays empty). Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
* @summary List Nodes for Infra Monitoring
*/
export const listNodes = (
inframonitoringtypesPostableNodesDTO: BodyType<InframonitoringtypesPostableNodesDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListNodes200>({
url: `/api/v2/infra_monitoring/nodes`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: inframonitoringtypesPostableNodesDTO,
signal,
});
};
export const getListNodesMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof listNodes>>,
TError,
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof listNodes>>,
TError,
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
TContext
> => {
const mutationKey = ['listNodes'];
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 listNodes>>,
{ data: BodyType<InframonitoringtypesPostableNodesDTO> }
> = (props) => {
const { data } = props ?? {};
return listNodes(data);
};
return { mutationFn, ...mutationOptions };
};
export type ListNodesMutationResult = NonNullable<
Awaited<ReturnType<typeof listNodes>>
>;
export type ListNodesMutationBody =
BodyType<InframonitoringtypesPostableNodesDTO>;
export type ListNodesMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List Nodes for Infra Monitoring
*/
export const useListNodes = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof listNodes>>,
TError,
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof listNodes>>,
TError,
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
TContext
> => {
const mutationOptions = getListNodesMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts: pendingPodCount, runningPodCount, succeededPodCount, failedPodCount, unknownPodCount derived from each pod's latest phase in the window). Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
* @summary List Pods for Infra Monitoring

View File

@@ -0,0 +1,399 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
DeleteLLMPricingRulePathParameters,
GetLLMPricingRule200,
GetLLMPricingRulePathParameters,
ListLLMPricingRules200,
ListLLMPricingRulesParams,
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all LLM pricing rules for the authenticated org, with pagination.
* @summary List pricing rules
*/
export const listLLMPricingRules = (
params?: ListLLMPricingRulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListLLMPricingRules200>({
url: `/api/v1/llm_pricing_rules`,
method: 'GET',
params,
signal,
});
};
export const getListLLMPricingRulesQueryKey = (
params?: ListLLMPricingRulesParams,
) => {
return [`/api/v1/llm_pricing_rules`, ...(params ? [params] : [])] as const;
};
export const getListLLMPricingRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListLLMPricingRulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listLLMPricingRules>>
> = ({ signal }) => listLLMPricingRules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListLLMPricingRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listLLMPricingRules>>
>;
export type ListLLMPricingRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List pricing rules
*/
export function useListLLMPricingRules<
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListLLMPricingRulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List pricing rules
*/
export const invalidateListLLMPricingRules = async (
queryClient: QueryClient,
params?: ListLLMPricingRulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListLLMPricingRulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.
* @summary Create or update pricing rules
*/
export const createOrUpdateLLMPricingRules = (
llmpricingruletypesUpdatableLLMPricingRulesDTO: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: llmpricingruletypesUpdatableLLMPricingRulesDTO,
});
};
export const getCreateOrUpdateLLMPricingRulesMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationKey = ['createOrUpdateLLMPricingRules'];
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 createOrUpdateLLMPricingRules>>,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> }
> = (props) => {
const { data } = props ?? {};
return createOrUpdateLLMPricingRules(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateOrUpdateLLMPricingRulesMutationResult = NonNullable<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>
>;
export type CreateOrUpdateLLMPricingRulesMutationBody =
BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>;
export type CreateOrUpdateLLMPricingRulesMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create or update pricing rules
*/
export const useCreateOrUpdateLLMPricingRules = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationOptions =
getCreateOrUpdateLLMPricingRulesMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.
* @summary Delete a pricing rule
*/
export const deleteLLMPricingRule = ({
id,
}: DeleteLLMPricingRulePathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteLLMPricingRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationKey = ['deleteLLMPricingRule'];
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 deleteLLMPricingRule>>,
{ pathParams: DeleteLLMPricingRulePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteLLMPricingRule(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteLLMPricingRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteLLMPricingRule>>
>;
export type DeleteLLMPricingRuleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a pricing rule
*/
export const useDeleteLLMPricingRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationOptions = getDeleteLLMPricingRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns a single LLM pricing rule by ID.
* @summary Get a pricing rule
*/
export const getLLMPricingRule = (
{ id }: GetLLMPricingRulePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetLLMPricingRule200>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetLLMPricingRuleQueryKey = ({
id,
}: GetLLMPricingRulePathParameters) => {
return [`/api/v1/llm_pricing_rules/${id}`] as const;
};
export const getGetLLMPricingRuleQueryOptions = <
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetLLMPricingRuleQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getLLMPricingRule>>
> = ({ signal }) => getLLMPricingRule({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetLLMPricingRuleQueryResult = NonNullable<
Awaited<ReturnType<typeof getLLMPricingRule>>
>;
export type GetLLMPricingRuleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get a pricing rule
*/
export function useGetLLMPricingRule<
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetLLMPricingRuleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get a pricing rule
*/
export const invalidateGetLLMPricingRule = async (
queryClient: QueryClient,
{ id }: GetLLMPricingRulePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetLLMPricingRuleQueryKey({ id }) },
options,
);
return queryClient;
};

View File

@@ -3243,78 +3243,6 @@ export interface InframonitoringtypesHostsDTO {
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export enum InframonitoringtypesNodeConditionDTO {
ready = 'ready',
not_ready = 'not_ready',
'' = '',
}
/**
* @nullable
*/
export type InframonitoringtypesNodeRecordDTOMeta = {
[key: string]: unknown;
} | null;
export interface InframonitoringtypesNodeRecordDTO {
condition: InframonitoringtypesNodeConditionDTO;
/**
* @type object
* @nullable true
*/
meta: InframonitoringtypesNodeRecordDTOMeta;
/**
* @type number
* @format double
*/
nodeCPU: number;
/**
* @type number
* @format double
*/
nodeCPUAllocatable: number;
/**
* @type number
* @format double
*/
nodeMemory: number;
/**
* @type number
* @format double
*/
nodeMemoryAllocatable: number;
/**
* @type string
*/
nodeName: string;
/**
* @type integer
*/
notReadyNodesCount: number;
/**
* @type integer
*/
readyNodesCount: number;
}
export interface InframonitoringtypesNodesDTO {
/**
* @type boolean
*/
endTimeBeforeRetention: boolean;
/**
* @type array
* @nullable true
*/
records: InframonitoringtypesNodeRecordDTO[] | null;
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
*/
total: number;
type: InframonitoringtypesResponseTypeDTO;
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export enum InframonitoringtypesPodPhaseDTO {
pending = 'pending',
running = 'running',
@@ -3445,34 +3373,6 @@ export interface InframonitoringtypesPostableHostsDTO {
start: number;
}
export interface InframonitoringtypesPostableNodesDTO {
/**
* @type integer
* @format int64
*/
end: number;
filter?: Querybuildertypesv5FilterDTO;
/**
* @type array
* @nullable true
*/
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
/**
* @type integer
*/
limit: number;
/**
* @type integer
*/
offset?: number;
orderBy?: Querybuildertypesv5OrderByDTO;
/**
* @type integer
* @format int64
*/
start: number;
}
export interface InframonitoringtypesPostablePodsDTO {
/**
* @type integer
@@ -3513,6 +3413,170 @@ export enum InframonitoringtypesResponseTypeDTO {
list = 'list',
grouped_list = 'grouped_list',
}
export interface LlmpricingruletypesGettablePricingRulesDTO {
/**
* @type array
* @nullable true
*/
items: LlmpricingruletypesLLMPricingRuleDTO[] | null;
/**
* @type integer
*/
limit: number;
/**
* @type integer
*/
offset: number;
/**
* @type integer
*/
total: number;
}
export interface LlmpricingruletypesLLMPricingCacheCostsDTO {
mode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
/**
* @type number
* @format double
*/
read?: number;
/**
* @type number
* @format double
*/
write?: number;
}
export interface LlmpricingruletypesLLMPricingRuleDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type boolean
*/
isOverride: boolean;
/**
* @type string
*/
modelName: string;
modelPattern: LlmpricingruletypesStringSliceDTO;
/**
* @type string
*/
orgId: string;
pricing: LlmpricingruletypesLLMRulePricingDTO;
/**
* @type string
*/
provider: string;
/**
* @type string
*/
sourceId?: string;
/**
* @type string
* @format date-time
* @nullable true
*/
syncedAt?: Date | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export enum LlmpricingruletypesLLMPricingRuleCacheModeDTO {
subtract = 'subtract',
additive = 'additive',
unknown = 'unknown',
}
export enum LlmpricingruletypesLLMPricingRuleUnitDTO {
per_million_tokens = 'per_million_tokens',
}
export interface LlmpricingruletypesLLMRulePricingDTO {
cache?: LlmpricingruletypesLLMPricingCacheCostsDTO;
/**
* @type number
* @format double
*/
input: number;
/**
* @type number
* @format double
*/
output: number;
}
/**
* @nullable
*/
export type LlmpricingruletypesStringSliceDTO = string[] | null;
export interface LlmpricingruletypesUpdatableLLMPricingRuleDTO {
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
* @nullable true
*/
id?: string | null;
/**
* @type boolean
* @nullable true
*/
isOverride?: boolean | null;
/**
* @type string
*/
modelName: string;
/**
* @type array
* @nullable true
*/
modelPattern: string[] | null;
pricing: LlmpricingruletypesLLMRulePricingDTO;
/**
* @type string
*/
provider: string;
/**
* @type string
* @nullable true
*/
sourceId?: string | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
}
export interface LlmpricingruletypesUpdatableLLMPricingRulesDTO {
/**
* @type array
* @nullable true
*/
rules: LlmpricingruletypesUpdatableLLMPricingRuleDTO[] | null;
}
export interface MetricsexplorertypesInspectMetricsRequestDTO {
/**
* @type integer
@@ -7104,6 +7168,41 @@ export type CreateInvite201 = {
status: string;
};
export type ListLLMPricingRulesParams = {
/**
* @type integer
* @description undefined
*/
offset?: number;
/**
* @type integer
* @description undefined
*/
limit?: number;
};
export type ListLLMPricingRules200 = {
data: LlmpricingruletypesGettablePricingRulesDTO;
/**
* @type string
*/
status: string;
};
export type DeleteLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRule200 = {
data: LlmpricingruletypesLLMPricingRuleDTO;
/**
* @type string
*/
status: string;
};
export type ListPromotedAndIndexedPaths200 = {
/**
* @type array
@@ -7617,14 +7716,6 @@ export type ListHosts200 = {
status: string;
};
export type ListNodes200 = {
data: InframonitoringtypesNodesDTO;
/**
* @type string
*/
status: string;
};
export type ListPods200 = {
data: InframonitoringtypesPodsDTO;
/**

View File

@@ -1,33 +0,0 @@
.error-state-container {
height: 240px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 3px;
.error-state-container-content {
display: flex;
flex-direction: column;
gap: 8px;
.error-state-text {
font-size: 14px;
font-weight: 500;
}
.error-state-additional-messages {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 4px;
.error-state-additional-text {
font-size: 12px;
font-weight: 400;
margin-left: 8px;
}
}
}
}

View File

@@ -1,59 +0,0 @@
import { Typography } from 'antd';
import APIError from '../../types/api/error';
import './Common.styles.scss';
interface ErrorStateComponentProps {
message?: string;
error?: APIError;
}
const defaultProps: Partial<ErrorStateComponentProps> = {
message: undefined,
error: undefined,
};
function ErrorStateComponent({
message,
error,
}: ErrorStateComponentProps): JSX.Element {
// Handle API Error object
if (error) {
const mainMessage = error.getErrorMessage();
const additionalErrors = error.getErrorDetails().error.errors || [];
return (
<div className="error-state-container">
<div className="error-state-container-content">
<Typography className="error-state-text">{mainMessage}</Typography>
{additionalErrors.length > 0 && (
<div className="error-state-additional-messages">
{additionalErrors.map((additionalError) => (
<Typography
key={`error-${additionalError.message}`}
className="error-state-additional-text"
>
{additionalError.message}
</Typography>
))}
</div>
)}
</div>
</div>
);
}
// Handle simple string message (backwards compatibility)
return (
<div className="error-state-container">
<div className="error-state-container-content">
<Typography className="error-state-text">{message}</Typography>
</div>
</div>
);
}
ErrorStateComponent.defaultProps = defaultProps;
export default ErrorStateComponent;

View File

@@ -1,4 +0,0 @@
.custom-date-picker {
display: flex;
flex-direction: column;
}

View File

@@ -1,105 +0,0 @@
import { Dispatch, SetStateAction, useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { DatePicker } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import {
CustomTimeType,
LexicalContext,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import dayjs, { Dayjs } from 'dayjs';
import { useTimezone } from 'providers/Timezone';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import './RangePickerModal.styles.scss';
interface RangePickerModalProps {
setCustomDTPickerVisible: Dispatch<SetStateAction<boolean>>;
setIsOpen: Dispatch<SetStateAction<boolean>>;
onCustomDateHandler: (
dateTimeRange: DateTimeRangeType,
lexicalContext?: LexicalContext | undefined,
) => void;
selectedTime: string;
onTimeChange?: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
}
function RangePickerModal(props: RangePickerModalProps): JSX.Element {
const {
setCustomDTPickerVisible,
setIsOpen,
onCustomDateHandler,
selectedTime,
onTimeChange,
} = props;
const { RangePicker } = DatePicker;
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
// Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
const disabledDate = (current: any): boolean => {
const currentDay = dayjs(current);
return currentDay.isAfter(dayjs());
};
const onPopoverClose = (visible: boolean): void => {
if (!visible) {
setCustomDTPickerVisible(false);
}
setIsOpen(visible);
};
const onModalOkHandler = (date_time: any): void => {
if (date_time?.[1]) {
onPopoverClose(false);
}
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
};
const { timezone } = useTimezone();
const rangeValue: [Dayjs, Dayjs] = useMemo(
() => [
dayjs(minTime / 1000_000).tz(timezone.value),
dayjs(maxTime / 1000_000).tz(timezone.value),
],
[maxTime, minTime, timezone.value],
);
return (
<div className="custom-date-picker">
<RangePicker
disabledDate={disabledDate}
allowClear
showTime
format={(date: Dayjs): string =>
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
}
onOk={onModalOkHandler}
data-1p-ignore
{...(selectedTime === 'custom' &&
!onTimeChange && {
value: rangeValue,
})}
// use default value if onTimeChange is provided
{...(selectedTime === 'custom' &&
onTimeChange && {
defaultValue: rangeValue,
})}
/>
</div>
);
}
RangePickerModal.defaultProps = {
onTimeChange: undefined,
};
export default RangePickerModal;

View File

@@ -1,93 +0,0 @@
.details-drawer {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--l1-border);
}
.ant-drawer-header {
background: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
.ant-drawer-header-title {
display: flex;
align-items: center;
.ant-drawer-close {
margin-inline-end: 0px;
padding: 0px;
padding-right: 16px;
border-right: 1px solid var(--l1-border);
}
.ant-drawer-title {
padding-left: 16px;
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
}
.ant-drawer-body {
padding: 16px;
background: var(--l2-background);
&::-webkit-scrollbar {
width: 0.1rem;
}
}
.details-drawer-tabs {
margin-top: 32px;
.ant-tabs-tab {
display: flex;
align-items: center;
justify-content: center;
width: 114px;
height: 32px;
flex-shrink: 0;
padding: 7px 20px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--l1-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: -0.06px;
.ant-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0px;
}
.ant-btn:hover {
background: unset;
}
}
.ant-tabs-tab-active {
background: var(--l3-background);
}
.ant-tabs-tab + .ant-tabs-tab {
margin-left: 0px;
}
.ant-tabs-nav::before {
border-bottom: 0px;
}
.ant-tabs-ink-bar {
background: none;
}
}
}

View File

@@ -1,57 +0,0 @@
import { Dispatch, SetStateAction } from 'react';
import { Drawer, Tabs, TabsProps } from 'antd';
import cx from 'classnames';
import './DetailsDrawer.styles.scss';
interface IDetailsDrawerProps {
open: boolean;
setOpen: Dispatch<SetStateAction<boolean>>;
title: string;
descriptiveContent: JSX.Element;
defaultActiveKey: string;
items: TabsProps['items'];
detailsDrawerClassName?: string;
tabBarExtraContent?: JSX.Element;
}
function DetailsDrawer(props: IDetailsDrawerProps): JSX.Element {
const {
open,
setOpen,
title,
descriptiveContent,
defaultActiveKey,
detailsDrawerClassName,
items,
tabBarExtraContent,
} = props;
return (
<Drawer
width="60%"
open={open}
afterOpenChange={setOpen}
mask={false}
title={title}
onClose={(): void => setOpen(false)}
className="details-drawer"
>
<div>{descriptiveContent}</div>
<Tabs
items={items}
addIcon
defaultActiveKey={defaultActiveKey}
animated
className={cx('details-drawer-tabs', detailsDrawerClassName)}
tabBarExtraContent={tabBarExtraContent}
/>
</Drawer>
);
}
DetailsDrawer.defaultProps = {
detailsDrawerClassName: '',
tabBarExtraContent: null,
};
export default DetailsDrawer;

View File

@@ -1,143 +0,0 @@
import { useState } from 'react';
import { Button } from 'antd';
import { withErrorBoundary } from './index';
/**
* Example component that can throw errors
*/
function ProblematicComponent(): JSX.Element {
const [shouldThrow, setShouldThrow] = useState(false);
if (shouldThrow) {
throw new Error('This is a test error from ProblematicComponent!');
}
return (
<div style={{ padding: '20px' }}>
<h3>Problematic Component</h3>
<p>This component can throw errors when the button is clicked.</p>
<Button type="primary" onClick={(): void => setShouldThrow(true)} danger>
Trigger Error
</Button>
</div>
);
}
/**
* Basic usage - wraps component with default error boundary
*/
export const SafeProblematicComponent = withErrorBoundary(ProblematicComponent);
/**
* Usage with custom fallback component
*/
function CustomErrorFallback(): JSX.Element {
return (
<div
style={{ padding: '20px', border: '1px solid red', borderRadius: '4px' }}
>
<h4 style={{ color: 'red' }}>Custom Error Fallback</h4>
<p>Something went wrong in this specific component!</p>
<Button onClick={(): void => window.location.reload()}>Reload Page</Button>
</div>
);
}
export const SafeProblematicComponentWithCustomFallback = withErrorBoundary(
ProblematicComponent,
{
fallback: <CustomErrorFallback />,
},
);
/**
* Usage with custom error handler
*/
export const SafeProblematicComponentWithErrorHandler = withErrorBoundary(
ProblematicComponent,
{
onError: (error, errorInfo) => {
console.error('Custom error handler:', error);
console.error('Error info:', errorInfo);
// You could also send to analytics, logging service, etc.
},
sentryOptions: {
tags: {
section: 'dashboard',
priority: 'high',
},
level: 'error',
},
},
);
/**
* Example of wrapping an existing component from the codebase
*/
function ExistingComponent({
title,
data,
}: {
title: string;
data: any[];
}): JSX.Element {
// This could be any existing component that might throw errors
return (
<div>
<h4>{title}</h4>
<ul>
{data.map((item, index) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index}>{item.name}</li>
))}
</ul>
</div>
);
}
export const SafeExistingComponent = withErrorBoundary(ExistingComponent, {
sentryOptions: {
tags: {
component: 'ExistingComponent',
feature: 'data-display',
},
},
});
/**
* Usage examples in a container component
*/
export function ErrorBoundaryExamples(): JSX.Element {
const sampleData = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' },
];
return (
<div style={{ padding: '20px' }}>
<h2>Error Boundary HOC Examples</h2>
<div style={{ marginBottom: '20px' }}>
<h3>1. Basic Usage</h3>
<SafeProblematicComponent />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>2. With Custom Fallback</h3>
<SafeProblematicComponentWithCustomFallback />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>3. With Custom Error Handler</h3>
<SafeProblematicComponentWithErrorHandler />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>4. Wrapped Existing Component</h3>
<SafeExistingComponent title="Sample Data" data={sampleData} />
</div>
</div>
);
}

View File

@@ -1,13 +0,0 @@
.query-builder-search-wrapper {
margin-top: 10px;
border: 1px solid var(--l1-border);
border-bottom: none;
.ant-select-selector {
border: none !important;
input {
font-size: 12px;
}
}
}

View File

@@ -1,79 +0,0 @@
import { Dispatch, SetStateAction, useEffect } from 'react';
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import './QueryBuilderSearchWrapper.styles.scss';
function QueryBuilderSearchWrapper({
log,
filters,
contextQuery,
isEdit,
suffixIcon,
setFilters,
setContextQuery,
}: QueryBuilderSearchWraperProps): JSX.Element {
const initialContextQuery = useInitialQuery(log);
useEffect(() => {
setContextQuery(initialContextQuery);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleSearch = (tagFilters: TagFilter): void => {
const tagFiltersLength = tagFilters.items.length;
if (
(!tagFiltersLength && (!filters || !filters.items.length)) ||
tagFiltersLength === filters?.items.length ||
!contextQuery
) {
return;
}
const nextQuery: Query = {
...contextQuery,
builder: {
...contextQuery.builder,
queryData: contextQuery.builder.queryData.map((item) => ({
...item,
filters: tagFilters,
})),
},
};
setFilters({ ...tagFilters });
setContextQuery({ ...nextQuery });
};
if (!contextQuery || !isEdit) {
return <></>;
}
return (
<QueryBuilderSearch
query={contextQuery?.builder.queryData[0]}
onChange={handleSearch}
className="query-builder-search-wrapper"
suffixIcon={suffixIcon}
/>
);
}
interface QueryBuilderSearchWraperProps {
log: ILog;
isEdit: boolean;
contextQuery: Query | undefined;
setContextQuery: Dispatch<SetStateAction<Query | undefined>>;
filters: TagFilter | null;
setFilters: Dispatch<SetStateAction<TagFilter | null>>;
suffixIcon?: React.ReactNode;
}
QueryBuilderSearchWrapper.defaultProps = {
suffixIcon: undefined,
};
export default QueryBuilderSearchWrapper;

View File

@@ -1,3 +0,0 @@
import { CSSProperties } from 'react';
export const rawLineStyle: CSSProperties = {};

View File

@@ -1,8 +0,0 @@
import { Button } from 'antd';
import styled from 'styled-components';
export const ButtonContainer = styled(Button)`
&&& {
padding-left: 0;
}
`;

View File

@@ -1,13 +0,0 @@
.custom-multiselect-dropdown {
.divider {
height: 1px;
background-color: #e8e8e8;
margin: 4px 0;
}
.all-option {
font-weight: 500;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 8px;
}
}

View File

@@ -1,19 +0,0 @@
.loading-panel-data {
padding: 24px 0;
height: 240px;
display: flex;
justify-content: center;
align-items: flex-start;
.loading-panel-data-content {
display: flex;
align-items: flex-start;
flex-direction: column;
.loading-gif {
height: 72px;
margin-left: -24px;
}
}
}

View File

@@ -1,17 +0,0 @@
import { Typography } from 'antd';
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
import './PanelDataLoading.styles.scss';
export function PanelDataLoading(): JSX.Element {
return (
<div className="loading-panel-data">
<div className="loading-panel-data-content">
<img className="loading-gif" src={loadingPlaneUrl} alt="wait-icon" />
<Typography.Text>Fetching data...</Typography.Text>
</div>
</div>
);
}

View File

@@ -1,41 +0,0 @@
import { css, FlattenSimpleInterpolation } from 'styled-components';
const cssProperty = (key: any, value: any): FlattenSimpleInterpolation =>
key &&
value &&
css`
${key}: ${value};
`;
interface IFlexProps {
flexDirection?: string; // Need to replace this with exact css props. Not able to find any :(
flex?: number | string;
}
export const Flex = ({
flexDirection,
flex,
}: IFlexProps): FlattenSimpleInterpolation => css`
${cssProperty('flex-direction', flexDirection)}
${cssProperty('flex', flex)}
`;
interface IDisplayProps {
display?: string;
}
export const Display = ({
display,
}: IDisplayProps): FlattenSimpleInterpolation => css`
${cssProperty('display', display)}
`;
interface ISpacingProps {
margin?: string;
padding?: string;
}
export const Spacing = ({
margin,
padding,
}: ISpacingProps): FlattenSimpleInterpolation => css`
${cssProperty('margin', margin)}
${cssProperty('padding', padding)}
`;

View File

@@ -1,5 +0,0 @@
export type TabLabelProps = {
isDisabled: boolean;
label: string;
tooltipText?: string;
};

View File

@@ -1,29 +0,0 @@
import { memo } from 'react';
import { Tooltip } from 'antd';
import { TabLabelProps } from './TabLabel.interfaces';
function TabLabel({
label,
isDisabled,
tooltipText,
}: TabLabelProps): JSX.Element {
const currentLabel = <span data-testid={`${label}`}>{label}</span>;
if (isDisabled) {
return (
<Tooltip
trigger="hover"
autoAdjustOverflow
placement="top"
title={tooltipText}
>
{currentLabel}
</Tooltip>
);
}
return currentLabel;
}
export default memo(TabLabel);

View File

@@ -1,5 +0,0 @@
.tab-title {
display: flex;
gap: 4px;
align-items: center;
}

View File

@@ -1,41 +0,0 @@
import { useState } from 'react';
import { Radio } from 'antd';
import type { RadioChangeEvent } from 'antd/lib';
import { History, Table } from 'lucide-react';
import { ALERT_TABS } from '../constants';
import './Tabs.styles.scss';
export function Tabs(): JSX.Element {
const [selectedTab, setSelectedTab] = useState('overview');
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedTab(e.target.value);
};
return (
<Radio.Group className="tabs" onChange={handleTabChange} value={selectedTab}>
<Radio.Button
className={
selectedTab === ALERT_TABS.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={ALERT_TABS.OVERVIEW}
>
<div className="tab-title">
<Table size={14} />
Overview
</div>
</Radio.Button>
<Radio.Button
className={selectedTab === ALERT_TABS.HISTORY ? 'selected_view tab' : 'tab'}
value={ALERT_TABS.HISTORY}
>
<div className="tab-title">
<History size={14} />
History
</div>
</Radio.Button>
</Radio.Group>
);
}

View File

@@ -1,18 +0,0 @@
@mixin flex-center {
display: flex;
justify-content: space-between;
align-items: center;
}
.tabs-and-filters {
@include flex-center;
margin-top: 1rem;
margin-bottom: 1rem;
.filters {
@include flex-center;
gap: 16px;
.reset-button {
@include flex-center;
}
}
}

View File

@@ -1,16 +0,0 @@
import { Filters } from 'components/AlertDetailsFilters/Filters';
import { Tabs } from './Tabs/Tabs';
import './TabsAndFilters.styles.scss';
function TabsAndFilters(): JSX.Element {
return (
<div className="tabs-and-filters">
<Tabs />
<Filters />
</div>
);
}
export default TabsAndFilters;

View File

@@ -1,5 +0,0 @@
export const ALERT_TABS = {
OVERVIEW: 'OVERVIEW',
HISTORY: 'HISTORY',
ACTIVITY: 'ACTIVITY',
} as const;

View File

@@ -1,73 +0,0 @@
import { useMemo } from 'react';
import type { ColumnSizingState } from '@tanstack/react-table';
import { Skeleton } from 'antd';
import { TableColumnDef } from './types';
import { getColumnWidthStyle } from './utils';
import tableStyles from './TanStackTable.module.scss';
import styles from './TanStackTableSkeleton.module.scss';
type TanStackTableSkeletonProps<TData> = {
columns: TableColumnDef<TData>[];
rowCount: number;
isDarkMode: boolean;
columnSizing?: ColumnSizingState;
};
export function TanStackTableSkeleton<TData>({
columns,
rowCount,
isDarkMode,
columnSizing,
}: TanStackTableSkeletonProps<TData>): JSX.Element {
const rows = useMemo(
() => Array.from({ length: rowCount }, (_, i) => i),
[rowCount],
);
return (
<table className={tableStyles.tanStackTable}>
<colgroup>
{columns.map((column, index) => (
<col
key={column.id}
style={getColumnWidthStyle(
column,
columnSizing?.[column.id],
index === columns.length - 1,
)}
/>
))}
</colgroup>
<thead>
<tr>
{columns.map((column) => (
<th
key={column.id}
className={tableStyles.tableHeaderCell}
data-dark-mode={isDarkMode}
>
{typeof column.header === 'function' ? (
<Skeleton.Input active size="small" className={styles.headerSkeleton} />
) : (
column.header
)}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((rowIndex) => (
<tr key={rowIndex} className={tableStyles.tableRow}>
{columns.map((column) => (
<td key={column.id} className={tableStyles.tableCell}>
<Skeleton.Input active size="small" className={styles.cellSkeleton} />
</td>
))}
</tr>
))}
</tbody>
</table>
);
}

View File

@@ -1,13 +0,0 @@
import styled from 'styled-components';
interface TextContainerProps {
noButtonMargin?: boolean;
}
export const TextContainer = styled.div<TextContainerProps>`
display: flex;
> button {
margin-left: ${({ noButtonMargin }): string =>
noButtonMargin ? '0' : '0.5rem'}
`;

View File

@@ -1,42 +0,0 @@
import { ReactChild } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Space, Typography } from 'antd';
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
import { Container, LeftContainer, Logo } from './styles';
const { Title } = Typography;
function WelcomeLeftContainer({
version,
children,
}: WelcomeLeftContainerProps): JSX.Element {
const { t } = useTranslation();
return (
<Container>
<LeftContainer direction="vertical">
<Space align="center">
<Logo src={signozBrandLogoUrl} alt="logo" />
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
</Space>
<Typography>{t('monitor_signup')}</Typography>
<Card
style={{ width: 'max-content' }}
bodyStyle={{ padding: '1px 8px', width: '100%' }}
>
SigNoz {version}
</Card>
</LeftContainer>
{children}
</Container>
);
}
interface WelcomeLeftContainerProps {
version: string;
children: ReactChild;
}
export default WelcomeLeftContainer;

View File

@@ -1,23 +0,0 @@
import { Space } from 'antd';
import styled from 'styled-components';
export const LeftContainer = styled(Space)`
flex: 1;
`;
export const Logo = styled.img`
width: 60px;
`;
export const Container = styled.div`
&&& {
display: flex;
justify-content: center;
gap: 16px;
align-items: center;
min-height: 100vh;
max-width: 1024px;
margin: 0 auto;
}
`;

View File

@@ -1,9 +0,0 @@
import {
AlertRuleTimelineTableResponse,
AlertRuleTimelineTableResponsePayload,
} from 'types/api/alerts/def';
export type TimelineTableProps = {
timelineData: AlertRuleTimelineTableResponse[];
totalItems: AlertRuleTimelineTableResponsePayload['data']['total'];
};

View File

@@ -1,2 +0,0 @@
// setting to 25 hours because we want to display the horizontal graph when the user selects 'Last 1 day' from date and time selector
export const HORIZONTAL_GRAPH_HOURS_THRESHOLD = 25;

View File

@@ -1,42 +0,0 @@
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import noDataUrl from '@/assets/Icons/no-data.svg';
import EndPointsDropDown from './EndPointsDropDown';
function EndPointDetailsZeroState({
setSelectedEndPointName,
endPointDropDownDataQuery,
}: {
setSelectedEndPointName: (endPointName: string) => void;
endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>>;
}): JSX.Element {
return (
<div className="end-point-details-zero-state-wrapper">
<div className="end-point-details-zero-state-content">
<img
src={noDataUrl}
alt="no-data"
width={32}
height={32}
className="end-point-details-zero-state-icon"
/>
<div className="end-point-details-zero-state-content-wrapper">
<div className="end-point-details-zero-state-text-content">
<div className="title">No endpoint selected yet</div>
<div className="description">Select an endpoint to see the details</div>
</div>
<EndPointsDropDown
setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".end-point-details-zero-state-wrapper"
dropdownStyle={{ width: '60%' }}
/>
</div>
</div>
</div>
);
}
export default EndPointDetailsZeroState;

View File

@@ -1,136 +0,0 @@
import { useMemo } from 'react';
import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Table } from 'antd';
import type { ColumnType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
createFiltersForSelectedRowData,
EndPointsTableRowData,
formatEndPointsDataForTable,
getEndPointsColumnsConfig,
getEndPointsQueryPayload,
} from 'container/ApiMonitoring/utils';
import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import { VIEW_TYPES, VIEWS } from '../constants';
function ExpandedRow({
domainName,
selectedRowData,
setSelectedEndPointName,
setSelectedView,
orderBy,
}: {
domainName: string;
selectedRowData: EndPointsTableRowData;
setSelectedEndPointName: (name: string) => void;
setSelectedView: (view: VIEWS) => void;
orderBy: OrderByPayload | null;
}): JSX.Element {
const nestedColumns = useMemo(() => getEndPointsColumnsConfig(false, []), []);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const groupedByRowDataQueryPayload = useMemo(() => {
if (!selectedRowData) {
return null;
}
const filters = createFiltersForSelectedRowData(selectedRowData);
const baseQueryPayload = getEndPointsQueryPayload(
[],
domainName,
Math.floor(minTime / 1e9),
Math.floor(maxTime / 1e9),
);
return baseQueryPayload.map((currentQueryPayload) => ({
...currentQueryPayload,
query: {
...currentQueryPayload.query,
builder: {
...currentQueryPayload.query.builder,
queryData: currentQueryPayload.query.builder.queryData.map(
(queryData) => ({
...queryData,
filters: {
items: [...(queryData.filters?.items || []), ...(filters?.items || [])],
op: 'AND',
},
}),
),
},
},
}));
}, [domainName, minTime, maxTime, selectedRowData]);
const groupedByRowQueries = useQueries(
groupedByRowDataQueryPayload
? groupedByRowDataQueryPayload.map((payload) => ({
queryKey: [
`${REACT_QUERY_KEY.GET_NESTED_ENDPOINTS_LIST}-${domainName}-${selectedRowData?.key}`,
payload,
ENTITY_VERSION_V4,
selectedRowData?.key,
],
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
enabled: !!payload && !!selectedRowData,
}))
: [],
);
const groupedByRowQuery = groupedByRowQueries[0];
return (
<div className="expanded-table-container">
{groupedByRowQuery?.isFetching || groupedByRowQuery?.isLoading ? (
<LoadingContainer />
) : (
<div className="expanded-table">
<Table
columns={nestedColumns as ColumnType<EndPointsTableRowData>[]}
dataSource={
groupedByRowQuery?.data
? formatEndPointsDataForTable(
groupedByRowQuery.data?.payload.data.result[0].table?.rows,
[],
orderBy,
)
: []
}
pagination={false}
scroll={{ x: true }}
tableLayout="fixed"
showHeader={false}
loading={{
spinning: groupedByRowQuery?.isFetching || groupedByRowQuery?.isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => {
setSelectedEndPointName(record.endpointName);
setSelectedView(VIEW_TYPES.ENDPOINT_STATS);
logEvent('API Monitoring: Endpoint name row clicked', {});
},
className: 'expanded-clickable-row',
})}
/>
</div>
)}
</div>
);
}
export default ExpandedRow;

View File

@@ -1,33 +0,0 @@
import { PureComponent } from 'react';
interface State {
hasError: boolean;
}
interface Props {
children: JSX.Element;
}
class ErrorLink extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): State {
return { hasError: true };
}
render(): JSX.Element {
const { children } = this.props;
const { hasError } = this.state;
if (hasError) {
return <div />;
}
return children;
}
}
export default ErrorLink;

View File

@@ -1,23 +0,0 @@
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
const isInternalLink = href.startsWith('/');
if (isInternalLink) {
return <Link to={href}>{children}</Link>;
}
return (
<a rel="noreferrer" target="_blank" href={href}>
{children}
</a>
);
}
interface LinkContainerProps {
children: ReactNode;
href: string;
}
export default LinkContainer;

View File

@@ -1,49 +0,0 @@
import { lazy, Suspense, useMemo } from 'react';
import { Menu, Space } from 'antd';
import Spinner from 'components/Spinner';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import { lazyRetry } from 'utils/lazyWithRetries';
import ErrorLink from './ErrorLink';
import LinkContainer from './Link';
function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
const sortedConfig = useMemo(
() => config.components.sort((a, b) => a.position - b.position),
[config.components],
);
const isDarkMode = useIsDarkMode();
const items = sortedConfig.map((item) => {
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
const Component = lazy(() =>
lazyRetry(() => import(`@ant-design/icons/es/icons/${iconName}.js`)),
);
return {
key: item.text + item.href,
label: (
<ErrorLink key={item.text + item.href}>
<Suspense fallback={<Spinner height="5vh" />}>
<LinkContainer href={item.href}>
<Space size="small" align="start">
<Component />
{item.text}
</Space>
</LinkContainer>
</Suspense>
</ErrorLink>
),
};
});
return <Menu items={items} />;
}
interface HelpToolTipProps {
config: ConfigProps;
}
export default HelpToolTip;

View File

@@ -1,76 +0,0 @@
import { useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import {
CaretDownFilled,
CaretUpFilled,
QuestionCircleFilled,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { Space } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { AppState } from 'store/reducers';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import AppReducer from 'types/reducer/app';
import HelpToolTip from './Config';
import { ConfigDropdown } from './styles';
function DynamicConfigDropdown({
frontendId,
}: DynamicConfigDropdownProps): JSX.Element {
const { configs } = useSelector<AppState, AppReducer>((state) => state.app);
const isDarkMode = useIsDarkMode();
const [isHelpDropDownOpen, setIsHelpDropDownOpen] = useState<boolean>(false);
const config = useMemo(
() =>
Object.values(configs).find(
(config) => config.frontendPositionId === frontendId,
),
[frontendId, configs],
);
const onToggleHandler = (): void => {
setIsHelpDropDownOpen(!isHelpDropDownOpen);
};
const menu = useMemo(
() => ({
items: [
{
key: '1',
label: <HelpToolTip config={config as ConfigProps} />,
},
],
}),
[config],
);
if (!config) {
return <div />;
}
const Icon = isDarkMode ? QuestionCircleOutlined : QuestionCircleFilled;
const DropDownIcon = isHelpDropDownOpen ? CaretUpFilled : CaretDownFilled;
return (
<ConfigDropdown
onOpenChange={onToggleHandler}
trigger={['click']}
menu={menu}
open={isHelpDropDownOpen}
>
<Space align="center">
<Icon style={{ fontSize: 26, color: 'white', paddingTop: 26 }} />
<DropDownIcon style={{ color: 'white' }} />
</Space>
</ConfigDropdown>
);
}
interface DynamicConfigDropdownProps {
frontendId: string;
}
export default DynamicConfigDropdown;

View File

@@ -1,6 +0,0 @@
import { Dropdown } from 'antd';
import styled from 'styled-components';
export const ConfigDropdown = styled(Dropdown)`
cursor: pointer;
`;

View File

@@ -1,3 +0,0 @@
import EvaluationSettings from './EvaluationSettings';
export default EvaluationSettings;

View File

@@ -1,27 +0,0 @@
import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
import Input from 'components/Input';
function DashboardName({ setName, name }: DashboardNameProps): JSX.Element {
const onChangeHandler = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
},
[setName],
);
return (
<Input
size="middle"
placeholder="Title"
value={name}
onChangeHandler={onChangeHandler}
/>
);
}
interface DashboardNameProps {
name: string;
setName: Dispatch<SetStateAction<string>>;
}
export default DashboardName;

View File

@@ -1,100 +0,0 @@
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { CopyFilled, DownloadOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
import Editor from 'components/Editor';
import { useNotifications } from 'hooks/useNotifications';
import { DashboardData } from 'types/api/dashboard/getAll';
import { downloadObjectAsJson } from './utils';
function ShareModal({
isJSONModalVisible,
onToggleHandler,
selectedData,
}: ShareModalProps): JSX.Element {
const getParsedValue = (): string => JSON.stringify(selectedData, null, 2);
const [jsonValue, setJSONValue] = useState<string>(getParsedValue());
const { t } = useTranslation(['dashboard', 'common']);
const [state, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications();
useEffect(() => {
if (state.error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
if (state.value) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
}
}, [state.error, state.value, t, notifications]);
const GetFooterComponent = useMemo(() => {
return (
<>
<Button
style={{
marginTop: '16px',
}}
onClick={(): void => setCopy(jsonValue)}
type="primary"
size="small"
>
<CopyFilled /> {t('copy_to_clipboard')}
</Button>
<Button
type="primary"
size="small"
onClick={(): void => {
downloadObjectAsJson(selectedData, selectedData.title);
}}
>
<DownloadOutlined /> {t('download_json')}
</Button>
</>
);
}, [jsonValue, selectedData, setCopy, t]);
return (
<Modal
open={isJSONModalVisible}
onCancel={(): void => {
onToggleHandler();
}}
width="80vw"
centered
title={t('share', {
ns: 'common',
})}
okText={t('download_json')}
cancelText={t('cancel')}
destroyOnClose
footer={GetFooterComponent}
>
<Editor
height="70vh"
onChange={(value): void => setJSONValue(value)}
value={jsonValue}
/>
</Modal>
);
}
interface ShareModalProps {
isJSONModalVisible: boolean;
onToggleHandler: VoidFunction;
selectedData: DashboardData;
}
export default ShareModal;

View File

@@ -1,35 +0,0 @@
import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
import { Input } from 'antd';
import { Container } from './styles';
const { TextArea } = Input;
function Description({
description,
setDescription,
}: DescriptionProps): JSX.Element {
const onChangeHandler = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
setDescription(e.target.value);
},
[setDescription],
);
return (
<Container>
<TextArea
placeholder="Description of the dashboard"
onChange={onChangeHandler}
value={description}
/>
</Container>
);
}
interface DescriptionProps {
description: string;
setDescription: Dispatch<SetStateAction<string>>;
}
export default Description;

View File

@@ -1,5 +0,0 @@
import styled from 'styled-components';
export const Container = styled.div`
margin-top: 1rem;
`;

View File

@@ -1,59 +0,0 @@
.download-logs-popover {
.ant-popover-inner {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 12px 18px 12px 14px;
.download-logs-content {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
.action-btns {
padding: 4px 0px !important;
width: 159px;
display: flex;
align-items: center;
color: var(--l2-foreground);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
gap: 6px;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.action-btns:hover {
&.ant-btn-text {
background-color: color-mix(
in srgb,
var(--bg-robin-200) 4%,
transparent
) !important;
}
}
.export-heading {
color: var(--muted-foreground);
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.88px;
text-transform: uppercase;
}
}
}
}

View File

@@ -1,94 +0,0 @@
import { useState } from 'react';
import { Button, Popover, Typography } from 'antd';
import { FileDigit, FileDown, Sheet } from 'lucide-react';
import { unparse } from 'papaparse';
import { DownloadProps } from './DownloadV2.types';
import './DownloadV2.styles.scss';
function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
const [isDownloading, setIsDownloading] = useState(false);
const downloadExcelFile = async (): Promise<void> => {
setIsDownloading(true);
try {
const headers = Object.keys(Object.assign({}, ...data)).map((item) => {
const updatedTitle = item
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return {
title: updatedTitle,
dataIndex: item,
};
});
const excelLib = await import('antd-table-saveas-excel');
const excel = new excelLib.Excel();
excel
.addSheet(fileName)
.addColumns(headers)
.addDataSource(data, {
str2Percent: true,
})
.saveAs(`${fileName}.xlsx`);
} finally {
setIsDownloading(false);
}
};
const downloadCsvFile = (): void => {
const csv = unparse(data);
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const csvUrl = URL.createObjectURL(csvBlob);
const downloadLink = document.createElement('a');
downloadLink.href = csvUrl;
downloadLink.download = `${fileName}.csv`;
downloadLink.click();
downloadLink.remove();
};
return (
<Popover
trigger={['click']}
placement="bottomRight"
rootClassName="download-logs-popover"
arrow={false}
content={
<div className="download-logs-content">
<Typography.Text className="export-heading">Export As</Typography.Text>
<Button
icon={<Sheet size={14} />}
type="text"
onClick={downloadExcelFile}
className="action-btns"
loading={isDownloading}
>
Excel (.xlsx)
</Button>
<Button
icon={<FileDigit size={14} />}
type="text"
onClick={downloadCsvFile}
className="action-btns"
>
CSV
</Button>
</div>
}
>
<Button
className="periscope-btn ghost"
loading={isLoading}
icon={<FileDown size={14} />}
/>
</Popover>
);
}
Download.defaultProps = {
isLoading: undefined,
};
export default Download;

View File

@@ -1,10 +0,0 @@
export type DownloadOptions = {
isDownloadEnabled: boolean;
fileName: string;
};
export type DownloadProps = {
data: Record<string, string>[];
isLoading?: boolean;
fileName: string;
};

View File

@@ -1,11 +0,0 @@
import styled from 'styled-components';
export const ButtonContainer = styled.div`
&&& {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 2rem;
}
`;

View File

@@ -1,8 +0,0 @@
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
export type ExplorerControlPanelProps = {
selectedOptionFormat: string;
isShowPageSize: boolean;
isLoading: boolean;
optionsMenuConfig?: OptionsMenuConfig;
};

View File

@@ -1,33 +0,0 @@
import { Col, Row } from 'antd';
import OptionsMenu from 'container/OptionsMenu';
import PageSizeSelect from 'container/PageSizeSelect';
import { ExplorerControlPanelProps } from './ExplorerControlPanel.interfaces';
import { ContainerStyled } from './styles';
function ExplorerControlPanel({
selectedOptionFormat,
isLoading,
isShowPageSize,
optionsMenuConfig,
}: ExplorerControlPanelProps): JSX.Element {
return (
<ContainerStyled>
<Row justify="end" gutter={30}>
{optionsMenuConfig && (
<Col>
<OptionsMenu
selectedOptionFormat={selectedOptionFormat}
config={optionsMenuConfig}
/>
</Col>
)}
<Col>
<PageSizeSelect isLoading={isLoading} isShow={isShowPageSize} />
</Col>
</Row>
</ContainerStyled>
);
}
export default ExplorerControlPanel;

View File

@@ -1,5 +0,0 @@
import styled from 'styled-components';
export const ContainerStyled = styled.div`
margin-bottom: 0.3rem;
`;

View File

@@ -1,61 +0,0 @@
import { Dispatch, SetStateAction } from 'react';
import { Form, Input, Select } from 'antd';
import { LabelFilterStatement } from 'container/CreateAlertChannels/config';
const { Option } = Select;
// LabelFilterForm supports filters or matchers on alert notifications
// presently un-used but will be introduced to the channel creation at some
// point
function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
return (
<Form.Item name="label_filter" label="Notify When (Optional)">
<Input.Group compact>
<Select
defaultValue="Severity"
style={{ width: '15%' }}
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.name = event;
return [first];
});
}}
>
<Option value="severity">Severity</Option>
<Option value="service">Service</Option>
</Select>
<Select
defaultValue="="
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.comparator = event;
return [first];
});
}}
>
<Option value="=">=</Option>
<Option value="!=">!=</Option>
</Select>
<Input
style={{ width: '20%' }}
placeholder="enter a text here"
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.value = event.target.value;
return [first];
});
}}
/>
</Input.Group>
</Form.Item>
);
}
export interface LabelFilterProps {
setFilter: Dispatch<SetStateAction<Partial<Array<LabelFilterStatement>>>>;
}
export default LabelFilterForm;

View File

@@ -1,164 +0,0 @@
import { Trans, useTranslation } from 'react-i18next';
import { Col, Row, Typography } from 'antd';
import TextToolTip from 'components/TextToolTip';
import { EQueryType } from 'types/common/dashboard';
import {
StyledList,
StyledListItem,
StyledMainContainer,
StyledTopic,
} from './styles';
function UserGuide({ queryType }: UserGuideProps): JSX.Element {
// init namespace for translations
const { t } = useTranslation('alerts');
const renderStep1QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step1')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step1a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1b')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1c')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1d')}</StyledListItem>
</StyledList>
</>
);
const renderStep2QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForQB = (): JSX.Element => (
<>
{renderStep1QB()}
{renderStep2QB()}
{renderStep3QB()}
</>
);
const renderStep1PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step1')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step1a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step1b')}</StyledListItem>
</StyledList>
</>
);
const renderStep2PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForPQL = (): JSX.Element => (
<>
{renderStep1PQL()}
{renderStep2PQL()}
{renderStep3PQL()}
</>
);
const renderStep1CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step1')}</StyledTopic>
<StyledList>
<StyledListItem>
<Trans
i18nKey="user_guide_ch_step1a"
t={t}
components={[
<a
key={1}
target="_blank"
href=" https://signoz.io/docs/tutorial/writing-clickhouse-queries-in-dashboard/?utm_source=frontend&utm_medium=product&utm_id=alerts</>"
/>,
]}
/>
</StyledListItem>
<StyledListItem>{t('user_guide_ch_step1b')}</StyledListItem>
</StyledList>
</>
);
const renderStep2CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_ch_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_ch_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_ch_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_ch_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForCH = (): JSX.Element => (
<>
{renderStep1CH()}
{renderStep2CH()}
{renderStep3CH()}
</>
);
return (
<StyledMainContainer>
<Row>
<Col flex="auto">
<Typography.Paragraph> {t('user_guide_headline')} </Typography.Paragraph>
</Col>
<Col flex="none">
<TextToolTip
text={t('user_tooltip_more_help')}
url="https://signoz.io/docs/userguide/alerts-management/?utm_source=product&utm_medium=create-alert#creating-a-new-alert-in-signoz"
/>
</Col>
</Row>
{queryType === EQueryType.QUERY_BUILDER && renderGuideForQB()}
{queryType === EQueryType.PROM && renderGuideForPQL()}
{queryType === EQueryType.CLICKHOUSE && renderGuideForCH()}
</StyledMainContainer>
);
}
interface UserGuideProps {
queryType: EQueryType;
}
export default UserGuide;

View File

@@ -1,17 +0,0 @@
import { Card, Typography } from 'antd';
import styled from 'styled-components';
export const StyledMainContainer = styled(Card)``;
export const StyledTopic = styled(Typography.Paragraph)`
font-weight: 600;
`;
export const StyledList = styled.ul`
padding-left: 18px;
`;
export const StyledListItem = styled.li`
font-style: italic;
padding-bottom: 0.5rem;
`;

View File

@@ -1,158 +0,0 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Button, Input } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ResizeTable } from 'components/ResizeTable';
import { useNotifications } from 'hooks/useNotifications';
import {
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
import { ExtendedChartDataset, GraphManagerProps } from './types';
import {
getDefaultTableDataSet,
saveLegendEntriesToLocalStorage,
} from './utils';
import './WidgetFullView.styles.scss';
function GraphManager({
data,
name,
yAxisUnit,
onToggleModelHandler,
setGraphsVisibilityStates,
graphsVisibilityStates = [], // not trimed
lineChartRef,
parentChartRef,
options,
}: GraphManagerProps): JSX.Element {
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
getDefaultTableDataSet(options, data),
);
useEffect(() => {
setTableDataSet(getDefaultTableDataSet(options, data));
}, [data, options]);
const { notifications } = useNotifications();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked);
const checkBoxOnChangeHandler = useCallback(
(e: CheckboxChangeEvent, index: number): void => {
const newStates = [...graphsVisibilityStates];
newStates[index] = e.target.checked;
lineChartRef?.current?.toggleGraph(index, e.target.checked);
parentChartRef?.current?.toggleGraph(index, e.target.checked);
setGraphsVisibilityStates([...newStates]);
},
[
graphsVisibilityStates,
lineChartRef,
parentChartRef,
setGraphsVisibilityStates,
],
);
const labelClickedHandler = useCallback(
(labelIndex: number): void => {
const newGraphVisibilityStates = Array<boolean>(data.length).fill(false);
newGraphVisibilityStates[labelIndex] = true;
newGraphVisibilityStates.forEach((state, index) => {
lineChartRef?.current?.toggleGraph(index, state);
parentChartRef?.current?.toggleGraph(index, state);
});
setGraphsVisibilityStates(newGraphVisibilityStates);
},
[data.length, lineChartRef, parentChartRef, setGraphsVisibilityStates],
);
const columns = getGraphManagerTableColumns({
tableDataSet,
checkBoxOnChangeHandler,
graphVisibilityState: graphsVisibilityStates,
labelClickedHandler,
yAxisUnit,
isGraphDisabled: isDashboardLocked,
});
const filterHandler = useCallback(
(event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value.toString().toLowerCase();
const updatedDataSet = tableDataSet.map((item) => {
if (item.label?.toLocaleLowerCase().includes(value)) {
return { ...item, show: true };
}
return { ...item, show: false };
});
setTableDataSet(updatedDataSet);
},
[tableDataSet],
);
const saveHandler = useCallback((): void => {
saveLegendEntriesToLocalStorage({
options,
graphVisibilityState: graphsVisibilityStates || [],
name,
});
notifications.success({
message: 'The updated graphs & legends are saved',
});
if (onToggleModelHandler) {
onToggleModelHandler();
}
}, [
graphsVisibilityStates,
name,
notifications,
onToggleModelHandler,
options,
]);
const dataSource = tableDataSet.filter(
(item, index) => index !== 0 && item.show,
);
return (
<div className="graph-manager-container">
<div className="graph-manager-header">
<Input onChange={filterHandler} placeholder="Filter Series" />
<div className="save-cancel-container">
<span className="save-cancel-button">
<Button type="default" onClick={onToggleModelHandler}>
Cancel
</Button>
</span>
<span className="save-cancel-button">
<Button type="primary" onClick={saveHandler}>
Save
</Button>
</span>
</div>
</div>
<div className="legends-list-container">
<ResizeTable
columns={columns}
dataSource={dataSource}
rowKey="index"
pagination={false}
style={{
maxHeight: 200,
overflowX: 'hidden',
overflowY: 'auto',
}}
/>
</div>
</div>
);
}
GraphManager.defaultProps = {
graphVisibilityStateHandler: undefined,
};
export default memo(GraphManager);

View File

@@ -1,18 +0,0 @@
import { TableColumnType as ColumnType } from 'antd';
import { DataSetProps } from '../types';
import Label from './Label';
export const getLabel = (
labelClickedHandler: (labelIndex: number) => void,
disabled?: boolean,
): ColumnType<DataSetProps> => ({
render: (label: string, record): JSX.Element => (
<Label
label={label}
labelIndex={record.index}
labelClickedHandler={labelClickedHandler}
disabled={disabled}
/>
),
});

View File

@@ -1,85 +0,0 @@
import { TableColumnType as ColumnType } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants';
import { DataSetProps, ExtendedChartDataset } from '../types';
import { getGraphManagerTableHeaderTitle } from '../utils';
import CustomCheckBox from './CustomCheckBox';
import { getLabel } from './GetLabel';
export const getGraphManagerTableColumns = ({
tableDataSet,
checkBoxOnChangeHandler,
graphVisibilityState,
labelClickedHandler,
yAxisUnit,
isGraphDisabled,
}: GetGraphManagerTableColumnsProps): ColumnType<DataSetProps>[] => [
{
title: '',
width: 50,
dataIndex: ColumnsKeyAndDataIndex.Index,
key: ColumnsKeyAndDataIndex.Index,
render: (_: string, record: DataSetProps): JSX.Element => (
<CustomCheckBox
data={tableDataSet}
index={record.index}
checkBoxOnChangeHandler={checkBoxOnChangeHandler}
graphVisibilityState={graphVisibilityState}
disabled={isGraphDisabled}
/>
),
},
{
title: ColumnsTitle[ColumnsKeyAndDataIndex.Label],
width: 300,
dataIndex: ColumnsKeyAndDataIndex.Label,
key: ColumnsKeyAndDataIndex.Label,
...getLabel(labelClickedHandler, isGraphDisabled),
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Avg],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Avg,
key: ColumnsKeyAndDataIndex.Avg,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Sum],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Sum,
key: ColumnsKeyAndDataIndex.Sum,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Max],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Max,
key: ColumnsKeyAndDataIndex.Max,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Min],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Min,
key: ColumnsKeyAndDataIndex.Min,
},
];
interface GetGraphManagerTableColumnsProps {
tableDataSet: ExtendedChartDataset[];
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
labelClickedHandler: (labelIndex: number) => void;
graphVisibilityState: boolean[];
yAxisUnit?: string;
isGraphDisabled?: boolean;
}

View File

@@ -1,34 +0,0 @@
import { Tooltip } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { LabelContainer } from '../styles';
import { LabelProps } from '../types';
import { getAbbreviatedLabel } from '../utils';
function Label({
labelClickedHandler,
labelIndex,
label,
disabled = false,
}: LabelProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const onClickHandler = (): void => {
labelClickedHandler(labelIndex);
};
return (
<LabelContainer
isDarkMode={isDarkMode}
type="button"
disabled={disabled}
onClick={onClickHandler}
>
<Tooltip title={label} placement="topLeft">
{getAbbreviatedLabel(label)}
</Tooltip>
</LabelContainer>
);
}
export default Label;

View File

@@ -1,15 +0,0 @@
import { ChartData } from 'chart.js';
export const mockTestData: ChartData = {
labels: ['test1', 'test2'],
datasets: [
{
label: 'customer',
data: [481.60377358490564, 730.0000000000002],
},
{
label: 'demo-app',
data: [4471.4285714285725],
},
],
};

View File

@@ -1,12 +0,0 @@
import { LegendEntryProps } from '../FullView/types';
export const mocklegendEntryResult: LegendEntryProps[] = [
{
label: 'customer',
show: true,
},
{
label: 'demo-app',
show: false,
},
];

View File

@@ -1,3 +0,0 @@
import { GridValueComponentProps } from './types';
export const GridValueConfig: Pick<GridValueComponentProps, 'title'> = {};

View File

@@ -1,3 +0,0 @@
import Home from './Home';
export default Home;

View File

@@ -1,19 +0,0 @@
.loading-host-metrics {
padding: 24px 0;
height: 600px;
display: flex;
justify-content: center;
align-items: center;
.loading-host-metrics-content {
display: flex;
align-items: center;
flex-direction: column;
.loading-gif {
height: 72px;
margin-left: -24px;
}
}
}

View File

@@ -1,24 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Typography } from 'antd';
import { DataSource } from 'types/common/queryBuilder';
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
import './HostMetricsLoading.styles.scss';
export function HostMetricsLoading(): JSX.Element {
const { t } = useTranslation('common');
return (
<div className="loading-host-metrics">
<div className="loading-host-metrics-content">
<img className="loading-gif" src={loadingPlaneUrl} alt="wait-icon" />
<Typography>
{t('pending_data_placeholder', {
dataSource: `host ${DataSource.METRICS}`,
})}
</Typography>
</div>
</div>
);
}

View File

@@ -37,10 +37,7 @@ import {
X,
} from 'lucide-react';
import { isCustomTimeRange, useGlobalTimeStore } from 'store/globalTime';
import {
getAutoRefreshQueryKey,
NANO_SECOND_MULTIPLIER,
} from 'store/globalTime/utils';
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
IBuilderQuery,
@@ -190,16 +187,19 @@ function K8sBaseDetails<T>({
);
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
const lastComputedMinMax = useGlobalTimeStore((s) => s.lastComputedMinMax);
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
const getAutoRefreshQueryKey = useGlobalTimeStore(
(s) => s.getAutoRefreshQueryKey,
);
const { startMs, endMs } = useMemo(() => {
const { minTime: startNs, maxTime: endNs } = getMinMaxTime(selectedTime);
return {
startMs: Math.floor(startNs / NANO_SECOND_MULTIPLIER),
endMs: Math.floor(endNs / NANO_SECOND_MULTIPLIER),
};
}, [getMinMaxTime, selectedTime]);
const { startMs, endMs } = useMemo(
() => ({
startMs: Math.floor(lastComputedMinMax.minTime / NANO_SECOND_MULTIPLIER),
endMs: Math.floor(lastComputedMinMax.maxTime / NANO_SECOND_MULTIPLIER),
}),
[lastComputedMinMax],
);
const [modalTimeRange, setModalTimeRange] = useState(() => ({
startTime: startMs,
@@ -246,7 +246,7 @@ function K8sBaseDetails<T>({
`${queryKeyPrefix}EntityDetails`,
selectedItem,
),
[queryKeyPrefix, selectedItem, selectedTime],
[getAutoRefreshQueryKey, queryKeyPrefix, selectedItem, selectedTime],
);
const {

View File

@@ -16,10 +16,7 @@ import { InfraMonitoringEvents } from 'constants/events';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { parseAsString, useQueryState } from 'nuqs';
import { useGlobalTimeStore } from 'store/globalTime';
import {
getAutoRefreshQueryKey,
NANO_SECOND_MULTIPLIER,
} from 'store/globalTime/utils';
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { buildAbsolutePath, isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
@@ -114,6 +111,9 @@ export function K8sBaseList<T>({
const refreshInterval = useGlobalTimeStore((s) => s.refreshInterval);
const isRefreshEnabled = useGlobalTimeStore((s) => s.isRefreshEnabled);
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
const getAutoRefreshQueryKey = useGlobalTimeStore(
(s) => s.getAutoRefreshQueryKey,
);
const queryKey = useMemo(() => {
return getAutoRefreshQueryKey(
@@ -127,6 +127,7 @@ export function K8sBaseList<T>({
JSON.stringify(groupBy),
);
}, [
getAutoRefreshQueryKey,
selectedTime,
entity,
pageSize,

View File

@@ -11,10 +11,7 @@ import {
} from 'antd';
import { CornerDownRight } from 'lucide-react';
import { useGlobalTimeStore } from 'store/globalTime';
import {
getAutoRefreshQueryKey,
NANO_SECOND_MULTIPLIER,
} from 'store/globalTime/utils';
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { buildAbsolutePath, isModifierKeyPressed } from 'utils/app';
@@ -118,6 +115,9 @@ export function K8sExpandedRow<T>({
const refreshInterval = useGlobalTimeStore((s) => s.refreshInterval);
const isRefreshEnabled = useGlobalTimeStore((s) => s.isRefreshEnabled);
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
const getAutoRefreshQueryKey = useGlobalTimeStore(
(s) => s.getAutoRefreshQueryKey,
);
const queryKey = useMemo(() => {
return getAutoRefreshQueryKey(selectedTime, [
@@ -126,7 +126,7 @@ export function K8sExpandedRow<T>({
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
]);
}, [selectedTime, record.key, queryFilters, orderBy]);
}, [getAutoRefreshQueryKey, selectedTime, record.key, queryFilters, orderBy]);
const { data, isFetching, isLoading, isError } = useQuery({
queryKey,

View File

@@ -1,42 +0,0 @@
.config-connection-status-popover {
.ant-popover-inner {
padding: 0;
background-color: var(--l2-background);
border-radius: 4px;
border: 1px solid var(--l3-background);
padding: 8px;
width: 240px;
.ant-popover-content {
padding: 0;
}
}
.config-connection-status-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
}
.config-connection-status-icon {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.config-connection-status-category-display-name {
color: var(--l2-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 138.462% */
}
}

View File

@@ -1,30 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { IConfigConnectionStatus } from 'container/Integrations/types';
import { CheckCircle, TriangleAlert } from 'lucide-react';
import './ConfigConnectionStatus.styles.scss';
export function ConfigConnectionStatus({
status,
}: {
status: IConfigConnectionStatus[] | null;
}): JSX.Element {
return (
<div className="config-connection-status-container">
{status?.map((status) => (
<div key={status.category} className="config-connection-status-item">
<div className="config-connection-status-icon">
{status.last_received_ts_ms && status.last_received_ts_ms > 0 ? (
<CheckCircle size={16} color={Color.BG_FOREST_500} />
) : (
<TriangleAlert size={16} color={Color.BG_AMBER_500} />
)}
</div>
<div className="config-connection-status-category-display-name">
{status.category_display_name}
</div>
</div>
))}
</div>
);
}

View File

@@ -1,39 +0,0 @@
import cx from 'classnames';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { Service } from './AmazonWebServices/types';
function ServiceItem({
service,
onClick,
isActive,
}: {
service: Service;
onClick: (serviceName: string) => void;
isActive?: boolean;
}): JSX.Element {
return (
<button
className={cx('service-item', { active: isActive })}
onClick={(): void => onClick(service.id)}
type="button"
>
<div className="service-item__icon-wrapper">
<img
src={service.icon}
alt={service.title}
className="service-item__icon"
/>
</div>
<div className="service-item__title">
<LineClampedText text={service.title} />
</div>
</button>
);
}
ServiceItem.defaultProps = {
isActive: false,
};
export default ServiceItem;

View File

@@ -1,111 +0,0 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LoadingOutlined } from '@ant-design/icons';
import { Button, Input, Space, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { IntegrationType } from 'container/Integrations/types';
import { useNotifications } from 'hooks/useNotifications';
import { Check } from 'lucide-react';
import './Integrations.styles.scss';
interface RequestIntegrationBtnProps {
type?: IntegrationType;
message?: string;
}
export function RequestIntegrationBtn({
type,
message,
}: RequestIntegrationBtnProps): JSX.Element {
const [
isSubmittingRequestForIntegration,
setIsSubmittingRequestForIntegration,
] = useState(false);
const [requestedIntegrationName, setRequestedIntegrationName] = useState('');
const { notifications } = useNotifications();
const { t } = useTranslation(['common']);
const handleRequestIntegrationSubmit = async (): Promise<void> => {
try {
setIsSubmittingRequestForIntegration(true);
const eventName =
type === IntegrationType.AWS_SERVICES
? 'AWS service integration requested'
: 'Integration requested';
const screenName =
type === IntegrationType.AWS_SERVICES
? 'AWS integration details'
: 'Integration list page';
const response = await logEvent(eventName, {
screen: screenName,
integration: requestedIntegrationName,
});
if (response.statusCode === 200) {
notifications.success({
message: 'Integration Request Submitted',
});
setIsSubmittingRequestForIntegration(false);
} else {
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
setIsSubmittingRequestForIntegration(false);
}
} catch (error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
setIsSubmittingRequestForIntegration(false);
}
};
return (
<div className="request-entity-container">
<Typography.Text>{message}</Typography.Text>
<div className="form-section">
<Space.Compact style={{ width: '100%' }}>
<Input
placeholder="Enter integration name..."
style={{ width: 300, marginBottom: 0 }}
value={requestedIntegrationName}
onChange={(e): void => setRequestedIntegrationName(e.target.value)}
/>
<Button
className="periscope-btn primary"
icon={
isSubmittingRequestForIntegration ? (
<LoadingOutlined />
) : (
<Check size={12} />
)
}
type="primary"
onClick={handleRequestIntegrationSubmit}
disabled={
isSubmittingRequestForIntegration ||
!requestedIntegrationName ||
requestedIntegrationName?.trim().length === 0
}
>
Submit
</Button>
</Space.Compact>
</div>
</div>
);
}

View File

@@ -1,3 +0,0 @@
import Integrations from './Integrations';
export default Integrations;

View File

@@ -1,17 +0,0 @@
import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
import { Data } from '../DashboardsList';
function Created(createdBy: Data['createdBy']): JSX.Element {
const time = new Date(createdBy);
const date = getFormattedDate(time);
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
return <Typography>{`${timeString}`}</Typography>;
}
export default Created;

View File

@@ -1,29 +0,0 @@
import { LockFilled } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { openInNewTab } from 'utils/navigation';
import { Data } from '../DashboardsList';
import { TableLinkText } from './styles';
function Name(name: Data['name'], data: Data): JSX.Element {
const { id: DashboardId, isLocked } = data;
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${DashboardId}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
if (event.metaKey || event.ctrlKey) {
openInNewTab(getLink());
} else {
history.push(getLink());
}
};
return (
<TableLinkText onClick={onClickHandler}>
{isLocked && <LockFilled />} {name}
</TableLinkText>
);
}
export default Name;

View File

@@ -1,15 +0,0 @@
import { Tag } from 'antd';
import { Data } from '../DashboardsList';
function Tags(data: Data['tags']): JSX.Element {
return (
<>
{data.map((e) => (
<Tag key={e}>{e}</Tag>
))}
</>
);
}
export default Tags;

View File

@@ -1,24 +0,0 @@
import { Dashboard } from 'types/api/dashboard/getAll';
interface IDashboardSearchData {
title: string;
description: string | undefined;
tags: string[];
id: string;
}
export const generateSearchData = (
dashboards: Dashboard[],
): IDashboardSearchData[] => {
const dashboardSearchData: IDashboardSearchData[] = [];
dashboards.forEach((dashboard) => {
dashboardSearchData.push({
id: dashboard.id,
title: dashboard.data.title,
description: dashboard.data.description,
tags: dashboard.data.tags || [],
});
});
return dashboardSearchData;
};

View File

@@ -1,25 +0,0 @@
import { Button, Row } from 'antd';
import styled from 'styled-components';
export const NewDashboardButton = styled(Button)`
&&& {
display: flex;
justify-content: center;
align-items: center;
margin-left: 1rem;
}
`;
export const TableContainer = styled(Row)`
&&& {
margin-top: 1rem;
}
`;
export const ButtonContainer = styled.div`
&&& {
display: flex;
align-items: center;
}
`;

View File

@@ -1,55 +0,0 @@
import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
PANEL_TYPES,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { constructCompositeQuery } from '../constants';
function BackButton(): JSX.Element {
const history = useHistory();
const { updateAllQueriesOperators } = useQueryBuilder();
const compositeQuery = useGetCompositeQueryParam();
const handleBack = useCallback(() => {
if (!compositeQuery) {
return;
}
const nextCompositeQuery = constructCompositeQuery({
query: compositeQuery,
initialQueryData: initialQueryBuilderFormValuesMap.logs,
customQueryData: { disabled: false },
});
const updatedQuery = updateAllQueriesOperators(
nextCompositeQuery,
PANEL_TYPES.LIST,
DataSource.LOGS,
);
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(updatedQuery));
const path = `${ROUTES.LOGS_EXPLORER}?${QueryParams.compositeQuery}=${JSONCompositeQuery}`;
history.push(path);
}, [history, compositeQuery, updateAllQueriesOperators]);
return (
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>
Exit live view
</Button>
);
}
export default BackButton;

View File

@@ -1,80 +0,0 @@
import { useCallback, useMemo } from 'react';
import { Col } from 'antd';
import { initialQueriesMap } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useEventSource } from 'providers/EventSource';
import {
IBuilderQuery,
Query,
TagFilter,
} from 'types/api/queryBuilder/queryBuilderData';
import { getQueryWithoutFilterId } from '../utils';
import {
ContainerStyled,
FilterSearchInputStyled,
SearchButtonStyled,
} from './styles';
function FiltersInput(): JSX.Element {
const {
stagedQuery,
handleSetQueryData,
redirectWithQueryBuilderData,
currentQuery,
} = useQueryBuilder();
const { initialLoading, handleSetInitialLoading } = useEventSource();
const handleChange = useCallback(
(filters: TagFilter) => {
const listQueryData = stagedQuery?.builder.queryData[0];
if (!listQueryData) {
return;
}
const queryData: IBuilderQuery = {
...listQueryData,
filters,
};
handleSetQueryData(0, queryData);
},
[stagedQuery, handleSetQueryData],
);
const query = useMemo(() => {
if (stagedQuery && stagedQuery.builder.queryData.length > 0) {
return stagedQuery?.builder.queryData[0];
}
return initialQueriesMap.logs.builder.queryData[0];
}, [stagedQuery]);
const handleSearch = useCallback(() => {
if (initialLoading) {
handleSetInitialLoading(false);
}
const preparedQuery: Query = getQueryWithoutFilterId(currentQuery);
redirectWithQueryBuilderData(preparedQuery);
}, [
initialLoading,
currentQuery,
redirectWithQueryBuilderData,
handleSetInitialLoading,
]);
return (
<ContainerStyled>
<Col flex={1}>
<FilterSearchInputStyled query={query} onChange={handleChange} />
</Col>
<SearchButtonStyled onSearch={handleSearch} />
</ContainerStyled>
);
}
export default FiltersInput;

View File

@@ -1,24 +0,0 @@
import { Input, Row } from 'antd';
import { themeColors } from 'constants/theme';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import styled from 'styled-components';
export const FilterSearchInputStyled = styled(QueryBuilderSearch)`
z-index: 1;
.ant-select-selector {
width: 100%;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
`;
export const ContainerStyled = styled(Row)`
color: ${themeColors.white};
`;
export const SearchButtonStyled = styled(Input.Search)`
width: 2rem;
.ant-input {
display: none;
}
`;

View File

@@ -1,77 +0,0 @@
import { useCallback } from 'react';
import { Button, Popover, Select } from 'antd';
import Spinner from 'components/Spinner';
import { LOCALSTORAGE } from 'constants/localStorage';
import { useOptionsMenu } from 'container/OptionsMenu';
import {
defaultSelectStyle,
logsOptions,
viewModeOptionList,
} from 'pages/Logs/config';
import PopoverContent from 'pages/Logs/PopoverContent';
import { useEventSource } from 'providers/EventSource';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { popupContainer } from 'utils/selectPopupContainer';
import { SpinnerWrapper } from './styles';
function ListViewPanel(): JSX.Element {
const { config } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: DataSource.LOGS,
aggregateOperator: StringOperators.NOOP,
});
const { isConnectionLoading } = useEventSource();
const isFormatButtonVisible = logsOptions.includes(config.format?.value);
const renderPopoverContent = useCallback(() => {
if (!config.maxLines) {
return null;
}
const linedPerRow = config.maxLines.value as number;
const handleLinesPerRowChange = config.maxLines.onChange as (
value: unknown,
) => void;
return (
<PopoverContent
linesPerRow={linedPerRow}
handleLinesPerRowChange={handleLinesPerRowChange}
/>
);
}, [config]);
return (
<div className="live-logs-settings-panel">
<Select
getPopupContainer={popupContainer}
style={defaultSelectStyle}
value={config.format?.value}
onChange={config.format?.onChange}
>
{viewModeOptionList.map((option) => (
<Select.Option key={option.value}>{option.label}</Select.Option>
))}
</Select>
{isFormatButtonVisible && (
<Popover
getPopupContainer={popupContainer}
placement="right"
content={renderPopoverContent}
>
<Button>Format</Button>
</Popover>
)}
{isConnectionLoading && (
<SpinnerWrapper>
<Spinner style={{ height: 'auto' }} />
</SpinnerWrapper>
)}
</div>
);
}
export default ListViewPanel;

View File

@@ -1,11 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
display: flex;
align-items: center;
gap: 1.5rem;
`;
export const SpinnerWrapper = styled.div`
margin-left: auto;
`;

View File

@@ -1,20 +0,0 @@
import { Row } from 'antd';
import { themeColors } from 'constants/theme';
import styled from 'styled-components';
import LiveLogsListChart from '../LiveLogsListChart';
export const LiveLogsChart = styled(LiveLogsListChart)`
margin-bottom: 0.5rem;
`;
export const ContentWrapper = styled(Row)`
color: rgba(${(themeColors.white, 0.85)});
`;
export const Wrapper = styled.div`
padding-bottom: 4rem;
padding-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
`;

View File

@@ -1,74 +0,0 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
Query,
TagFilter,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
const getIdFilter = (filtersItems: TagFilterItem[]): TagFilterItem | null =>
filtersItems.find((item) => item.key?.key === 'id') || null;
const getFilter = (
filters: TagFilter,
tagFilter: BaseAutocompleteData,
value: string,
): TagFilter => {
let newItems = filters.items;
const isExistIdFilter = getIdFilter(newItems);
if (isExistIdFilter) {
newItems = newItems.map((item) =>
item.key?.key === 'id' ? { ...item, value } : item,
);
} else {
newItems = [
...newItems,
{ value, key: tagFilter, op: OPERATORS['>'], id: uuid() },
];
}
return { items: newItems, op: filters.op };
};
export const prepareQueryByFilter = (
query: Query,
tagFilter: BaseAutocompleteData,
value: string | null,
): Query => {
const preparedQuery: Query = {
...query,
builder: {
...query.builder,
queryData: query.builder.queryData?.map((item) => ({
...item,
filters: value
? getFilter(item.filters || { items: [], op: 'AND' }, tagFilter, value)
: item.filters,
})),
},
};
return preparedQuery;
};
export const getQueryWithoutFilterId = (query: Query): Query => {
const preparedQuery: Query = {
...query,
builder: {
...query.builder,
queryData: query.builder.queryData?.map((item) => ({
...item,
filters: {
...item.filters,
items: item.filters?.items?.filter((item) => item.key?.key !== 'id') || [],
op: item.filters?.op || 'AND',
},
})),
},
};
return preparedQuery;
};

View File

@@ -1,71 +0,0 @@
import { memo, useCallback, useMemo } from 'react';
import { PauseCircleFilled, PlayCircleFilled } from '@ant-design/icons';
import { Space } from 'antd';
import BackButton from 'container/LiveLogs/BackButton';
import { getQueryWithoutFilterId } from 'container/LiveLogs/utils';
import LocalTopNav from 'container/LocalTopNav';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useEventSource } from 'providers/EventSource';
import { LiveButtonStyled } from './styles';
function LiveLogsTopNav(): JSX.Element {
const {
isConnectionOpen,
isConnectionLoading,
initialLoading,
handleCloseConnection,
handleSetInitialLoading,
} = useEventSource();
const { redirectWithQueryBuilderData, currentQuery } = useQueryBuilder();
const isPlaying = isConnectionOpen || isConnectionLoading || initialLoading;
const onLiveButtonClick = useCallback(() => {
if (initialLoading) {
handleSetInitialLoading(false);
}
if ((!isConnectionOpen && isConnectionLoading) || isConnectionOpen) {
handleCloseConnection();
} else {
const preparedQuery = getQueryWithoutFilterId(currentQuery);
redirectWithQueryBuilderData(preparedQuery);
}
}, [
initialLoading,
isConnectionOpen,
isConnectionLoading,
currentQuery,
handleSetInitialLoading,
handleCloseConnection,
redirectWithQueryBuilderData,
]);
const liveButton = useMemo(
() => (
<Space size={16}>
<LiveButtonStyled
icon={isPlaying ? <PauseCircleFilled /> : <PlayCircleFilled />}
danger={isPlaying}
onClick={onLiveButtonClick}
type="primary"
>
{isPlaying ? 'Pause' : 'Resume'}
</LiveButtonStyled>
<BackButton />
</Space>
),
[isPlaying, onLiveButtonClick],
);
return (
<LocalTopNav
actions={liveButton}
renderPermissions={{ isDateTimeEnabled: false }}
/>
);
}
export default memo(LiveLogsTopNav);

View File

@@ -1,19 +0,0 @@
import { Button, ButtonProps } from 'antd';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
export const LiveButtonStyled = styled(Button)<ButtonProps>`
background-color: #1eb475;
${({ danger }): FlattenSimpleInterpolation =>
!danger
? css`
&:hover {
background-color: #1eb475 !important;
}
&:active {
background-color: #1eb475 !important;
}
`
: css``}
`;

View File

@@ -1,50 +0,0 @@
import { useLocation } from 'react-use';
import { Col, Row, Space, Typography } from 'antd';
import ROUTES from 'constants/routes';
import NewExplorerCTA from 'container/NewExplorerCTA';
import { FileText } from 'lucide-react';
import DateTimeSelector from '../TopNav/DateTimeSelectionV2';
import { Container } from './styles';
import { LocalTopNavProps } from './types';
function LocalTopNav({
actions,
renderPermissions,
}: LocalTopNavProps): JSX.Element | null {
const { pathname } = useLocation();
const isLiveLogsPage = pathname === ROUTES.LIVE_LOGS;
return (
<Container>
{isLiveLogsPage && (
<Col span={16}>
<Space>
<FileText color="#fff" size={16} />
<Typography.Title level={4} style={{ marginTop: 0, marginBottom: 0 }}>
Live Logs
</Typography.Title>
</Space>
</Col>
)}
<Col span={isLiveLogsPage ? 8 : 24}>
<Row justify="end">
<Space align="start" size={30} direction="horizontal">
<NewExplorerCTA />
{actions}
{renderPermissions?.isDateTimeEnabled && (
<div>
<DateTimeSelector showAutoRefresh={false} />
</div>
)}
</Space>
</Row>
</Col>
</Container>
);
}
export default LocalTopNav;

View File

@@ -1,9 +0,0 @@
import { Row } from 'antd';
import styled from 'styled-components';
export const Container = styled(Row)`
&&& {
margin-top: 1rem;
min-height: 8vh;
}
`;

View File

@@ -1,6 +0,0 @@
import { ReactNode } from 'react';
export type LocalTopNavProps = {
actions?: ReactNode;
renderPermissions?: { isDateTimeEnabled: boolean };
};

View File

@@ -1,3 +0,0 @@
.log-context-container {
border: 1px solid var(--l1-border);
}

View File

@@ -1,55 +0,0 @@
import RawLogView from 'components/Logs/RawLogView';
import LogsContextList from 'container/LogsContextList';
import { FontSize } from 'container/OptionsMenu/types';
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import './LogContext.styles.scss';
interface LogContextProps {
log: ILog;
contextQuery: Query | undefined;
filters: TagFilter | null;
isEdit: boolean;
}
function LogContext({
log,
filters,
contextQuery,
isEdit,
}: LogContextProps): JSX.Element {
if (!contextQuery) {
return <></>;
}
return (
<div className="log-context-container">
<LogsContextList
order={ORDERBY_FILTERS.ASC}
filters={filters}
isEdit={isEdit}
log={log}
query={contextQuery}
/>
<RawLogView
isActiveLog
isReadOnly
isTextOverflowEllipsisDisabled={false}
data={log}
linesPerRow={1}
fontSize={FontSize.SMALL}
/>
<LogsContextList
order={ORDERBY_FILTERS.DESC}
filters={filters}
isEdit={isEdit}
log={log}
query={contextQuery}
/>
</div>
);
}
export default LogContext;

View File

@@ -1,32 +0,0 @@
.context-logs-list {
position: relative;
.show-more-button {
position: absolute;
opacity: 1;
z-index: 1;
cursor: pointer;
&.up {
top: -1px;
left: -1px;
}
&.down {
bottom: -1px;
left: -1px;
}
&.disabled {
cursor: not-allowed;
}
}
.virtuoso-list {
&::-webkit-scrollbar {
width: 0.3rem;
height: 0.3rem;
}
}
}

View File

@@ -1,227 +0,0 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { Virtuoso } from 'react-virtuoso';
import RawLogView from 'components/Logs/RawLogView';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import Spinner from 'components/Spinner';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { FontSize } from 'container/OptionsMenu/types';
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { SuccessResponse } from 'types/api';
import { ILog } from 'types/api/logs/log';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import {
getOrderByTimestamp,
INITIAL_PAGE_SIZE,
LOGS_MORE_PAGE_SIZE,
} from './configs';
import ShowButton from './ShowButton';
import { EmptyText, ListContainer } from './styles';
import { getRequestData } from './utils';
import './LogsContextList.styles.scss';
interface LogsContextListProps {
className?: string;
isEdit: boolean;
query: Query;
log: ILog;
order: string;
filters: TagFilter | null;
}
function LogsContextList({
className,
isEdit,
query,
log,
order,
filters,
}: LogsContextListProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const [logs, setLogs] = useState<ILog[]>([]);
const [page, setPage] = useState<number>(1);
const firstLog = useMemo(() => logs[0], [logs]);
const lastLog = useMemo(() => logs[logs.length - 1], [logs]);
const orderByTimestamp = useMemo(() => getOrderByTimestamp(order), [order]);
const logsMorePageSize = useMemo(
() => (page - 1) * LOGS_MORE_PAGE_SIZE,
[page],
);
const pageSize = useMemo(
() => (page <= 1 ? INITIAL_PAGE_SIZE : logsMorePageSize + INITIAL_PAGE_SIZE),
[page, logsMorePageSize],
);
const isDisabledFetch = useMemo(
() => logs.length < pageSize,
[logs.length, pageSize],
);
const currentStagedQueryData = useMemo(() => {
if (!query || query.builder.queryData.length !== 1) {
return null;
}
return query.builder.queryData[0];
}, [query]);
const initialLogsRequest = useMemo(
() =>
getRequestData({
stagedQueryData: currentStagedQueryData,
query,
log,
orderByTimestamp,
page,
}),
[currentStagedQueryData, page, log, query, orderByTimestamp],
);
const [requestData, setRequestData] = useState<Query | null>(
initialLogsRequest,
);
const handleSuccess = useCallback(
(data: SuccessResponse<MetricRangePayloadProps, unknown>) => {
const currentData = data?.payload?.data?.newResult?.data?.result || [];
if (currentData.length > 0 && currentData[0].list) {
const currentLogs: ILog[] = currentData[0].list.map((item) => ({
...item.data,
timestamp: item.timestamp,
}));
if (order === ORDERBY_FILTERS.ASC) {
const reversedCurrentLogs = currentLogs.reverse();
setLogs((prevLogs) => [...reversedCurrentLogs, ...prevLogs]);
} else {
setLogs((prevLogs) => [...prevLogs, ...currentLogs]);
}
}
},
[order],
);
const { isError, isFetching } = useGetExplorerQueryRange(
requestData,
PANEL_TYPES.LIST,
DEFAULT_ENTITY_VERSION,
{
keepPreviousData: true,
enabled: !!requestData,
onSuccess: handleSuccess,
},
);
const handleShowNextLines = useCallback(() => {
if (isDisabledFetch) {
return;
}
const log = order === ORDERBY_FILTERS.ASC ? firstLog : lastLog;
const newRequestData = getRequestData({
stagedQueryData: currentStagedQueryData,
query,
log,
orderByTimestamp,
page: page + 1,
pageSize: LOGS_MORE_PAGE_SIZE,
});
setPage((prevPage) => prevPage + 1);
setRequestData(newRequestData);
}, [
query,
firstLog,
lastLog,
page,
order,
currentStagedQueryData,
isDisabledFetch,
orderByTimestamp,
]);
useEffect(() => {
if (!isEdit) {
return;
}
const newRequestData = getRequestData({
stagedQueryData: currentStagedQueryData,
query,
log,
orderByTimestamp,
page: 1,
});
setPage(1);
setLogs([]);
setRequestData(newRequestData);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters]);
const getItemContent = useCallback(
(_: number, log: ILog): JSX.Element => (
<RawLogView
isReadOnly
isTextOverflowEllipsisDisabled
key={log.id}
data={log}
linesPerRow={1}
fontSize={FontSize.SMALL}
/>
),
[],
);
return (
<div className={`context-logs-list ${className}`}>
{order === ORDERBY_FILTERS.ASC && (
<ShowButton
isLoading={isFetching}
isDisabled={isDisabledFetch}
order={order}
onClick={handleShowNextLines}
/>
)}
<ListContainer $isDarkMode={isDarkMode}>
{((!logs.length && !isFetching) || isError) && (
<EmptyText>No Data</EmptyText>
)}
{isFetching && <Spinner size="large" height="10rem" />}
<OverlayScrollbar isVirtuoso>
<Virtuoso
className="virtuoso-list"
initialTopMostItemIndex={0}
data={logs}
itemContent={getItemContent}
followOutput={order === ORDERBY_FILTERS.DESC}
/>
</OverlayScrollbar>
</ListContainer>
{order === ORDERBY_FILTERS.DESC && (
<ShowButton
isLoading={isFetching}
isDisabled={isDisabledFetch}
order={order}
onClick={handleShowNextLines}
/>
)}
</div>
);
}
LogsContextList.defaultProps = {
className: '',
};
export default memo(LogsContextList);

View File

@@ -1,20 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { Typography } from 'antd';
import styled from 'styled-components';
export const ListContainer = styled.div<{ $isDarkMode: boolean }>`
position: relative;
height: 21rem;
overflow: hidden;
background-color: ${({ $isDarkMode }): string =>
$isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100};
`;
export const EmptyText = styled(Typography)`
padding: 0 1.5rem;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;

View File

@@ -1,118 +0,0 @@
import { memo, useCallback, useState } from 'react';
import { EditFilled } from '@ant-design/icons';
import { Modal, Typography } from 'antd';
import RawLogView from 'components/Logs/RawLogView';
import LogsContextList from 'container/LogsContextList';
import { FontSize } from 'container/OptionsMenu/types';
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { EditButton, LogContainer, TitleWrapper } from './styles';
import { LogsExplorerContextProps } from './types';
import useInitialQuery from './useInitialQuery';
function LogsExplorerContext({
log,
onClose,
}: LogsExplorerContextProps): JSX.Element | null {
const initialContextQuery = useInitialQuery(log);
const [contextQuery, setContextQuery] = useState<Query>(initialContextQuery);
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
const isDarkMode = useIsDarkMode();
const handleClickEditButton = useCallback(
() => setIsEdit((prevValue) => !prevValue),
[],
);
const handleSearch = useCallback(
(tagFilters: TagFilter): void => {
const tagFiltersLength = tagFilters.items.length;
if (
(!tagFiltersLength && (!filters || !filters.items.length)) ||
tagFiltersLength === filters?.items.length
) {
return;
}
const nextQuery: Query = {
...contextQuery,
builder: {
...contextQuery.builder,
queryData: contextQuery.builder.queryData.map((item) => ({
...item,
filters: tagFilters,
})),
},
};
setFilters(tagFilters);
setContextQuery(nextQuery);
},
[contextQuery, filters],
);
return (
<Modal
centered
destroyOnClose
open
width={816}
onCancel={onClose}
onOk={onClose}
footer={null}
title={
<TitleWrapper block>
<Typography>Logs Context</Typography>
<EditButton
$isDarkMode={isDarkMode}
size="small"
type="text"
icon={<EditFilled />}
onClick={handleClickEditButton}
/>
</TitleWrapper>
}
>
{isEdit && (
<QueryBuilderSearch
query={contextQuery?.builder.queryData[0]}
onChange={handleSearch}
/>
)}
<LogsContextList
order={ORDERBY_FILTERS.ASC}
filters={filters}
isEdit={isEdit}
log={log}
query={contextQuery}
/>
<LogContainer>
<RawLogView
isActiveLog
isReadOnly
isTextOverflowEllipsisDisabled
data={log}
linesPerRow={1}
fontSize={FontSize.SMALL}
/>
</LogContainer>
<LogsContextList
order={ORDERBY_FILTERS.DESC}
filters={filters}
isEdit={isEdit}
log={log}
query={contextQuery}
/>
</Modal>
);
}
export default memo(LogsExplorerContext);

View File

@@ -1,34 +0,0 @@
import { Button, Space } from 'antd';
import { themeColors } from 'constants/theme';
import styled from 'styled-components';
import getAlphaColor from 'utils/getAlphaColor';
export const TitleWrapper = styled(Space.Compact)`
justify-content: space-between;
align-items: center;
`;
export const EditButton = styled(Button)<{ $isDarkMode: boolean }>`
margin-right: 0.938rem;
width: 1.375rem !important;
height: 1.375rem;
position: absolute;
top: 1rem;
right: 1.563rem;
padding: 0;
border-radius: 0.125rem;
border-start-start-radius: 0.125rem !important;
border-end-start-radius: 0.125rem !important;
color: ${({ $isDarkMode }): string =>
$isDarkMode
? getAlphaColor(themeColors.white)[45]
: getAlphaColor(themeColors.black)[45]};
`;
export const LogContainer = styled.div`
overflow-x: auto;
`;

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