mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-21 19:00:42 +01:00
Compare commits
2 Commits
fix/ui-tes
...
nv/remove-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9ed313681 | ||
|
|
ee97363179 |
@@ -3454,29 +3454,6 @@ components:
|
||||
required:
|
||||
- customValue
|
||||
type: object
|
||||
DashboardtypesDashboard:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesStorableDashboardData'
|
||||
id:
|
||||
type: string
|
||||
locked:
|
||||
type: boolean
|
||||
org_id:
|
||||
type: string
|
||||
source:
|
||||
$ref: '#/components/schemas/DashboardtypesSource'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
type: object
|
||||
DashboardtypesDashboardPanelRef:
|
||||
properties:
|
||||
dashboardId:
|
||||
@@ -3656,13 +3633,6 @@ components:
|
||||
timeRangeEnabled:
|
||||
type: boolean
|
||||
type: object
|
||||
DashboardtypesGettablePublicDashboardData:
|
||||
properties:
|
||||
dashboard:
|
||||
$ref: '#/components/schemas/DashboardtypesDashboard'
|
||||
publicDashboard:
|
||||
$ref: '#/components/schemas/DashboardtypesGettablePublicDasbhboard'
|
||||
type: object
|
||||
DashboardtypesGettablePublicDashboardDataV2:
|
||||
properties:
|
||||
dashboard:
|
||||
@@ -4447,9 +4417,6 @@ components:
|
||||
- normal
|
||||
- percent
|
||||
type: string
|
||||
DashboardtypesStorableDashboardData:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
DashboardtypesTableFormatting:
|
||||
properties:
|
||||
columnUnits:
|
||||
@@ -7225,22 +7192,6 @@ components:
|
||||
- attributes
|
||||
- totalKeys
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboard:
|
||||
properties:
|
||||
dashboardId:
|
||||
type: string
|
||||
dashboardName:
|
||||
type: string
|
||||
widgetId:
|
||||
type: string
|
||||
widgetName:
|
||||
type: string
|
||||
required:
|
||||
- dashboardName
|
||||
- dashboardId
|
||||
- widgetId
|
||||
- widgetName
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboardPanelsResponse:
|
||||
properties:
|
||||
dashboards:
|
||||
@@ -7251,16 +7202,6 @@ components:
|
||||
required:
|
||||
- dashboards
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboardsResponse:
|
||||
properties:
|
||||
dashboards:
|
||||
items:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricDashboard'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- dashboards
|
||||
type: object
|
||||
MetricsexplorertypesMetricHighlightsResponse:
|
||||
properties:
|
||||
activeTimeSeries:
|
||||
@@ -13303,112 +13244,6 @@ paths:
|
||||
summary: Update org preference
|
||||
tags:
|
||||
- preferences
|
||||
/api/v1/public/dashboards/{id}:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns the sanitized dashboard data for public access
|
||||
operationId: GetPublicDashboardData
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesGettablePublicDashboardData'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- anonymous:
|
||||
- public-dashboard:read
|
||||
summary: Get public dashboard data
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v1/public/dashboards/{id}/widgets/{idx}/query_range:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint return query range results for a widget of public
|
||||
dashboard
|
||||
operationId: GetPublicDashboardWidgetQueryRange
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- in: path
|
||||
name: idx
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryRangeResponse'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- anonymous:
|
||||
- public-dashboard:read
|
||||
summary: Get query range result
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v1/roles:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -19660,74 +19495,6 @@ paths:
|
||||
summary: Get metric attributes
|
||||
tags:
|
||||
- metrics
|
||||
/api/v2/metrics/dashboards:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns associated dashboards for a specified metric
|
||||
operationId: GetMetricDashboards
|
||||
parameters:
|
||||
- description: The name of the metric. May contain slashes (e.g. cloud-provider
|
||||
metrics like run.googleapis.com/request_latencies).
|
||||
in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
description: The name of the metric. May contain slashes (e.g. cloud-provider
|
||||
metrics like run.googleapis.com/request_latencies).
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricDashboardsResponse'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get metric dashboards
|
||||
tags:
|
||||
- metrics
|
||||
/api/v2/metrics/highlights:
|
||||
get:
|
||||
deprecated: false
|
||||
|
||||
@@ -52,7 +52,7 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
|
||||
dashboard, err := module.GetV2(ctx, orgID, publicDashboard.DashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,15 +90,6 @@ func (module *module) GetPublic(ctx context.Context, orgID valuer.UUID, dashboar
|
||||
return dashboardtypes.NewPublicDashboardFromStorablePublicDashboard(storablePublicDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByPublicID(ctx context.Context, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
storableDashboard, err := module.store.GetDashboardByPublicID(ctx, id.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id valuer.UUID, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
orgIDs := make([]string, len(orgs))
|
||||
for idx, org := range orgs {
|
||||
@@ -116,24 +107,6 @@ func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id
|
||||
}, storableDashboard.OrgID, nil
|
||||
}
|
||||
|
||||
func (module *module) GetPublicWidgetQueryRange(ctx context.Context, id valuer.UUID, widgetIdx, startTime, endTime uint64) (*querybuildertypesv5.QueryRangeResponse, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.CodeNamespace: "dashboard",
|
||||
instrumentationtypes.CodeFunctionName: "GetPublicWidgetQueryRange",
|
||||
})
|
||||
dashboard, err := module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query, err := dashboard.GetWidgetQuery(startTime, endTime, widgetIdx, module.settings.Logger())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return module.querier.QueryRange(ctx, dashboard.OrgID, query)
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByPublicIDV2(ctx context.Context, id valuer.UUID) (*dashboardtypes.DashboardV2, error) {
|
||||
storableDashboard, err := module.store.GetDashboardByPublicID(ctx, id.StringValue())
|
||||
if err != nil {
|
||||
@@ -189,7 +162,7 @@ func (module *module) UpdatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
|
||||
dashboard, err := module.GetV2(ctx, orgID, publicDashboard.DashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -200,34 +173,13 @@ func (module *module) UpdatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return module.store.UpdatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dashboard.ErrIfNotDeletable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.Locked {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
return module.delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeleteUnsafe(ctx context.Context, orgID, id valuer.UUID) error {
|
||||
return module.delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, dashboardID)
|
||||
dashboard, err := module.GetV2(ctx, orgID, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -260,10 +212,6 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, source, data)
|
||||
}
|
||||
|
||||
func (module *module) CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, postable dashboardtypes.PostableDashboardV2) (*dashboardtypes.DashboardV2, error) {
|
||||
return module.pkgDashboardModule.CreateV2(ctx, orgID, createdBy, creator, source, postable)
|
||||
}
|
||||
@@ -346,30 +294,10 @@ func (module *module) DeleteView(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return module.pkgDashboardModule.DeleteView(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]dashboardtypes.DashboardPanelRef, error) {
|
||||
return module.pkgDashboardModule.GetByMetricNames(ctx, orgID, metricNames)
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNamesV2(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]dashboardtypes.DashboardPanelRef, error) {
|
||||
return module.pkgDashboardModule.GetByMetricNamesV2(ctx, orgID, metricNames)
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.List(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) ReconcileSystemDashboards(ctx context.Context, orgID valuer.UUID) error {
|
||||
return module.pkgDashboardModule.ReconcileSystemDashboards(ctx, orgID)
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ func (m *module) relatedAssetImpact(ctx context.Context, orgID valuer.UUID, metr
|
||||
droppedSet[label] = struct{}{}
|
||||
}
|
||||
|
||||
if dashboards, err := m.dashboard.GetByMetricNames(ctx, orgID, []string{metricName}); err != nil {
|
||||
if dashboards, err := m.dashboard.GetByMetricNamesV2(ctx, orgID, []string{metricName}); err != nil {
|
||||
m.logger.WarnContext(ctx, "failed to fetch related dashboards for reduction preview", slog.String("metric_name", metricName), errors.Attr(err))
|
||||
} else {
|
||||
for _, item := range dashboards[metricName] {
|
||||
|
||||
@@ -36,16 +36,12 @@ import type {
|
||||
GetDashboardV2200,
|
||||
GetDashboardV2PathParameters,
|
||||
GetPublicDashboard200,
|
||||
GetPublicDashboardData200,
|
||||
GetPublicDashboardDataPathParameters,
|
||||
GetPublicDashboardDataV2200,
|
||||
GetPublicDashboardDataV2PathParameters,
|
||||
GetPublicDashboardPanelQueryRangeV2200,
|
||||
GetPublicDashboardPanelQueryRangeV2Params,
|
||||
GetPublicDashboardPanelQueryRangeV2PathParameters,
|
||||
GetPublicDashboardPathParameters,
|
||||
GetPublicDashboardWidgetQueryRange200,
|
||||
GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
GetSystemDashboard200,
|
||||
GetSystemDashboardPathParameters,
|
||||
ListDashboardViews200,
|
||||
@@ -474,217 +470,6 @@ export const useUpdatePublicDashboard = <
|
||||
> => {
|
||||
return useMutation(getUpdatePublicDashboardMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the sanitized dashboard data for public access
|
||||
* @summary Get public dashboard data
|
||||
*/
|
||||
export const getPublicDashboardData = (
|
||||
{ id }: GetPublicDashboardDataPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetPublicDashboardData200>({
|
||||
url: `/api/v1/public/dashboards/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetPublicDashboardDataQueryKey = ({
|
||||
id,
|
||||
}: GetPublicDashboardDataPathParameters) => {
|
||||
return [`/api/v1/public/dashboards/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetPublicDashboardDataQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getPublicDashboardData>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetPublicDashboardDataPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardData>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetPublicDashboardDataQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getPublicDashboardData>>
|
||||
> = ({ signal }) => getPublicDashboardData({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: id !== null && id !== undefined,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardData>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetPublicDashboardDataQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getPublicDashboardData>>
|
||||
>;
|
||||
export type GetPublicDashboardDataQueryError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get public dashboard data
|
||||
*/
|
||||
|
||||
export function useGetPublicDashboardData<
|
||||
TData = Awaited<ReturnType<typeof getPublicDashboardData>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetPublicDashboardDataPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardData>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetPublicDashboardDataQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get public dashboard data
|
||||
*/
|
||||
export const invalidateGetPublicDashboardData = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetPublicDashboardDataPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetPublicDashboardDataQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint return query range results for a widget of public dashboard
|
||||
* @summary Get query range result
|
||||
*/
|
||||
export const getPublicDashboardWidgetQueryRange = (
|
||||
{ id, idx }: GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetPublicDashboardWidgetQueryRange200>({
|
||||
url: `/api/v1/public/dashboards/${id}/widgets/${idx}/query_range`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetPublicDashboardWidgetQueryRangeQueryKey = ({
|
||||
id,
|
||||
idx,
|
||||
}: GetPublicDashboardWidgetQueryRangePathParameters) => {
|
||||
return [`/api/v1/public/dashboards/${id}/widgets/${idx}/query_range`] as const;
|
||||
};
|
||||
|
||||
export const getGetPublicDashboardWidgetQueryRangeQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id, idx }: GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ??
|
||||
getGetPublicDashboardWidgetQueryRangeQueryKey({ id, idx });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>
|
||||
> = ({ signal }) => getPublicDashboardWidgetQueryRange({ id, idx }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: id !== null && id !== undefined && idx !== null && idx !== undefined,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetPublicDashboardWidgetQueryRangeQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>
|
||||
>;
|
||||
export type GetPublicDashboardWidgetQueryRangeQueryError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get query range result
|
||||
*/
|
||||
|
||||
export function useGetPublicDashboardWidgetQueryRange<
|
||||
TData = Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id, idx }: GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getPublicDashboardWidgetQueryRange>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetPublicDashboardWidgetQueryRangeQueryOptions(
|
||||
{ id, idx },
|
||||
options,
|
||||
);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get query range result
|
||||
*/
|
||||
export const invalidateGetPublicDashboardWidgetQueryRange = async (
|
||||
queryClient: QueryClient,
|
||||
{ id, idx }: GetPublicDashboardWidgetQueryRangePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetPublicDashboardWidgetQueryRangeQueryKey({ id, idx }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns every saved view in the calling user's org. Saved views are shared org-wide.
|
||||
* @summary List dashboard saved views
|
||||
|
||||
@@ -24,8 +24,6 @@ import type {
|
||||
GetMetricAlertsParams,
|
||||
GetMetricAttributes200,
|
||||
GetMetricAttributesParams,
|
||||
GetMetricDashboards200,
|
||||
GetMetricDashboardsParams,
|
||||
GetMetricDashboardsV2200,
|
||||
GetMetricDashboardsV2Params,
|
||||
GetMetricHighlights200,
|
||||
@@ -1096,104 +1094,6 @@ export const invalidateGetMetricAttributes = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint returns associated dashboards for a specified metric
|
||||
* @summary Get metric dashboards
|
||||
*/
|
||||
export const getMetricDashboards = (
|
||||
params: GetMetricDashboardsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricDashboards200>({
|
||||
url: `/api/v2/metrics/dashboards`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetMetricDashboardsQueryKey = (
|
||||
params?: GetMetricDashboardsParams,
|
||||
) => {
|
||||
return [`/api/v2/metrics/dashboards`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetMetricDashboardsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetMetricDashboardsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>
|
||||
> = ({ signal }) => getMetricDashboards(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetMetricDashboardsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>
|
||||
>;
|
||||
export type GetMetricDashboardsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get metric dashboards
|
||||
*/
|
||||
|
||||
export function useGetMetricDashboards<
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetMetricDashboardsQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get metric dashboards
|
||||
*/
|
||||
export const invalidateGetMetricDashboards = async (
|
||||
queryClient: QueryClient,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetMetricDashboardsQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint returns highlights like number of datapoints, totaltimeseries, active time series, last received time for a specified metric
|
||||
* @summary Get metric highlights
|
||||
|
||||
@@ -4886,50 +4886,6 @@ export interface DashboardtypesCustomVariableSpecDTO {
|
||||
customValue: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesStorableDashboardDataDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export enum DashboardtypesSourceDTO {
|
||||
user = 'user',
|
||||
system = 'system',
|
||||
integration = 'integration',
|
||||
}
|
||||
export interface DashboardtypesDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
data?: DashboardtypesStorableDashboardDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
locked?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
org_id?: string;
|
||||
source?: DashboardtypesSourceDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface DashboardtypesDashboardPanelRefDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -5868,6 +5824,11 @@ export interface DashboardtypesDashboardViewDTO {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export enum DashboardtypesSourceDTO {
|
||||
user = 'user',
|
||||
system = 'system',
|
||||
integration = 'integration',
|
||||
}
|
||||
export interface TagtypesGettableTagDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -5945,11 +5906,6 @@ export interface DashboardtypesGettablePublicDasbhboardDTO {
|
||||
timeRangeEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardtypesGettablePublicDashboardDataDTO {
|
||||
dashboard?: DashboardtypesDashboardDTO;
|
||||
publicDashboard?: DashboardtypesGettablePublicDasbhboardDTO;
|
||||
}
|
||||
|
||||
export interface DashboardtypesGettablePublicDashboardDataV2DTO {
|
||||
dashboard?: DashboardtypesGettableDashboardV2DTO;
|
||||
publicDashboard?: DashboardtypesGettablePublicDasbhboardDTO;
|
||||
@@ -9022,25 +8978,6 @@ export interface MetricsexplorertypesMetricAttributesResponseDTO {
|
||||
totalKeys: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardName: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetName: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardPanelsResponseDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
@@ -9048,13 +8985,6 @@ export interface MetricsexplorertypesMetricDashboardPanelsResponseDTO {
|
||||
dashboards: DashboardtypesDashboardPanelRefDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardsResponseDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
dashboards: MetricsexplorertypesMetricDashboardDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricHighlightsResponseDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -12488,29 +12418,6 @@ export type GetOrgPreference200 = {
|
||||
export type UpdateOrgPreferencePathParameters = {
|
||||
name: string;
|
||||
};
|
||||
export type GetPublicDashboardDataPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetPublicDashboardData200 = {
|
||||
data: DashboardtypesGettablePublicDashboardDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetPublicDashboardWidgetQueryRangePathParameters = {
|
||||
id: string;
|
||||
idx: string;
|
||||
};
|
||||
export type GetPublicDashboardWidgetQueryRange200 = {
|
||||
data: Querybuildertypesv5QueryRangeResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListRoles200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -13468,22 +13375,6 @@ export type GetMetricAttributes200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetMetricDashboardsParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description The name of the metric. May contain slashes (e.g. cloud-provider metrics like run.googleapis.com/request_latencies).
|
||||
*/
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricDashboards200 = {
|
||||
data: MetricsexplorertypesMetricDashboardsResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetMetricHighlightsParams = {
|
||||
/**
|
||||
* @type string
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
MetricsexplorertypesMetricAlertDTO,
|
||||
MetricsexplorertypesMetricAttributeDTO,
|
||||
MetricsexplorertypesMetricDashboardDTO,
|
||||
MetricsexplorertypesMetricHighlightsResponseDTO,
|
||||
MetricsexplorertypesMetricMetadataDTO,
|
||||
MetrictypesTemporalityDTO,
|
||||
@@ -55,8 +54,6 @@ export type MetricHighlight = MetricsexplorertypesMetricHighlightsResponseDTO;
|
||||
|
||||
export type MetricAlert = MetricsexplorertypesMetricAlertDTO;
|
||||
|
||||
export type MetricDashboard = MetricsexplorertypesMetricDashboardDTO;
|
||||
|
||||
export type MetricMetadata = MetricsexplorertypesMetricMetadataDTO;
|
||||
export interface MetricMetadataFormState {
|
||||
type: MetrictypesTypeDTO;
|
||||
|
||||
@@ -630,62 +630,6 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authzMiddleware.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicData,
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
|
||||
}, []string{}), handler.OpenAPIDef{
|
||||
ID: "GetPublicDashboardData",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get public dashboard data",
|
||||
Description: "This endpoint returns the sanitized dashboard data for public access",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(dashboardtypes.GettablePublicDashboardData),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authzMiddleware.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicWidgetQueryRange,
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
coretypes.ResourceMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]coretypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
|
||||
}, []string{}), handler.OpenAPIDef{
|
||||
ID: "GetPublicDashboardWidgetQueryRange",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get query range result",
|
||||
Description: "This endpoint return query range results for a widget of public dashboard",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(querybuildertypesv5.QueryRangeResponse),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newAnonymousSecuritySchemes([]string{coretypes.ResourceMetaResourcePublicDashboard.Scope(coretypes.VerbRead)}),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/public/dashboards/{id}", handler.New(provider.authzMiddleware.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicDataV2,
|
||||
authtypes.Relation{Verb: coretypes.VerbRead},
|
||||
|
||||
@@ -167,26 +167,6 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/metrics/dashboards", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricDashboards),
|
||||
handler.OpenAPIDef{
|
||||
ID: "GetMetricDashboards",
|
||||
Tags: []string{"metrics"},
|
||||
Summary: "Get metric dashboards",
|
||||
Description: "This endpoint returns associated dashboards for a specified metric",
|
||||
Request: nil,
|
||||
RequestQuery: new(metricsexplorertypes.MetricNameQuery),
|
||||
RequestContentType: "",
|
||||
Response: new(metricsexplorertypes.MetricDashboardsResponse),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v3/metrics/dashboards", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricDashboardsV2),
|
||||
handler.OpenAPIDef{
|
||||
|
||||
@@ -19,12 +19,6 @@ type Module interface {
|
||||
// gets the public sharing config for the dashboard
|
||||
GetPublic(context.Context, valuer.UUID, valuer.UUID) (*dashboardtypes.PublicDashboard, error)
|
||||
|
||||
// get the dashboard data by public dashboard id
|
||||
GetDashboardByPublicID(context.Context, valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
// gets the query results by widget index and public shared id for a dashboard
|
||||
GetPublicWidgetQueryRange(context.Context, valuer.UUID, uint64, uint64, uint64) (*querybuildertypesv5.QueryRangeResponse, error)
|
||||
|
||||
// gets the selectors and org for the given public dashboard
|
||||
GetPublicDashboardSelectorsAndOrg(context.Context, valuer.UUID, []*types.Organization) ([]coretypes.Selector, valuer.UUID, error)
|
||||
|
||||
@@ -34,23 +28,6 @@ type Module interface {
|
||||
// deletes the public sharing config and disables public sharing for the dashboard
|
||||
DeletePublic(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
|
||||
|
||||
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error
|
||||
|
||||
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
// DeleteUnsafe deletes a dashboard bypassing the guards. Intended for internal system callers.
|
||||
DeleteUnsafe(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]dashboardtypes.DashboardPanelRef, error)
|
||||
|
||||
statsreporter.StatsCollector
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
@@ -118,10 +95,6 @@ type Handler interface {
|
||||
|
||||
GetPublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicData(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicWidgetQueryRange(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicDataV2(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicWidgetQueryRangeV2(http.ResponseWriter, *http.Request)
|
||||
@@ -130,14 +103,6 @@ type Handler interface {
|
||||
|
||||
DeletePublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
Create(http.ResponseWriter, *http.Request)
|
||||
|
||||
Update(http.ResponseWriter, *http.Request)
|
||||
|
||||
LockUnlock(http.ResponseWriter, *http.Request)
|
||||
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// v2 dashboard methods
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -3,11 +3,9 @@ package impldashboard
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
@@ -29,22 +27,6 @@ func NewHandler(module dashboard.Module, providerSettings factory.ProviderSettin
|
||||
return &handler{module: module, providerSettings: providerSettings, authz: authz}
|
||||
}
|
||||
|
||||
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("create a dashboard with POST /api/v2/dashboards"))
|
||||
}
|
||||
|
||||
func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("update a dashboard with PUT /api/v2/dashboards/{id}, or patch it with PATCH /api/v2/dashboards/{id}"))
|
||||
}
|
||||
|
||||
func (handler *handler) LockUnlock(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("lock a dashboard with PUT /api/v2/dashboards/{id}/lock, or unlock it with DELETE /api/v2/dashboards/{id}/lock"))
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("delete a dashboard with DELETE /api/v2/dashboards/{id}"))
|
||||
}
|
||||
|
||||
func (handler *handler) CreatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
@@ -61,7 +43,7 @@ func (handler *handler) CreatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
_, err = handler.module.GetV2(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -99,7 +81,7 @@ func (handler *handler) GetPublic(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
_, err = handler.module.GetV2(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -114,107 +96,6 @@ func (handler *handler) GetPublic(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Success(rw, http.StatusOK, dashboardtypes.NewGettablePublicDashboard(publicDashboard))
|
||||
}
|
||||
|
||||
func (handler *handler) GetPublicData(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, dashboard.OrgID, valuer.MustNewUUID(dashboard.ID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettablePublicDashboardData, err := dashboardtypes.NewPublicDashboardDataFromDashboard(dashboard, publicDashboard)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, gettablePublicDashboardData)
|
||||
}
|
||||
|
||||
func (handler *handler) GetPublicWidgetQueryRange(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
widgetIndex, ok := mux.Vars(r)["idx"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "widget index is missing from the path"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, dashboard.OrgID, valuer.MustNewUUID(dashboard.ID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
widgetIdx, err := strconv.ParseUint(widgetIndex, 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid widget index"))
|
||||
return
|
||||
}
|
||||
|
||||
var startTime, endTime uint64
|
||||
if publicDashboard.TimeRangeEnabled {
|
||||
startTimeUint, err := strconv.ParseUint(r.URL.Query().Get("startTime"), 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid startTime"))
|
||||
return
|
||||
}
|
||||
|
||||
endTimeUint, err := strconv.ParseUint(r.URL.Query().Get("endTime"), 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid endTime"))
|
||||
return
|
||||
}
|
||||
|
||||
startTime = startTimeUint
|
||||
endTime = endTimeUint
|
||||
} else {
|
||||
timeRange, err := time.ParseDuration(publicDashboard.DefaultTimeRange)
|
||||
if err != nil {
|
||||
// this should't happen as we shouldn't let such values in DB
|
||||
panic(err)
|
||||
}
|
||||
|
||||
startTime = uint64(time.Now().Add(-timeRange).UnixMilli())
|
||||
endTime = uint64(time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
queryRangeResults, err := handler.module.GetPublicWidgetQueryRange(ctx, id, widgetIdx, startTime, endTime)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, queryRangeResults)
|
||||
}
|
||||
|
||||
func (handler *handler) UpdatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
@@ -231,7 +112,7 @@ func (handler *handler) UpdatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
_, err = handler.module.GetV2(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -275,7 +156,7 @@ func (handler *handler) DeletePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
_, err = handler.module.GetV2(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -2,7 +2,6 @@ package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
@@ -42,194 +41,6 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
|
||||
systemDashboardRegistry: systemDashboardRegistry,
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, source, postableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module.analytics.TrackUser(ctx, orgID.String(), creator.String(), "Dashboard Created", dashboardtypes.NewStatsFromStorableDashboards([]*dashboardtypes.StorableDashboard{storableDashboard}))
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
storableDashboard, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
storableDashboards, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// system dashboards are hidden from the listing endpoint but still gettable by id.
|
||||
filtered := make([]*dashboardtypes.StorableDashboard, 0, len(storableDashboards))
|
||||
for _, storable := range storableDashboards {
|
||||
if storable.Source == dashboardtypes.SourceSystem {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, storable)
|
||||
}
|
||||
|
||||
return dashboardtypes.NewDashboardsFromStorableDashboards(filtered), nil
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, updatableDashboard dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dashboard.ErrIfNotMutable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dashboard.Update(ctx, updatableDashboard, updatedBy, diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = module.store.Update(ctx, orgID, storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dashboard.ErrIfNotLockable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dashboard.LockUnlock(lock, isAdmin, updatedBy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.Update(ctx, orgID, storableDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dashboard.ErrIfNotDeletable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.Locked {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
err = module.store.Delete(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) DeleteUnsafe(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]dashboardtypes.DashboardPanelRef, error) {
|
||||
dashboards, err := module.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string][]dashboardtypes.DashboardPanelRef)
|
||||
|
||||
for _, dashboard := range dashboards {
|
||||
dashData := dashboard.Data
|
||||
dashTitle, _ := dashData["title"].(string)
|
||||
widgets, ok := dashData["widgets"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, w := range widgets {
|
||||
widget, ok := w.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
widgetTitle, _ := widget["title"].(string)
|
||||
widgetID, _ := widget["id"].(string)
|
||||
|
||||
query, ok := widget["query"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Track which metrics were found in this widget, along with the
|
||||
// group-by and filter labels referenced for each metric. CH/PromQL
|
||||
// paths are presence-only and leave the label sets empty.
|
||||
foundMetrics := make(map[string]bool)
|
||||
groupByByMetric := make(map[string][]string)
|
||||
filterByByMetric := make(map[string][]string)
|
||||
|
||||
// Check all three query types
|
||||
module.checkBuilderQueriesForMetricNames(query, metricNames, foundMetrics, groupByByMetric, filterByByMetric)
|
||||
module.checkClickHouseQueriesForMetricNames(ctx, query, metricNames, foundMetrics)
|
||||
module.checkPromQLQueriesForMetricNames(ctx, query, metricNames, foundMetrics)
|
||||
|
||||
// Add widget to results for all found metrics
|
||||
for metricName := range foundMetrics {
|
||||
result[metricName] = append(result[metricName], dashboardtypes.DashboardPanelRef{
|
||||
DashboardID: dashboard.ID,
|
||||
DashboardName: dashTitle,
|
||||
PanelID: widgetID,
|
||||
PanelName: widgetTitle,
|
||||
GroupBy: groupByByMetric[metricName],
|
||||
FilterBy: filterByByMetric[metricName],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
dashboards, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
@@ -248,14 +59,6 @@ func (module *module) GetPublic(_ context.Context, _, _ valuer.UUID) (*dashboard
|
||||
return nil, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByPublicID(_ context.Context, _ valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (module *module) GetPublicWidgetQueryRange(context.Context, valuer.UUID, uint64, uint64, uint64) (*qbtypes.QueryRangeResponse, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByPublicIDV2(_ context.Context, _ valuer.UUID) (*dashboardtypes.DashboardV2, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
@@ -276,100 +79,6 @@ func (module *module) DeletePublic(_ context.Context, _ valuer.UUID, _ valuer.UU
|
||||
return errors.Newf(errors.TypeUnsupported, dashboardtypes.ErrCodePublicDashboardUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
// checkBuilderQueriesForMetricNames checks builder.queryData[] for aggregations[].metricName.
|
||||
// For each queryData entry whose dataSource is "metrics" and that references a
|
||||
// target metric, it accumulates (deduped) the group-by and filter labels used
|
||||
// by that entry into groupByByMetric/filterByByMetric, keyed by metric name.
|
||||
func (module *module) checkBuilderQueriesForMetricNames(query map[string]interface{}, metricNames []string, foundMetrics map[string]bool, groupByByMetric, filterByByMetric map[string][]string) {
|
||||
builder, ok := query["builder"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
queryData, ok := builder["queryData"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, qd := range queryData {
|
||||
data, ok := qd.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check dataSource is metrics
|
||||
if dataSource, ok := data["dataSource"].(string); !ok || dataSource != "metrics" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check aggregations[].metricName
|
||||
aggregations, ok := data["aggregations"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
entryMetrics := make([]string, 0, len(aggregations))
|
||||
for _, agg := range aggregations {
|
||||
aggMap, ok := agg.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
metricName, ok := aggMap["metricName"].(string)
|
||||
if !ok || metricName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if slices.Contains(metricNames, metricName) {
|
||||
foundMetrics[metricName] = true
|
||||
entryMetrics = append(entryMetrics, metricName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(entryMetrics) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
groupBy := extractBuilderGroupByLabels(data)
|
||||
filterBy := extractBuilderFilterLabels(data)
|
||||
for _, metricName := range entryMetrics {
|
||||
groupByByMetric[metricName] = appendDedup(groupByByMetric[metricName], groupBy...)
|
||||
filterByByMetric[metricName] = appendDedup(filterByByMetric[metricName], filterBy...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractBuilderGroupByLabels(data map[string]interface{}) []string {
|
||||
gb, ok := data["groupBy"].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(gb))
|
||||
for _, g := range gb {
|
||||
gm, ok := g.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if name, ok := gm["name"].(string); ok && name != "" {
|
||||
out = append(out, name)
|
||||
continue
|
||||
}
|
||||
// v3: groupBy[].key may be a plain string ...
|
||||
if key, ok := gm["key"].(string); ok && key != "" {
|
||||
out = append(out, key)
|
||||
continue
|
||||
}
|
||||
// ... or a nested object {key: "<name>"}.
|
||||
if km, ok := gm["key"].(map[string]interface{}); ok {
|
||||
if key, ok := km["key"].(string); ok && key != "" {
|
||||
out = append(out, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func extractBuilderFilterLabels(data map[string]interface{}) []string {
|
||||
out := []string{}
|
||||
|
||||
@@ -415,73 +124,3 @@ func appendDedup(dst []string, values ...string) []string {
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// checkClickHouseQueriesForMetricNames checks clickhouse_sql[] array for metric names in query strings.
|
||||
func (module *module) checkClickHouseQueriesForMetricNames(ctx context.Context, query map[string]interface{}, metricNames []string, foundMetrics map[string]bool) {
|
||||
clickhouseSQL, ok := query["clickhouse_sql"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, chQuery := range clickhouseSQL {
|
||||
chQueryMap, ok := chQuery.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
queryStr, ok := chQueryMap["query"].(string)
|
||||
if !ok || queryStr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse query to extract metric names
|
||||
result, err := module.queryParser.AnalyzeQueryFilter(ctx, qbtypes.QueryTypeClickHouseSQL, queryStr)
|
||||
if err != nil {
|
||||
// Log warning and continue - parsing errors shouldn't break the search
|
||||
module.settings.Logger().WarnContext(ctx, "failed to parse ClickHouse query", slog.String("query", queryStr), errors.Attr(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if any of the search metric names are in the extracted metric names
|
||||
for _, metricName := range metricNames {
|
||||
if slices.Contains(result.MetricNames, metricName) {
|
||||
foundMetrics[metricName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkPromQLQueriesForMetricNames checks promql[] array for metric names in query strings.
|
||||
func (module *module) checkPromQLQueriesForMetricNames(ctx context.Context, query map[string]interface{}, metricNames []string, foundMetrics map[string]bool) {
|
||||
promQL, ok := query["promql"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, promQuery := range promQL {
|
||||
promQueryMap, ok := promQuery.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
queryStr, ok := promQueryMap["query"].(string)
|
||||
if !ok || queryStr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse query to extract metric names
|
||||
result, err := module.queryParser.AnalyzeQueryFilter(ctx, qbtypes.QueryTypePromQL, queryStr)
|
||||
if err != nil {
|
||||
// Log warning and continue - parsing errors shouldn't break the search
|
||||
module.settings.Logger().WarnContext(ctx, "failed to parse PromQL query", slog.String("query", queryStr), errors.Attr(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if any of the search metric names are in the extracted metric names
|
||||
for _, metricName := range metricNames {
|
||||
if slices.Contains(result.MetricNames, metricName) {
|
||||
foundMetrics[metricName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,38 +196,6 @@ func (h *handler) GetMetricAlerts(rw http.ResponseWriter, req *http.Request) {
|
||||
render.Success(rw, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (h *handler) GetMetricDashboards(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
var in metricsexplorertypes.MetricNameQuery
|
||||
if err := binding.Query.BindQuery(req.URL.Query(), &in); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
if err := in.Validate(); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
if err := h.checkMetricExists(req.Context(), orgID, in.MetricName); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := h.module.GetMetricDashboards(req.Context(), orgID, in.MetricName)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
render.Success(rw, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (h *handler) GetMetricDashboardsV2(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/telemetryschema/metricstelemetryschema"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
|
||||
@@ -390,18 +389,6 @@ func (m *module) GetMetricAlerts(ctx context.Context, orgID valuer.UUID, metricN
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *module) GetMetricDashboards(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricDashboardsResponse, error) {
|
||||
if metricName == "" {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "metricName is required")
|
||||
}
|
||||
data, err := m.dashboardModule.GetByMetricNames(ctx, orgID, []string{metricName})
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to get dashboards for metric")
|
||||
}
|
||||
|
||||
return newMetricDashboardsResponse(data[metricName]), nil
|
||||
}
|
||||
|
||||
func (m *module) GetMetricDashboardsV2(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricDashboardPanelsResponse, error) {
|
||||
if metricName == "" {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "metricName is required")
|
||||
@@ -414,22 +401,6 @@ func (m *module) GetMetricDashboardsV2(ctx context.Context, orgID valuer.UUID, m
|
||||
return metricsexplorertypes.NewMetricDashboardPanelsResponse(data[metricName]), nil
|
||||
}
|
||||
|
||||
func newMetricDashboardsResponse(dashboardList []dashboardtypes.DashboardPanelRef) *metricsexplorertypes.MetricDashboardsResponse {
|
||||
dashboards := make([]metricsexplorertypes.MetricDashboard, 0, len(dashboardList))
|
||||
for _, item := range dashboardList {
|
||||
dashboards = append(dashboards, metricsexplorertypes.MetricDashboard{
|
||||
DashboardName: item.DashboardName,
|
||||
DashboardID: item.DashboardID,
|
||||
WidgetID: item.PanelID,
|
||||
WidgetName: item.PanelName,
|
||||
})
|
||||
}
|
||||
|
||||
return &metricsexplorertypes.MetricDashboardsResponse{
|
||||
Dashboards: dashboards,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMetricHighlights returns highlights for a metric including data points, last received, total time series, and active time series.
|
||||
func (m *module) GetMetricHighlights(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricHighlightsResponse, error) {
|
||||
if metricName == "" {
|
||||
|
||||
@@ -17,7 +17,6 @@ type Handler interface {
|
||||
GetMetricAttributes(http.ResponseWriter, *http.Request)
|
||||
UpdateMetricMetadata(http.ResponseWriter, *http.Request)
|
||||
GetMetricAlerts(http.ResponseWriter, *http.Request)
|
||||
GetMetricDashboards(http.ResponseWriter, *http.Request)
|
||||
GetMetricDashboardsV2(http.ResponseWriter, *http.Request)
|
||||
GetMetricHighlights(http.ResponseWriter, *http.Request)
|
||||
GetOnboardingStatus(http.ResponseWriter, *http.Request)
|
||||
@@ -33,7 +32,6 @@ type Module interface {
|
||||
GetMetricMetadataMulti(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string]*metricsexplorertypes.MetricMetadata, error)
|
||||
UpdateMetricMetadata(ctx context.Context, orgID valuer.UUID, req *metricsexplorertypes.UpdateMetricMetadataRequest) error
|
||||
GetMetricAlerts(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricAlertsResponse, error)
|
||||
GetMetricDashboards(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricDashboardsResponse, error)
|
||||
GetMetricDashboardsV2(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricDashboardPanelsResponse, error)
|
||||
GetMetricHighlights(ctx context.Context, orgID valuer.UUID, metricName string) (*metricsexplorertypes.MetricHighlightsResponse, error)
|
||||
GetMetricAttributes(ctx context.Context, orgID valuer.UUID, req *metricsexplorertypes.MetricAttributesRequest) (*metricsexplorertypes.MetricAttributesResponse, error)
|
||||
|
||||
@@ -58,7 +58,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
@@ -376,12 +375,6 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/top_contributors", am.ViewAccess(aH.getRuleStateHistoryTopContributors)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/rules/{id}/history/overall_status", am.ViewAccess(aH.getOverallStateTransitions)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/dashboards", am.ViewAccess(aH.List)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/dashboards", am.EditAccess(aH.Signoz.Handlers.Dashboard.Create)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}", am.ViewAccess(aH.Get)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Update)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Delete)).Methods(http.MethodDelete)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}/lock", am.EditAccess(aH.Signoz.Handlers.Dashboard.LockUnlock)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
|
||||
@@ -946,14 +939,6 @@ func prepareQuery(r *http.Request) (string, error) {
|
||||
return newQuery, nil
|
||||
}
|
||||
|
||||
func (aH *APIHandler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("get a dashboard with GET /api/v2/dashboards/{id}"))
|
||||
}
|
||||
|
||||
func (aH *APIHandler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, dashboardtypes.NewV1DeprecatedError("list dashboards with GET /api/v2/dashboards"))
|
||||
}
|
||||
|
||||
func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Request) {
|
||||
query, err := prepareQuery(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -22,7 +16,6 @@ var (
|
||||
ErrCodeDashboardImmutable = errors.MustNewCode("dashboard_immutable")
|
||||
ErrCodeDashboardInvalidPatch = errors.MustNewCode("dashboard_invalid_patch")
|
||||
ErrCodeDashboardMigrationFailed = errors.MustNewCode("dashboard_migration_failed")
|
||||
ErrCodeDashboardV1Deprecated = errors.MustNewCode("dashboard_deprecated")
|
||||
)
|
||||
|
||||
type StorableDashboard struct {
|
||||
@@ -38,31 +31,8 @@ type StorableDashboard struct {
|
||||
Name string `bun:"name,type:text,notnull"`
|
||||
}
|
||||
|
||||
type Dashboard struct {
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
|
||||
ID string `json:"id"`
|
||||
Data StorableDashboardData `json:"data"`
|
||||
Locked bool `json:"locked"`
|
||||
OrgID valuer.UUID `json:"org_id"`
|
||||
Source Source `json:"source"`
|
||||
}
|
||||
|
||||
type LockUnlockDashboard struct {
|
||||
Locked *bool `json:"locked"`
|
||||
}
|
||||
|
||||
type (
|
||||
StorableDashboardData map[string]interface{}
|
||||
|
||||
GettableDashboard = Dashboard
|
||||
|
||||
UpdatableDashboard = StorableDashboardData
|
||||
|
||||
PostableDashboard = StorableDashboardData
|
||||
|
||||
ListableDashboard []*GettableDashboard
|
||||
StorableDashboardData map[string]any
|
||||
)
|
||||
|
||||
// readString reads a string field from the untyped data blob, yielding "" when
|
||||
@@ -72,135 +42,6 @@ func (d StorableDashboardData) readString(key string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func NewStorableDashboardFromDashboard(dashboard *Dashboard) (*StorableDashboard, error) {
|
||||
dashboardID, err := valuer.NewUUID(dashboard.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid")
|
||||
}
|
||||
|
||||
if !dashboard.Source.IsValid() {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardInvalidSource, "invalid dashboard source %q, must be one of user, system, integration", dashboard.Source.StringValue())
|
||||
}
|
||||
|
||||
return &StorableDashboard{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: dashboardID,
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: dashboard.CreatedAt,
|
||||
UpdatedAt: dashboard.UpdatedAt,
|
||||
},
|
||||
UserAuditable: types.UserAuditable{
|
||||
CreatedBy: dashboard.CreatedBy,
|
||||
UpdatedBy: dashboard.UpdatedBy,
|
||||
},
|
||||
OrgID: dashboard.OrgID,
|
||||
Data: dashboard.Data,
|
||||
Locked: dashboard.Locked,
|
||||
Source: dashboard.Source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDashboard(orgID valuer.UUID, createdBy string, source Source, storableDashboardData StorableDashboardData) (*Dashboard, error) {
|
||||
if !source.IsValid() {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardInvalidSource, "invalid dashboard source %q, must be one of user, system, integration", source.StringValue())
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
return &Dashboard{
|
||||
ID: valuer.GenerateUUID().StringValue(),
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: currentTime,
|
||||
UpdatedAt: currentTime,
|
||||
},
|
||||
UserAuditable: types.UserAuditable{
|
||||
CreatedBy: createdBy,
|
||||
UpdatedBy: createdBy,
|
||||
},
|
||||
OrgID: orgID,
|
||||
Data: storableDashboardData,
|
||||
Locked: source == SourceIntegration,
|
||||
Source: source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) *Dashboard {
|
||||
return &Dashboard{
|
||||
ID: storableDashboard.ID.StringValue(),
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: storableDashboard.CreatedAt,
|
||||
UpdatedAt: storableDashboard.UpdatedAt,
|
||||
},
|
||||
UserAuditable: types.UserAuditable{
|
||||
CreatedBy: storableDashboard.CreatedBy,
|
||||
UpdatedBy: storableDashboard.UpdatedBy,
|
||||
},
|
||||
OrgID: storableDashboard.OrgID,
|
||||
Data: storableDashboard.Data,
|
||||
Locked: storableDashboard.Locked,
|
||||
Source: storableDashboard.Source,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDashboardsFromStorableDashboards(storableDashboards []*StorableDashboard) []*Dashboard {
|
||||
dashboards := make([]*Dashboard, len(storableDashboards))
|
||||
for idx, storableDashboard := range storableDashboards {
|
||||
dashboards[idx] = NewDashboardFromStorableDashboard(storableDashboard)
|
||||
}
|
||||
|
||||
return dashboards
|
||||
}
|
||||
|
||||
func NewGettableDashboardsFromDashboards(dashboards []*Dashboard) ([]*GettableDashboard, error) {
|
||||
gettableDashboards := make([]*GettableDashboard, len(dashboards))
|
||||
for idx, dashboard := range dashboards {
|
||||
gettableDashboard, err := NewGettableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gettableDashboards[idx] = gettableDashboard
|
||||
}
|
||||
return gettableDashboards, nil
|
||||
}
|
||||
|
||||
func NewGettableDashboardFromDashboard(dashboard *Dashboard) (*GettableDashboard, error) {
|
||||
return &GettableDashboard{
|
||||
ID: dashboard.ID,
|
||||
TimeAuditable: dashboard.TimeAuditable,
|
||||
UserAuditable: dashboard.UserAuditable,
|
||||
OrgID: dashboard.OrgID,
|
||||
Data: dashboard.Data,
|
||||
Locked: dashboard.Locked,
|
||||
Source: dashboard.Source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (storableDashboardData *StorableDashboardData) GetWidgetIds() []string {
|
||||
data := *storableDashboardData
|
||||
widgetIds := []string{}
|
||||
if data != nil && data["widgets"] != nil {
|
||||
widgets, ok := data["widgets"]
|
||||
if ok {
|
||||
data, ok := widgets.([]interface{})
|
||||
if ok {
|
||||
for _, widget := range data {
|
||||
sData, ok := widget.(map[string]interface{})
|
||||
if ok && sData["query"] != nil && sData["id"] != nil {
|
||||
id, ok := sData["id"].(string)
|
||||
|
||||
if ok {
|
||||
widgetIds = append(widgetIds, id)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return widgetIds
|
||||
}
|
||||
|
||||
// ErrIfNotDeletable gates deletion on the columns alone, never on Data, so a
|
||||
// dashboard whose data is corrupt or stuck on the v1 schema stays deletable.
|
||||
func (storable StorableDashboard) ErrIfNotDeletable() error {
|
||||
@@ -212,247 +53,3 @@ func (storable StorableDashboard) ErrIfNotDeletable() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) ErrIfNotMutable() error {
|
||||
if dashboard.Source == SourceIntegration {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "integration dashboards cannot be modified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) ErrIfNotDeletable() error {
|
||||
if err := dashboard.ErrIfNotMutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dashboard.Source == SourceSystem {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "system dashboards cannot be deleted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) ErrIfNotLockable() error {
|
||||
if err := dashboard.ErrIfNotMutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dashboard.Source == SourceSystem {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "system dashboards cannot be locked or unlocked")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) ErrIfNotPublishable() error {
|
||||
if err := dashboard.ErrIfNotMutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dashboard.Source == SourceSystem {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "system dashboards cannot be made public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) CanUpdate(ctx context.Context, data StorableDashboardData, diff int) error {
|
||||
if dashboard.Locked {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot update a locked dashboard, please unlock the dashboard to update")
|
||||
}
|
||||
|
||||
existingIDs := dashboard.Data.GetWidgetIds()
|
||||
newIDs := data.GetWidgetIds()
|
||||
newIdsMap := make(map[string]bool)
|
||||
for _, id := range newIDs {
|
||||
newIdsMap[id] = true
|
||||
}
|
||||
|
||||
differenceMap := make(map[string]bool)
|
||||
difference := []string{}
|
||||
for _, id := range existingIDs {
|
||||
if _, found := newIdsMap[id]; !found && !differenceMap[id] {
|
||||
difference = append(difference, id)
|
||||
differenceMap[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Allow multiple decisions only if diff == 0
|
||||
if diff > 0 && len(difference) > diff {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "deleting more than one panel is not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) Update(ctx context.Context, updatableDashboard UpdatableDashboard, updatedBy string, diff int) error {
|
||||
err := dashboard.CanUpdate(ctx, updatableDashboard, diff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dashboard.UpdatedBy = updatedBy
|
||||
dashboard.UpdatedAt = time.Now()
|
||||
dashboard.Data = updatableDashboard
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) CanLockUnlock(isAdmin bool, updatedBy string) error {
|
||||
if dashboard.CreatedBy != updatedBy && !isAdmin {
|
||||
return errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "you are not authorized to lock/unlock this dashboard")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) LockUnlock(lock bool, isAdmin bool, updatedBy string) error {
|
||||
err := dashboard.CanLockUnlock(isAdmin, updatedBy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dashboard.Locked = lock
|
||||
dashboard.UpdatedBy = updatedBy
|
||||
dashboard.UpdatedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lockUnlockDashboard *LockUnlockDashboard) UnmarshalJSON(src []byte) error {
|
||||
var lockUnlock struct {
|
||||
Locked *bool `json:"lock"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(src, &lockUnlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if lockUnlock.Locked == nil {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "lock is missing in the request payload")
|
||||
}
|
||||
|
||||
lockUnlockDashboard.Locked = lockUnlock.Locked
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) GetWidgetQuery(startTime, endTime, widgetIndex uint64, logger *slog.Logger) (*querybuildertypesv5.QueryRangeRequest, error) {
|
||||
type dashboardData struct {
|
||||
Widgets []struct {
|
||||
PanelTypes string `json:"panelTypes"`
|
||||
Query struct {
|
||||
Builder struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
} `json:"builder"`
|
||||
ClickhouseSQL []map[string]any `json:"clickhouse_sql"`
|
||||
PromQL []map[string]any `json:"promql"`
|
||||
QueryType string `json:"queryType"`
|
||||
} `json:"query"`
|
||||
FillGaps bool `json:"fillSpans"`
|
||||
} `json:"widgets"`
|
||||
}
|
||||
|
||||
dataJSON, err := json.Marshal(dashboard.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
var data dashboardData
|
||||
err = json.Unmarshal(dataJSON, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
if int(widgetIndex) >= len(data.Widgets) {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardInvalidInput, "widget with index %v doesn't exist", widgetIndex)
|
||||
}
|
||||
|
||||
compositeQueries := []any{}
|
||||
widgetData := data.Widgets[widgetIndex]
|
||||
switch widgetData.Query.QueryType {
|
||||
case "builder":
|
||||
for _, query := range widgetData.Query.Builder.QueryData {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_query"))
|
||||
}
|
||||
for _, query := range widgetData.Query.Builder.QueryFormulas {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_formula"))
|
||||
}
|
||||
for _, query := range widgetData.Query.Builder.QueryTraceOperator {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, querybuildertypesv5.WrapInV5Envelope(queryName, query, "builder_trace_operator"))
|
||||
}
|
||||
case "clickhouse_sql":
|
||||
for _, query := range widgetData.Query.ClickhouseSQL {
|
||||
envelope := map[string]any{
|
||||
"type": "clickhouse_sql",
|
||||
"spec": map[string]any{
|
||||
"name": query["name"],
|
||||
"query": query["query"],
|
||||
"disabled": query["disabled"],
|
||||
"legend": query["legend"],
|
||||
},
|
||||
}
|
||||
compositeQueries = append(compositeQueries, envelope)
|
||||
}
|
||||
case "promql":
|
||||
for _, query := range widgetData.Query.PromQL {
|
||||
envelope := map[string]any{
|
||||
"type": "promql",
|
||||
"spec": map[string]any{
|
||||
"name": query["name"],
|
||||
"query": query["query"],
|
||||
"disabled": query["disabled"],
|
||||
"legend": query["legend"],
|
||||
},
|
||||
}
|
||||
compositeQueries = append(compositeQueries, envelope)
|
||||
}
|
||||
}
|
||||
|
||||
queryRangeReq := map[string]any{
|
||||
"schemaVersion": "v1",
|
||||
"start": startTime,
|
||||
"end": endTime,
|
||||
"requestType": dashboard.getQueryRequestTypeFromPanelType(widgetData.PanelTypes),
|
||||
"compositeQuery": map[string]any{
|
||||
"queries": compositeQueries,
|
||||
},
|
||||
"formatOptions": map[string]any{
|
||||
"fillGaps": widgetData.FillGaps,
|
||||
"formatTableResultForUI": widgetData.PanelTypes == "table",
|
||||
},
|
||||
}
|
||||
|
||||
req, err := json.Marshal(queryRangeReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "invalid query request")
|
||||
}
|
||||
|
||||
queryRangeRequest := new(querybuildertypesv5.QueryRangeRequest)
|
||||
err = json.Unmarshal(req, queryRangeRequest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "invalid query request")
|
||||
}
|
||||
|
||||
return queryRangeRequest, nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) getQueryRequestTypeFromPanelType(panelType string) querybuildertypesv5.RequestType {
|
||||
switch panelType {
|
||||
case "graph", "bar":
|
||||
return querybuildertypesv5.RequestTypeTimeSeries
|
||||
case "table", "pie", "value":
|
||||
return querybuildertypesv5.RequestTypeScalar
|
||||
case "trace":
|
||||
return querybuildertypesv5.RequestTypeTrace
|
||||
case "list":
|
||||
return querybuildertypesv5.RequestTypeRaw
|
||||
case "histogram":
|
||||
return querybuildertypesv5.RequestTypeDistribution
|
||||
}
|
||||
|
||||
return querybuildertypesv5.RequestTypeUnknown
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -20,69 +19,6 @@ func makeTestWidgets(ids ...string) []interface{} {
|
||||
return widgets
|
||||
}
|
||||
|
||||
func TestCanUpdate_MultipleDeletions_ByDiff(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
diff int
|
||||
updated []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "diff-0-allows-multi-delete",
|
||||
diff: 0,
|
||||
updated: []string{"a"}, // deleting 2 widgets (b, c)
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "diff-1-blocks-multi-delete",
|
||||
diff: 1,
|
||||
updated: []string{"a"}, // deleting 2 widgets (b, c) > diff(1)
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "diff-1-allows-single-delete",
|
||||
diff: 1,
|
||||
updated: []string{"a", "b"}, // deleting 1 widget (c) = diff(1)
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "diff-2-allows-two-deletions",
|
||||
diff: 2,
|
||||
updated: []string{"a"}, // deleting 2 widgets (b, c) = diff(2)
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "diff-1-blocks-three-deletions",
|
||||
diff: 1,
|
||||
updated: []string{}, // deleting all 3 widgets > diff(1)
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
orgID := valuer.GenerateUUID()
|
||||
initial := StorableDashboardData{
|
||||
"widgets": makeTestWidgets("a", "b", "c"),
|
||||
}
|
||||
d, err := NewDashboard(orgID, "tester", SourceUser, initial)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updated := StorableDashboardData{
|
||||
"widgets": makeTestWidgets(tc.updated...),
|
||||
}
|
||||
err = d.CanUpdate(ctx, updated, tc.diff)
|
||||
if tc.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorableDashboardErrIfNotDeletable(t *testing.T) {
|
||||
testCases := []struct {
|
||||
subtestName string
|
||||
|
||||
@@ -95,6 +95,16 @@ func (d *DashboardV2) ErrIfNotUpdatable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *DashboardV2) ErrIfNotPublishable() error {
|
||||
if err := dashboard.ErrIfNotMutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if dashboard.Source == SourceSystem {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "system dashboards cannot be made public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DashboardV2) Update(updatable UpdatableDashboardV2, updatedBy string, resolvedTags []*tagtypes.Tag) error {
|
||||
if err := d.ErrIfNotUpdatable(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -53,11 +53,6 @@ type UpdatablePublicDashboard struct {
|
||||
DefaultTimeRange string `json:"defaultTimeRange"`
|
||||
}
|
||||
|
||||
type GettablePublicDashboardData struct {
|
||||
Dashboard *Dashboard `json:"dashboard"`
|
||||
PublicDashboard *GettablePublicDasbhboard `json:"publicDashboard"`
|
||||
}
|
||||
|
||||
func NewPublicDashboard(timeRangeEnabled bool, defaultTimeRange string, dashboardID valuer.UUID) *PublicDashboard {
|
||||
return &PublicDashboard{
|
||||
Identifiable: types.Identifiable{
|
||||
@@ -101,108 +96,6 @@ func NewGettablePublicDashboard(publicDashboard *PublicDashboard) *GettablePubli
|
||||
}
|
||||
}
|
||||
|
||||
func NewPublicDashboardDataFromDashboard(dashboard *Dashboard, publicDashboard *PublicDashboard) (*GettablePublicDashboardData, error) {
|
||||
type dashboardData struct {
|
||||
Widgets []struct {
|
||||
PanelTypes string `json:"panelTypes"`
|
||||
Query struct {
|
||||
Builder struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
} `json:"builder"`
|
||||
ClickhouseSQL []map[string]any `json:"clickhouse_sql"`
|
||||
PromQL []map[string]any `json:"promql"`
|
||||
QueryType string `json:"queryType"`
|
||||
} `json:"query"`
|
||||
} `json:"widgets"`
|
||||
}
|
||||
|
||||
dataJSON, err := json.Marshal(dashboard.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
var data dashboardData
|
||||
err = json.Unmarshal(dataJSON, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
for idx, widget := range data.Widgets {
|
||||
updatedQueryData := []map[string]any{}
|
||||
for _, queryData := range widget.Query.Builder.QueryData {
|
||||
updatedQueryMap := map[string]any{}
|
||||
updatedQueryMap["aggregations"] = queryData["aggregations"]
|
||||
updatedQueryMap["legend"] = queryData["legend"]
|
||||
updatedQueryMap["queryName"] = queryData["queryName"]
|
||||
updatedQueryMap["expression"] = queryData["expression"]
|
||||
updatedQueryMap["groupBy"] = queryData["groupBy"]
|
||||
updatedQueryMap["dataSource"] = queryData["dataSource"]
|
||||
updatedQueryData = append(updatedQueryData, updatedQueryMap)
|
||||
}
|
||||
widget.Query.Builder.QueryData = updatedQueryData
|
||||
|
||||
updatedQueryFormulas := []map[string]any{}
|
||||
for _, queryFormula := range widget.Query.Builder.QueryFormulas {
|
||||
updatedQueryFormulaMap := map[string]any{}
|
||||
updatedQueryFormulaMap["legend"] = queryFormula["legend"]
|
||||
updatedQueryFormulaMap["queryName"] = queryFormula["queryName"]
|
||||
updatedQueryFormulaMap["expression"] = queryFormula["expression"]
|
||||
updatedQueryFormulas = append(updatedQueryFormulas, updatedQueryFormulaMap)
|
||||
}
|
||||
widget.Query.Builder.QueryFormulas = updatedQueryFormulas
|
||||
|
||||
updatedQueryTraceOperator := []map[string]any{}
|
||||
for _, queryTraceOperator := range widget.Query.Builder.QueryTraceOperator {
|
||||
updatedQueryTraceOperatorMap := map[string]any{}
|
||||
updatedQueryTraceOperatorMap["aggregations"] = queryTraceOperator["aggregations"]
|
||||
updatedQueryTraceOperatorMap["legend"] = queryTraceOperator["legend"]
|
||||
updatedQueryTraceOperatorMap["queryName"] = queryTraceOperator["queryName"]
|
||||
updatedQueryTraceOperatorMap["expression"] = queryTraceOperator["expression"]
|
||||
updatedQueryTraceOperatorMap["groupBy"] = queryTraceOperator["groupBy"]
|
||||
updatedQueryTraceOperatorMap["dataSource"] = queryTraceOperator["dataSource"]
|
||||
updatedQueryTraceOperator = append(updatedQueryTraceOperator, updatedQueryTraceOperatorMap)
|
||||
}
|
||||
widget.Query.Builder.QueryTraceOperator = updatedQueryTraceOperator
|
||||
|
||||
updatedClickhouseSQLQuery := []map[string]any{}
|
||||
for _, clickhouseSQLQuery := range widget.Query.ClickhouseSQL {
|
||||
updatedClickhouseSQLQueryMap := make(map[string]any)
|
||||
updatedClickhouseSQLQueryMap["legend"] = clickhouseSQLQuery["legend"]
|
||||
updatedClickhouseSQLQueryMap["name"] = clickhouseSQLQuery["name"]
|
||||
updatedClickhouseSQLQuery = append(updatedClickhouseSQLQuery, updatedClickhouseSQLQueryMap)
|
||||
}
|
||||
widget.Query.ClickhouseSQL = updatedClickhouseSQLQuery
|
||||
|
||||
updatedPromQLQuery := []map[string]any{}
|
||||
for _, promQLQuery := range widget.Query.PromQL {
|
||||
updatedPromQLQueryMap := make(map[string]any)
|
||||
updatedPromQLQueryMap["legend"] = promQLQuery["legend"]
|
||||
updatedPromQLQueryMap["name"] = promQLQuery["name"]
|
||||
updatedPromQLQuery = append(updatedPromQLQuery, updatedPromQLQueryMap)
|
||||
}
|
||||
widget.Query.PromQL = updatedPromQLQuery
|
||||
|
||||
if widgets, ok := dashboard.Data["widgets"].([]any); ok {
|
||||
if widgetMap, ok := widgets[idx].(map[string]any); ok {
|
||||
widgetMap["query"] = widget.Query
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &GettablePublicDashboardData{
|
||||
Dashboard: &Dashboard{
|
||||
Data: dashboard.Data,
|
||||
},
|
||||
PublicDashboard: &GettablePublicDasbhboard{
|
||||
TimeRangeEnabled: publicDashboard.TimeRangeEnabled,
|
||||
DefaultTimeRange: publicDashboard.DefaultTimeRange,
|
||||
PublicPath: publicDashboard.PublicPath(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (typ *PublicDashboard) Update(timeRangeEnabled bool, defaultTimeRange string) {
|
||||
typ.TimeRangeEnabled = timeRangeEnabled
|
||||
typ.DefaultTimeRange = defaultTimeRange
|
||||
|
||||
@@ -50,21 +50,17 @@ func TestErrIfNotMutable_BySource(t *testing.T) {
|
||||
cases := []struct {
|
||||
source Source
|
||||
mutable bool
|
||||
deletable bool
|
||||
lockable bool
|
||||
publishable bool
|
||||
}{
|
||||
{SourceUser, true, true, true, true},
|
||||
{SourceSystem, true, false, false, false},
|
||||
{SourceIntegration, false, false, false, false},
|
||||
{SourceUser, true, true},
|
||||
{SourceSystem, true, false},
|
||||
{SourceIntegration, false, false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.source.StringValue(), func(t *testing.T) {
|
||||
d := &Dashboard{Source: tc.source}
|
||||
d := &DashboardV2{Source: tc.source}
|
||||
assert.Equal(t, tc.mutable, d.ErrIfNotMutable() == nil)
|
||||
assert.Equal(t, tc.deletable, d.ErrIfNotDeletable() == nil)
|
||||
assert.Equal(t, tc.lockable, d.ErrIfNotLockable() == nil)
|
||||
assert.Equal(t, tc.publishable, d.ErrIfNotPublishable() == nil)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package dashboardtypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/errors"
|
||||
|
||||
// V2DashboardAPIDocsLink documents the v2 dashboard API — its endpoints and request bodies.
|
||||
const V2DashboardAPIDocsLink = "https://signoz.io/docs/dashboards/dashboards-v2-api/"
|
||||
|
||||
// NewV1DeprecatedError builds the error a deprecated v1 dashboard endpoint returns.
|
||||
// useInstead names the v2 endpoint to call; the docs link is appended for the details.
|
||||
func NewV1DeprecatedError(useInstead string) error {
|
||||
return errors.Newf(errors.TypeUnsupported, ErrCodeDashboardV1Deprecated,
|
||||
"the v1 dashboard API is deprecated; instead, %s. See %s", useInstead, V2DashboardAPIDocsLink)
|
||||
}
|
||||
@@ -240,19 +240,6 @@ type MetricAlertsResponse struct {
|
||||
Alerts []MetricAlert `json:"alerts" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
// MetricDashboard represents a dashboard/widget referencing a metric.
|
||||
type MetricDashboard struct {
|
||||
DashboardName string `json:"dashboardName" required:"true"`
|
||||
DashboardID string `json:"dashboardId" required:"true"`
|
||||
WidgetID string `json:"widgetId" required:"true"`
|
||||
WidgetName string `json:"widgetName" required:"true"`
|
||||
}
|
||||
|
||||
// MetricDashboardsResponse represents the response for metric dashboards endpoint.
|
||||
type MetricDashboardsResponse struct {
|
||||
Dashboards []MetricDashboard `json:"dashboards" required:"true" nullable:"true"`
|
||||
}
|
||||
|
||||
// MetricDashboardPanelsResponse is the response for the v2 metric dashboards
|
||||
// endpoint: the dashboard panels that reference the metric.
|
||||
type MetricDashboardPanelsResponse struct {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
|
||||
from fixtures.types import Operation, SigNoz, TestContainerDocker
|
||||
|
||||
DOCS_LINK = "https://signoz.io/docs/dashboards/dashboards-v2-api/"
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
add_license(signoz, make_http_mocks, get_token)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"deprecated_request",
|
||||
[
|
||||
("POST", "/api/v1/dashboards", {"title": "Sample Title", "uploadedGrafana": False, "version": "v5"}),
|
||||
("GET", "/api/v1/dashboards", None),
|
||||
("GET", f"/api/v1/dashboards/{uuid.uuid4()}", None),
|
||||
("PUT", f"/api/v1/dashboards/{uuid.uuid4()}", {"title": "Sample Title"}),
|
||||
("DELETE", f"/api/v1/dashboards/{uuid.uuid4()}", None),
|
||||
("PUT", f"/api/v1/dashboards/{uuid.uuid4()}/lock", {"lock": True}),
|
||||
],
|
||||
)
|
||||
def test_v1_dashboard_endpoints_are_deprecated(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
deprecated_request: tuple[str, str, dict | None],
|
||||
):
|
||||
"""Every v1 dashboard endpoint is superseded by v2 and answers with a
|
||||
deprecation error naming its replacement — the id in the path is never
|
||||
resolved, so a random one still gets the deprecation error rather than a 404."""
|
||||
method, path, body = deprecated_request
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.request(
|
||||
method,
|
||||
signoz.self.host_configs["8080"].get(path),
|
||||
json=body,
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.NOT_IMPLEMENTED, response.text
|
||||
error = response.json()["error"]
|
||||
assert error["code"] == "dashboard_deprecated"
|
||||
assert "the v1 dashboard API is deprecated" in error["message"]
|
||||
assert "/api/v2/dashboards" in error["message"]
|
||||
assert DOCS_LINK in error["message"]
|
||||
Reference in New Issue
Block a user