mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-06 01:42:15 +00:00
Compare commits
15 Commits
feat/root-
...
update-spe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91dc5731d0 | ||
|
|
ef80fb39fd | ||
|
|
594d4dc737 | ||
|
|
01415b58be | ||
|
|
95ad9c3fd6 | ||
|
|
64201601b3 | ||
|
|
13ad5397ae | ||
|
|
51ec67f1e7 | ||
|
|
f7728c9019 | ||
|
|
cb9c3f790f | ||
|
|
dca0f8ca86 | ||
|
|
c207b059bf | ||
|
|
3fffe6e198 | ||
|
|
4594f4ffe3 | ||
|
|
b660dc8573 |
@@ -2665,6 +2665,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2719,6 +2720,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2774,6 +2776,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2940,6 +2943,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -3807,6 +3811,9 @@ components:
|
||||
type: string
|
||||
alertName:
|
||||
type: string
|
||||
required:
|
||||
- alertName
|
||||
- alertId
|
||||
type: object
|
||||
MetricsexplorertypesMetricAlertsResponse:
|
||||
properties:
|
||||
@@ -3815,6 +3822,8 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricAlert'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- alerts
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttribute:
|
||||
properties:
|
||||
@@ -3828,6 +3837,10 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- values
|
||||
- valueCount
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttributesRequest:
|
||||
properties:
|
||||
@@ -3839,6 +3852,8 @@ components:
|
||||
start:
|
||||
nullable: true
|
||||
type: integer
|
||||
required:
|
||||
- metricName
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttributesResponse:
|
||||
properties:
|
||||
@@ -3850,6 +3865,9 @@ components:
|
||||
totalKeys:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- attributes
|
||||
- totalKeys
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboard:
|
||||
properties:
|
||||
@@ -3861,6 +3879,11 @@ components:
|
||||
type: string
|
||||
widgetName:
|
||||
type: string
|
||||
required:
|
||||
- dashboardName
|
||||
- dashboardId
|
||||
- widgetId
|
||||
- widgetName
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboardsResponse:
|
||||
properties:
|
||||
@@ -3869,6 +3892,8 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricDashboard'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- dashboards
|
||||
type: object
|
||||
MetricsexplorertypesMetricHighlightsResponse:
|
||||
properties:
|
||||
@@ -3884,6 +3909,11 @@ components:
|
||||
totalTimeSeries:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- dataPoints
|
||||
- lastReceived
|
||||
- totalTimeSeries
|
||||
- activeTimeSeries
|
||||
type: object
|
||||
MetricsexplorertypesMetricMetadata:
|
||||
properties:
|
||||
@@ -3892,11 +3922,27 @@ components:
|
||||
isMonotonic:
|
||||
type: boolean
|
||||
temporality:
|
||||
enum:
|
||||
- delta
|
||||
- cumulative
|
||||
- unspecified
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- gauge
|
||||
- sum
|
||||
- histogram
|
||||
- summary
|
||||
- exponentialhistogram
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- description
|
||||
- type
|
||||
- unit
|
||||
- temporality
|
||||
- isMonotonic
|
||||
type: object
|
||||
MetricsexplorertypesStat:
|
||||
properties:
|
||||
@@ -3911,9 +3957,22 @@ components:
|
||||
minimum: 0
|
||||
type: integer
|
||||
type:
|
||||
enum:
|
||||
- gauge
|
||||
- sum
|
||||
- histogram
|
||||
- summary
|
||||
- exponentialhistogram
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- metricName
|
||||
- description
|
||||
- type
|
||||
- unit
|
||||
- timeseries
|
||||
- samples
|
||||
type: object
|
||||
MetricsexplorertypesStatsRequest:
|
||||
properties:
|
||||
@@ -3931,6 +3990,10 @@ components:
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
MetricsexplorertypesStatsResponse:
|
||||
properties:
|
||||
@@ -3942,6 +4005,9 @@ components:
|
||||
total:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- metrics
|
||||
- total
|
||||
type: object
|
||||
MetricsexplorertypesTreemapEntry:
|
||||
properties:
|
||||
@@ -3953,6 +4019,10 @@ components:
|
||||
totalValue:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- metricName
|
||||
- percentage
|
||||
- totalValue
|
||||
type: object
|
||||
MetricsexplorertypesTreemapRequest:
|
||||
properties:
|
||||
@@ -3964,10 +4034,18 @@ components:
|
||||
limit:
|
||||
type: integer
|
||||
mode:
|
||||
enum:
|
||||
- timeseries
|
||||
- samples
|
||||
type: string
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
- mode
|
||||
type: object
|
||||
MetricsexplorertypesTreemapResponse:
|
||||
properties:
|
||||
@@ -3981,6 +4059,9 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesTreemapEntry'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- timeseries
|
||||
- samples
|
||||
type: object
|
||||
MetricsexplorertypesUpdateMetricMetadataRequest:
|
||||
properties:
|
||||
@@ -3991,11 +4072,28 @@ components:
|
||||
metricName:
|
||||
type: string
|
||||
temporality:
|
||||
enum:
|
||||
- delta
|
||||
- cumulative
|
||||
- unspecified
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- gauge
|
||||
- sum
|
||||
- histogram
|
||||
- summary
|
||||
- exponentialhistogram
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- metricName
|
||||
- type
|
||||
- description
|
||||
- unit
|
||||
- temporality
|
||||
- isMonotonic
|
||||
type: object
|
||||
PreferencetypesPreference:
|
||||
properties:
|
||||
|
||||
@@ -28,8 +28,10 @@ import type {
|
||||
GatewaytypesPostableIngestionKeyLimitDTO,
|
||||
GatewaytypesUpdatableIngestionKeyLimitDTO,
|
||||
GetIngestionKeys200,
|
||||
GetIngestionKeysParams,
|
||||
RenderErrorResponseDTO,
|
||||
SearchIngestionKeys200,
|
||||
SearchIngestionKeysParams,
|
||||
UpdateIngestionKeyLimitPathParameters,
|
||||
UpdateIngestionKeyPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -42,35 +44,44 @@ type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
* This endpoint returns the ingestion keys for a workspace
|
||||
* @summary Get ingestion keys for workspace
|
||||
*/
|
||||
export const getIngestionKeys = (signal?: AbortSignal) => {
|
||||
export const getIngestionKeys = (
|
||||
params?: GetIngestionKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetIngestionKeys200>({
|
||||
url: `/api/v2/gateway/ingestion_keys`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIngestionKeysQueryKey = () => {
|
||||
return ['getIngestionKeys'] as const;
|
||||
export const getGetIngestionKeysQueryKey = (
|
||||
params?: GetIngestionKeysParams,
|
||||
) => {
|
||||
return ['getIngestionKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetIngestionKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
>(
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetIngestionKeysQueryKey();
|
||||
const queryKey = queryOptions?.queryKey ?? getGetIngestionKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getIngestionKeys>>> = ({
|
||||
signal,
|
||||
}) => getIngestionKeys(signal);
|
||||
}) => getIngestionKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
@@ -91,14 +102,17 @@ export type GetIngestionKeysQueryError = RenderErrorResponseDTO;
|
||||
export function useGetIngestionKeys<
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeysQueryOptions(options);
|
||||
>(
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
@@ -114,10 +128,11 @@ export function useGetIngestionKeys<
|
||||
*/
|
||||
export const invalidateGetIngestionKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetIngestionKeysQueryKey() },
|
||||
{ queryKey: getGetIngestionKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -662,35 +677,45 @@ export const useUpdateIngestionKeyLimit = <
|
||||
* This endpoint returns the ingestion keys for a workspace
|
||||
* @summary Search ingestion keys for workspace
|
||||
*/
|
||||
export const searchIngestionKeys = (signal?: AbortSignal) => {
|
||||
export const searchIngestionKeys = (
|
||||
params?: SearchIngestionKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<SearchIngestionKeys200>({
|
||||
url: `/api/v2/gateway/ingestion_keys/search`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSearchIngestionKeysQueryKey = () => {
|
||||
return ['searchIngestionKeys'] as const;
|
||||
export const getSearchIngestionKeysQueryKey = (
|
||||
params?: SearchIngestionKeysParams,
|
||||
) => {
|
||||
return ['searchIngestionKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getSearchIngestionKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
>(
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getSearchIngestionKeysQueryKey();
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getSearchIngestionKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>
|
||||
> = ({ signal }) => searchIngestionKeys(signal);
|
||||
> = ({ signal }) => searchIngestionKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
@@ -711,14 +736,17 @@ export type SearchIngestionKeysQueryError = RenderErrorResponseDTO;
|
||||
export function useSearchIngestionKeys<
|
||||
TData = Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getSearchIngestionKeysQueryOptions(options);
|
||||
>(
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getSearchIngestionKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
@@ -734,10 +762,11 @@ export function useSearchIngestionKeys<
|
||||
*/
|
||||
export const invalidateSearchIngestionKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getSearchIngestionKeysQueryKey() },
|
||||
{ queryKey: getSearchIngestionKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
* @summary Get metric alerts
|
||||
*/
|
||||
export const getMetricAlerts = (
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricAlerts200>({
|
||||
@@ -66,7 +66,7 @@ export const getGetMetricAlertsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
@@ -103,7 +103,7 @@ export function useGetMetricAlerts<
|
||||
TData = Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
@@ -128,7 +128,7 @@ export function useGetMetricAlerts<
|
||||
*/
|
||||
export const invalidateGetMetricAlerts = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -144,7 +144,7 @@ export const invalidateGetMetricAlerts = async (
|
||||
* @summary Get metric dashboards
|
||||
*/
|
||||
export const getMetricDashboards = (
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricDashboards200>({
|
||||
@@ -165,7 +165,7 @@ export const getGetMetricDashboardsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
@@ -203,7 +203,7 @@ export function useGetMetricDashboards<
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
@@ -228,7 +228,7 @@ export function useGetMetricDashboards<
|
||||
*/
|
||||
export const invalidateGetMetricDashboards = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -244,7 +244,7 @@ export const invalidateGetMetricDashboards = async (
|
||||
* @summary Get metric highlights
|
||||
*/
|
||||
export const getMetricHighlights = (
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricHighlights200>({
|
||||
@@ -265,7 +265,7 @@ export const getGetMetricHighlightsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
@@ -303,7 +303,7 @@ export function useGetMetricHighlights<
|
||||
TData = Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
@@ -328,7 +328,7 @@ export function useGetMetricHighlights<
|
||||
*/
|
||||
export const invalidateGetMetricHighlights = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -526,7 +526,7 @@ export const useGetMetricAttributes = <
|
||||
* @summary Get metric metadata
|
||||
*/
|
||||
export const getMetricMetadata = (
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricMetadata200>({
|
||||
@@ -547,7 +547,7 @@ export const getGetMetricMetadataQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
@@ -585,7 +585,7 @@ export function useGetMetricMetadata<
|
||||
TData = Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
@@ -610,7 +610,7 @@ export function useGetMetricMetadata<
|
||||
*/
|
||||
export const invalidateGetMetricMetadata = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
|
||||
@@ -650,11 +650,11 @@ export interface MetricsexplorertypesMetricAlertDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alertId?: string;
|
||||
alertId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alertName?: string;
|
||||
alertName: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAlertsResponseDTO {
|
||||
@@ -662,24 +662,24 @@ export interface MetricsexplorertypesMetricAlertsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
alerts?: MetricsexplorertypesMetricAlertDTO[] | null;
|
||||
alerts: MetricsexplorertypesMetricAlertDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAttributeDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
key?: string;
|
||||
key: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
valueCount?: number;
|
||||
valueCount: number;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
values?: string[] | null;
|
||||
values: string[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAttributesRequestDTO {
|
||||
@@ -691,7 +691,7 @@ export interface MetricsexplorertypesMetricAttributesRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @nullable true
|
||||
@@ -704,31 +704,31 @@ export interface MetricsexplorertypesMetricAttributesResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
attributes?: MetricsexplorertypesMetricAttributeDTO[] | null;
|
||||
attributes: MetricsexplorertypesMetricAttributeDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
totalKeys?: number;
|
||||
totalKeys: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardId?: string;
|
||||
dashboardId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardName?: string;
|
||||
dashboardName: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetId?: string;
|
||||
widgetId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetName?: string;
|
||||
widgetName: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardsResponseDTO {
|
||||
@@ -736,7 +736,7 @@ export interface MetricsexplorertypesMetricDashboardsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
dashboards?: MetricsexplorertypesMetricDashboardDTO[] | null;
|
||||
dashboards: MetricsexplorertypesMetricDashboardDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricHighlightsResponseDTO {
|
||||
@@ -744,74 +744,96 @@ export interface MetricsexplorertypesMetricHighlightsResponseDTO {
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
activeTimeSeries?: number;
|
||||
activeTimeSeries: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
dataPoints?: number;
|
||||
dataPoints: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
lastReceived?: number;
|
||||
lastReceived: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalTimeSeries?: number;
|
||||
totalTimeSeries: number;
|
||||
}
|
||||
|
||||
export enum MetricsexplorertypesMetricMetadataDTOTemporality {
|
||||
delta = 'delta',
|
||||
cumulative = 'cumulative',
|
||||
unspecified = 'unspecified',
|
||||
}
|
||||
export enum MetricsexplorertypesMetricMetadataDTOType {
|
||||
gauge = 'gauge',
|
||||
sum = 'sum',
|
||||
histogram = 'histogram',
|
||||
summary = 'summary',
|
||||
exponentialhistogram = 'exponentialhistogram',
|
||||
}
|
||||
export interface MetricsexplorertypesMetricMetadataDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isMonotonic?: boolean;
|
||||
isMonotonic: boolean;
|
||||
/**
|
||||
* @enum delta,cumulative,unspecified
|
||||
* @type string
|
||||
*/
|
||||
temporality: MetricsexplorertypesMetricMetadataDTOTemporality;
|
||||
/**
|
||||
* @enum gauge,sum,histogram,summary,exponentialhistogram
|
||||
* @type string
|
||||
*/
|
||||
type: MetricsexplorertypesMetricMetadataDTOType;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
temporality?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export enum MetricsexplorertypesStatDTOType {
|
||||
gauge = 'gauge',
|
||||
sum = 'sum',
|
||||
histogram = 'histogram',
|
||||
summary = 'summary',
|
||||
exponentialhistogram = 'exponentialhistogram',
|
||||
}
|
||||
export interface MetricsexplorertypesStatDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
samples?: number;
|
||||
samples: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
timeseries?: number;
|
||||
timeseries: number;
|
||||
/**
|
||||
* @enum gauge,sum,histogram,summary,exponentialhistogram
|
||||
* @type string
|
||||
*/
|
||||
type: MetricsexplorertypesStatDTOType;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesStatsRequestDTO {
|
||||
@@ -819,12 +841,12 @@ export interface MetricsexplorertypesStatsRequestDTO {
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end?: number;
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit?: number;
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
@@ -834,7 +856,7 @@ export interface MetricsexplorertypesStatsRequestDTO {
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start?: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesStatsResponseDTO {
|
||||
@@ -842,51 +864,56 @@ export interface MetricsexplorertypesStatsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
metrics?: MetricsexplorertypesStatDTO[] | null;
|
||||
metrics: MetricsexplorertypesStatDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
total?: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesTreemapEntryDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
percentage?: number;
|
||||
percentage: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalValue?: number;
|
||||
totalValue: number;
|
||||
}
|
||||
|
||||
export enum MetricsexplorertypesTreemapRequestDTOMode {
|
||||
timeseries = 'timeseries',
|
||||
samples = 'samples',
|
||||
}
|
||||
export interface MetricsexplorertypesTreemapRequestDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end?: number;
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit?: number;
|
||||
limit: number;
|
||||
/**
|
||||
* @enum timeseries,samples
|
||||
* @type string
|
||||
*/
|
||||
mode?: string;
|
||||
mode: MetricsexplorertypesTreemapRequestDTOMode;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start?: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesTreemapResponseDTO {
|
||||
@@ -894,39 +921,53 @@ export interface MetricsexplorertypesTreemapResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
samples?: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
samples: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
timeseries?: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
timeseries: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
}
|
||||
|
||||
export enum MetricsexplorertypesUpdateMetricMetadataRequestDTOTemporality {
|
||||
delta = 'delta',
|
||||
cumulative = 'cumulative',
|
||||
unspecified = 'unspecified',
|
||||
}
|
||||
export enum MetricsexplorertypesUpdateMetricMetadataRequestDTOType {
|
||||
gauge = 'gauge',
|
||||
sum = 'sum',
|
||||
histogram = 'histogram',
|
||||
summary = 'summary',
|
||||
exponentialhistogram = 'exponentialhistogram',
|
||||
}
|
||||
export interface MetricsexplorertypesUpdateMetricMetadataRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isMonotonic?: boolean;
|
||||
isMonotonic: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @enum delta,cumulative,unspecified
|
||||
* @type string
|
||||
*/
|
||||
temporality: MetricsexplorertypesUpdateMetricMetadataRequestDTOTemporality;
|
||||
/**
|
||||
* @enum gauge,sum,histogram,summary,exponentialhistogram
|
||||
* @type string
|
||||
*/
|
||||
type: MetricsexplorertypesUpdateMetricMetadataRequestDTOType;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
temporality?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface PreferencetypesPreferenceDTO {
|
||||
@@ -1851,6 +1892,19 @@ export type GetFeatures200 = {
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetIngestionKeysParams = {
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
per_page?: number;
|
||||
};
|
||||
|
||||
export type GetIngestionKeys200 = {
|
||||
data?: GatewaytypesGettableIngestionKeysDTO;
|
||||
/**
|
||||
@@ -1890,6 +1944,24 @@ export type DeleteIngestionKeyLimitPathParameters = {
|
||||
export type UpdateIngestionKeyLimitPathParameters = {
|
||||
limitId: string;
|
||||
};
|
||||
export type SearchIngestionKeysParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
per_page?: number;
|
||||
};
|
||||
|
||||
export type SearchIngestionKeys200 = {
|
||||
data?: GatewaytypesGettableIngestionKeysDTO;
|
||||
/**
|
||||
@@ -1903,7 +1975,7 @@ export type GetMetricAlertsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricAlerts200 = {
|
||||
@@ -1919,7 +1991,7 @@ export type GetMetricDashboardsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricDashboards200 = {
|
||||
@@ -1935,7 +2007,7 @@ export type GetMetricHighlightsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricHighlights200 = {
|
||||
@@ -1962,7 +2034,7 @@ export type GetMetricMetadataParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricMetadata200 = {
|
||||
|
||||
@@ -11,9 +11,14 @@ import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryFunction } from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { UseQueryOperations } from 'types/common/operations.types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
import {
|
||||
DataSource,
|
||||
QueryBuilderContextType,
|
||||
QueryFunctionsTypes,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
@@ -45,12 +50,17 @@ const mockedUseQueryOperations = jest.mocked(
|
||||
|
||||
describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
let handleRunQueryMock: jest.MockedFunction<() => void>;
|
||||
let handleQueryFunctionsUpdatesMock: jest.MockedFunction<() => void>;
|
||||
let baseQBContext: QueryBuilderContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockCloneQuery = jest.fn() as jest.MockedFunction<
|
||||
(type: string, q: IBuilderQuery) => void
|
||||
>;
|
||||
handleRunQueryMock = jest.fn() as jest.MockedFunction<() => void>;
|
||||
handleQueryFunctionsUpdatesMock = jest.fn() as jest.MockedFunction<
|
||||
() => void
|
||||
>;
|
||||
const baseQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.LOGS,
|
||||
@@ -91,7 +101,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
const updateQueriesData: QueryBuilderContextType['updateQueriesData'] = (q) =>
|
||||
q;
|
||||
|
||||
mockedUseQueryBuilder.mockReturnValue(({
|
||||
const baseContext = ({
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
@@ -124,7 +134,10 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
} as unknown) as QueryBuilderContextType);
|
||||
} as unknown) as QueryBuilderContextType;
|
||||
|
||||
baseQBContext = baseContext;
|
||||
mockedUseQueryBuilder.mockReturnValue(baseQBContext);
|
||||
|
||||
mockedUseQueryOperations.mockReturnValue({
|
||||
isTracePanelType: false,
|
||||
@@ -139,7 +152,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
handleDeleteQuery: jest.fn(),
|
||||
handleChangeQueryData: (jest.fn() as unknown) as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeFormulaData: jest.fn(),
|
||||
handleQueryFunctionsUpdates: jest.fn(),
|
||||
handleQueryFunctionsUpdates: handleQueryFunctionsUpdatesMock,
|
||||
listOfAdditionalFormulaFilters: [],
|
||||
});
|
||||
});
|
||||
@@ -199,4 +212,56 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
|
||||
expect(handleRunQueryMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fx button is disabled when functions already exist', () => {
|
||||
const currentQueryBase = baseQBContext.currentQuery as Query;
|
||||
const supersetQueryBase = baseQBContext.supersetQuery as Query;
|
||||
|
||||
mockedUseQueryBuilder.mockReturnValueOnce({
|
||||
...baseQBContext,
|
||||
currentQuery: {
|
||||
...currentQueryBase,
|
||||
builder: {
|
||||
...currentQueryBase.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQueryBase.builder.queryData[0],
|
||||
functions: [
|
||||
{ name: QueryFunctionsTypes.TIME_SHIFT, args: [] } as QueryFunction,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
supersetQuery: {
|
||||
...supersetQueryBase,
|
||||
builder: {
|
||||
...supersetQueryBase.builder,
|
||||
queryData: [
|
||||
{
|
||||
...supersetQueryBase.builder.queryData[0],
|
||||
functions: [
|
||||
{ name: QueryFunctionsTypes.TIME_SHIFT, args: [] } as QueryFunction,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<QueryBuilderV2 panelType={PANEL_TYPES.TABLE} version="v4" />);
|
||||
|
||||
const fxButton = document.querySelector('.function-btn') as HTMLButtonElement;
|
||||
expect(fxButton).toBeInTheDocument();
|
||||
expect(fxButton).toBeDisabled();
|
||||
|
||||
const deleteButton = document.querySelector(
|
||||
'.query-function-delete-btn',
|
||||
) as HTMLButtonElement;
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
userEvent.click(deleteButton);
|
||||
waitFor(() => {
|
||||
expect(fxButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -338,7 +338,7 @@ describe('CreateAlertV2 utils', () => {
|
||||
const props = getCreateAlertLocalStateFromAlertDef(args);
|
||||
expect(props).toBeDefined();
|
||||
expect(props).toMatchObject({
|
||||
basicAlertState: {
|
||||
basic: {
|
||||
...INITIAL_ALERT_STATE,
|
||||
name: 'test-alert',
|
||||
labels: {
|
||||
@@ -348,10 +348,10 @@ describe('CreateAlertV2 utils', () => {
|
||||
yAxisUnit: UniversalYAxisUnit.MINUTES,
|
||||
},
|
||||
// as we have already verified these utils in their respective tests
|
||||
thresholdState: expect.any(Object),
|
||||
advancedOptionsState: expect.any(Object),
|
||||
evaluationWindowState: expect.any(Object),
|
||||
notificationSettingsState: expect.any(Object),
|
||||
threshold: expect.any(Object),
|
||||
advancedOptions: expect.any(Object),
|
||||
evaluationWindow: expect.any(Object),
|
||||
notificationSettings: expect.any(Object),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,3 +196,11 @@ export const INITIAL_NOTIFICATION_SETTINGS_STATE: NotificationSettingsState = {
|
||||
description: NOTIFICATION_MESSAGE_PLACEHOLDER,
|
||||
routingPolicies: false,
|
||||
};
|
||||
|
||||
export const INITIAL_CREATE_ALERT_STATE = {
|
||||
basic: INITIAL_ALERT_STATE,
|
||||
threshold: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettings: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
};
|
||||
|
||||
@@ -17,26 +17,22 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
import { INITIAL_CREATE_ALERT_STATE } from './constants';
|
||||
import {
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
INITIAL_ALERT_STATE,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
} from './constants';
|
||||
import {
|
||||
AdvancedOptionsAction,
|
||||
AlertThresholdAction,
|
||||
AlertThresholdMatchType,
|
||||
CreateAlertAction,
|
||||
CreateAlertSlice,
|
||||
EvaluationWindowAction,
|
||||
ICreateAlertContextProps,
|
||||
ICreateAlertProviderProps,
|
||||
NotificationSettingsAction,
|
||||
} from './types';
|
||||
import {
|
||||
advancedOptionsReducer,
|
||||
alertCreationReducer,
|
||||
alertThresholdReducer,
|
||||
buildInitialAlertDef,
|
||||
evaluationWindowReducer,
|
||||
createAlertReducer,
|
||||
getInitialAlertTypeFromURL,
|
||||
notificationSettingsReducer,
|
||||
} from './utils';
|
||||
|
||||
const CreateAlertContext = createContext<ICreateAlertContextProps | null>(null);
|
||||
@@ -65,10 +61,65 @@ export function CreateAlertProvider(
|
||||
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
|
||||
const [alertState, setAlertState] = useReducer(alertCreationReducer, {
|
||||
...INITIAL_ALERT_STATE,
|
||||
yAxisUnit: currentQuery.unit,
|
||||
});
|
||||
const [createAlertState, setCreateAlertState] = useReducer(
|
||||
createAlertReducer,
|
||||
{
|
||||
...INITIAL_CREATE_ALERT_STATE,
|
||||
basic: {
|
||||
...INITIAL_CREATE_ALERT_STATE.basic,
|
||||
yAxisUnit: currentQuery.unit,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const setAlertState = useCallback(
|
||||
(action: CreateAlertAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.BASIC,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setThresholdState = useCallback(
|
||||
(action: AlertThresholdAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setEvaluationWindow = useCallback(
|
||||
(action: EvaluationWindowAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.EVALUATION_WINDOW,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setAdvancedOptions = useCallback(
|
||||
(action: AdvancedOptionsAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.ADVANCED_OPTIONS,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
const setNotificationSettings = useCallback(
|
||||
(action: NotificationSettingsAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.NOTIFICATION_SETTINGS,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
@@ -104,92 +155,56 @@ export function CreateAlertProvider(
|
||||
[redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
const [thresholdState, setThresholdState] = useReducer(
|
||||
alertThresholdReducer,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
);
|
||||
|
||||
const [evaluationWindow, setEvaluationWindow] = useReducer(
|
||||
evaluationWindowReducer,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
);
|
||||
|
||||
const [advancedOptions, setAdvancedOptions] = useReducer(
|
||||
advancedOptionsReducer,
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
);
|
||||
|
||||
const [notificationSettings, setNotificationSettings] = useReducer(
|
||||
notificationSettingsReducer,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setThresholdState({
|
||||
type: 'RESET',
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'RESET',
|
||||
},
|
||||
});
|
||||
|
||||
if (thresholdsFromURL) {
|
||||
try {
|
||||
const thresholds = JSON.parse(thresholdsFromURL);
|
||||
setThresholdState({
|
||||
type: 'SET_THRESHOLDS',
|
||||
payload: thresholds,
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'SET_THRESHOLDS',
|
||||
payload: thresholds,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error parsing thresholds from URL:', error);
|
||||
}
|
||||
|
||||
setEvaluationWindow({
|
||||
type: 'SET_INITIAL_STATE_FOR_METER',
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.EVALUATION_WINDOW,
|
||||
action: {
|
||||
type: 'SET_INITIAL_STATE_FOR_METER',
|
||||
},
|
||||
});
|
||||
|
||||
setThresholdState({
|
||||
type: 'SET_MATCH_TYPE',
|
||||
payload: AlertThresholdMatchType.IN_TOTAL,
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'SET_MATCH_TYPE',
|
||||
payload: AlertThresholdMatchType.IN_TOTAL,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [alertType, thresholdsFromURL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditMode && initialAlertState) {
|
||||
setAlertState({
|
||||
setCreateAlertState({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.basicAlertState,
|
||||
});
|
||||
setThresholdState({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.thresholdState,
|
||||
});
|
||||
setEvaluationWindow({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.evaluationWindowState,
|
||||
});
|
||||
setAdvancedOptions({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.advancedOptionsState,
|
||||
});
|
||||
setNotificationSettings({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.notificationSettingsState,
|
||||
payload: initialAlertState,
|
||||
});
|
||||
}
|
||||
}, [initialAlertState, isEditMode]);
|
||||
|
||||
const discardAlertRule = useCallback(() => {
|
||||
setAlertState({
|
||||
type: 'RESET',
|
||||
});
|
||||
setThresholdState({
|
||||
type: 'RESET',
|
||||
});
|
||||
setEvaluationWindow({
|
||||
type: 'RESET',
|
||||
});
|
||||
setAdvancedOptions({
|
||||
type: 'RESET',
|
||||
});
|
||||
setNotificationSettings({
|
||||
setCreateAlertState({
|
||||
type: 'RESET',
|
||||
});
|
||||
handleAlertTypeChange(AlertTypes.METRICS_BASED_ALERT);
|
||||
@@ -212,17 +227,17 @@ export function CreateAlertProvider(
|
||||
|
||||
const contextValue: ICreateAlertContextProps = useMemo(
|
||||
() => ({
|
||||
alertState,
|
||||
alertState: createAlertState.basic,
|
||||
setAlertState,
|
||||
alertType,
|
||||
setAlertType: handleAlertTypeChange,
|
||||
thresholdState,
|
||||
thresholdState: createAlertState.threshold,
|
||||
setThresholdState,
|
||||
evaluationWindow,
|
||||
evaluationWindow: createAlertState.evaluationWindow,
|
||||
setEvaluationWindow,
|
||||
advancedOptions,
|
||||
advancedOptions: createAlertState.advancedOptions,
|
||||
setAdvancedOptions,
|
||||
notificationSettings,
|
||||
notificationSettings: createAlertState.notificationSettings,
|
||||
setNotificationSettings,
|
||||
discardAlertRule,
|
||||
createAlertRule,
|
||||
@@ -234,13 +249,14 @@ export function CreateAlertProvider(
|
||||
isEditMode: isEditMode || false,
|
||||
}),
|
||||
[
|
||||
alertState,
|
||||
createAlertState,
|
||||
setAlertState,
|
||||
setThresholdState,
|
||||
setEvaluationWindow,
|
||||
setAdvancedOptions,
|
||||
setNotificationSettings,
|
||||
alertType,
|
||||
handleAlertTypeChange,
|
||||
thresholdState,
|
||||
evaluationWindow,
|
||||
advancedOptions,
|
||||
notificationSettings,
|
||||
discardAlertRule,
|
||||
createAlertRule,
|
||||
isCreatingAlertRule,
|
||||
|
||||
@@ -9,8 +9,6 @@ import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
import { Labels } from 'types/api/alerts/def';
|
||||
|
||||
import { GetCreateAlertLocalStateFromAlertDefReturn } from '../types';
|
||||
|
||||
export interface ICreateAlertContextProps {
|
||||
alertState: AlertState;
|
||||
setAlertState: Dispatch<CreateAlertAction>;
|
||||
@@ -52,7 +50,7 @@ export interface ICreateAlertContextProps {
|
||||
export interface ICreateAlertProviderProps {
|
||||
children: React.ReactNode;
|
||||
initialAlertType: AlertTypes;
|
||||
initialAlertState?: GetCreateAlertLocalStateFromAlertDefReturn;
|
||||
initialAlertState?: CreateAlertState;
|
||||
isEditMode?: boolean;
|
||||
ruleId?: string;
|
||||
}
|
||||
@@ -272,3 +270,31 @@ export type NotificationSettingsAction =
|
||||
| { type: 'SET_ROUTING_POLICIES'; payload: boolean }
|
||||
| { type: 'SET_INITIAL_STATE'; payload: NotificationSettingsState }
|
||||
| { type: 'RESET' };
|
||||
|
||||
export type CreateAlertState = {
|
||||
basic: AlertState;
|
||||
threshold: AlertThresholdState;
|
||||
advancedOptions: AdvancedOptionsState;
|
||||
evaluationWindow: EvaluationWindowState;
|
||||
notificationSettings: NotificationSettingsState;
|
||||
};
|
||||
|
||||
export enum CreateAlertSlice {
|
||||
BASIC = 'basic',
|
||||
THRESHOLD = 'threshold',
|
||||
ADVANCED_OPTIONS = 'advancedOptions',
|
||||
EVALUATION_WINDOW = 'evaluationWindow',
|
||||
NOTIFICATION_SETTINGS = 'notificationSettings',
|
||||
}
|
||||
|
||||
export type CreateAlertReducerAction =
|
||||
| { slice: CreateAlertSlice.BASIC; action: CreateAlertAction }
|
||||
| { slice: CreateAlertSlice.THRESHOLD; action: AlertThresholdAction }
|
||||
| { slice: CreateAlertSlice.ADVANCED_OPTIONS; action: AdvancedOptionsAction }
|
||||
| { slice: CreateAlertSlice.EVALUATION_WINDOW; action: EvaluationWindowAction }
|
||||
| {
|
||||
slice: CreateAlertSlice.NOTIFICATION_SETTINGS;
|
||||
action: NotificationSettingsAction;
|
||||
}
|
||||
| { type: 'RESET' }
|
||||
| { type: 'SET_INITIAL_STATE'; payload: CreateAlertState };
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
INITIAL_ALERT_STATE,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
INITIAL_CREATE_ALERT_STATE,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
} from './constants';
|
||||
@@ -28,6 +29,9 @@ import {
|
||||
AlertThresholdAction,
|
||||
AlertThresholdState,
|
||||
CreateAlertAction,
|
||||
CreateAlertReducerAction,
|
||||
CreateAlertSlice,
|
||||
CreateAlertState,
|
||||
EvaluationWindowAction,
|
||||
EvaluationWindowState,
|
||||
NotificationSettingsAction,
|
||||
@@ -251,3 +255,57 @@ export const notificationSettingsReducer = (
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const createAlertReducer = (
|
||||
state: CreateAlertState,
|
||||
action: CreateAlertReducerAction,
|
||||
): CreateAlertState => {
|
||||
// Global actions
|
||||
if ('type' in action) {
|
||||
switch (action.type) {
|
||||
case 'RESET':
|
||||
return INITIAL_CREATE_ALERT_STATE;
|
||||
case 'SET_INITIAL_STATE':
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// Slice actions
|
||||
switch (action.slice) {
|
||||
case CreateAlertSlice.BASIC:
|
||||
return { ...state, basic: alertCreationReducer(state.basic, action.action) };
|
||||
case CreateAlertSlice.THRESHOLD:
|
||||
return {
|
||||
...state,
|
||||
threshold: alertThresholdReducer(state.threshold, action.action),
|
||||
};
|
||||
case CreateAlertSlice.ADVANCED_OPTIONS:
|
||||
return {
|
||||
...state,
|
||||
advancedOptions: advancedOptionsReducer(
|
||||
state.advancedOptions,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
case CreateAlertSlice.EVALUATION_WINDOW:
|
||||
return {
|
||||
...state,
|
||||
evaluationWindow: evaluationWindowReducer(
|
||||
state.evaluationWindow,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
case CreateAlertSlice.NOTIFICATION_SETTINGS:
|
||||
return {
|
||||
...state,
|
||||
notificationSettings: notificationSettingsReducer(
|
||||
state.notificationSettings,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
AlertThresholdMatchType,
|
||||
AlertThresholdOperator,
|
||||
AlertThresholdState,
|
||||
CreateAlertState,
|
||||
EvaluationWindowState,
|
||||
NotificationSettingsState,
|
||||
} from './context/types';
|
||||
import { EVALUATION_WINDOW_TIMEFRAME } from './EvaluationSettings/constants';
|
||||
import { GetCreateAlertLocalStateFromAlertDefReturn } from './types';
|
||||
|
||||
export function Spinner(): JSX.Element | null {
|
||||
const { isCreatingAlertRule, isUpdatingAlertRule } = useCreateAlertState();
|
||||
@@ -265,14 +265,14 @@ export function getThresholdStateFromAlertDef(
|
||||
|
||||
export function getCreateAlertLocalStateFromAlertDef(
|
||||
alertDef: PostableAlertRuleV2 | undefined,
|
||||
): GetCreateAlertLocalStateFromAlertDefReturn {
|
||||
): CreateAlertState {
|
||||
if (!alertDef) {
|
||||
return {
|
||||
basicAlertState: INITIAL_ALERT_STATE,
|
||||
thresholdState: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptionsState: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindowState: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettingsState: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
basic: INITIAL_ALERT_STATE,
|
||||
threshold: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettings: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
};
|
||||
}
|
||||
// Basic alert state
|
||||
@@ -294,10 +294,10 @@ export function getCreateAlertLocalStateFromAlertDef(
|
||||
);
|
||||
|
||||
return {
|
||||
basicAlertState,
|
||||
thresholdState,
|
||||
advancedOptionsState,
|
||||
evaluationWindowState,
|
||||
notificationSettingsState,
|
||||
basic: basicAlertState,
|
||||
threshold: thresholdState,
|
||||
advancedOptions: advancedOptionsState,
|
||||
evaluationWindow: evaluationWindowState,
|
||||
notificationSettings: notificationSettingsState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { PenLine, Trash2 } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { TVariableMode } from './types';
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Row } from 'antd';
|
||||
import { ALL_SELECTED_VALUE } from 'components/NewSelect/utils';
|
||||
import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
|
||||
import {
|
||||
useDashboardVariables,
|
||||
useDashboardVariablesSelector,
|
||||
} from 'hooks/dashboard/useDashboardVariables';
|
||||
import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
@@ -12,13 +15,7 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import DynamicVariableSelection from './DynamicVariableSelection';
|
||||
import {
|
||||
buildDependencies,
|
||||
buildDependencyGraph,
|
||||
buildParentDependencyGraph,
|
||||
IDependencyData,
|
||||
onUpdateVariableNode,
|
||||
} from './util';
|
||||
import { onUpdateVariableNode } from './util';
|
||||
import VariableItem from './VariableItem';
|
||||
|
||||
import './DashboardVariableSelection.styles.scss';
|
||||
@@ -35,11 +32,11 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
const { updateUrlVariable, getUrlVariables } = useVariablesFromUrl();
|
||||
|
||||
const { dashboardVariables } = useDashboardVariables();
|
||||
|
||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||
|
||||
const [dependencyData, setDependencyData] = useState<IDependencyData | null>(
|
||||
null,
|
||||
const sortedVariablesArray = useDashboardVariablesSelector(
|
||||
(state) => state.sortedVariablesArray,
|
||||
);
|
||||
const dependencyData = useDashboardVariablesSelector(
|
||||
(state) => state.dependencyData,
|
||||
);
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
@@ -47,24 +44,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const tableRowData = [];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [key, value] of Object.entries(dashboardVariables)) {
|
||||
const { id } = value;
|
||||
|
||||
tableRowData.push({
|
||||
key,
|
||||
name: key,
|
||||
...dashboardVariables[key],
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
tableRowData.sort((a, b) => a.order - b.order);
|
||||
|
||||
setVariablesTableData(tableRowData);
|
||||
|
||||
// Initialize variables with default values if not in URL
|
||||
initializeDefaultVariables(
|
||||
dashboardVariables,
|
||||
@@ -73,30 +52,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
);
|
||||
}, [getUrlVariables, updateUrlVariable, dashboardVariables]);
|
||||
|
||||
useEffect(() => {
|
||||
if (variablesTableData.length > 0) {
|
||||
const depGrp = buildDependencies(variablesTableData);
|
||||
const { order, graph, hasCycle, cycleNodes } = buildDependencyGraph(depGrp);
|
||||
const parentDependencyGraph = buildParentDependencyGraph(graph);
|
||||
|
||||
// cleanup order to only include variables that are of type 'QUERY'
|
||||
const cleanedOrder = order.filter((variable) => {
|
||||
const variableData = variablesTableData.find(
|
||||
(v: IDashboardVariable) => v.name === variable,
|
||||
);
|
||||
return variableData?.type === 'QUERY';
|
||||
});
|
||||
|
||||
setDependencyData({
|
||||
order: cleanedOrder,
|
||||
graph,
|
||||
parentDependencyGraph,
|
||||
hasCycle,
|
||||
cycleNodes,
|
||||
});
|
||||
}
|
||||
}, [dashboardVariables, variablesTableData]);
|
||||
|
||||
// this handles the case where the dependency order changes i.e. variable list updated via creation or deletion etc. and we need to refetch the variables
|
||||
// also trigger when the global time changes
|
||||
useEffect(
|
||||
@@ -186,45 +141,30 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
}
|
||||
};
|
||||
|
||||
if (!dashboardVariables) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const orderBasedSortedVariables = variablesTableData.sort(
|
||||
(a: { order: number }, b: { order: number }) => a.order - b.order,
|
||||
);
|
||||
|
||||
return (
|
||||
<Row style={{ display: 'flex', gap: '12px' }}>
|
||||
{orderBasedSortedVariables &&
|
||||
Array.isArray(orderBasedSortedVariables) &&
|
||||
orderBasedSortedVariables.length > 0 &&
|
||||
orderBasedSortedVariables.map((variable) =>
|
||||
variable.type === 'DYNAMIC' ? (
|
||||
<DynamicVariableSelection
|
||||
key={`${variable.name}${variable.id}${variable.order}`}
|
||||
existingVariables={dashboardVariables}
|
||||
variableData={{
|
||||
name: variable.name,
|
||||
...variable,
|
||||
}}
|
||||
onValueUpdate={onValueUpdate}
|
||||
/>
|
||||
) : (
|
||||
<VariableItem
|
||||
key={`${variable.name}${variable.id}}${variable.order}`}
|
||||
existingVariables={dashboardVariables}
|
||||
variableData={{
|
||||
name: variable.name,
|
||||
...variable,
|
||||
}}
|
||||
onValueUpdate={onValueUpdate}
|
||||
variablesToGetUpdated={variablesToGetUpdated}
|
||||
setVariablesToGetUpdated={setVariablesToGetUpdated}
|
||||
dependencyData={dependencyData}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{sortedVariablesArray.map((variable) => {
|
||||
const key = `${variable.name}${variable.id}${variable.order}`;
|
||||
|
||||
return variable.type === 'DYNAMIC' ? (
|
||||
<DynamicVariableSelection
|
||||
key={key}
|
||||
existingVariables={dashboardVariables}
|
||||
variableData={variable}
|
||||
onValueUpdate={onValueUpdate}
|
||||
/>
|
||||
) : (
|
||||
<VariableItem
|
||||
key={key}
|
||||
existingVariables={dashboardVariables}
|
||||
variableData={variable}
|
||||
onValueUpdate={onValueUpdate}
|
||||
variablesToGetUpdated={variablesToGetUpdated}
|
||||
setVariablesToGetUpdated={setVariablesToGetUpdated}
|
||||
dependencyData={dependencyData}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
|
||||
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
|
||||
import { debounce, isArray, isEmpty, isString } from 'lodash-es';
|
||||
import { IDependencyData } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { VariableResponseProps } from 'types/api/dashboard/variables/query';
|
||||
@@ -24,7 +25,7 @@ import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { ALL_SELECT_VALUE, variablePropsToPayloadVariables } from '../utils';
|
||||
import { SelectItemStyle } from './styles';
|
||||
import { areArraysEqual, checkAPIInvocation, IDependencyData } from './util';
|
||||
import { areArraysEqual, checkAPIInvocation } from './util';
|
||||
|
||||
import './DashboardVariableSelection.styles.scss';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
||||
import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { OptionData } from 'components/NewSelect/types';
|
||||
import { isEmpty, isNull } from 'lodash-es';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import {
|
||||
IDashboardVariables,
|
||||
IDependencyData,
|
||||
} from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
export function areArraysEqual(
|
||||
@@ -97,14 +100,6 @@ export const buildDependencies = (
|
||||
return graph;
|
||||
};
|
||||
|
||||
export interface IDependencyData {
|
||||
order: string[];
|
||||
graph: VariableGraph;
|
||||
parentDependencyGraph: VariableGraph;
|
||||
hasCycle: boolean;
|
||||
cycleNodes?: string[];
|
||||
}
|
||||
|
||||
export const buildParentDependencyGraph = (
|
||||
graph: VariableGraph,
|
||||
): VariableGraph => {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import ChartLayout from 'container/DashboardContainer/visualization/layout/ChartLayout/ChartLayout';
|
||||
import Legend from 'lib/uPlotV2/components/Legend/Legend';
|
||||
import Tooltip from 'lib/uPlotV2/components/Tooltip/Tooltip';
|
||||
import {
|
||||
LegendPosition,
|
||||
TooltipRenderArgs,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import UPlotChart from 'lib/uPlotV2/components/UPlotChart';
|
||||
import { PlotContextProvider } from 'lib/uPlotV2/context/PlotContext';
|
||||
import TooltipPlugin from 'lib/uPlotV2/plugins/TooltipPlugin/TooltipPlugin';
|
||||
import _noop from 'lodash-es/noop';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { ChartProps } from '../types';
|
||||
|
||||
export default function TimeSeries({
|
||||
legendConfig = { position: LegendPosition.BOTTOM },
|
||||
config,
|
||||
data,
|
||||
width: containerWidth,
|
||||
height: containerHeight,
|
||||
disableTooltip = false,
|
||||
canPinTooltip = false,
|
||||
timezone,
|
||||
yAxisUnit,
|
||||
decimalPrecision,
|
||||
syncMode,
|
||||
syncKey,
|
||||
onDestroy = _noop,
|
||||
children,
|
||||
layoutChildren,
|
||||
'data-testid': testId,
|
||||
}: ChartProps): JSX.Element {
|
||||
const plotInstanceRef = useRef<uPlot | null>(null);
|
||||
|
||||
const legendComponent = useCallback(
|
||||
(averageLegendWidth: number): React.ReactNode => {
|
||||
return (
|
||||
<Legend
|
||||
config={config}
|
||||
position={legendConfig.position}
|
||||
averageLegendWidth={averageLegendWidth}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[config, legendConfig.position],
|
||||
);
|
||||
|
||||
return (
|
||||
<PlotContextProvider>
|
||||
<ChartLayout
|
||||
config={config}
|
||||
containerWidth={containerWidth}
|
||||
containerHeight={containerHeight}
|
||||
legendConfig={legendConfig}
|
||||
legendComponent={legendComponent}
|
||||
layoutChildren={layoutChildren}
|
||||
>
|
||||
{({ chartWidth, chartHeight }): JSX.Element => (
|
||||
<UPlotChart
|
||||
config={config}
|
||||
data={data}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
plotRef={(plot): void => {
|
||||
plotInstanceRef.current = plot;
|
||||
}}
|
||||
onDestroy={(plot: uPlot): void => {
|
||||
plotInstanceRef.current = null;
|
||||
onDestroy(plot);
|
||||
}}
|
||||
data-testid={testId}
|
||||
>
|
||||
{children}
|
||||
{!disableTooltip && (
|
||||
<TooltipPlugin
|
||||
config={config}
|
||||
canPinTooltip={canPinTooltip}
|
||||
syncMode={syncMode}
|
||||
syncKey={syncKey}
|
||||
render={(props: TooltipRenderArgs): React.ReactNode => (
|
||||
<Tooltip
|
||||
{...props}
|
||||
timezone={timezone}
|
||||
yAxisUnit={yAxisUnit}
|
||||
decimalPrecision={decimalPrecision}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</UPlotChart>
|
||||
)}
|
||||
</ChartLayout>
|
||||
</PlotContextProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { LegendConfig } from 'lib/uPlotV2/components/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
|
||||
interface BaseChartProps {
|
||||
width: number;
|
||||
height: number;
|
||||
disableTooltip?: boolean;
|
||||
timezone: string;
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
canPinTooltip?: boolean;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
}
|
||||
|
||||
interface TimeSeriesChartProps extends BaseChartProps {
|
||||
config: UPlotConfigBuilder;
|
||||
legendConfig: LegendConfig;
|
||||
data: uPlot.AlignedData;
|
||||
plotRef?: (plot: uPlot | null) => void;
|
||||
onDestroy?: (plot: uPlot) => void;
|
||||
children?: React.ReactNode;
|
||||
layoutChildren?: React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
export type ChartProps = TimeSeriesChartProps;
|
||||
@@ -5,25 +5,32 @@ export interface ChartDimensions {
|
||||
height: number;
|
||||
legendWidth: number;
|
||||
legendHeight: number;
|
||||
legendsPerSet: number;
|
||||
averageLegendWidth: number;
|
||||
}
|
||||
|
||||
const AVG_CHAR_WIDTH = 8;
|
||||
const LEGEND_WIDTH_PERCENTILE = 0.85;
|
||||
const DEFAULT_AVG_LABEL_LENGTH = 15;
|
||||
const LEGEND_GAP = 16;
|
||||
const BASE_LEGEND_WIDTH = 16;
|
||||
const LEGEND_PADDING = 12;
|
||||
const LEGEND_LINE_HEIGHT = 36;
|
||||
const LEGEND_LINE_HEIGHT = 28;
|
||||
|
||||
/**
|
||||
* Average text width from series labels (for legendsPerSet).
|
||||
* Calculates the average width of the legend items based on the labels of the series.
|
||||
* @param legends - The labels of the series.
|
||||
* @returns The average width of the legend items.
|
||||
*/
|
||||
export function calculateAverageLegendWidth(legends: string[]): number {
|
||||
if (legends.length === 0) {
|
||||
return DEFAULT_AVG_LABEL_LENGTH;
|
||||
return DEFAULT_AVG_LABEL_LENGTH * AVG_CHAR_WIDTH;
|
||||
}
|
||||
const averageLabelLength =
|
||||
legends.reduce((sum, l) => sum + l.length, 0) / legends.length;
|
||||
return averageLabelLength * AVG_CHAR_WIDTH;
|
||||
|
||||
const lengths = legends.map((l) => l.length).sort((a, b) => a - b);
|
||||
|
||||
const index = Math.ceil(LEGEND_WIDTH_PERCENTILE * lengths.length) - 1;
|
||||
const percentileLength = lengths[Math.max(0, index)];
|
||||
|
||||
return BASE_LEGEND_WIDTH + percentileLength * AVG_CHAR_WIDTH;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +71,7 @@ export function calculateChartDimensions({
|
||||
height: 0,
|
||||
legendWidth: 0,
|
||||
legendHeight: 0,
|
||||
legendsPerSet: 0,
|
||||
averageLegendWidth: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,13 +92,15 @@ export function calculateChartDimensions({
|
||||
legendWidth: rightLegendWidth,
|
||||
legendHeight: containerHeight,
|
||||
// Single vertical list on the right.
|
||||
legendsPerSet: 1,
|
||||
averageLegendWidth: rightLegendWidth,
|
||||
};
|
||||
}
|
||||
|
||||
const legendRowHeight = LEGEND_LINE_HEIGHT + LEGEND_PADDING;
|
||||
|
||||
const legendItemWidth = Math.min(approxLegendItemWidth, 400);
|
||||
const legendItemWidth = Math.ceil(
|
||||
Math.min(approxLegendItemWidth, MAX_LEGEND_WIDTH),
|
||||
);
|
||||
const legendItemsPerRow = Math.max(
|
||||
1,
|
||||
Math.floor((containerWidth - LEGEND_PADDING * 2) / legendItemWidth),
|
||||
@@ -114,17 +123,11 @@ export function calculateChartDimensions({
|
||||
maxAllowedLegendHeight,
|
||||
);
|
||||
|
||||
// How many legend items per row in the Legend component.
|
||||
const legendsPerSet = Math.ceil(
|
||||
(containerWidth + LEGEND_GAP) /
|
||||
(Math.min(MAX_LEGEND_WIDTH, approxLegendItemWidth) + LEGEND_GAP),
|
||||
);
|
||||
|
||||
return {
|
||||
width: containerWidth,
|
||||
height: Math.max(0, containerHeight - bottomLegendHeight),
|
||||
legendWidth: containerWidth,
|
||||
legendHeight: bottomLegendHeight,
|
||||
legendsPerSet,
|
||||
averageLegendWidth: legendItemWidth,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
.chart-layout__legend-wrapper {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function ChartLayout({
|
||||
width: chartDimensions.legendWidth,
|
||||
}}
|
||||
>
|
||||
{legendComponent(chartDimensions.legendsPerSet)}
|
||||
{legendComponent(chartDimensions.averageLegendWidth)}
|
||||
</div>
|
||||
</div>
|
||||
{layoutChildren}
|
||||
|
||||
@@ -13,3 +13,16 @@ export interface GraphVisibilityState {
|
||||
name: string;
|
||||
dataIndex: SeriesVisibilityItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Context in which a panel is rendered. Used to vary behavior (e.g. persistence,
|
||||
* interactions) per context.
|
||||
*/
|
||||
export enum PanelMode {
|
||||
/** Panel opened in full-screen / standalone view (e.g. from a dashboard widget). */
|
||||
STANDALONE_VIEW = 'STANDALONE_VIEW',
|
||||
/** Panel in the widget builder while editing a dashboard. */
|
||||
DASHBOARD_EDIT = 'DASHBOARD_EDIT',
|
||||
/** Panel rendered as a widget on a dashboard (read-only view). */
|
||||
DASHBOARD_VIEW = 'DASHBOARD_VIEW',
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { MutableRefObject } from 'react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
@@ -156,6 +157,7 @@ describe('PanelWrapper with DragSelect', () => {
|
||||
|
||||
render(
|
||||
<PanelWrapper
|
||||
panelMode={PanelMode.STANDALONE_VIEW}
|
||||
widget={mockWidget}
|
||||
queryResponse={mockQueryResponse}
|
||||
onDragSelect={mockOnDragSelect}
|
||||
|
||||
@@ -17,6 +17,7 @@ import WarningPopover from 'components/WarningPopover/WarningPopover';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import useDrilldown from 'container/GridCardLayout/GridCard/FullView/useDrilldown';
|
||||
import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util';
|
||||
import {
|
||||
@@ -366,6 +367,7 @@ function FullView({
|
||||
/>
|
||||
)}
|
||||
<PanelWrapper
|
||||
panelMode={PanelMode.STANDALONE_VIEW}
|
||||
queryResponse={response}
|
||||
widget={widget}
|
||||
setRequestData={setRequestData}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplor
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import useGetResolvedText from 'hooks/dashboard/useGetResolvedText';
|
||||
@@ -407,6 +408,7 @@ function WidgetGraphComponent({
|
||||
ref={graphRef}
|
||||
>
|
||||
<PanelWrapper
|
||||
panelMode={PanelMode.DASHBOARD_VIEW}
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplor
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
|
||||
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
|
||||
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
|
||||
@@ -183,6 +184,7 @@ function WidgetGraph({
|
||||
}}
|
||||
>
|
||||
<PanelWrapper
|
||||
panelMode={PanelMode.DASHBOARD_EDIT}
|
||||
widget={selectedWidget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
|
||||
@@ -21,6 +21,7 @@ function PanelWrapper({
|
||||
onOpenTraceBtnClick,
|
||||
customSeries,
|
||||
customOnRowClick,
|
||||
panelMode,
|
||||
enableDrillDown = false,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const Component = PanelTypeVsPanelWrapper[
|
||||
@@ -33,6 +34,7 @@ function PanelWrapper({
|
||||
}
|
||||
return (
|
||||
<Component
|
||||
panelMode={panelMode}
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
@@ -11,6 +12,7 @@ describe('Table panel wrappper tests', () => {
|
||||
it('table should render fine with the query response and column units', () => {
|
||||
const { container, getByText } = render(
|
||||
<TablePanelWrapper
|
||||
panelMode={PanelMode.DASHBOARD_VIEW}
|
||||
widget={(tablePanelWidgetQuery as unknown) as Widgets}
|
||||
queryResponse={(tablePanelQueryResponse as unknown) as any}
|
||||
onDragSelect={(): void => {}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
@@ -20,6 +21,7 @@ describe('Value panel wrappper tests', () => {
|
||||
it('should render value panel correctly with yaxis unit', () => {
|
||||
const { getByText } = render(
|
||||
<ValuePanelWrapper
|
||||
panelMode={PanelMode.DASHBOARD_VIEW}
|
||||
widget={(valuePanelWidget as unknown) as Widgets}
|
||||
queryResponse={(valuePanelQueryResponse as unknown) as any}
|
||||
onDragSelect={(): void => {}}
|
||||
@@ -34,6 +36,7 @@ describe('Value panel wrappper tests', () => {
|
||||
it('should render tooltip when there are conflicting thresholds', () => {
|
||||
const { getByTestId, container } = render(
|
||||
<ValuePanelWrapper
|
||||
panelMode={PanelMode.DASHBOARD_VIEW}
|
||||
widget={({ ...valuePanelWidget, thresholds } as unknown) as Widgets}
|
||||
queryResponse={(valuePanelQueryResponse as unknown) as any}
|
||||
onDragSelect={(): void => {}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { PanelMode } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { WidgetGraphComponentProps } from 'container/GridCardLayout/GridCard/types';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
@@ -31,6 +32,7 @@ export type PanelWrapperProps = {
|
||||
customOnRowClick?: (record: RowData) => void;
|
||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||
enableDrillDown?: boolean;
|
||||
panelMode: PanelMode;
|
||||
};
|
||||
|
||||
export type TooltipData = {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
pointer-events: none;
|
||||
// line-height: 18px;
|
||||
|
||||
color: var(--bg-sakura-400) !important;
|
||||
|
||||
@@ -96,6 +96,7 @@ export default function QueryFunctions({
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const hasAnomalyFunction = functions.some((func) => func.name === 'anomaly');
|
||||
const hasFunctions = functions.length > 0;
|
||||
|
||||
const handleAddNewFunction = (): void => {
|
||||
const defaultFunctionStruct =
|
||||
@@ -180,10 +181,14 @@ export default function QueryFunctions({
|
||||
<div
|
||||
className={cx(
|
||||
'query-functions-container',
|
||||
functions && functions.length > 0 ? 'hasFunctions' : '',
|
||||
hasFunctions ? 'hasFunctions' : '',
|
||||
)}
|
||||
>
|
||||
<Button className="periscope-btn function-btn">
|
||||
<Button
|
||||
className="periscope-btn function-btn"
|
||||
disabled={hasFunctions}
|
||||
onClick={handleAddNewFunction}
|
||||
>
|
||||
<FunctionIcon
|
||||
className="function-icon"
|
||||
fillColor={!isDarkMode ? '#0B0C0E' : 'white'}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
initialQueriesMap,
|
||||
initialQueryBuilderFormValues,
|
||||
} from 'constants/queryBuilder';
|
||||
import { IUseDashboardVariablesReturn } from 'hooks/dashboard/useDashboardVariables';
|
||||
import { IUseDashboardVariablesReturn } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
|
||||
import useGetResolvedText from '../useGetResolvedText';
|
||||
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
import { useCallback, useRef, useSyncExternalStore } from 'react';
|
||||
import { dashboardVariablesStore } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore';
|
||||
import {
|
||||
dashboardVariablesStore,
|
||||
IDashboardVariables,
|
||||
} from '../../providers/Dashboard/store/dashboardVariablesStore';
|
||||
IDashboardVariablesStoreState,
|
||||
IUseDashboardVariablesReturn,
|
||||
} from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
|
||||
export interface IUseDashboardVariablesReturn {
|
||||
dashboardVariables: IDashboardVariables;
|
||||
}
|
||||
/**
|
||||
* Generic selector hook for dashboard variables store
|
||||
* Allows granular subscriptions to any part of the store state
|
||||
*
|
||||
* @example
|
||||
* ! Select top-level field
|
||||
* const variables = useDashboardVariablesSelector(s => s.variables);
|
||||
*
|
||||
* ! Select specific variable
|
||||
* const fooVar = useDashboardVariablesSelector(s => s.variables['foo']);
|
||||
*
|
||||
* ! Select derived value
|
||||
* const hasVariables = useDashboardVariablesSelector(s => Object.keys(s.variables).length > 0);
|
||||
*/
|
||||
export const useDashboardVariablesSelector = <T>(
|
||||
selector: (state: IDashboardVariablesStoreState) => T,
|
||||
): T => {
|
||||
const selectorRef = useRef(selector);
|
||||
selectorRef.current = selector;
|
||||
|
||||
export const useDashboardVariables = (): IUseDashboardVariablesReturn => {
|
||||
const dashboardVariables = useSyncExternalStore(
|
||||
dashboardVariablesStore.subscribe,
|
||||
dashboardVariablesStore.getSnapshot,
|
||||
const getSnapshot = useCallback(
|
||||
() => selectorRef.current(dashboardVariablesStore.getSnapshot()),
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
dashboardVariables,
|
||||
};
|
||||
return useSyncExternalStore(dashboardVariablesStore.subscribe, getSnapshot);
|
||||
};
|
||||
|
||||
export const useDashboardVariables = (): IUseDashboardVariablesReturn => {
|
||||
const dashboardVariables = useDashboardVariablesSelector((s) => s.variables);
|
||||
|
||||
return { dashboardVariables };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
|
||||
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import store from 'store';
|
||||
|
||||
export const getDashboardVariables = (
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
.legend-search-container {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
|
||||
.legend-search-input {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -17,6 +28,38 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.virtuoso-grid-list {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-template-columns: repeat(
|
||||
auto-fill,
|
||||
minmax(var(--legend-average-width, 240px), 1fr)
|
||||
);
|
||||
row-gap: 4px;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
.virtuoso-grid-item {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.legend-virtuoso-container-right {
|
||||
.virtuoso-grid-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
&.legend-virtuoso-container-single-row {
|
||||
.virtuoso-grid-list {
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--legend-average-width, 240px), max-content)
|
||||
);
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
}
|
||||
@@ -58,9 +101,15 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&.legend-item-right {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.legend-item-off {
|
||||
opacity: 0.3;
|
||||
text-decoration: line-through;
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { Tooltip as AntdTooltip } from 'antd';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { VirtuosoGrid } from 'react-virtuoso';
|
||||
import { Input, Tooltip as AntdTooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { LegendItem } from 'lib/uPlotV2/config/types';
|
||||
import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync';
|
||||
import { LegendPosition } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { LegendProps } from '../types';
|
||||
import { LegendPosition, LegendProps } from '../types';
|
||||
import { useLegendActions } from './useLegendActions';
|
||||
|
||||
import './Legend.styles.scss';
|
||||
|
||||
export const MAX_LEGEND_WIDTH = 320;
|
||||
const LEGENDS_PER_SET_DEFAULT = 5;
|
||||
export const MAX_LEGEND_WIDTH = 240;
|
||||
|
||||
export default function Legend({
|
||||
position = LegendPosition.BOTTOM,
|
||||
config,
|
||||
legendsPerSet = LEGENDS_PER_SET_DEFAULT,
|
||||
averageLegendWidth = MAX_LEGEND_WIDTH,
|
||||
}: LegendProps): JSX.Element {
|
||||
const {
|
||||
legendItemsMap,
|
||||
@@ -33,54 +31,54 @@ export default function Legend({
|
||||
focusedSeriesIndex,
|
||||
});
|
||||
const legendContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [legendSearchQuery, setLegendSearchQuery] = useState('');
|
||||
|
||||
// Chunk legend items into rows of LEGENDS_PER_ROW items each
|
||||
const legendRows = useMemo(() => {
|
||||
const legendItems = Object.values(legendItemsMap);
|
||||
const legendItems = useMemo(() => Object.values(legendItemsMap), [
|
||||
legendItemsMap,
|
||||
]);
|
||||
|
||||
return legendItems.reduce((acc: LegendItem[][], curr, i) => {
|
||||
if (i % legendsPerSet === 0) {
|
||||
acc.push([]);
|
||||
}
|
||||
acc[acc.length - 1].push(curr);
|
||||
return acc;
|
||||
}, [] as LegendItem[][]);
|
||||
}, [legendItemsMap, legendsPerSet]);
|
||||
const isSingleRow = useMemo(() => {
|
||||
if (!legendContainerRef.current || position !== LegendPosition.BOTTOM) {
|
||||
return false;
|
||||
}
|
||||
const containerWidth = legendContainerRef.current.clientWidth;
|
||||
|
||||
const renderLegendRow = useCallback(
|
||||
(rowIndex: number, row: LegendItem[]): JSX.Element => (
|
||||
<div
|
||||
key={rowIndex}
|
||||
className={cx(
|
||||
'legend-row',
|
||||
`legend-row-${position.toLowerCase()}`,
|
||||
legendRows.length === 1 && position === LegendPosition.BOTTOM
|
||||
? 'legend-single-row'
|
||||
: '',
|
||||
)}
|
||||
>
|
||||
{row.map((item) => (
|
||||
<AntdTooltip key={item.seriesIndex} title={item.label}>
|
||||
<div
|
||||
data-legend-item-id={item.seriesIndex}
|
||||
className={cx('legend-item', {
|
||||
'legend-item-off': !item.show,
|
||||
'legend-item-focused': focusedSeriesIndex === item.seriesIndex,
|
||||
})}
|
||||
style={{ maxWidth: `min(${MAX_LEGEND_WIDTH}px, 100%)` }}
|
||||
>
|
||||
<div
|
||||
className="legend-marker"
|
||||
style={{ borderColor: String(item.color) }}
|
||||
data-is-legend-marker={true}
|
||||
/>
|
||||
<span className="legend-label">{item.label}</span>
|
||||
</div>
|
||||
</AntdTooltip>
|
||||
))}
|
||||
</div>
|
||||
const totalLegendWidth = legendItems.length * (averageLegendWidth + 16);
|
||||
const totalRows = Math.ceil(totalLegendWidth / containerWidth);
|
||||
return totalRows <= 1;
|
||||
}, [averageLegendWidth, legendContainerRef, legendItems.length, position]);
|
||||
|
||||
const visibleLegendItems = useMemo(() => {
|
||||
if (position !== LegendPosition.RIGHT || !legendSearchQuery.trim()) {
|
||||
return legendItems;
|
||||
}
|
||||
|
||||
const query = legendSearchQuery.trim().toLowerCase();
|
||||
return legendItems.filter((item) =>
|
||||
item.label?.toLowerCase().includes(query),
|
||||
);
|
||||
}, [position, legendSearchQuery, legendItems]);
|
||||
|
||||
const renderLegendItem = useCallback(
|
||||
(item: LegendItem): JSX.Element => (
|
||||
<AntdTooltip key={item.seriesIndex} title={item.label}>
|
||||
<div
|
||||
data-legend-item-id={item.seriesIndex}
|
||||
className={cx('legend-item', `legend-item-${position.toLowerCase()}`, {
|
||||
'legend-item-off': !item.show,
|
||||
'legend-item-focused': focusedSeriesIndex === item.seriesIndex,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="legend-marker"
|
||||
style={{ borderColor: String(item.color) }}
|
||||
data-is-legend-marker={true}
|
||||
/>
|
||||
<span className="legend-label">{item.label}</span>
|
||||
</div>
|
||||
</AntdTooltip>
|
||||
),
|
||||
[focusedSeriesIndex, position, legendRows],
|
||||
[focusedSeriesIndex, position],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -90,11 +88,29 @@ export default function Legend({
|
||||
onClick={onLegendClick}
|
||||
onMouseMove={onLegendMouseMove}
|
||||
onMouseLeave={onLegendMouseLeave}
|
||||
style={{
|
||||
['--legend-average-width' as string]: `${averageLegendWidth + 16}px`, // 16px is the marker width
|
||||
}}
|
||||
>
|
||||
<Virtuoso
|
||||
className="legend-virtuoso-container"
|
||||
data={legendRows}
|
||||
itemContent={(index, row): JSX.Element => renderLegendRow(index, row)}
|
||||
{position === LegendPosition.RIGHT && (
|
||||
<div className="legend-search-container">
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="Search..."
|
||||
value={legendSearchQuery}
|
||||
onChange={(e): void => setLegendSearchQuery(e.target.value)}
|
||||
className="legend-search-input"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<VirtuosoGrid
|
||||
className={cx(
|
||||
'legend-virtuoso-container',
|
||||
`legend-virtuoso-container-${position.toLowerCase()}`,
|
||||
{ 'legend-virtuoso-container-single-row': isSingleRow },
|
||||
)}
|
||||
data={visibleLegendItems}
|
||||
itemContent={(_, item): JSX.Element => renderLegendItem(item)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { LineChart } from 'lucide-react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import uPlot, { AlignedData, Options } from 'uplot';
|
||||
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
import { usePlotContext } from '../context/PlotContext';
|
||||
import { UPlotChartProps } from './types';
|
||||
|
||||
@@ -49,7 +48,6 @@ export default function UPlotChart({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const plotInstanceRef = useRef<uPlot | null>(null);
|
||||
const prevPropsRef = useRef<UPlotChartProps | null>(null);
|
||||
const configUsedForPlotRef = useRef<UPlotConfigBuilder | null>(null);
|
||||
|
||||
/**
|
||||
* Destroy the existing plot instance if present.
|
||||
@@ -57,12 +55,6 @@ export default function UPlotChart({
|
||||
const destroyPlot = useCallback((): void => {
|
||||
if (plotInstanceRef.current) {
|
||||
onDestroy?.(plotInstanceRef.current);
|
||||
// Clean up the config builder that was used to create this plot (not the current prop)
|
||||
if (configUsedForPlotRef.current) {
|
||||
configUsedForPlotRef.current.destroy();
|
||||
}
|
||||
configUsedForPlotRef.current = null;
|
||||
|
||||
plotInstanceRef.current.destroy();
|
||||
plotInstanceRef.current = null;
|
||||
setPlotContextInitialState({ uPlotInstance: null });
|
||||
@@ -97,13 +89,14 @@ export default function UPlotChart({
|
||||
if (plotRef) {
|
||||
plotRef(plot);
|
||||
}
|
||||
|
||||
setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: config.getWidgetId(),
|
||||
shouldSaveSelectionPreference: config.getShouldSaveSelectionPreference(),
|
||||
});
|
||||
|
||||
plotInstanceRef.current = plot;
|
||||
configUsedForPlotRef.current = config;
|
||||
}, [
|
||||
config,
|
||||
data,
|
||||
@@ -143,11 +136,13 @@ export default function UPlotChart({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the plot instance's container has been unmounted (e.g., after "No Data" state)
|
||||
// If so, we need to recreate the plot with the new container
|
||||
// Check if the plot instance's root is no longer attached to our container
|
||||
// (e.g., after React has re‑mounted the container div). In uPlot, `root`
|
||||
// is a child of the container, so we must compare against its parent node.
|
||||
const isPlotOrphaned =
|
||||
plotInstanceRef.current &&
|
||||
plotInstanceRef.current.root !== containerRef.current;
|
||||
!!plotInstanceRef.current &&
|
||||
(!containerRef.current ||
|
||||
plotInstanceRef.current.root.parentElement !== containerRef.current);
|
||||
|
||||
// Update dimensions without reinitializing if only size changed
|
||||
if (
|
||||
|
||||
@@ -75,7 +75,7 @@ export interface LegendConfig {
|
||||
export interface LegendProps {
|
||||
position?: LegendPosition;
|
||||
config: UPlotConfigBuilder;
|
||||
legendsPerSet?: number;
|
||||
averageLegendWidth?: number;
|
||||
}
|
||||
|
||||
export interface TooltipContentItem {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getStoredSeriesVisibility } from 'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils';
|
||||
import { ThresholdsDrawHookOptions } from 'lib/uPlotV2/hooks/types';
|
||||
import { thresholdsDrawHook } from 'lib/uPlotV2/hooks/useThresholdsDrawHook';
|
||||
import { resolveSeriesVisibility } from 'lib/uPlotV2/utils/seriesVisibility';
|
||||
import { merge } from 'lodash-es';
|
||||
import noop from 'lodash-es/noop';
|
||||
import uPlot, { Cursor, Hooks, Options } from 'uplot';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
DEFAULT_CURSOR_CONFIG,
|
||||
DEFAULT_PLOT_CONFIG,
|
||||
LegendItem,
|
||||
SelectionPreferencesSource,
|
||||
} from './types';
|
||||
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
||||
import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
|
||||
@@ -36,6 +38,10 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
> {
|
||||
series: UPlotSeriesBuilder[] = [];
|
||||
|
||||
private selectionPreferencesSource: SelectionPreferencesSource =
|
||||
SelectionPreferencesSource.IN_MEMORY;
|
||||
private shouldSaveSelectionPreference = false;
|
||||
|
||||
private axes: Record<string, UPlotAxisBuilder> = {};
|
||||
|
||||
readonly scales: UPlotScaleBuilder[] = [];
|
||||
@@ -64,11 +70,15 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
|
||||
private onDragSelect: (startTime: number, endTime: number) => void;
|
||||
|
||||
private cleanups: Array<() => void> = [];
|
||||
|
||||
constructor(args?: ConfigBuilderProps) {
|
||||
super(args ?? {});
|
||||
const { widgetId, onDragSelect, tzDate } = args ?? {};
|
||||
const {
|
||||
widgetId,
|
||||
onDragSelect,
|
||||
tzDate,
|
||||
selectionPreferencesSource,
|
||||
shouldSaveSelectionPreference,
|
||||
} = args ?? {};
|
||||
if (widgetId) {
|
||||
this.widgetId = widgetId;
|
||||
}
|
||||
@@ -77,25 +87,38 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
this.tzDate = tzDate;
|
||||
}
|
||||
|
||||
this.onDragSelect = noop;
|
||||
if (selectionPreferencesSource) {
|
||||
this.selectionPreferencesSource = selectionPreferencesSource;
|
||||
}
|
||||
|
||||
if (shouldSaveSelectionPreference) {
|
||||
this.shouldSaveSelectionPreference = shouldSaveSelectionPreference;
|
||||
}
|
||||
|
||||
this.onDragSelect = noop;
|
||||
if (onDragSelect) {
|
||||
this.onDragSelect = onDragSelect;
|
||||
// Add a hook to handle the select event
|
||||
const cleanup = this.addHook('setSelect', (self: uPlot): void => {
|
||||
this.addHook('setSelect', (self: uPlot): void => {
|
||||
const selection = self.select;
|
||||
// Only trigger onDragSelect when there's an actual drag range (width > 0)
|
||||
// A click without dragging produces width === 0, which should be ignored
|
||||
if (selection && selection.width > 0) {
|
||||
const startTime = self.posToVal(selection.left, 'x');
|
||||
const endTime = self.posToVal(selection.left + selection.width, 'x');
|
||||
|
||||
this.onDragSelect(startTime * 1000, endTime * 1000);
|
||||
}
|
||||
});
|
||||
this.cleanups.push(cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the save selection preferences
|
||||
*/
|
||||
getShouldSaveSelectionPreference(): boolean {
|
||||
return this.shouldSaveSelectionPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or merge an axis configuration
|
||||
*/
|
||||
@@ -158,8 +181,7 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
addThresholds(options: ThresholdsDrawHookOptions): void {
|
||||
if (!this.thresholds[options.scaleKey]) {
|
||||
this.thresholds[options.scaleKey] = options;
|
||||
const cleanup = this.addHook('draw', thresholdsDrawHook(options));
|
||||
this.cleanups.push(cleanup);
|
||||
this.addHook('draw', thresholdsDrawHook(options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,20 +234,39 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
this.tzDate = tzDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stored series visibility map from localStorage when preferences source is LOCAL_STORAGE, otherwise null.
|
||||
*/
|
||||
private getStoredVisibilityMap(): Map<string, boolean> | null {
|
||||
if (
|
||||
this.widgetId &&
|
||||
this.selectionPreferencesSource === SelectionPreferencesSource.LOCAL_STORAGE
|
||||
) {
|
||||
return getStoredSeriesVisibility(this.widgetId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get legend items with visibility state restored from localStorage if available
|
||||
*/
|
||||
getLegendItems(): Record<number, LegendItem> {
|
||||
const visibilityMap = this.widgetId
|
||||
? getStoredSeriesVisibility(this.widgetId)
|
||||
: null;
|
||||
const visibilityMap = this.getStoredVisibilityMap();
|
||||
const isAnySeriesHidden = !!(
|
||||
visibilityMap && Array.from(visibilityMap.values()).some((show) => !show)
|
||||
);
|
||||
|
||||
return this.series.reduce((acc, s: UPlotSeriesBuilder, index: number) => {
|
||||
const seriesConfig = s.getConfig();
|
||||
const label = seriesConfig.label ?? '';
|
||||
const seriesIndex = index + 1; // +1 because the first series is the timestamp
|
||||
|
||||
// Priority: stored visibility > series config > default (true)
|
||||
const show = visibilityMap?.get(label) ?? seriesConfig.show ?? true;
|
||||
const show = resolveSeriesVisibility(
|
||||
label,
|
||||
seriesConfig.show,
|
||||
visibilityMap,
|
||||
isAnySeriesHidden,
|
||||
);
|
||||
|
||||
acc[seriesIndex] = {
|
||||
seriesIndex,
|
||||
@@ -238,13 +279,6 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
}, {} as Record<number, LegendItem>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all hooks and cleanup functions
|
||||
*/
|
||||
destroy(): void {
|
||||
this.cleanups.forEach((cleanup) => cleanup());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the widget id
|
||||
*/
|
||||
@@ -260,9 +294,27 @@ export class UPlotConfigBuilder extends ConfigBuilder<
|
||||
...DEFAULT_PLOT_CONFIG,
|
||||
};
|
||||
|
||||
const visibilityMap = this.getStoredVisibilityMap();
|
||||
const isAnySeriesHidden = !!(
|
||||
visibilityMap && Array.from(visibilityMap.values()).some((show) => !show)
|
||||
);
|
||||
|
||||
config.series = [
|
||||
{ value: (): string => '' }, // Base series for timestamp
|
||||
...this.series.map((s) => s.getConfig()),
|
||||
...this.series.map((s) => {
|
||||
const series = s.getConfig();
|
||||
const label = series.label ?? '';
|
||||
const visible = resolveSeriesVisibility(
|
||||
label,
|
||||
series.show,
|
||||
visibilityMap,
|
||||
isAnySeriesHidden,
|
||||
);
|
||||
return {
|
||||
...series,
|
||||
show: visible,
|
||||
};
|
||||
}),
|
||||
];
|
||||
config.axes = Object.values(this.axes).map((a) => a.getConfig());
|
||||
config.scales = this.scales.reduce(
|
||||
|
||||
@@ -22,6 +22,14 @@ export abstract class ConfigBuilder<P, T> {
|
||||
merge?(props: Partial<P>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preferences source for the uPlot config builder
|
||||
*/
|
||||
export enum SelectionPreferencesSource {
|
||||
LOCAL_STORAGE = 'LOCAL_STORAGE',
|
||||
IN_MEMORY = 'IN_MEMORY',
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for configuring the uPlot config builder
|
||||
*/
|
||||
@@ -29,6 +37,8 @@ export interface ConfigBuilderProps {
|
||||
widgetId?: string;
|
||||
onDragSelect?: (startTime: number, endTime: number) => void;
|
||||
tzDate?: uPlot.LocalDateFromUnix;
|
||||
selectionPreferencesSource?: SelectionPreferencesSource;
|
||||
shouldSaveSelectionPreference?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,16 +9,17 @@ import {
|
||||
import type { SeriesVisibilityItem } from 'container/DashboardContainer/visualization/panels/types';
|
||||
import { updateSeriesVisibilityToLocalStorage } from 'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils';
|
||||
import type uPlot from 'uplot';
|
||||
|
||||
export interface PlotContextInitialState {
|
||||
uPlotInstance: uPlot | null;
|
||||
widgetId?: string;
|
||||
shouldSaveSelectionPreference?: boolean;
|
||||
}
|
||||
export interface IPlotContext {
|
||||
setPlotContextInitialState: (state: PlotContextInitialState) => void;
|
||||
onToggleSeriesVisibility: (seriesIndex: number) => void;
|
||||
onToggleSeriesOnOff: (seriesIndex: number) => void;
|
||||
onFocusSeries: (seriesIndex: number | null) => void;
|
||||
syncSeriesVisibilityToLocalStorage: () => void;
|
||||
}
|
||||
|
||||
export const PlotContext = createContext<IPlotContext | null>(null);
|
||||
@@ -29,68 +30,84 @@ export const PlotContextProvider = ({
|
||||
const uPlotInstanceRef = useRef<uPlot | null>(null);
|
||||
const activeSeriesIndex = useRef<number | undefined>(undefined);
|
||||
const widgetIdRef = useRef<string | undefined>(undefined);
|
||||
const shouldSavePreferencesRef = useRef<boolean>(false);
|
||||
|
||||
const setPlotContextInitialState = useCallback(
|
||||
({ uPlotInstance, widgetId }: PlotContextInitialState): void => {
|
||||
({
|
||||
uPlotInstance,
|
||||
widgetId,
|
||||
shouldSaveSelectionPreference,
|
||||
}: PlotContextInitialState): void => {
|
||||
uPlotInstanceRef.current = uPlotInstance;
|
||||
widgetIdRef.current = widgetId;
|
||||
activeSeriesIndex.current = undefined;
|
||||
shouldSavePreferencesRef.current = !!shouldSaveSelectionPreference;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onToggleSeriesVisibility = useCallback((seriesIndex: number): void => {
|
||||
const syncSeriesVisibilityToLocalStorage = useCallback((): void => {
|
||||
const plot = uPlotInstanceRef.current;
|
||||
if (!plot) {
|
||||
if (!plot || !widgetIdRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isReset = activeSeriesIndex.current === seriesIndex;
|
||||
activeSeriesIndex.current = isReset ? undefined : seriesIndex;
|
||||
const seriesVisibility: SeriesVisibilityItem[] = plot.series.map(
|
||||
(series) => ({
|
||||
label: series.label ?? '',
|
||||
show: series.show ?? true,
|
||||
}),
|
||||
);
|
||||
|
||||
plot.batch(() => {
|
||||
plot.series.forEach((_, index) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
const currentSeriesIndex = index;
|
||||
plot.setSeries(currentSeriesIndex, {
|
||||
show: isReset || currentSeriesIndex === seriesIndex,
|
||||
});
|
||||
});
|
||||
if (widgetIdRef.current) {
|
||||
const seriesVisibility: SeriesVisibilityItem[] = plot.series.map(
|
||||
(series) => ({
|
||||
label: series.label ?? '',
|
||||
show: series.show ?? true,
|
||||
}),
|
||||
);
|
||||
updateSeriesVisibilityToLocalStorage(widgetIdRef.current, seriesVisibility);
|
||||
updateSeriesVisibilityToLocalStorage(widgetIdRef.current, seriesVisibility);
|
||||
}, []);
|
||||
|
||||
const onToggleSeriesVisibility = useCallback(
|
||||
(seriesIndex: number): void => {
|
||||
const plot = uPlotInstanceRef.current;
|
||||
if (!plot) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onToggleSeriesOnOff = useCallback((seriesIndex: number): void => {
|
||||
const plot = uPlotInstanceRef.current;
|
||||
if (!plot) {
|
||||
return;
|
||||
}
|
||||
const isReset = activeSeriesIndex.current === seriesIndex;
|
||||
activeSeriesIndex.current = isReset ? undefined : seriesIndex;
|
||||
|
||||
const series = plot.series[seriesIndex];
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
plot.setSeries(seriesIndex, { show: !series.show });
|
||||
if (widgetIdRef.current) {
|
||||
const seriesVisibility: SeriesVisibilityItem[] = plot.series.map(
|
||||
(series) => ({
|
||||
label: series.label ?? '',
|
||||
show: series.show ?? true,
|
||||
}),
|
||||
);
|
||||
updateSeriesVisibilityToLocalStorage(widgetIdRef.current, seriesVisibility);
|
||||
}
|
||||
}, []);
|
||||
plot.batch(() => {
|
||||
plot.series.forEach((_, index) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
const currentSeriesIndex = index;
|
||||
plot.setSeries(currentSeriesIndex, {
|
||||
show: isReset || currentSeriesIndex === seriesIndex,
|
||||
});
|
||||
});
|
||||
if (widgetIdRef.current && shouldSavePreferencesRef.current) {
|
||||
syncSeriesVisibilityToLocalStorage();
|
||||
}
|
||||
});
|
||||
},
|
||||
[syncSeriesVisibilityToLocalStorage],
|
||||
);
|
||||
|
||||
const onToggleSeriesOnOff = useCallback(
|
||||
(seriesIndex: number): void => {
|
||||
const plot = uPlotInstanceRef.current;
|
||||
if (!plot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const series = plot.series[seriesIndex];
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
plot.setSeries(seriesIndex, { show: !series.show });
|
||||
if (widgetIdRef.current && shouldSavePreferencesRef.current) {
|
||||
syncSeriesVisibilityToLocalStorage();
|
||||
}
|
||||
},
|
||||
[syncSeriesVisibilityToLocalStorage],
|
||||
);
|
||||
|
||||
const onFocusSeries = useCallback((seriesIndex: number | null): void => {
|
||||
const plot = uPlotInstanceRef.current;
|
||||
@@ -113,12 +130,14 @@ export const PlotContextProvider = ({
|
||||
setPlotContextInitialState,
|
||||
onToggleSeriesOnOff,
|
||||
onFocusSeries,
|
||||
syncSeriesVisibilityToLocalStorage,
|
||||
}),
|
||||
[
|
||||
onToggleSeriesVisibility,
|
||||
setPlotContextInitialState,
|
||||
onToggleSeriesOnOff,
|
||||
onFocusSeries,
|
||||
syncSeriesVisibilityToLocalStorage,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
11
frontend/src/lib/uPlotV2/utils/seriesVisibility.ts
Normal file
11
frontend/src/lib/uPlotV2/utils/seriesVisibility.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function resolveSeriesVisibility(
|
||||
label: string,
|
||||
seriesShow: boolean | undefined | null,
|
||||
visibilityMap: Map<string, boolean> | null,
|
||||
isAnySeriesHidden: boolean,
|
||||
): boolean {
|
||||
if (isAnySeriesHidden) {
|
||||
return visibilityMap?.get(label) ?? false;
|
||||
}
|
||||
return seriesShow ?? true;
|
||||
}
|
||||
@@ -45,8 +45,11 @@ import APIError from 'types/api/error';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
import { useDashboardVariables } from '../../hooks/dashboard/useDashboardVariables';
|
||||
import { setDashboardVariablesStore } from './store/dashboardVariablesStore';
|
||||
import { useDashboardVariablesSelector } from '../../hooks/dashboard/useDashboardVariables';
|
||||
import {
|
||||
setDashboardVariablesStore,
|
||||
updateDashboardVariablesStore,
|
||||
} from './store/dashboardVariables/dashboardVariablesStore';
|
||||
import {
|
||||
DashboardSortOrder,
|
||||
IDashboardContext,
|
||||
@@ -198,14 +201,23 @@ export function DashboardProvider({
|
||||
: isDashboardWidgetPage?.params.dashboardId) || '';
|
||||
|
||||
const [selectedDashboard, setSelectedDashboard] = useState<Dashboard>();
|
||||
const dashboardVariables = useDashboardVariables();
|
||||
const dashboardVariables = useDashboardVariablesSelector((s) => s.variables);
|
||||
const savedDashboardId = useDashboardVariablesSelector((s) => s.dashboardId);
|
||||
|
||||
useEffect(() => {
|
||||
const existingVariables = dashboardVariables;
|
||||
const updatedVariables = selectedDashboard?.data.variables || {};
|
||||
|
||||
if (!isEqual(existingVariables, updatedVariables)) {
|
||||
setDashboardVariablesStore(updatedVariables);
|
||||
if (savedDashboardId !== dashboardId) {
|
||||
setDashboardVariablesStore({
|
||||
dashboardId,
|
||||
variables: updatedVariables,
|
||||
});
|
||||
} else if (!isEqual(existingVariables, updatedVariables)) {
|
||||
updateDashboardVariablesStore({
|
||||
dashboardId,
|
||||
variables: updatedVariables,
|
||||
});
|
||||
}
|
||||
}, [selectedDashboard]);
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import createStore from '../store';
|
||||
import { IDashboardVariablesStoreState } from './dashboardVariablesStoreTypes';
|
||||
import {
|
||||
computeDerivedValues,
|
||||
updateDerivedValues,
|
||||
} from './dashboardVariablesStoreUtils';
|
||||
|
||||
const initialState: IDashboardVariablesStoreState = {
|
||||
dashboardId: '',
|
||||
variables: {},
|
||||
sortedVariablesArray: [],
|
||||
dependencyData: null,
|
||||
};
|
||||
|
||||
export const dashboardVariablesStore = createStore<IDashboardVariablesStoreState>(
|
||||
initialState,
|
||||
);
|
||||
|
||||
/**
|
||||
* Set dashboard variables (replaces all variables)
|
||||
*/
|
||||
export function setDashboardVariablesStore({
|
||||
dashboardId,
|
||||
variables,
|
||||
}: {
|
||||
dashboardId: string;
|
||||
variables: IDashboardVariablesStoreState['variables'];
|
||||
}): void {
|
||||
dashboardVariablesStore.set(() => {
|
||||
return {
|
||||
dashboardId,
|
||||
variables,
|
||||
...computeDerivedValues(variables),
|
||||
} as IDashboardVariablesStoreState;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update specific dashboard variables (merges with existing)
|
||||
*/
|
||||
export function updateDashboardVariablesStore({
|
||||
dashboardId,
|
||||
variables,
|
||||
}: {
|
||||
dashboardId: string;
|
||||
variables: IDashboardVariablesStoreState['variables'];
|
||||
}): void {
|
||||
dashboardVariablesStore.update((draft) => {
|
||||
if (draft.dashboardId !== dashboardId) {
|
||||
// If dashboardId doesn't match, we replace the entire state
|
||||
draft.dashboardId = dashboardId;
|
||||
}
|
||||
draft.variables = variables;
|
||||
|
||||
updateDerivedValues(draft);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
export type VariableGraph = Record<string, string[]>;
|
||||
|
||||
export interface IDependencyData {
|
||||
order: string[];
|
||||
graph: VariableGraph;
|
||||
parentDependencyGraph: VariableGraph;
|
||||
hasCycle: boolean;
|
||||
cycleNodes?: string[];
|
||||
}
|
||||
|
||||
export type IDashboardVariables = Record<string, IDashboardVariable>;
|
||||
|
||||
export interface IDashboardVariablesStoreState {
|
||||
// dashboard id
|
||||
dashboardId: string;
|
||||
|
||||
// Raw variables keyed by id/name
|
||||
variables: IDashboardVariables;
|
||||
|
||||
// Derived: sorted array of variables by order
|
||||
sortedVariablesArray: IDashboardVariable[];
|
||||
|
||||
// Derived: dependency data for QUERY variables
|
||||
dependencyData: IDependencyData | null;
|
||||
}
|
||||
|
||||
export interface IUseDashboardVariablesReturn {
|
||||
dashboardVariables: IDashboardVariablesStoreState['variables'];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
buildDependencies,
|
||||
buildDependencyGraph,
|
||||
} from 'container/DashboardContainer/DashboardVariablesSelection/util';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import {
|
||||
IDashboardVariables,
|
||||
IDashboardVariablesStoreState,
|
||||
IDependencyData,
|
||||
} from './dashboardVariablesStoreTypes';
|
||||
|
||||
/**
|
||||
* Build a sorted array of variables by their order property
|
||||
*/
|
||||
export function buildSortedVariablesArray(
|
||||
variables: IDashboardVariables,
|
||||
): IDashboardVariable[] {
|
||||
const sortedVariablesArray: IDashboardVariable[] = [];
|
||||
|
||||
Object.values(variables).forEach((value) => {
|
||||
sortedVariablesArray.push({ ...value });
|
||||
});
|
||||
|
||||
sortedVariablesArray.sort((a, b) => a.order - b.order);
|
||||
|
||||
return sortedVariablesArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build dependency data from sorted variables array
|
||||
* This includes the dependency graph, topological order, and cycle detection
|
||||
*/
|
||||
export function buildDependencyData(
|
||||
sortedVariablesArray: IDashboardVariable[],
|
||||
): IDependencyData | null {
|
||||
if (sortedVariablesArray.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dependencies = buildDependencies(sortedVariablesArray);
|
||||
const {
|
||||
order,
|
||||
graph,
|
||||
parentDependencyGraph,
|
||||
hasCycle,
|
||||
cycleNodes,
|
||||
} = buildDependencyGraph(dependencies);
|
||||
|
||||
// Filter order to only include QUERY type variables
|
||||
const queryVariableOrder = order.filter((variable: string) => {
|
||||
const variableData = sortedVariablesArray.find((v) => v.name === variable);
|
||||
return variableData?.type === 'QUERY';
|
||||
});
|
||||
|
||||
return {
|
||||
order: queryVariableOrder,
|
||||
graph,
|
||||
parentDependencyGraph,
|
||||
hasCycle,
|
||||
cycleNodes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute derived values from variables
|
||||
* This is a composition of buildSortedVariablesArray and buildDependencyData
|
||||
*/
|
||||
export function computeDerivedValues(
|
||||
variables: IDashboardVariablesStoreState['variables'],
|
||||
): Pick<
|
||||
IDashboardVariablesStoreState,
|
||||
'sortedVariablesArray' | 'dependencyData'
|
||||
> {
|
||||
const sortedVariablesArray = buildSortedVariablesArray(variables);
|
||||
const dependencyData = buildDependencyData(sortedVariablesArray);
|
||||
|
||||
return { sortedVariablesArray, dependencyData };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update derived values in the store state (for use with immer)
|
||||
*/
|
||||
export function updateDerivedValues(
|
||||
draft: IDashboardVariablesStoreState,
|
||||
): void {
|
||||
draft.sortedVariablesArray = buildSortedVariablesArray(draft.variables);
|
||||
draft.dependencyData = buildDependencyData(draft.sortedVariablesArray);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import createStore from './store';
|
||||
|
||||
// export type IDashboardVariables = DashboardData['variables'];
|
||||
export type IDashboardVariables = Record<string, IDashboardVariable>;
|
||||
|
||||
export const dashboardVariablesStore = createStore<IDashboardVariables>({});
|
||||
|
||||
export function setDashboardVariablesStore(
|
||||
variables: Partial<IDashboardVariables>,
|
||||
): void {
|
||||
dashboardVariablesStore.set(() => ({ ...variables }));
|
||||
}
|
||||
@@ -569,8 +569,8 @@ func (d *Dispatcher) getOrCreateRoute(receiver string) *dispatch.Route {
|
||||
route := &dispatch.Route{
|
||||
RouteOpts: dispatch.RouteOpts{
|
||||
Receiver: receiver,
|
||||
GroupWait: 30 * time.Second,
|
||||
GroupInterval: 5 * time.Minute,
|
||||
GroupWait: d.route.RouteOpts.GroupWait,
|
||||
GroupInterval: d.route.RouteOpts.GroupInterval,
|
||||
GroupByAll: false,
|
||||
},
|
||||
Matchers: labels.Matchers{{
|
||||
|
||||
@@ -1183,8 +1183,8 @@ func TestDispatcherRaceOnFirstAlertNotDeliveredWhenGroupWaitIsZero(t *testing.T)
|
||||
|
||||
route:
|
||||
group_by: ['alertname']
|
||||
group_wait: 1h
|
||||
group_interval: 1h
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
receiver: 'slack'`
|
||||
conf, err := config.Load(confData)
|
||||
if err != nil {
|
||||
@@ -1308,3 +1308,95 @@ func TestDispatcher_DoMaintenance(t *testing.T) {
|
||||
require.False(t, isMuted)
|
||||
require.Empty(t, mutedBy)
|
||||
}
|
||||
|
||||
func TestDispatcher_GetOrCreateRoute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
confData string
|
||||
expectedReceiver string
|
||||
expectedGroupWait time.Duration
|
||||
expectedGroupInterval time.Duration
|
||||
expectedGroupByAll bool
|
||||
expectedMatchersLen int
|
||||
expectedMatcherName string
|
||||
expectedMatcherValue string
|
||||
}{
|
||||
{
|
||||
name: "create route for slack receiver",
|
||||
confData: `receivers:
|
||||
- name: 'slack'
|
||||
- name: 'email'
|
||||
- name: 'pagerduty'
|
||||
|
||||
route:
|
||||
group_by: ['alertname']
|
||||
group_wait: 1m
|
||||
group_interval: 1m
|
||||
receiver: 'slack'`,
|
||||
expectedReceiver: "slack",
|
||||
expectedGroupWait: 1 * time.Minute,
|
||||
expectedGroupInterval: 1 * time.Minute,
|
||||
expectedGroupByAll: false,
|
||||
expectedMatchersLen: 1,
|
||||
expectedMatcherName: "__receiver__",
|
||||
expectedMatcherValue: "slack",
|
||||
},
|
||||
{
|
||||
name: "no group_wait and group_interval use default values",
|
||||
confData: `receivers:
|
||||
- name: 'slack'
|
||||
|
||||
route:
|
||||
group_by: ['alertname']
|
||||
receiver: 'slack'`,
|
||||
expectedReceiver: "slack",
|
||||
expectedGroupWait: 30 * time.Second,
|
||||
expectedGroupInterval: 5 * time.Minute,
|
||||
expectedGroupByAll: false,
|
||||
expectedMatchersLen: 1,
|
||||
expectedMatcherName: "__receiver__",
|
||||
expectedMatcherValue: "slack",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf, err := config.Load(tc.confData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
providerSettings := createTestProviderSettings()
|
||||
logger := providerSettings.Logger
|
||||
route := dispatch.NewRoute(conf.Route, nil)
|
||||
marker := alertmanagertypes.NewMarker(prometheus.NewRegistry())
|
||||
alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, nil, logger, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer alerts.Close()
|
||||
|
||||
timeout := func(d time.Duration) time.Duration { return time.Duration(0) }
|
||||
recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*alertmanagertypes.Alert)}
|
||||
metrics := NewDispatcherMetrics(false, prometheus.NewRegistry())
|
||||
store := nfroutingstoretest.NewMockSQLRouteStore()
|
||||
store.MatchExpectationsInOrder(false)
|
||||
nfManager, err := rulebasednotification.New(context.Background(), providerSettings, nfmanager.Config{}, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, metrics, nfManager, "test-org")
|
||||
// setup the dispatcher for tests
|
||||
d.receiverRoutes = map[string]*dispatch.Route{}
|
||||
|
||||
newRoute := d.getOrCreateRoute(tc.expectedReceiver)
|
||||
require.Equal(t, tc.expectedReceiver, newRoute.RouteOpts.Receiver)
|
||||
require.Equal(t, tc.expectedGroupWait, newRoute.RouteOpts.GroupWait)
|
||||
require.Equal(t, tc.expectedGroupInterval, newRoute.RouteOpts.GroupInterval)
|
||||
require.Equal(t, tc.expectedGroupByAll, newRoute.RouteOpts.GroupByAll)
|
||||
require.Equal(t, tc.expectedMatchersLen, len(newRoute.Matchers))
|
||||
require.Equal(t, tc.expectedMatcherName, newRoute.Matchers[0].Name)
|
||||
require.Equal(t, tc.expectedMatcherValue, newRoute.Matchers[0].Value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ var (
|
||||
// StatsRequest represents the payload accepted by the metrics stats endpoint.
|
||||
type StatsRequest struct {
|
||||
Filter *qbtypes.Filter `json:"filter,omitempty"`
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
Limit int `json:"limit"`
|
||||
Start int64 `json:"start" required:"true"`
|
||||
End int64 `json:"end" required:"true"`
|
||||
Limit int `json:"limit" required:"true"`
|
||||
Offset int `json:"offset"`
|
||||
OrderBy *qbtypes.OrderBy `json:"orderBy,omitempty"`
|
||||
}
|
||||
@@ -96,26 +96,26 @@ func (req *StatsRequest) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// Stat represents the summary information returned per metric.
|
||||
type Stat struct {
|
||||
MetricName string `json:"metricName"`
|
||||
Description string `json:"description"`
|
||||
MetricType metrictypes.Type `json:"type"`
|
||||
MetricUnit string `json:"unit"`
|
||||
TimeSeries uint64 `json:"timeseries"`
|
||||
Samples uint64 `json:"samples"`
|
||||
MetricName string `json:"metricName" required:"true"`
|
||||
Description string `json:"description" required:"true"`
|
||||
MetricType metrictypes.Type `json:"type" required:"true" enum:"gauge,sum,histogram,summary,exponentialhistogram"`
|
||||
MetricUnit string `json:"unit" required:"true"`
|
||||
TimeSeries uint64 `json:"timeseries" required:"true"`
|
||||
Samples uint64 `json:"samples" required:"true"`
|
||||
}
|
||||
|
||||
// StatsResponse represents the aggregated metrics statistics.
|
||||
type StatsResponse struct {
|
||||
Metrics []Stat `json:"metrics"`
|
||||
Total uint64 `json:"total"`
|
||||
Metrics []Stat `json:"metrics" required:"true" nullable:"true"`
|
||||
Total uint64 `json:"total" required:"true"`
|
||||
}
|
||||
|
||||
type MetricMetadata struct {
|
||||
Description string `json:"description"`
|
||||
MetricType metrictypes.Type `json:"type"`
|
||||
MetricUnit string `json:"unit"`
|
||||
Temporality metrictypes.Temporality `json:"temporality"`
|
||||
IsMonotonic bool `json:"isMonotonic"`
|
||||
Description string `json:"description" required:"true"`
|
||||
MetricType metrictypes.Type `json:"type" required:"true" enum:"gauge,sum,histogram,summary,exponentialhistogram"`
|
||||
MetricUnit string `json:"unit" required:"true"`
|
||||
Temporality metrictypes.Temporality `json:"temporality" required:"true" enum:"delta,cumulative,unspecified"`
|
||||
IsMonotonic bool `json:"isMonotonic" required:"true"`
|
||||
}
|
||||
|
||||
// MarshalBinary implements cachetypes.Cacheable interface
|
||||
@@ -130,21 +130,21 @@ func (m *MetricMetadata) UnmarshalBinary(data []byte) error {
|
||||
|
||||
// UpdateMetricMetadataRequest represents the payload for updating metric metadata.
|
||||
type UpdateMetricMetadataRequest struct {
|
||||
MetricName string `json:"metricName"`
|
||||
Type metrictypes.Type `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Unit string `json:"unit"`
|
||||
Temporality metrictypes.Temporality `json:"temporality"`
|
||||
IsMonotonic bool `json:"isMonotonic"`
|
||||
MetricName string `json:"metricName" required:"true"`
|
||||
Type metrictypes.Type `json:"type" required:"true" enum:"gauge,sum,histogram,summary,exponentialhistogram"`
|
||||
Description string `json:"description" required:"true"`
|
||||
Unit string `json:"unit" required:"true"`
|
||||
Temporality metrictypes.Temporality `json:"temporality" required:"true" enum:"delta,cumulative,unspecified"`
|
||||
IsMonotonic bool `json:"isMonotonic" required:"true"`
|
||||
}
|
||||
|
||||
// TreemapRequest represents the payload for the metrics treemap endpoint.
|
||||
type TreemapRequest struct {
|
||||
Filter *qbtypes.Filter `json:"filter,omitempty"`
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
Limit int `json:"limit"`
|
||||
Mode TreemapMode `json:"mode"`
|
||||
Start int64 `json:"start" required:"true"`
|
||||
End int64 `json:"end" required:"true"`
|
||||
Limit int `json:"limit" required:"true"`
|
||||
Mode TreemapMode `json:"mode" required:"true" enum:"timeseries,samples"`
|
||||
}
|
||||
|
||||
// Validate enforces basic constraints on TreemapRequest.
|
||||
@@ -210,52 +210,52 @@ func (req *TreemapRequest) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// TreemapEntry represents each node in the treemap response.
|
||||
type TreemapEntry struct {
|
||||
MetricName string `json:"metricName"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
TotalValue uint64 `json:"totalValue"`
|
||||
MetricName string `json:"metricName" required:"true"`
|
||||
Percentage float64 `json:"percentage" required:"true"`
|
||||
TotalValue uint64 `json:"totalValue" required:"true"`
|
||||
}
|
||||
|
||||
// TreemapResponse is the output structure for the treemap endpoint.
|
||||
type TreemapResponse struct {
|
||||
TimeSeries []TreemapEntry `json:"timeseries"`
|
||||
Samples []TreemapEntry `json:"samples"`
|
||||
TimeSeries []TreemapEntry `json:"timeseries" required:"true" nullable:"true"`
|
||||
Samples []TreemapEntry `json:"samples" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
// MetricAlert represents an alert associated with a metric.
|
||||
type MetricAlert struct {
|
||||
AlertName string `json:"alertName"`
|
||||
AlertID string `json:"alertId"`
|
||||
AlertName string `json:"alertName" required:"true"`
|
||||
AlertID string `json:"alertId" required:"true"`
|
||||
}
|
||||
|
||||
// MetricAlertsResponse represents the response for metric alerts endpoint.
|
||||
type MetricAlertsResponse struct {
|
||||
Alerts []MetricAlert `json:"alerts"`
|
||||
Alerts []MetricAlert `json:"alerts" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
// MetricDashboard represents a dashboard/widget referencing a metric.
|
||||
type MetricDashboard struct {
|
||||
DashboardName string `json:"dashboardName"`
|
||||
DashboardID string `json:"dashboardId"`
|
||||
WidgetID string `json:"widgetId"`
|
||||
WidgetName string `json:"widgetName"`
|
||||
DashboardName string `json:"dashboardName" required:"true"`
|
||||
DashboardID string `json:"dashboardId" required:"true"`
|
||||
WidgetID string `json:"widgetId" required:"true"`
|
||||
WidgetName string `json:"widgetName" required:"true"`
|
||||
}
|
||||
|
||||
// MetricDashboardsResponse represents the response for metric dashboards endpoint.
|
||||
type MetricDashboardsResponse struct {
|
||||
Dashboards []MetricDashboard `json:"dashboards"`
|
||||
Dashboards []MetricDashboard `json:"dashboards" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
// MetricHighlightsResponse is the output structure for the metric highlights endpoint.
|
||||
type MetricHighlightsResponse struct {
|
||||
DataPoints uint64 `json:"dataPoints"`
|
||||
LastReceived uint64 `json:"lastReceived"`
|
||||
TotalTimeSeries uint64 `json:"totalTimeSeries"`
|
||||
ActiveTimeSeries uint64 `json:"activeTimeSeries"`
|
||||
DataPoints uint64 `json:"dataPoints" required:"true"`
|
||||
LastReceived uint64 `json:"lastReceived" required:"true"`
|
||||
TotalTimeSeries uint64 `json:"totalTimeSeries" required:"true"`
|
||||
ActiveTimeSeries uint64 `json:"activeTimeSeries" required:"true"`
|
||||
}
|
||||
|
||||
// MetricAttributesRequest represents the payload for the metric attributes endpoint.
|
||||
type MetricAttributesRequest struct {
|
||||
MetricName string `json:"metricName"`
|
||||
MetricName string `json:"metricName" required:"true"`
|
||||
Start *int64 `json:"start,omitempty"`
|
||||
End *int64 `json:"end,omitempty"`
|
||||
}
|
||||
@@ -292,17 +292,17 @@ func (req *MetricAttributesRequest) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// MetricAttribute represents a single attribute with its values and count.
|
||||
type MetricAttribute struct {
|
||||
Key string `json:"key"`
|
||||
Values []string `json:"values"`
|
||||
ValueCount uint64 `json:"valueCount"`
|
||||
Key string `json:"key" required:"true"`
|
||||
Values []string `json:"values" required:"true" nullable:"true"`
|
||||
ValueCount uint64 `json:"valueCount" required:"true"`
|
||||
}
|
||||
|
||||
// MetricAttributesResponse is the output structure for the metric attributes endpoint.
|
||||
type MetricAttributesResponse struct {
|
||||
Attributes []MetricAttribute `json:"attributes"`
|
||||
TotalKeys int64 `json:"totalKeys"`
|
||||
Attributes []MetricAttribute `json:"attributes" required:"true" nullable:"true"`
|
||||
TotalKeys int64 `json:"totalKeys" required:"true"`
|
||||
}
|
||||
|
||||
type MetricNameParams struct {
|
||||
MetricName string `query:"metricName"`
|
||||
MetricName string `query:"metricName" required:"true"`
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ var (
|
||||
SumType = Type{valuer.NewString("sum")}
|
||||
HistogramType = Type{valuer.NewString("histogram")}
|
||||
SummaryType = Type{valuer.NewString("summary")}
|
||||
ExpHistogramType = Type{valuer.NewString("exponential_histogram")}
|
||||
ExpHistogramType = Type{valuer.NewString("exponentialhistogram")}
|
||||
UnspecifiedType = Type{valuer.NewString("")}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user