mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-03 15:40:34 +01:00
Compare commits
3 Commits
issue_5201
...
nv/perses-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abf8a6188 | ||
|
|
259ad8a760 | ||
|
|
95b98a50af |
@@ -432,7 +432,7 @@ cloudintegration:
|
||||
version: v0.0.8
|
||||
|
||||
##################### Trace Detail #####################
|
||||
traces:
|
||||
tracedetail:
|
||||
waterfall:
|
||||
# Number of spans returned per request when the trace is too large to show all at once.
|
||||
span_page_size: 500
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.127.0
|
||||
image: signoz/signoz:v0.126.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.4
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -241,7 +241,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.4
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.127.0
|
||||
image: signoz/signoz:v0.126.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
@@ -139,7 +139,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.4
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.4
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.127.0}
|
||||
image: signoz/signoz:${VERSION:-v0.126.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.127.0}
|
||||
image: signoz/signoz:${VERSION:-v0.126.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.4}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -1261,7 +1261,6 @@ components:
|
||||
- sqs
|
||||
- storageaccountsblob
|
||||
- cdnprofile
|
||||
- appservice
|
||||
type: string
|
||||
CloudintegrationtypesServiceMetadata:
|
||||
properties:
|
||||
@@ -2381,6 +2380,26 @@ components:
|
||||
repeatVariable:
|
||||
type: string
|
||||
type: object
|
||||
DashboardLink:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
renderVariables:
|
||||
type: boolean
|
||||
targetBlank:
|
||||
type: boolean
|
||||
tooltip:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
DashboardPanelDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
DashboardTextVariableSpec:
|
||||
properties:
|
||||
constant:
|
||||
@@ -2511,7 +2530,7 @@ components:
|
||||
type: array
|
||||
links:
|
||||
items:
|
||||
$ref: '#/components/schemas/V1Link'
|
||||
$ref: '#/components/schemas/DashboardLink'
|
||||
type: array
|
||||
panels:
|
||||
additionalProperties:
|
||||
@@ -2648,8 +2667,8 @@ components:
|
||||
type: object
|
||||
DashboardtypesLayout:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpec'
|
||||
DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpec:
|
||||
- $ref: '#/components/schemas/DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpec'
|
||||
DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
@@ -2732,7 +2751,7 @@ components:
|
||||
DashboardtypesPanel:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
$ref: '#/components/schemas/DashboardtypesPanelKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesPanelSpec'
|
||||
type: object
|
||||
@@ -2743,6 +2762,10 @@ components:
|
||||
unit:
|
||||
type: string
|
||||
type: object
|
||||
DashboardtypesPanelKind:
|
||||
enum:
|
||||
- Panel
|
||||
type: string
|
||||
DashboardtypesPanelPlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpec'
|
||||
@@ -2849,10 +2872,10 @@ components:
|
||||
DashboardtypesPanelSpec:
|
||||
properties:
|
||||
display:
|
||||
$ref: '#/components/schemas/V1PanelDisplay'
|
||||
$ref: '#/components/schemas/DashboardPanelDisplay'
|
||||
links:
|
||||
items:
|
||||
$ref: '#/components/schemas/V1Link'
|
||||
$ref: '#/components/schemas/DashboardLink'
|
||||
type: array
|
||||
plugin:
|
||||
$ref: '#/components/schemas/DashboardtypesPanelPlugin'
|
||||
@@ -2911,10 +2934,18 @@ components:
|
||||
DashboardtypesQuery:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
$ref: '#/components/schemas/DashboardtypesQueryKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardtypesQuerySpec'
|
||||
type: object
|
||||
DashboardtypesQueryKind:
|
||||
enum:
|
||||
- scalar
|
||||
- time_series
|
||||
- raw
|
||||
- raw_stream
|
||||
- trace
|
||||
type: string
|
||||
DashboardtypesQueryPlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpec'
|
||||
@@ -3158,8 +3189,8 @@ components:
|
||||
DashboardtypesVariable:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesListVariableSpec'
|
||||
- $ref: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpec'
|
||||
DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpec:
|
||||
- $ref: '#/components/schemas/DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpec'
|
||||
DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
@@ -7177,26 +7208,6 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
V1Link:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
renderVariables:
|
||||
type: boolean
|
||||
targetBlank:
|
||||
type: boolean
|
||||
tooltip:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
V1PanelDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
VariableDefaultValue:
|
||||
type: object
|
||||
VariableDisplay:
|
||||
|
||||
@@ -2574,7 +2574,6 @@ export enum CloudintegrationtypesServiceIDDTO {
|
||||
sqs = 'sqs',
|
||||
storageaccountsblob = 'storageaccountsblob',
|
||||
cdnprofile = 'cdnprofile',
|
||||
appservice = 'appservice',
|
||||
}
|
||||
export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
|
||||
/**
|
||||
@@ -3090,6 +3089,40 @@ export interface DashboardGridLayoutSpecDTO {
|
||||
repeatVariable?: string;
|
||||
}
|
||||
|
||||
export interface DashboardLinkDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
renderVariables?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
targetBlank?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
tooltip?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface DashboardPanelDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface VariableDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -3787,40 +3820,9 @@ export type DashboardtypesDashboardSpecDTODatasources = {
|
||||
[key: string]: DashboardtypesDatasourceSpecDTO;
|
||||
};
|
||||
|
||||
export interface V1PanelDisplayDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
export enum DashboardtypesPanelKindDTO {
|
||||
Panel = 'Panel',
|
||||
}
|
||||
|
||||
export interface V1LinkDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
renderVariables?: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
targetBlank?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
tooltip?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesTimeSeriesPanelSpecDTOKind {
|
||||
'signoz/TimeSeriesPanel' = 'signoz/TimeSeriesPanel',
|
||||
}
|
||||
@@ -4062,6 +4064,13 @@ export type DashboardtypesPanelPluginDTO =
|
||||
| DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesHistogramPanelSpecDTO
|
||||
| DashboardtypesPanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesListPanelSpecDTO;
|
||||
|
||||
export enum DashboardtypesQueryKindDTO {
|
||||
scalar = 'scalar',
|
||||
time_series = 'time_series',
|
||||
raw = 'raw',
|
||||
raw_stream = 'raw_stream',
|
||||
trace = 'trace',
|
||||
}
|
||||
export enum DashboardtypesQueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesBuilderQuerySpecDTOKind {
|
||||
'signoz/BuilderQuery' = 'signoz/BuilderQuery',
|
||||
}
|
||||
@@ -4366,19 +4375,16 @@ export interface DashboardtypesQuerySpecDTO {
|
||||
}
|
||||
|
||||
export interface DashboardtypesQueryDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
kind?: string;
|
||||
kind?: DashboardtypesQueryKindDTO;
|
||||
spec?: DashboardtypesQuerySpecDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesPanelSpecDTO {
|
||||
display?: V1PanelDisplayDTO;
|
||||
display?: DashboardPanelDisplayDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
links?: V1LinkDTO[];
|
||||
links?: DashboardLinkDTO[];
|
||||
plugin?: DashboardtypesPanelPluginDTO;
|
||||
/**
|
||||
* @type array
|
||||
@@ -4387,10 +4393,7 @@ export interface DashboardtypesPanelSpecDTO {
|
||||
}
|
||||
|
||||
export interface DashboardtypesPanelDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
kind?: string;
|
||||
kind?: DashboardtypesPanelKindDTO;
|
||||
spec?: DashboardtypesPanelSpecDTO;
|
||||
}
|
||||
|
||||
@@ -4404,20 +4407,20 @@ export type DashboardtypesDashboardSpecDTOPanelsAnyOf = {
|
||||
export type DashboardtypesDashboardSpecDTOPanels =
|
||||
DashboardtypesDashboardSpecDTOPanelsAnyOf | null;
|
||||
|
||||
export enum DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpecDTOKind {
|
||||
export enum DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpecDTOKind {
|
||||
Grid = 'Grid',
|
||||
}
|
||||
export interface DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpecDTO {
|
||||
export interface DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpecDTO {
|
||||
/**
|
||||
* @enum Grid
|
||||
* @type string
|
||||
*/
|
||||
kind: DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpecDTOKind;
|
||||
kind: DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpecDTOKind;
|
||||
spec: DashboardGridLayoutSpecDTO;
|
||||
}
|
||||
|
||||
export type DashboardtypesLayoutDTO =
|
||||
DashboardtypesLayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpecDTO;
|
||||
DashboardtypesLayoutEnvelopeGithubComPersesSpecGoDashboardGridLayoutSpecDTO;
|
||||
|
||||
export enum DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesListVariableSpecDTOKind {
|
||||
ListVariable = 'ListVariable',
|
||||
@@ -4521,21 +4524,21 @@ export interface DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDash
|
||||
spec: DashboardtypesListVariableSpecDTO;
|
||||
}
|
||||
|
||||
export enum DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpecDTOKind {
|
||||
export enum DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpecDTOKind {
|
||||
TextVariable = 'TextVariable',
|
||||
}
|
||||
export interface DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpecDTO {
|
||||
export interface DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpecDTO {
|
||||
/**
|
||||
* @enum TextVariable
|
||||
* @type string
|
||||
*/
|
||||
kind: DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpecDTOKind;
|
||||
kind: DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpecDTOKind;
|
||||
spec: DashboardTextVariableSpecDTO;
|
||||
}
|
||||
|
||||
export type DashboardtypesVariableDTO =
|
||||
| DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesListVariableSpecDTO
|
||||
| DashboardtypesVariableEnvelopeGithubComPersesPersesPkgModelApiV1DashboardTextVariableSpecDTO;
|
||||
| DashboardtypesVariableEnvelopeGithubComPersesSpecGoDashboardTextVariableSpecDTO;
|
||||
|
||||
export interface DashboardtypesDashboardSpecDTO {
|
||||
/**
|
||||
@@ -4554,7 +4557,7 @@ export interface DashboardtypesDashboardSpecDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
links?: V1LinkDTO[];
|
||||
links?: DashboardLinkDTO[];
|
||||
/**
|
||||
* @type object,null
|
||||
*/
|
||||
|
||||
@@ -349,7 +349,7 @@ function convertV5DataByType(
|
||||
*/
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export function convertV5ResponseToLegacy(
|
||||
v5Response: SuccessResponse<MetricRangePayloadV5, QueryRangeRequestV5>,
|
||||
v5Response: SuccessResponse<MetricRangePayloadV5>,
|
||||
legendMap: Record<string, string>,
|
||||
formatForWeb?: boolean,
|
||||
): SuccessResponse<MetricRangePayloadV3> & { warning?: Warning } {
|
||||
@@ -357,7 +357,7 @@ export function convertV5ResponseToLegacy(
|
||||
const v5Data = payload?.data;
|
||||
|
||||
const aggregationPerQuery =
|
||||
params?.compositeQuery?.queries
|
||||
(params as QueryRangeRequestV5)?.compositeQuery?.queries
|
||||
?.filter((query) => query.type === 'builder_query')
|
||||
.reduce(
|
||||
(acc, query) => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { usePanelContextMenu } from '../usePanelContextMenu';
|
||||
|
||||
@@ -46,7 +47,10 @@ const mockWidget = { id: 'w-1', query: {} } as unknown as Widgets;
|
||||
const mockQueryResponse = {
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
} as unknown as UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
} as unknown as UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
|
||||
describe('usePanelContextMenu', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -10,13 +10,17 @@ import {
|
||||
PopoverPosition,
|
||||
useCoordinates,
|
||||
} from 'periscope/components/ContextMenu';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
interface UseTimeSeriesContextMenuParams {
|
||||
widget: Widgets;
|
||||
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
enableDrillDown?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ 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/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import {
|
||||
MetricQueryRangeSuccessResponse,
|
||||
MetricRangePayloadProps,
|
||||
} from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
@@ -23,7 +21,10 @@ export interface GraphVisibilityLegendEntryProps {
|
||||
|
||||
export interface WidgetGraphComponentProps {
|
||||
widget: Widgets;
|
||||
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
errorMessage: string | undefined;
|
||||
version?: string;
|
||||
threshold?: ReactNode;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { ContextLinksData, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
export type GridValueComponentProps = {
|
||||
@@ -12,7 +13,10 @@ export type GridValueComponentProps = {
|
||||
thresholds?: ThresholdProps[];
|
||||
// Context menu related props
|
||||
widget?: Widgets;
|
||||
queryResponse?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryResponse?: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
contextLinks?: ContextLinksData;
|
||||
enableDrillDown?: boolean;
|
||||
};
|
||||
|
||||
@@ -28,8 +28,9 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
function WidgetGraph({
|
||||
@@ -201,7 +202,10 @@ function WidgetGraph({
|
||||
|
||||
interface WidgetGraphProps {
|
||||
selectedWidget: Widgets;
|
||||
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
enableDrillDown?: boolean;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { QueryRangeRequestV5 } from 'api/v5/v5';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Column, QueryData, QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export function populateMultipleResults(
|
||||
responseData: SuccessResponse<MetricRangePayloadProps, QueryRangeRequestV5>,
|
||||
): SuccessResponse<MetricRangePayloadProps, QueryRangeRequestV5> {
|
||||
responseData: SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
): SuccessResponse<MetricRangePayloadProps, unknown> {
|
||||
const queryResults = responseData?.payload?.data?.newResult?.data?.result;
|
||||
const allFormattedResults: QueryData[] = [];
|
||||
|
||||
@@ -67,19 +66,17 @@ export function populateMultipleResults(
|
||||
}
|
||||
|
||||
// Create a copy instead of mutating the original
|
||||
const updatedResponseData: SuccessResponse<
|
||||
MetricRangePayloadProps,
|
||||
QueryRangeRequestV5
|
||||
> = {
|
||||
...responseData,
|
||||
payload: {
|
||||
...responseData.payload,
|
||||
data: {
|
||||
...responseData.payload.data,
|
||||
result: allFormattedResults,
|
||||
const updatedResponseData: SuccessResponse<MetricRangePayloadProps, unknown> =
|
||||
{
|
||||
...responseData,
|
||||
payload: {
|
||||
...responseData.payload,
|
||||
data: {
|
||||
...responseData.payload.data,
|
||||
result: allFormattedResults,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return updatedResponseData;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
getSelectedWidgetIndex,
|
||||
} from 'providers/Dashboard/util';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
ColumnUnit,
|
||||
ContextLinksData,
|
||||
@@ -60,7 +61,7 @@ import {
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@@ -397,7 +398,7 @@ function NewWidget({
|
||||
|
||||
// State to hold query response for sharing between left and right containers
|
||||
const [queryResponse, setQueryResponse] = useState<
|
||||
UseQueryResult<MetricQueryRangeSuccessResponse, Error>
|
||||
UseQueryResult<SuccessResponse<MetricRangePayloadProps, unknown>, Error>
|
||||
>(null as any);
|
||||
|
||||
// request data should be handled by the parent and the child components should consume the same
|
||||
|
||||
@@ -2,8 +2,9 @@ import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { SuccessResponse, Warning } from 'types/api';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { timePreferance } from './RightContainer/timeItems';
|
||||
|
||||
@@ -28,7 +29,9 @@ export interface WidgetGraphProps {
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
isLoadingPanelData: boolean;
|
||||
setQueryResponse?: Dispatch<
|
||||
SetStateAction<UseQueryResult<MetricQueryRangeSuccessResponse, Error>>
|
||||
SetStateAction<
|
||||
UseQueryResult<SuccessResponse<MetricRangePayloadProps, unknown>, Error>
|
||||
>
|
||||
>;
|
||||
enableDrillDown?: boolean;
|
||||
dashboardData: Dashboard | undefined;
|
||||
@@ -36,7 +39,12 @@ export interface WidgetGraphProps {
|
||||
}
|
||||
|
||||
export type WidgetGraphContainerProps = {
|
||||
queryResponse: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown> & {
|
||||
warning?: Warning;
|
||||
},
|
||||
Error
|
||||
>;
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
selectedWidget: Widgets;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GridTableComponent from 'container/GridTableComponent';
|
||||
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
|
||||
import { QueryRangeRequestV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
import { PanelWrapperProps } from './panelWrapper.types';
|
||||
|
||||
@@ -19,7 +20,7 @@ function TablePanelWrapper({
|
||||
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || [];
|
||||
const { thresholds } = widget;
|
||||
|
||||
const queryRangeRequest = queryResponse.data?.params;
|
||||
const queryRangeRequest = queryResponse.data?.params as QueryRangeRequestV5;
|
||||
|
||||
return (
|
||||
<GridTableComponent
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import useDashboardVarConfig from 'container/QueryTable/Drilldown/useDashboardVarConfig';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { ContextLinksData } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -43,7 +44,10 @@ const useAggregateDrilldown = ({
|
||||
aggregateData: AggregateData | null;
|
||||
contextLinks?: ContextLinksData;
|
||||
panelType?: PANEL_TYPES;
|
||||
queryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryRange?: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
}): {
|
||||
aggregateDrilldownConfig: {
|
||||
header?: string | React.ReactNode;
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { ContextLinksData } from 'types/api/dashboard/getAll';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { isValidQueryName } from './drilldownUtils';
|
||||
@@ -19,7 +20,10 @@ interface UseGraphContextMenuProps {
|
||||
setSubMenu: (subMenu: string) => void;
|
||||
contextLinks?: ContextLinksData;
|
||||
panelType?: PANEL_TYPES;
|
||||
queryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>;
|
||||
queryRange?: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
}
|
||||
|
||||
export function useGraphContextMenu({
|
||||
|
||||
@@ -7,7 +7,8 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { SuccessResponse, Warning } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -18,13 +19,16 @@ export const useGetExplorerQueryRange = (
|
||||
requestData: Query | null,
|
||||
panelType: PANEL_TYPES | null,
|
||||
version: string,
|
||||
options?: UseQueryOptions<MetricQueryRangeSuccessResponse, Error>,
|
||||
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
|
||||
params?: Record<string, unknown>,
|
||||
isDependentOnQB = true,
|
||||
keyRef?: MutableRefObject<any>,
|
||||
headers?: Record<string, string>,
|
||||
selectedTimeInterval?: GetQueryResultsProps['globalSelectedInterval'],
|
||||
): UseQueryResult<MetricQueryRangeSuccessResponse, Error> => {
|
||||
): UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps> & { warning?: Warning },
|
||||
Error
|
||||
> => {
|
||||
const { isEnabledQuery } = useQueryBuilder();
|
||||
const {
|
||||
selectedTime: globalSelectedInterval,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FullScreenHandle } from 'react-full-screen';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import {
|
||||
ClipboardCopy,
|
||||
Ellipsis,
|
||||
FileJson,
|
||||
Fullscreen,
|
||||
LockKeyhole,
|
||||
PenLine,
|
||||
Plus,
|
||||
} from '@signozhq/icons';
|
||||
import { Popover } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import type { DashboardtypesGettableDashboardV2DTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { DeleteButton } from 'container/ListOfDashboard/TableComponents/DeleteButton';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
handle: FullScreenHandle;
|
||||
isDashboardLocked: boolean;
|
||||
editDashboard: boolean;
|
||||
isAuthor: boolean;
|
||||
addPanelPermission: boolean;
|
||||
onAddPanel: () => void;
|
||||
onLockToggle: () => void;
|
||||
onOpenRename: () => void;
|
||||
}
|
||||
|
||||
function DashboardActions({
|
||||
dashboard,
|
||||
handle,
|
||||
isDashboardLocked,
|
||||
editDashboard,
|
||||
isAuthor,
|
||||
addPanelPermission,
|
||||
onAddPanel,
|
||||
onLockToggle,
|
||||
onOpenRename,
|
||||
}: Props): JSX.Element {
|
||||
const { user } = useAppContext();
|
||||
const { t } = useTranslation(['dashboard', 'common']);
|
||||
|
||||
const id = dashboard.id;
|
||||
const title = dashboard.spec?.display?.name ?? '';
|
||||
|
||||
const [isDashboardSettingsOpen, setIsDashboardSettingsOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [state, setCopy] = useCopyToClipboard();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
toast.error(t('something_went_wrong', { ns: 'common' }));
|
||||
}
|
||||
if (state.value) {
|
||||
toast.success(t('success', { ns: 'common' }));
|
||||
}
|
||||
}, [state.error, state.value, t]);
|
||||
|
||||
const dashboardDataJSON = (): string => JSON.stringify(dashboard, null, 2);
|
||||
|
||||
const exportJSON = (): void => {
|
||||
const blob = new Blob([dashboardDataJSON()], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${title || 'dashboard'}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.rightSection}>
|
||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
|
||||
<Popover
|
||||
open={isDashboardSettingsOpen}
|
||||
arrow={false}
|
||||
onOpenChange={(visible): void => setIsDashboardSettingsOpen(visible)}
|
||||
rootClassName={styles.dashboardSettings}
|
||||
content={
|
||||
<div className={styles.menuContent}>
|
||||
<section className={styles.section1}>
|
||||
{(isAuthor || user.role === USER_ROLES.ADMIN) && (
|
||||
<TooltipSimple
|
||||
title={
|
||||
dashboard.createdBy === 'integration'
|
||||
? 'Dashboards created by integrations cannot be unlocked'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<LockKeyhole size={14} />}
|
||||
disabled={dashboard.createdBy === 'integration'}
|
||||
onClick={(): void => {
|
||||
setIsDashboardSettingsOpen(false);
|
||||
onLockToggle();
|
||||
}}
|
||||
testId="lock-unlock-dashboard"
|
||||
>
|
||||
{isDashboardLocked ? 'Unlock Dashboard' : 'Lock Dashboard'}
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{!isDashboardLocked && editDashboard && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<PenLine size={14} />}
|
||||
onClick={(): void => {
|
||||
onOpenRename();
|
||||
setIsDashboardSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<Fullscreen size={14} />}
|
||||
onClick={handle.enter}
|
||||
>
|
||||
Full screen
|
||||
</Button>
|
||||
</section>
|
||||
<section className={styles.section2}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<FileJson size={14} />}
|
||||
onClick={(): void => {
|
||||
exportJSON();
|
||||
setIsDashboardSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
Export JSON
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<ClipboardCopy size={14} />}
|
||||
onClick={(): void => {
|
||||
setCopy(dashboardDataJSON());
|
||||
setIsDashboardSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
Copy as JSON
|
||||
</Button>
|
||||
</section>
|
||||
<section className={styles.deleteDashboard}>
|
||||
<DeleteButton
|
||||
createdBy={dashboard.createdBy || ''}
|
||||
name={title}
|
||||
id={id}
|
||||
isLocked={isDashboardLocked}
|
||||
routeToListPage
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
prefix={<Ellipsis size={14} />}
|
||||
className={styles.icons}
|
||||
testId="options"
|
||||
/>
|
||||
</Popover>
|
||||
{!isDashboardLocked && addPanelPermission && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
className={styles.addPanelBtn}
|
||||
onClick={onAddPanel}
|
||||
prefix={<Plus size="md" />}
|
||||
testId="add-panel-header"
|
||||
>
|
||||
New Panel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardActions;
|
||||
@@ -1,303 +0,0 @@
|
||||
.dashboardDescriptionContainer {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: unset;
|
||||
color: var(--l2-foreground);
|
||||
|
||||
:global(.ant-card-body) {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dashboardDetails {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 16px 16px 0px 16px;
|
||||
align-items: flex-start;
|
||||
|
||||
.leftSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 45%;
|
||||
|
||||
.dashboardImg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.dashboardTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 150% */
|
||||
letter-spacing: -0.08px;
|
||||
max-width: 80%;
|
||||
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.publicDashboardIcon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
display: flex;
|
||||
width: 55%;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 34px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 10px; /* 83.333% */
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.icons:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.configureButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 93px;
|
||||
height: 34px;
|
||||
padding: 6px;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 10px; /* 83.333% */
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.addPanelBtn {
|
||||
display: flex;
|
||||
width: 119px;
|
||||
height: 34px;
|
||||
padding: 5.937px 11.875px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--primary-foreground);
|
||||
background: var(--primary-background);
|
||||
font-family: Inter;
|
||||
font-size: 11.875px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 17.812px; /* 150% */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardTags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 16px 16px 0px 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
|
||||
color: var(--bg-sienna-400);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
margin-inline-end: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardDescriptionSection {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px; /* 157.143% */
|
||||
letter-spacing: -0.07px;
|
||||
padding: 20px 16px 0px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboardSettings {
|
||||
width: 191px;
|
||||
height: 302px;
|
||||
flex-shrink: 0;
|
||||
|
||||
:global(.ant-popover-inner) {
|
||||
padding: 0px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
color-mix(in srgb, var(--card) 80%, transparent) 0%,
|
||||
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
|
||||
) !important;
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.menuContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: unset;
|
||||
padding: 8px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.14px;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.section1,
|
||||
.section2 {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.deleteDashboard button {
|
||||
color: var(--bg-cherry-400) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.renameDashboard {
|
||||
:global(.ant-modal-content) {
|
||||
width: 384px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 0px;
|
||||
|
||||
:global(.ant-modal-header) {
|
||||
height: 52px;
|
||||
padding: 16px;
|
||||
background: var(--l2-background);
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
margin-bottom: 0px;
|
||||
|
||||
:global(.ant-modal-title) {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
width: 349px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-modal-body) {
|
||||
padding: 16px;
|
||||
|
||||
.dashboardContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.nameText {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.dashboardNameInput {
|
||||
display: flex;
|
||||
padding: 6px 6px 6px 8px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-modal-footer) {
|
||||
padding: 16px;
|
||||
margin-top: 0px;
|
||||
|
||||
.dashboardRename {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 12px;
|
||||
|
||||
.cancelBtn {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
.renameBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 169px;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--primary-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface Props {
|
||||
tags: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
function DashboardMeta({ tags, description }: Props): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{tags.length > 0 && (
|
||||
<div className={styles.dashboardTags}>
|
||||
{tags.map((tag) => (
|
||||
<Badge key={tag} className={styles.tag}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!isEmpty(description) && (
|
||||
<section className={styles.dashboardDescriptionSection}>
|
||||
{description}
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardMeta;
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Globe, LockKeyhole } from '@signozhq/icons';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
image: string;
|
||||
isPublicDashboard: boolean;
|
||||
isDashboardLocked: boolean;
|
||||
}
|
||||
|
||||
function DashboardTitle({
|
||||
title,
|
||||
image,
|
||||
isPublicDashboard,
|
||||
isDashboardLocked,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<div className={styles.leftSection}>
|
||||
<img src={image} alt="dashboard-img" className={styles.dashboardImg} />
|
||||
<TooltipSimple title={title.length > 30 ? title : ''}>
|
||||
<Typography.Text
|
||||
className={styles.dashboardTitle}
|
||||
data-testid="dashboard-title"
|
||||
>
|
||||
{title}
|
||||
</Typography.Text>
|
||||
</TooltipSimple>
|
||||
|
||||
{isPublicDashboard && (
|
||||
<TooltipSimple title="This dashboard is publicly accessible">
|
||||
<Globe size={14} className={styles.publicDashboardIcon} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
|
||||
{isDashboardLocked && (
|
||||
<TooltipSimple title="This dashboard is locked">
|
||||
<LockKeyhole size={14} />
|
||||
</TooltipSimple>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardTitle;
|
||||
@@ -1,70 +0,0 @@
|
||||
import { Input, Modal } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Check, X } from '@signozhq/icons';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import styles from '../DashboardDescription.module.scss';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
value: string;
|
||||
isLoading: boolean;
|
||||
onChange: (value: string) => void;
|
||||
onRename: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function RenameDashboardModal({
|
||||
open,
|
||||
value,
|
||||
isLoading,
|
||||
onChange,
|
||||
onRename,
|
||||
onClose,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title="Rename Dashboard"
|
||||
onOk={onRename}
|
||||
onCancel={onClose}
|
||||
rootClassName={styles.renameDashboard}
|
||||
footer={
|
||||
<div className={styles.dashboardRename}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
prefix={<Check size={14} />}
|
||||
className={styles.renameBtn}
|
||||
onClick={onRename}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Rename Dashboard
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<X size={14} />}
|
||||
className={styles.cancelBtn}
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={styles.dashboardContent}>
|
||||
<Typography.Text className={styles.nameText}>
|
||||
Enter a new name
|
||||
</Typography.Text>
|
||||
<Input
|
||||
data-testid="dashboard-name"
|
||||
className={styles.dashboardNameInput}
|
||||
value={value}
|
||||
onChange={(e): void => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenameDashboardModal;
|
||||
@@ -1,159 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FullScreenHandle } from 'react-full-screen';
|
||||
import { Card } from 'antd';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
lockDashboardV2,
|
||||
patchDashboardV2,
|
||||
unlockDashboardV2,
|
||||
} from 'api/generated/services/dashboard';
|
||||
import type {
|
||||
DashboardtypesGettableDashboardV2DTO,
|
||||
DashboardtypesJSONPatchOperationDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import DashboardHeader from '../components/DashboardHeader/DashboardHeader';
|
||||
import DashboardActions from './DashboardActions/DashboardActions';
|
||||
import DashboardMeta from './DashboardMeta/DashboardMeta';
|
||||
import DashboardTitle from './DashboardTitle/DashboardTitle';
|
||||
import RenameDashboardModal from './RenameDashboardModal/RenameDashboardModal';
|
||||
|
||||
import styles from './DashboardDescription.module.scss';
|
||||
|
||||
interface DashboardDescriptionProps {
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
handle: FullScreenHandle;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const { dashboard, handle, refetch } = props;
|
||||
|
||||
const id = dashboard.id;
|
||||
const isDashboardLocked = !!dashboard.locked;
|
||||
|
||||
const title = dashboard.spec?.display?.name ?? '';
|
||||
const description = dashboard.spec?.display?.description ?? '';
|
||||
const image = dashboard.image || Base64Icons[0];
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
(dashboard.tags ?? []).map((t) =>
|
||||
t.key === t.value ? t.key : `${t.key}:${t.value}`,
|
||||
),
|
||||
[dashboard.tags],
|
||||
);
|
||||
|
||||
const { user } = useAppContext();
|
||||
const [editDashboard] = useComponentPermission(['edit_dashboard'], user.role);
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const isAuthor =
|
||||
!!user?.email && !!dashboard.createdBy && dashboard.createdBy === user.email;
|
||||
const addPanelPermission = !isDashboardLocked;
|
||||
// V2 public dashboard wiring lives separately; treat as not-public for chrome.
|
||||
const isPublicDashboard = false;
|
||||
|
||||
const [isRenameDashboardOpen, setIsRenameDashboardOpen] =
|
||||
useState<boolean>(false);
|
||||
const [updatedTitle, setUpdatedTitle] = useState<string>(title);
|
||||
const [isRenameLoading, setIsRenameLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setUpdatedTitle(title);
|
||||
}, [title]);
|
||||
|
||||
const handleLockDashboardToggle = async (): Promise<void> => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (isDashboardLocked) {
|
||||
await unlockDashboardV2({ id });
|
||||
toast.success('Dashboard unlocked');
|
||||
} else {
|
||||
await lockDashboardV2({ id });
|
||||
toast.success('Dashboard locked');
|
||||
}
|
||||
refetch();
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
};
|
||||
|
||||
const onNameChangeHandler = async (): Promise<void> => {
|
||||
const trimmed = updatedTitle.trim();
|
||||
if (!id || !trimmed || trimmed === title) {
|
||||
setIsRenameDashboardOpen(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsRenameLoading(true);
|
||||
const patch: DashboardtypesJSONPatchOperationDTO[] = [
|
||||
{
|
||||
op: 'replace' as DashboardtypesJSONPatchOperationDTO['op'],
|
||||
path: '/spec/display/name',
|
||||
value: trimmed,
|
||||
},
|
||||
];
|
||||
await patchDashboardV2({ id }, patch);
|
||||
toast.success('Dashboard renamed successfully');
|
||||
setIsRenameDashboardOpen(false);
|
||||
refetch();
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
setIsRenameDashboardOpen(true);
|
||||
} finally {
|
||||
setIsRenameLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onEmptyWidgetHandler = (): void => {
|
||||
void logEvent('Dashboard Detail V2: Add new panel clicked', {
|
||||
dashboardId: id,
|
||||
});
|
||||
toast.info('V2 panel editor coming next');
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={styles.dashboardDescriptionContainer}>
|
||||
<DashboardHeader title={title} image={image} />
|
||||
<section className={styles.dashboardDetails}>
|
||||
<DashboardTitle
|
||||
title={title}
|
||||
image={image}
|
||||
isPublicDashboard={isPublicDashboard}
|
||||
isDashboardLocked={isDashboardLocked}
|
||||
/>
|
||||
<DashboardActions
|
||||
dashboard={dashboard}
|
||||
handle={handle}
|
||||
isDashboardLocked={isDashboardLocked}
|
||||
editDashboard={editDashboard}
|
||||
isAuthor={isAuthor}
|
||||
addPanelPermission={addPanelPermission}
|
||||
onAddPanel={onEmptyWidgetHandler}
|
||||
onLockToggle={handleLockDashboardToggle}
|
||||
onOpenRename={(): void => setIsRenameDashboardOpen(true)}
|
||||
/>
|
||||
</section>
|
||||
<DashboardMeta tags={tags} description={description} />
|
||||
|
||||
<RenameDashboardModal
|
||||
open={isRenameDashboardOpen}
|
||||
value={updatedTitle}
|
||||
isLoading={isRenameLoading}
|
||||
onChange={setUpdatedTitle}
|
||||
onRename={onNameChangeHandler}
|
||||
onClose={(): void => setIsRenameDashboardOpen(false)}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardDescription;
|
||||
@@ -1,52 +0,0 @@
|
||||
.panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--bg-ink-400, #0b0c0e);
|
||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--bg-slate-400, #1d212d);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
color: var(--bg-vanilla-400, #8993ae);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bodyKind {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { EllipsisVertical } from '@signozhq/icons';
|
||||
import type { DashboardtypesPanelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import cx from 'classnames';
|
||||
|
||||
import styles from './Panel.module.scss';
|
||||
|
||||
interface Props {
|
||||
panel: DashboardtypesPanelDTO | undefined;
|
||||
panelId: string;
|
||||
/**
|
||||
* Placeholder: true once this panel's section enters the viewport. The panel
|
||||
* query-loading implementation (later PR) will consume this to lazily fetch
|
||||
* data. Currently unused on purpose.
|
||||
*/
|
||||
isVisible?: boolean;
|
||||
}
|
||||
|
||||
function Panel({ panel, panelId, isVisible }: Props): JSX.Element {
|
||||
const name = panel?.spec?.display?.name || `Panel ${panelId.slice(0, 6)}`;
|
||||
const description = panel?.spec?.display?.description;
|
||||
const kind = panel?.spec?.plugin?.kind?.replace(/^signoz\//, '') ?? 'unknown';
|
||||
const queryCount = panel?.spec?.queries?.length ?? 0;
|
||||
|
||||
const headerTitle = useMemo(() => {
|
||||
if (!description) {
|
||||
return name;
|
||||
}
|
||||
return (
|
||||
<TooltipSimple title={description}>
|
||||
<span>{name}</span>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}, [name, description]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.panel}
|
||||
data-panel-visible={isVisible ? 'true' : 'false'}
|
||||
>
|
||||
<div className={cx(styles.header, 'panel-drag-handle')}>
|
||||
<div className={styles.headerLeft}>
|
||||
<Typography.Text className={styles.headerTitle}>
|
||||
{headerTitle}
|
||||
</Typography.Text>
|
||||
<Badge className={styles.badge}>{kind}</Badge>
|
||||
</div>
|
||||
<EllipsisVertical size={14} />
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div>
|
||||
<div className={styles.bodyKind}>{kind} panel</div>
|
||||
<div>
|
||||
{queryCount} {queryCount === 1 ? 'query' : 'queries'} · chart rendering
|
||||
coming next
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Panel;
|
||||
@@ -1,10 +0,0 @@
|
||||
.body {
|
||||
flex: 1;
|
||||
padding: 12px 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
padding: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
.section {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
|
||||
|
||||
import type { DashboardSection } from '../../../utils';
|
||||
import SectionGrid from '../SectionGrid/SectionGrid';
|
||||
import SectionHeader from '../SectionHeader/SectionHeader';
|
||||
import styles from './Section.module.scss';
|
||||
|
||||
interface Props {
|
||||
section: DashboardSection;
|
||||
}
|
||||
|
||||
function Section({ section }: Props): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
// Placeholder signal for lazy panel query-loading (consumed in a later PR):
|
||||
// true once the section scrolls into (or near) the viewport.
|
||||
const isVisible = useIntersectionObserver(containerRef, {
|
||||
rootMargin: '200px',
|
||||
});
|
||||
|
||||
const [open, setOpen] = useState<boolean>(section.open);
|
||||
const toggle = (): void => setOpen((prev) => !prev);
|
||||
|
||||
const grid = <SectionGrid items={section.items} isVisible={isVisible} />;
|
||||
|
||||
if (!section.title) {
|
||||
// Untitled section — just the grid (no header chrome), but still observed
|
||||
// for the viewport signal.
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
data-testid={`dashboard-section-${section.id}`}
|
||||
data-section-layout-index={section.layoutIndex}
|
||||
>
|
||||
{grid}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={styles.section}
|
||||
data-testid={`dashboard-section-${section.id}`}
|
||||
data-section-layout-index={section.layoutIndex}
|
||||
>
|
||||
<SectionHeader
|
||||
sectionId={section.id}
|
||||
title={section.title}
|
||||
open={open}
|
||||
onToggle={toggle}
|
||||
repeatVariable={section.repeatVariable}
|
||||
/>
|
||||
{open ? grid : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Section;
|
||||
@@ -1,12 +0,0 @@
|
||||
.grid {
|
||||
// Override react-grid-layout's default red drag/resize placeholder with the
|
||||
// SigNoz brand blue.
|
||||
:global(.react-grid-item.react-grid-placeholder) {
|
||||
background: var(--bg-robin-500);
|
||||
opacity: 0.2;
|
||||
border-radius: 4px;
|
||||
transition-duration: 100ms;
|
||||
z-index: 2;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import GridLayout, { WidthProvider, type Layout } from 'react-grid-layout';
|
||||
|
||||
import type { DashboardSection } from '../../../utils';
|
||||
import Panel from '../../Panel/Panel';
|
||||
import styles from './SectionGrid.module.scss';
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(GridLayout);
|
||||
|
||||
interface Props {
|
||||
items: DashboardSection['items'];
|
||||
/** Forwarded to panels — true when the parent section is in the viewport. */
|
||||
isVisible?: boolean;
|
||||
}
|
||||
|
||||
function SectionGrid({ items, isVisible }: Props): JSX.Element {
|
||||
const rglLayout = useMemo<Layout[]>(
|
||||
() =>
|
||||
items.map((item) => ({
|
||||
i: item.id,
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
w: item.width,
|
||||
h: item.height,
|
||||
})),
|
||||
[items],
|
||||
);
|
||||
|
||||
return (
|
||||
<ResponsiveGridLayout
|
||||
className={styles.grid}
|
||||
cols={12}
|
||||
rowHeight={45}
|
||||
autoSize
|
||||
useCSSTransforms
|
||||
layout={rglLayout}
|
||||
isDraggable={false}
|
||||
isResizable={false}
|
||||
margin={[8, 8]}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<div key={item.id}>
|
||||
<Panel panel={item.panel} panelId={item.id} isVisible={isVisible} />
|
||||
</div>
|
||||
))}
|
||||
</ResponsiveGridLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionGrid;
|
||||
@@ -1,52 +0,0 @@
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
|
||||
&.headerOpen {
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bg-vanilla-400, #8993ae);
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.repeatBadge {
|
||||
margin-left: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { ChevronDown, ChevronRight } from '@signozhq/icons';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
|
||||
import styles from './SectionHeader.module.scss';
|
||||
|
||||
interface Props {
|
||||
sectionId: string;
|
||||
title: string;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
repeatVariable?: string;
|
||||
}
|
||||
|
||||
function SectionHeader({
|
||||
sectionId,
|
||||
title,
|
||||
open,
|
||||
onToggle,
|
||||
repeatVariable,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<div className={cx(styles.header, { [styles.headerOpen]: open })}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.toggle}
|
||||
onClick={onToggle}
|
||||
data-testid={`dashboard-section-toggle-${sectionId}`}
|
||||
>
|
||||
{open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||
<Typography.Text className={styles.title}>{title}</Typography.Text>
|
||||
{repeatVariable ? (
|
||||
<Typography.Text className={styles.repeatBadge}>
|
||||
(repeats per ${repeatVariable})
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionHeader;
|
||||
@@ -1,53 +0,0 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
|
||||
import { Empty } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type {
|
||||
DashboardtypesLayoutDTO,
|
||||
DashboardtypesPanelDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { layoutsToSections } from '../utils';
|
||||
import Section from './Section/Section/Section';
|
||||
import styles from './PanelsAndSectionsLayout.module.scss';
|
||||
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css';
|
||||
|
||||
interface Props {
|
||||
layouts: DashboardtypesLayoutDTO[];
|
||||
panels: Record<string, DashboardtypesPanelDTO | undefined>;
|
||||
}
|
||||
|
||||
function PanelsAndSectionsLayout({ layouts, panels }: Props): JSX.Element {
|
||||
const sections = useMemo(
|
||||
() => layoutsToSections(layouts, panels),
|
||||
[layouts, panels],
|
||||
);
|
||||
|
||||
const isEmpty =
|
||||
sections.length === 0 || sections.every((s) => s.items.length === 0);
|
||||
|
||||
const renderContent = (): ReactNode => {
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={
|
||||
<Typography.Text>No panels in this dashboard yet</Typography.Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return sections.map((section) => (
|
||||
<Section key={section.id} section={section} />
|
||||
));
|
||||
};
|
||||
|
||||
return <div className={styles.body}>{renderContent()}</div>;
|
||||
}
|
||||
|
||||
export default PanelsAndSectionsLayout;
|
||||
@@ -1,63 +0,0 @@
|
||||
.dashboardBreadcrumbs {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
max-width: 80%;
|
||||
|
||||
.dashboardBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
padding: 0px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.dashboardBtn:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.idBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0px 2px;
|
||||
border-radius: 2px;
|
||||
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
|
||||
color: var(--bg-robin-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
height: 20px;
|
||||
|
||||
max-width: calc(100% - 120px);
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:global(.ant-btn-icon) {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
}
|
||||
.idBtn:hover {
|
||||
background: color-mix(in srgb, var(--bg-robin-400) 10%, transparent);
|
||||
color: var(--bg-robin-300);
|
||||
}
|
||||
|
||||
.dashboardIconImage {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import getSessionStorageApi from 'api/browser/sessionstorage/get';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { LayoutGrid } from '@signozhq/icons';
|
||||
|
||||
import styles from './DashboardBreadcrumbs.module.scss';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
function DashboardBreadcrumbs({ title, image }: Props): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
const goToListPage = useCallback(() => {
|
||||
const dashboardsListQueryParamsString = getSessionStorageApi(
|
||||
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
|
||||
);
|
||||
|
||||
if (dashboardsListQueryParamsString) {
|
||||
safeNavigate({
|
||||
pathname: ROUTES.ALL_DASHBOARD,
|
||||
search: `?${dashboardsListQueryParamsString}`,
|
||||
});
|
||||
} else {
|
||||
safeNavigate(ROUTES.ALL_DASHBOARD);
|
||||
}
|
||||
}, [safeNavigate]);
|
||||
|
||||
return (
|
||||
<div className={styles.dashboardBreadcrumbs}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
prefix={<LayoutGrid size={14} />}
|
||||
className={styles.dashboardBtn}
|
||||
onClick={goToListPage}
|
||||
>
|
||||
Dashboard /
|
||||
</Button>
|
||||
<Button variant="ghost" className={styles.idBtn}>
|
||||
<img
|
||||
src={image}
|
||||
alt="dashboard-icon"
|
||||
className={styles.dashboardIconImage}
|
||||
/>
|
||||
{title}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardBreadcrumbs;
|
||||
@@ -1,9 +0,0 @@
|
||||
.dashboardHeader {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { memo } from 'react';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
|
||||
import DashboardBreadcrumbs from './DashboardBreadcrumbs';
|
||||
|
||||
import styles from './DashboardHeader.module.scss';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
function DashboardHeader({ title, image }: Props): JSX.Element {
|
||||
return (
|
||||
<div className={styles.dashboardHeader}>
|
||||
<DashboardBreadcrumbs title={title} image={image} />
|
||||
<HeaderRightSection enableAnnouncements={false} enableShare enableFeedback />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(DashboardHeader);
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
||||
|
||||
import type { DashboardtypesGettableDashboardV2DTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import DashboardDescription from './DashboardDescription';
|
||||
import PanelsAndSectionsLayout from './PanelsAndSectionsLayout';
|
||||
import styles from './DashboardContainer.module.scss';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
function DashboardContainer({ dashboard, refetch }: Props): JSX.Element {
|
||||
const fullScreenHandle = useFullScreenHandle();
|
||||
|
||||
const { spec } = dashboard;
|
||||
const layouts = useMemo(() => spec?.layouts ?? [], [spec?.layouts]);
|
||||
const panels = useMemo(() => spec?.panels ?? {}, [spec?.panels]);
|
||||
|
||||
return (
|
||||
<FullScreen handle={fullScreenHandle}>
|
||||
<div className={styles.container}>
|
||||
<DashboardDescription
|
||||
dashboard={dashboard}
|
||||
handle={fullScreenHandle}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<PanelsAndSectionsLayout layouts={layouts} panels={panels} />
|
||||
</div>
|
||||
</FullScreen>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardContainer;
|
||||
@@ -1,154 +0,0 @@
|
||||
import type {
|
||||
DashboardtypesLayoutDTO,
|
||||
DashboardtypesPanelDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export interface GridItem {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
panel: DashboardtypesPanelDTO | undefined;
|
||||
}
|
||||
|
||||
const PANEL_REF_PREFIX = '#/spec/panels/';
|
||||
|
||||
export function extractPanelIdFromRef(ref: string | undefined): string | null {
|
||||
if (!ref) {
|
||||
return null;
|
||||
}
|
||||
if (!ref.startsWith(PANEL_REF_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
return ref.slice(PANEL_REF_PREFIX.length);
|
||||
}
|
||||
|
||||
export function flattenGridLayout(
|
||||
layouts: DashboardtypesLayoutDTO[] | undefined | null,
|
||||
panels: Record<string, DashboardtypesPanelDTO | undefined> | undefined,
|
||||
): GridItem[] {
|
||||
if (!layouts?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const items: GridItem[] = [];
|
||||
layouts.forEach((layoutEnvelope) => {
|
||||
if (layoutEnvelope?.kind !== 'Grid') {
|
||||
return;
|
||||
}
|
||||
const gridItems = layoutEnvelope.spec?.items ?? [];
|
||||
gridItems.forEach((item) => {
|
||||
const id = extractPanelIdFromRef(item.content?.$ref);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
items.push({
|
||||
id,
|
||||
x: item.x ?? 0,
|
||||
y: item.y ?? 0,
|
||||
width: item.width ?? 6,
|
||||
height: item.height ?? 6,
|
||||
panel: panels?.[id],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* A section corresponds to one entry in `spec.layouts`. If the Grid has a
|
||||
* `display.title`, it renders with a collapsible header; otherwise it is a
|
||||
* "default" untitled section (visually just the grid).
|
||||
*/
|
||||
export interface DashboardSection {
|
||||
/**
|
||||
* Stable identity used for React keys and dnd-kit sortable item ids. Derived
|
||||
* from the section's content (its first panel ref) so it survives reordering
|
||||
* — unlike the positional `layoutIndex`. See `getSectionStableId`.
|
||||
*/
|
||||
id: string;
|
||||
/** Position of this section's Grid in `spec.layouts`. All JSON-Patch ops target by this. */
|
||||
layoutIndex: number;
|
||||
title: string | undefined;
|
||||
open: boolean;
|
||||
items: GridItem[];
|
||||
repeatVariable: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a stable id for a section from its content. Reordering sections changes
|
||||
* their `layoutIndex` but not their content, so keying off the first panel ref
|
||||
* keeps React component instances (and any local state) bound to the right
|
||||
* section across a reorder. Empty sections fall back to a positional id — they
|
||||
* are rarely reordered, and a future backend `id` on the layout spec is the
|
||||
* proper long-term fix.
|
||||
*/
|
||||
export function getSectionStableId(
|
||||
items: GridItem[],
|
||||
layoutIndex: number,
|
||||
): string {
|
||||
if (items.length > 0) {
|
||||
return `sec-${items[0].id}`;
|
||||
}
|
||||
return `sec-empty-${layoutIndex}`;
|
||||
}
|
||||
|
||||
export function layoutsToSections(
|
||||
layouts: DashboardtypesLayoutDTO[] | undefined | null,
|
||||
panels: Record<string, DashboardtypesPanelDTO | undefined> | undefined,
|
||||
): DashboardSection[] {
|
||||
if (!layouts?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return layouts
|
||||
.map((layoutEnvelope, idx) => {
|
||||
if (layoutEnvelope?.kind !== 'Grid') {
|
||||
return null;
|
||||
}
|
||||
const spec = layoutEnvelope.spec;
|
||||
const items: GridItem[] = (spec?.items ?? [])
|
||||
.map((item) => {
|
||||
const id = extractPanelIdFromRef(item.content?.$ref);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
x: item.x ?? 0,
|
||||
y: item.y ?? 0,
|
||||
width: item.width ?? 6,
|
||||
height: item.height ?? 6,
|
||||
panel: panels?.[id],
|
||||
};
|
||||
})
|
||||
.filter((it): it is GridItem => it !== null);
|
||||
|
||||
const title = spec?.display?.title;
|
||||
// `open` defaults to true when no collapse field is set (the section
|
||||
// is expanded by default).
|
||||
const open = spec?.display?.collapse?.open !== false;
|
||||
|
||||
return {
|
||||
id: getSectionStableId(items, idx),
|
||||
layoutIndex: idx,
|
||||
title,
|
||||
open,
|
||||
items,
|
||||
repeatVariable: spec?.repeatVariable,
|
||||
};
|
||||
})
|
||||
.filter((s): s is DashboardSection => s !== null);
|
||||
}
|
||||
|
||||
export function getPanelKindLabel(
|
||||
panel: DashboardtypesPanelDTO | undefined,
|
||||
): string {
|
||||
const kind = panel?.spec?.plugin?.kind;
|
||||
if (!kind) {
|
||||
return 'unknown';
|
||||
}
|
||||
return kind.replace(/^signoz\//, '');
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.errorState {
|
||||
padding: 24px;
|
||||
}
|
||||
@@ -1,43 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useGetDashboardV2 } from 'api/generated/services/dashboard';
|
||||
import Spinner from 'components/Spinner';
|
||||
|
||||
import DashboardContainer from './DashboardContainer';
|
||||
import styles from './DashboardPageV2.module.scss';
|
||||
|
||||
function DashboardPageV2(): JSX.Element {
|
||||
const { dashboardId } = useParams<{ dashboardId: string }>();
|
||||
|
||||
const { data, isLoading, isError, error, refetch } = useGetDashboardV2({
|
||||
id: dashboardId,
|
||||
});
|
||||
|
||||
const dashboard = data?.data;
|
||||
const name = dashboard?.spec?.display?.name;
|
||||
|
||||
useEffect(() => {
|
||||
if (name) {
|
||||
document.title = name;
|
||||
}
|
||||
}, [name]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner tip="Loading dashboard..." />;
|
||||
}
|
||||
|
||||
if (isError || !dashboard) {
|
||||
return (
|
||||
<div className={styles.errorState}>
|
||||
<Typography.Title>Failed to load dashboard</Typography.Title>
|
||||
<Typography.Text>{(error as Error)?.message}</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <DashboardContainer dashboard={dashboard} refetch={refetch} />;
|
||||
return <>DashboardPageV2</>;
|
||||
}
|
||||
|
||||
export default DashboardPageV2;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
IClickHouseQuery,
|
||||
IPromQLQuery,
|
||||
} from '../queryBuilder/queryBuilderData';
|
||||
import { ExecStats, QueryRangeRequestV5 } from '../v5/queryRange';
|
||||
import { ExecStats } from '../v5/queryRange';
|
||||
import { QueryData, QueryDataV3 } from '../widgets/getQuery';
|
||||
|
||||
export type QueryRangePayload = {
|
||||
@@ -39,16 +39,11 @@ export interface MetricRangePayloadProps {
|
||||
meta?: ExecStats;
|
||||
}
|
||||
|
||||
/** Query range success response. `params` is the request that produced the
|
||||
* payload; `warning` and `meta` are lifted from the payload to the top level
|
||||
* by `getQueryResults.ts` for consumer convenience. */
|
||||
export interface MetricQueryRangeSuccessResponse extends SuccessResponse<
|
||||
/** Query range success response including optional warning and meta */
|
||||
export type MetricQueryRangeSuccessResponse = SuccessResponse<
|
||||
MetricRangePayloadProps,
|
||||
QueryRangeRequestV5
|
||||
> {
|
||||
warning?: Warning;
|
||||
meta?: ExecStats;
|
||||
}
|
||||
unknown
|
||||
> & { warning?: Warning; meta?: ExecStats };
|
||||
|
||||
export interface MetricRangePayloadV3 {
|
||||
data: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import store from 'store';
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { QueryRangeRequestV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
export const getTimeRangeFromQueryRangeRequest = (
|
||||
@@ -27,9 +28,13 @@ export const getTimeRangeFromQueryRangeRequest = (
|
||||
};
|
||||
|
||||
export const getTimeRange = (
|
||||
widgetQueryRange?: UseQueryResult<MetricQueryRangeSuccessResponse, Error>,
|
||||
widgetQueryRange?: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>,
|
||||
): Record<string, number> => {
|
||||
const widgetParams = widgetQueryRange?.data?.params;
|
||||
const widgetParams =
|
||||
(widgetQueryRange?.data?.params as QueryRangeRequestV5) || null;
|
||||
|
||||
return getTimeRangeFromQueryRangeRequest(widgetParams);
|
||||
};
|
||||
|
||||
7
go.mod
7
go.mod
@@ -42,7 +42,7 @@ require (
|
||||
github.com/openfga/api/proto v0.0.0-20260319214821-f153694bfc20
|
||||
github.com/openfga/language/pkg/go v0.2.1
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/perses/perses v0.53.1
|
||||
github.com/perses/spec v0.1.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/alertmanager v0.31.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
@@ -137,9 +137,8 @@ require (
|
||||
github.com/huandu/go-clone v1.7.3 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/muhlemmer/gu v0.3.1 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/perses/common v0.30.2 // indirect
|
||||
github.com/nxadm/tail v1.4.11 // indirect
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260325093428-d8591d0db856 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.15.1 // indirect
|
||||
@@ -150,8 +149,6 @@ require (
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zitadel/oidc/v3 v3.45.4 // indirect
|
||||
github.com/zitadel/schema v1.3.2 // indirect
|
||||
go.opentelemetry.io/collector/client v1.54.0 // indirect
|
||||
go.opentelemetry.io/collector/config/configoptional v1.50.0 // indirect
|
||||
go.opentelemetry.io/collector/config/configretry v1.50.0 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@@ -330,6 +330,7 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
@@ -830,8 +831,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -841,8 +840,6 @@ github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk=
|
||||
github.com/natefinch/wrap v0.2.0/go.mod h1:6gMHlAl12DwYEfKP3TkuykYUfLSEAvHw67itm4/KAS8=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nexucis/lamenv v0.5.2 h1:tK/u3XGhCq9qIoVNcXsK9LZb8fKopm0A5weqSRvHd7M=
|
||||
github.com/nexucis/lamenv v0.5.2/go.mod h1:HusJm6ltmmT7FMG8A750mOLuME6SHCsr2iFYxp5fFi0=
|
||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
@@ -905,10 +902,8 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/perses/common v0.30.2 h1:RAiVxUpX76lTCb4X7pfcXSvYdXQmZwKi4oDKAEO//u0=
|
||||
github.com/perses/common v0.30.2/go.mod h1:DFtur1QPah2/ChXbKKhw7djYdwNgz27s5fPKpiK0Xao=
|
||||
github.com/perses/perses v0.53.1 h1:9VY/6p9QWrZwPSV7qiwTMSOsgcB37Lb1AXKT0ORXc6I=
|
||||
github.com/perses/perses v0.53.1/go.mod h1:ro8fsgBkHYOdrL/MV+fdP9mflKzYCy/+gcbxiaReI/A=
|
||||
github.com/perses/spec v0.1.2 h1:yGoygcR3ZusuGDCmRMwsVXCMvMwi1qZndKV6NYNreEw=
|
||||
github.com/perses/spec v0.1.2/go.mod h1:NoGI5jmGwRdkdPgyYSZJTBL4/Py+dqIPKS2QV8NOvGE=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
@@ -1169,10 +1164,6 @@ github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A=
|
||||
github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/zitadel/oidc/v3 v3.45.4 h1:GKyWaPRVQ8sCu9XgJ3NgNGtG52FzwVJpzXjIUG2+YrI=
|
||||
github.com/zitadel/oidc/v3 v3.45.4/go.mod h1:XALmFXS9/kSom9B6uWin1yJ2WTI/E4Ti5aXJdewAVEs=
|
||||
github.com/zitadel/schema v1.3.2 h1:gfJvt7dOMfTmxzhscZ9KkapKo3Nei3B6cAxjav+lyjI=
|
||||
github.com/zitadel/schema v1.3.2/go.mod h1:IZmdfF9Wu62Zu6tJJTH3UsArevs3Y4smfJIj3L8fzxw=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
|
||||
@@ -1617,6 +1608,7 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
<svg id="b70acf0a-34b4-4bdf-9024-7496043ff915" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><radialGradient id="e2cf8746-c9a8-4eee-86c2-4951983c6032" cx="13428.81" cy="3518.86" r="56.67" gradientTransform="translate(-2005.33 -518.83) scale(0.15)" gradientUnits="userSpaceOnUse"><stop offset="0.18" stop-color="#5ea0ef"/><stop offset="1" stop-color="#0078d4"/></radialGradient><linearGradient id="bdd213dd-d313-473c-8ff4-0133fd3a9033" x1="4.4" y1="11.48" x2="4.37" y2="7.53" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="afcc63c5-3649-4476-a742-bcb53a569f3c" x1="10.13" y1="15.45" x2="10.13" y2="11.9" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="bd873f0b-9954-4aa5-a3df-9f4c64e8729d" x1="14.18" y1="11.15" x2="14.18" y2="7.38" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient></defs><title>Icon-web-41</title><path id="ee75dd06-1aca-4f76-9d11-d05a284020ad" d="M14.21,15.72A8.5,8.5,0,0,1,3.79,2.28l.09-.06a8.5,8.5,0,0,1,10.33,13.5" fill="url(#e2cf8746-c9a8-4eee-86c2-4951983c6032)"/><path d="M6.69,7.23A13,13,0,0,1,15.6,3.65a8.47,8.47,0,0,0-1.49-1.44,14.34,14.34,0,0,0-4.69,1.1A12.54,12.54,0,0,0,5.34,6.13,2.76,2.76,0,0,1,6.69,7.23Z" fill="#fff" opacity="0.6"/><path d="M2.48,10.65a17.86,17.86,0,0,0-.83,2.62,7.82,7.82,0,0,0,.62.92c.18.23.35.44.55.65A17.94,17.94,0,0,1,3.9,11.37,2.76,2.76,0,0,1,2.48,10.65Z" fill="#fff" opacity="0.6"/><path d="M3.46,6.11a12,12,0,0,1-.69-2.94,8.15,8.15,0,0,0-1.1,1.45A12.69,12.69,0,0,0,2.24,7,2.69,2.69,0,0,1,3.46,6.11Z" fill="#f2f2f2" opacity="0.55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#bdd213dd-d313-473c-8ff4-0133fd3a9033)"/><path d="M8.36,13.67A1.77,1.77,0,0,1,8.9,12.4a11.88,11.88,0,0,1-2.53-1.86,2.74,2.74,0,0,1-1.49.83,13.1,13.1,0,0,0,1.45,1.28A12.12,12.12,0,0,0,8.38,13.9,1.79,1.79,0,0,1,8.36,13.67Z" fill="#f2f2f2" opacity="0.55"/><path d="M14.66,13.88a12,12,0,0,1-2.76-.32.41.41,0,0,1,0,.11,1.75,1.75,0,0,1-.51,1.24,13.69,13.69,0,0,0,3.42.24A8.21,8.21,0,0,0,16,13.81,11.5,11.5,0,0,1,14.66,13.88Z" fill="#f2f2f2" opacity="0.55"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#afcc63c5-3649-4476-a742-bcb53a569f3c)"/><path d="M12.32,8.93a1.83,1.83,0,0,1,.61-1A25.5,25.5,0,0,1,8.47,3.79a16.91,16.91,0,0,1-2-2.92,7.64,7.64,0,0,0-1.09.42A18.14,18.14,0,0,0,7.53,4.47,26.44,26.44,0,0,0,12.32,8.93Z" fill="#f2f2f2" opacity="0.7"/><circle cx="14.18" cy="9.27" r="1.89" fill="url(#bd873f0b-9954-4aa5-a3df-9f4c64e8729d)"/><path d="M17.35,10.54,17,10.37l0,0-.3-.16-.06,0L16.38,10l-.07,0L16,9.8a1.76,1.76,0,0,1-.64.92c.12.08.25.15.38.22l.08.05.35.19,0,0,.86.45h0a8.63,8.63,0,0,0,.29-1.11Z" fill="#f2f2f2" opacity="0.55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#bdd213dd-d313-473c-8ff4-0133fd3a9033)"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#afcc63c5-3649-4476-a742-bcb53a569f3c)"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,272 +0,0 @@
|
||||
{
|
||||
"id": "appservice",
|
||||
"title": "App Services",
|
||||
"icon": "file://icon.svg",
|
||||
"overview": "file://overview.md",
|
||||
"supportedSignals": {
|
||||
"metrics": true,
|
||||
"logs": true
|
||||
},
|
||||
"dataCollected": {
|
||||
"metrics": [
|
||||
{
|
||||
"name": "azure_averagememoryworkingset_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_bytesreceived_total",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_bytessent_total",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_backendrequestcount_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_cputime_count",
|
||||
"unit": "Milliseconds",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_cputime_total",
|
||||
"unit": "Milliseconds",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_cputime_minimum",
|
||||
"unit": "Milliseconds",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_cputime_maximum",
|
||||
"unit": "Milliseconds",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_currentassemblies_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_filesystemusage_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_gen0collections_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_ge10collections_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_gen2collections_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_handles_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_healthcheckstatus_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_http101_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http2xx_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http3xx_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http401_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http403_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http404_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http406_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http4xx_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_http5xx_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
{
|
||||
"name": "azure_httpresponsetime_average",
|
||||
"unit": "Milliseconds",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_iootherbytespersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_iootheroperationspersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_ioreadbytespersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_ioreadoperationspersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_iowritebytespersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_iowriteoperationspersecond_total",
|
||||
"unit": "BytesPerSecond",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_privatebytes_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_requests_total",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_requestsinapplicationqueue_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_thread_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_totalappdomains_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"name": "azure_totalappdomainsunloaded_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": ""
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"name": "Resource ID",
|
||||
"path": "resources.azure.resource.id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"telemetryCollectionStrategy": {
|
||||
"azure": {
|
||||
"resourceProvider": "Microsoft.Web",
|
||||
"resourceType": "sites",
|
||||
"metrics": {},
|
||||
"logs": {
|
||||
"categoryGroups": ["allLogs"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"assets": {
|
||||
"dashboards": [
|
||||
{
|
||||
"id": "overview",
|
||||
"title": "App Services Overview",
|
||||
"description": "Overview of App Services metrics",
|
||||
"definition": "file://assets/dashboards/overview.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
### Monitor Azure App Services with SigNoz
|
||||
|
||||
Collect key App Services metrics and view them with an out of the box dashboard.
|
||||
|
||||
Note: This integration DO NOT collect metrics for any database that was setup with your App Service (if any).
|
||||
@@ -19,7 +19,7 @@ type WaterfallConfig struct {
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("traces"), newConfig)
|
||||
return factory.NewConfigFactory(factory.MustNewName("tracedetail"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
@@ -34,13 +34,16 @@ func newConfig() factory.Config {
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.Waterfall.SpanPageSize <= 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.waterfall.span_limit_per_request must be positive, got %v", c.Waterfall.SpanPageSize)
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"tracedetail.waterfall.span_limit_per_request must be positive, got %v", c.Waterfall.SpanPageSize)
|
||||
}
|
||||
if c.Waterfall.MaxDepthToAutoExpand < 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.waterfall.max_depth_for_selected_children cannot be negative, got %d", c.Waterfall.MaxDepthToAutoExpand)
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"tracedetail.waterfall.max_depth_for_selected_children cannot be negative, got %d", c.Waterfall.MaxDepthToAutoExpand)
|
||||
}
|
||||
if c.Waterfall.MaxLimitToSelectAllSpans == 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.waterfall.max_limit_to_select_all_spans must be positive")
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput,
|
||||
"tracedetail.waterfall.max_limit_to_select_all_spans must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,34 +7,21 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/types/spantypes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store spantypes.TraceStore
|
||||
settings factory.ScopedProviderSettings
|
||||
config tracedetail.Config
|
||||
metrics *moduleMetrics
|
||||
}
|
||||
|
||||
func NewModule(traceStore spantypes.TraceStore, providerSettings factory.ProviderSettings, cfg tracedetail.Config) *module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/tracedetail/impltracedetail")
|
||||
|
||||
metrics, err := newModuleMetrics(scopedProviderSettings.Meter())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
m := &module{
|
||||
return &module{
|
||||
config: cfg,
|
||||
store: traceStore,
|
||||
settings: scopedProviderSettings,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
m.metrics.waterfallSpanLimit.Record(context.Background(), int64(cfg.Waterfall.MaxLimitToSelectAllSpans), metric.WithAttributes(attrResponseType.String(attrResponseTypeWindowed)))
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *module) GetWaterfall(ctx context.Context, traceID string, req *spantypes.PostableWaterfall) (*spantypes.GettableWaterfallTrace, error) {
|
||||
@@ -93,9 +80,6 @@ func (m *module) GetWaterfallV4(ctx context.Context, traceID string, selectedSpa
|
||||
}
|
||||
effectiveLimit := min(selectAllLimit, m.config.Waterfall.MaxLimitToSelectAllSpans)
|
||||
if summary.NumSpans > uint64(effectiveLimit) {
|
||||
attrs := metric.WithAttributes(attrResponseType.String(attrResponseTypeWindowed))
|
||||
m.metrics.waterfallRequestCount.Add(ctx, 1, attrs)
|
||||
m.metrics.waterfallSpanCount.Add(ctx, int64(summary.NumSpans), attrs)
|
||||
return m.getWindowedWaterfall(ctx, traceID, selectedSpanID, uncollapsedSpans, summary.Start, summary.End)
|
||||
}
|
||||
return m.getFullWaterfall(ctx, traceID, summary)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package impltracedetail
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
)
|
||||
|
||||
const (
|
||||
attrResponseType = attribute.Key("response_type")
|
||||
attrResponseTypeWindowed = "windowed"
|
||||
)
|
||||
|
||||
type moduleMetrics struct {
|
||||
waterfallSpanLimit metric.Int64Gauge
|
||||
waterfallRequestCount metric.Int64Counter
|
||||
waterfallSpanCount metric.Int64Counter
|
||||
}
|
||||
|
||||
func newModuleMetrics(meter metric.Meter) (*moduleMetrics, error) {
|
||||
var errs error
|
||||
|
||||
spanLimit, err := meter.Int64Gauge(
|
||||
"signoz.traces.waterfall.span.limit",
|
||||
metric.WithDescription("The span count limit above which windowed waterfall is returned instead of the full waterfall."),
|
||||
metric.WithUnit("{span}"),
|
||||
)
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
requestCount, err := meter.Int64Counter(
|
||||
"signoz.traces.waterfall.request.count",
|
||||
metric.WithDescription("Total number of waterfall requests, by response_type."),
|
||||
metric.WithUnit("{request}"),
|
||||
)
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
spanCount, err := meter.Int64Counter(
|
||||
"signoz.traces.waterfall.span.count",
|
||||
metric.WithDescription("Total number of spans across waterfall requests, by response_type."),
|
||||
metric.WithUnit("{span}"),
|
||||
)
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
return &moduleMetrics{
|
||||
waterfallSpanLimit: spanLimit,
|
||||
waterfallRequestCount: requestCount,
|
||||
waterfallSpanCount: spanCount,
|
||||
}, errs
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
}
|
||||
}
|
||||
preseededResults := make(map[string]any)
|
||||
for _, name := range missingMetricQueries {
|
||||
for _, name := range missingMetricQueries { // at this point missing metrics will not have any non existent metrics, only normal ones
|
||||
switch req.RequestType {
|
||||
case qbtypes.RequestTypeTimeSeries:
|
||||
preseededResults[name] = &qbtypes.TimeSeriesData{QueryName: name}
|
||||
@@ -375,24 +375,11 @@ func (q *querier) resolveMetricMetadata(ctx context.Context, queries []qbtypes.Q
|
||||
return missingMetricQueries, "", nil
|
||||
}
|
||||
|
||||
isInternalMetric := func(n string) bool { return strings.HasPrefix(n, "signoz.") || strings.HasPrefix(n, "signoz_") }
|
||||
externalMissingMetrics := make([]string, 0, len(missingMetrics))
|
||||
for _, m := range missingMetrics {
|
||||
if !isInternalMetric(m) {
|
||||
externalMissingMetrics = append(externalMissingMetrics, m)
|
||||
}
|
||||
}
|
||||
if len(externalMissingMetrics) == 0 {
|
||||
// this means all missing metrics are internal, and since internal metrics
|
||||
// aren't user-controlled, skip errors/warnings for them since users can't act on them
|
||||
return missingMetricQueries, "", nil
|
||||
}
|
||||
|
||||
// Classify each missing metric: never-seen → NotFound error; seen-but-no-
|
||||
// data-in-window → dormant warning.
|
||||
lastSeenInfo, _ := q.metadataStore.FetchLastSeenInfoMulti(ctx, externalMissingMetrics...)
|
||||
lastSeenInfo, _ := q.metadataStore.FetchLastSeenInfoMulti(ctx, missingMetrics...)
|
||||
nonExistentMetrics := []string{}
|
||||
for _, name := range externalMissingMetrics {
|
||||
for _, name := range missingMetrics {
|
||||
if ts, ok := lastSeenInfo[name]; ok && ts > 0 {
|
||||
continue
|
||||
}
|
||||
@@ -413,11 +400,11 @@ func (q *querier) resolveMetricMetadata(ctx context.Context, queries []qbtypes.Q
|
||||
}
|
||||
return name
|
||||
}
|
||||
if len(externalMissingMetrics) == 1 {
|
||||
if len(missingMetrics) == 1 {
|
||||
dormantWarning = fmt.Sprintf("no data found for the metric %s in the query time range", lastSeenStr(missingMetrics[0]))
|
||||
} else {
|
||||
parts := make([]string, len(externalMissingMetrics))
|
||||
for i, m := range externalMissingMetrics {
|
||||
parts := make([]string, len(missingMetrics))
|
||||
for i, m := range missingMetrics {
|
||||
parts[i] = lastSeenStr(m)
|
||||
}
|
||||
dormantWarning = fmt.Sprintf("no data found for the following metrics in the query time range: %s", strings.Join(parts, ", "))
|
||||
|
||||
@@ -143,7 +143,7 @@ type Config struct {
|
||||
CloudIntegration cloudintegration.Config `mapstructure:"cloudintegration"`
|
||||
|
||||
// TraceDetail config
|
||||
TraceDetail tracedetail.Config `mapstructure:"traces"`
|
||||
TraceDetail tracedetail.Config `mapstructure:"tracedetail"`
|
||||
|
||||
// Authz config
|
||||
Authz authz.Config `mapstructure:"authz"`
|
||||
|
||||
@@ -27,7 +27,6 @@ var (
|
||||
// Azure services.
|
||||
AzureServiceStorageAccountsBlob = ServiceID{valuer.NewString("storageaccountsblob")}
|
||||
AzureServiceCDNProfile = ServiceID{valuer.NewString("cdnprofile")}
|
||||
AzureServiceAppService = ServiceID{valuer.NewString("appservice")}
|
||||
)
|
||||
|
||||
func (ServiceID) Enum() []any {
|
||||
@@ -47,7 +46,6 @@ func (ServiceID) Enum() []any {
|
||||
AWSServiceSQS,
|
||||
AzureServiceStorageAccountsBlob,
|
||||
AzureServiceCDNProfile,
|
||||
AzureServiceAppService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +69,6 @@ var SupportedServices = map[CloudProviderType][]ServiceID{
|
||||
CloudProviderTypeAzure: {
|
||||
AzureServiceStorageAccountsBlob,
|
||||
AzureServiceCDNProfile,
|
||||
AzureServiceAppService,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/spec/go/common"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/spec/go/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -43,7 +44,7 @@ func newTestDashboardV2(t *testing.T, orgID valuer.UUID, source Source) *Dashboa
|
||||
},
|
||||
Queries: []Query{
|
||||
{
|
||||
Kind: "TimeSeriesQuery",
|
||||
Kind: QueryKind(qb.RequestTypeTimeSeries),
|
||||
Spec: QuerySpec{
|
||||
Plugin: QueryPlugin{
|
||||
Kind: QueryKindPromQL,
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/spec/go/common"
|
||||
"github.com/perses/spec/go/dashboard"
|
||||
)
|
||||
|
||||
// DashboardSpec is the SigNoz dashboard v2 spec shape. It mirrors
|
||||
// v1.DashboardSpec (Perses) field-for-field, except every common.Plugin
|
||||
// dashboard.Spec (Perses) field-for-field, except every common.Plugin
|
||||
// occurrence is replaced with a typed SigNoz plugin whose OpenAPI schema is a
|
||||
// per-site discriminated oneOf.
|
||||
type DashboardSpec struct {
|
||||
@@ -24,7 +24,7 @@ type DashboardSpec struct {
|
||||
Layouts []Layout `json:"layouts"`
|
||||
Duration common.DurationString `json:"duration"`
|
||||
RefreshInterval common.DurationString `json:"refreshInterval,omitempty"`
|
||||
Links []v1.Link `json:"links,omitempty"`
|
||||
Links []dashboard.Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
@@ -133,6 +133,21 @@ func TestInvalidateUnknownPluginKind(t *testing.T) {
|
||||
}`,
|
||||
wantContain: "NonExistentPanel",
|
||||
},
|
||||
{
|
||||
name: "unknown panel envelope kind",
|
||||
data: `{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Row",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`,
|
||||
wantContain: "unknown panel kind",
|
||||
},
|
||||
{
|
||||
name: "unknown query plugin",
|
||||
data: `{
|
||||
@@ -142,7 +157,7 @@ func TestInvalidateUnknownPluginKind(t *testing.T) {
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {"kind": "FakeQueryPlugin", "spec": {}}
|
||||
}
|
||||
@@ -154,6 +169,48 @@ func TestInvalidateUnknownPluginKind(t *testing.T) {
|
||||
}`,
|
||||
wantContain: "FakeQueryPlugin",
|
||||
},
|
||||
{
|
||||
name: "unknown query envelope kind",
|
||||
data: `{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "A", "signal": "metrics"}}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`,
|
||||
wantContain: "unknown query kind",
|
||||
},
|
||||
{
|
||||
name: "empty query envelope kind",
|
||||
data: `{
|
||||
"panels": {
|
||||
"p1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [{
|
||||
"kind": "",
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "A", "signal": "metrics"}}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layouts": []
|
||||
}`,
|
||||
wantContain: "unknown query kind",
|
||||
},
|
||||
{
|
||||
name: "unknown variable plugin",
|
||||
data: `{
|
||||
@@ -246,7 +303,7 @@ func TestRejectUnknownFieldsInPluginSpec(t *testing.T) {
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/PromQLQuery",
|
||||
@@ -324,7 +381,7 @@ func TestInvalidateWrongFieldTypeInPluginSpec(t *testing.T) {
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/PromQLQuery",
|
||||
@@ -389,7 +446,7 @@ func TestInvalidateBadPanelSpecValues(t *testing.T) {
|
||||
"spec": {}
|
||||
},
|
||||
"queries": [{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -620,8 +677,8 @@ func TestInvalidatePanelWithMultipleDirectQueries(t *testing.T) {
|
||||
"spec": {
|
||||
"plugin": {"kind": "signoz/TimeSeriesPanel", "spec": {}},
|
||||
"queries": [
|
||||
{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "A", "signal": "metrics"}}}},
|
||||
{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "B", "signal": "metrics"}}}}
|
||||
{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "A", "signal": "metrics"}}}},
|
||||
{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/BuilderQuery", "spec": {"name": "B", "signal": "metrics"}}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -738,7 +795,7 @@ func TestTimeSeriesPanelDefaults(t *testing.T) {
|
||||
"kind": "signoz/TimeSeriesPanel",
|
||||
"spec": {}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -786,7 +843,7 @@ func TestNumberPanelDefaults(t *testing.T) {
|
||||
"kind": "signoz/NumberPanel",
|
||||
"spec": {"thresholds": [{"value": 100, "color": "Red"}]}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -847,7 +904,7 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
"kind": "signoz/TimeSeriesPanel",
|
||||
"spec": {}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
},
|
||||
"p2": {
|
||||
@@ -857,7 +914,7 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||
"kind": "signoz/NumberPanel",
|
||||
"spec": {"thresholds": [{"value": 100, "color": "Red"}]}
|
||||
},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/PromQLQuery", "spec": {"name": "A", "query": "up"}}}}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1062,7 +1119,7 @@ func TestPanelTypeQueryTypeCompatibility(t *testing.T) {
|
||||
return []byte(`{
|
||||
"panels": {"p1": {"kind": "Panel", "spec": {
|
||||
"plugin": {"kind": "` + panelKind + `", "spec": {}},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "` + queryKind + `", "spec": ` + querySpec + `}}}]
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "` + queryKind + `", "spec": ` + querySpec + `}}}]
|
||||
}}},
|
||||
"layouts": []
|
||||
}`)
|
||||
@@ -1071,7 +1128,7 @@ func TestPanelTypeQueryTypeCompatibility(t *testing.T) {
|
||||
return []byte(`{
|
||||
"panels": {"p1": {"kind": "Panel", "spec": {
|
||||
"plugin": {"kind": "` + panelKind + `", "spec": {}},
|
||||
"queries": [{"kind": "TimeSeriesQuery", "spec": {"plugin": {"kind": "signoz/CompositeQuery", "spec": {
|
||||
"queries": [{"kind": "time_series", "spec": {"plugin": {"kind": "signoz/CompositeQuery", "spec": {
|
||||
"queries": [{"type": "` + subType + `", "spec": ` + subSpec + `}]
|
||||
}}}}]
|
||||
}}},
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/dashboard"
|
||||
"github.com/perses/spec/go/dashboard"
|
||||
"github.com/perses/spec/go/datasource"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -22,12 +22,12 @@ func TestDashboardSpecMatchesPerses(t *testing.T) {
|
||||
ours reflect.Type
|
||||
perses reflect.Type
|
||||
}{
|
||||
{"DashboardSpec", typeOf[DashboardSpec](), typeOf[v1.DashboardSpec]()},
|
||||
{"Panel", typeOf[Panel](), typeOf[v1.Panel]()},
|
||||
{"PanelSpec", typeOf[PanelSpec](), typeOf[v1.PanelSpec]()},
|
||||
{"Query", typeOf[Query](), typeOf[v1.Query]()},
|
||||
{"QuerySpec", typeOf[QuerySpec](), typeOf[v1.QuerySpec]()},
|
||||
{"DatasourceSpec", typeOf[DatasourceSpec](), typeOf[v1.DatasourceSpec]()},
|
||||
{"DashboardSpec", typeOf[DashboardSpec](), typeOf[dashboard.Spec]()},
|
||||
{"Panel", typeOf[Panel](), typeOf[dashboard.Panel]()},
|
||||
{"PanelSpec", typeOf[PanelSpec](), typeOf[dashboard.PanelSpec]()},
|
||||
{"Query", typeOf[Query](), typeOf[dashboard.Query]()},
|
||||
{"QuerySpec", typeOf[QuerySpec](), typeOf[dashboard.QuerySpec]()},
|
||||
{"DatasourceSpec", typeOf[DatasourceSpec](), typeOf[datasource.Spec]()},
|
||||
{"Variable", typeOf[Variable](), typeOf[dashboard.Variable]()},
|
||||
{"ListVariableSpec", typeOf[ListVariableSpec](), typeOf[dashboard.ListVariableSpec]()},
|
||||
{"Layout", typeOf[Layout](), typeOf[dashboard.Layout]()},
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
v1 "github.com/perses/perses/pkg/model/api/v1"
|
||||
"github.com/perses/perses/pkg/model/api/v1/common"
|
||||
"github.com/perses/perses/pkg/model/api/v1/dashboard"
|
||||
"github.com/perses/perses/pkg/model/api/v1/variable"
|
||||
qb "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/perses/spec/go/common"
|
||||
"github.com/perses/spec/go/dashboard"
|
||||
"github.com/perses/spec/go/dashboard/variable"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
@@ -27,15 +29,36 @@ type DatasourceSpec struct {
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type Panel struct {
|
||||
Kind string `json:"kind"`
|
||||
Kind PanelKind `json:"kind"`
|
||||
Spec PanelSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// PanelKind is the panel envelope discriminator. Perses leaves it a free
|
||||
// string; SigNoz locks it to the single valid value.
|
||||
type PanelKind string
|
||||
|
||||
const PanelKindPanel PanelKind = "Panel"
|
||||
|
||||
// Enum surfaces the allowed value in the generated OpenAPI schema.
|
||||
func (PanelKind) Enum() []any { return []any{PanelKindPanel} }
|
||||
|
||||
func (k *PanelKind) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid panel kind")
|
||||
}
|
||||
if PanelKind(s) != PanelKindPanel {
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown panel kind %q; allowed values: %s", s, allowedValuesForKind([]PanelKind{PanelKindPanel}))
|
||||
}
|
||||
*k = PanelKind(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
type PanelSpec struct {
|
||||
Display *v1.PanelDisplay `json:"display,omitempty"`
|
||||
Plugin PanelPlugin `json:"plugin"`
|
||||
Queries []Query `json:"queries,omitempty"`
|
||||
Links []v1.Link `json:"links,omitempty"`
|
||||
Display *dashboard.PanelDisplay `json:"display,omitempty"`
|
||||
Plugin PanelPlugin `json:"plugin"`
|
||||
Queries []Query `json:"queries,omitempty"`
|
||||
Links []dashboard.Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -43,10 +66,29 @@ type PanelSpec struct {
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
type Query struct {
|
||||
Kind string `json:"kind"`
|
||||
Kind QueryKind `json:"kind"`
|
||||
Spec QuerySpec `json:"spec"`
|
||||
}
|
||||
|
||||
type QueryKind qb.RequestType
|
||||
|
||||
func (QueryKind) Enum() []any { return (qb.RequestType{}).Enum() }
|
||||
|
||||
func (k *QueryKind) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeDashboardInvalidInput, "invalid query kind")
|
||||
}
|
||||
rt := qb.RequestType{String: valuer.NewString(s)}
|
||||
for _, allowed := range (qb.RequestType{}).Enum() {
|
||||
if allowed == rt {
|
||||
*k = QueryKind(rt)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.NewInvalidInputf(ErrCodeDashboardInvalidInput, "unknown query kind %q; allowed values: %s", s, "`scalar`, `time_series`, `raw`, `raw_stream`, `trace`")
|
||||
}
|
||||
|
||||
type QuerySpec struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Plugin QueryPlugin `json:"plugin"`
|
||||
|
||||
24
pkg/types/dashboardtypes/testdata/perses.json
vendored
24
pkg/types/dashboardtypes/testdata/perses.json
vendored
@@ -132,7 +132,7 @@
|
||||
],
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -191,7 +191,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/CompositeQuery",
|
||||
@@ -269,7 +269,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -334,7 +334,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -373,7 +373,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -418,7 +418,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -476,7 +476,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/ClickHouseSQL",
|
||||
@@ -517,7 +517,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "LogQuery",
|
||||
"kind": "raw",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -571,7 +571,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -610,7 +610,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/CompositeQuery",
|
||||
@@ -681,7 +681,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/CompositeQuery",
|
||||
@@ -722,7 +722,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/PromQLQuery",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
@@ -85,7 +85,7 @@
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"kind": "TimeSeriesQuery",
|
||||
"kind": "time_series",
|
||||
"spec": {
|
||||
"plugin": {
|
||||
"kind": "signoz/BuilderQuery",
|
||||
|
||||
@@ -640,32 +640,6 @@ def test_non_existent_metrics_returns_404(
|
||||
assert get_error_message(response.json()) == "could not find the metric whatevergoennnsgoeshere"
|
||||
|
||||
|
||||
def test_non_existent_internal_metrics_returns_no_warning(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
|
||||
now = datetime.now(tz=UTC).replace(second=0, microsecond=0)
|
||||
metric_name = "signoz_calls_total"
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
"doesnotreallymatter",
|
||||
"sum",
|
||||
)
|
||||
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
|
||||
start_2h = int((now - timedelta(hours=2)).timestamp() * 1000)
|
||||
response = make_query_request(signoz, token, start_2h, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
data = response.json()
|
||||
assert get_all_warnings(data) == []
|
||||
|
||||
|
||||
# Verify /api/v1/fields/values filters label values by metricNamespace prefix.
|
||||
# Inserts metrics under ns.a and ns.b, then asserts a specific prefix returns
|
||||
# only matching values while a common prefix returns both.
|
||||
|
||||
Reference in New Issue
Block a user