mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-05 18:10:31 +01:00
Compare commits
6 Commits
postproces
...
feat/overv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef6a13a5da | ||
|
|
5fb008351e | ||
|
|
09836a4487 | ||
|
|
285cfd4c01 | ||
|
|
e46733036a | ||
|
|
fc55bbf5a5 |
@@ -2220,6 +2220,8 @@ components:
|
||||
type: boolean
|
||||
org_id:
|
||||
type: string
|
||||
source:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -2251,6 +2253,7 @@ components:
|
||||
type: object
|
||||
DashboardtypesStorableDashboardData:
|
||||
additionalProperties: {}
|
||||
nullable: true
|
||||
type: object
|
||||
DashboardtypesUpdatablePublicDashboard:
|
||||
properties:
|
||||
@@ -5321,9 +5324,6 @@ components:
|
||||
sub_tree_node_count:
|
||||
minimum: 0
|
||||
type: integer
|
||||
time_unix:
|
||||
minimum: 0
|
||||
type: integer
|
||||
trace_id:
|
||||
type: string
|
||||
trace_state:
|
||||
@@ -10600,6 +10600,186 @@ paths:
|
||||
summary: Update a span mapper
|
||||
tags:
|
||||
- spanmapper
|
||||
/api/v1/system/{source}:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns the system dashboard for the callers org
|
||||
keyed by source (e.g. ai-o11y-overview).
|
||||
operationId: GetSystemDashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: source
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesDashboard'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get system dashboard
|
||||
tags:
|
||||
- systemdashboard
|
||||
put:
|
||||
deprecated: false
|
||||
description: This endpoint replaces the system dashboard for the callers org
|
||||
with the provided payload.
|
||||
operationId: UpdateSystemDashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: source
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardtypesStorableDashboardData'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesDashboard'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
summary: Update system dashboard
|
||||
tags:
|
||||
- systemdashboard
|
||||
/api/v1/system/{source}/reset:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This resets edited/updated system dashboard to default system dashboard.
|
||||
operationId: ResetSystemDashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: source
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/DashboardtypesDashboard'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
summary: Reset system dashboard to defaults
|
||||
tags:
|
||||
- systemdashboard
|
||||
/api/v1/testChannel:
|
||||
post:
|
||||
deprecated: true
|
||||
|
||||
@@ -4196,6 +4196,10 @@ export interface DashboardtypesDashboardDTO {
|
||||
* @type string
|
||||
*/
|
||||
org_id?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -4238,9 +4242,12 @@ export interface DashboardtypesPostablePublicDashboardDTO {
|
||||
timeRangeEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardtypesStorableDashboardDataDTO {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesStorableDashboardDataDTO = {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
} | null;
|
||||
|
||||
export interface DashboardtypesUpdatablePublicDashboardDTO {
|
||||
/**
|
||||
@@ -7714,11 +7721,6 @@ export interface TracedetailtypesWaterfallSpanDTO {
|
||||
* @minimum 0
|
||||
*/
|
||||
sub_tree_node_count?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
time_unix?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -8970,6 +8972,39 @@ export type UpdateSpanMapperPathParameters = {
|
||||
groupId: string;
|
||||
mapperId: string;
|
||||
};
|
||||
export type GetSystemDashboardPathParameters = {
|
||||
source: string;
|
||||
};
|
||||
export type GetSystemDashboard200 = {
|
||||
data: DashboardtypesDashboardDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UpdateSystemDashboardPathParameters = {
|
||||
source: string;
|
||||
};
|
||||
export type UpdateSystemDashboard200 = {
|
||||
data: DashboardtypesDashboardDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ResetSystemDashboardPathParameters = {
|
||||
source: string;
|
||||
};
|
||||
export type ResetSystemDashboard200 = {
|
||||
data: DashboardtypesDashboardDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListUsersDeprecated200 = {
|
||||
/**
|
||||
* @type array
|
||||
|
||||
318
frontend/src/api/generated/services/systemdashboard/index.ts
Normal file
318
frontend/src/api/generated/services/systemdashboard/index.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
DashboardtypesStorableDashboardDataDTO,
|
||||
GetSystemDashboard200,
|
||||
GetSystemDashboardPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
ResetSystemDashboard200,
|
||||
ResetSystemDashboardPathParameters,
|
||||
UpdateSystemDashboard200,
|
||||
UpdateSystemDashboardPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* This endpoint returns the system dashboard for the callers org keyed by source (e.g. ai-o11y-overview).
|
||||
* @summary Get system dashboard
|
||||
*/
|
||||
export const getSystemDashboard = (
|
||||
{ source }: GetSystemDashboardPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetSystemDashboard200>({
|
||||
url: `/api/v1/system/${source}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetSystemDashboardQueryKey = ({
|
||||
source,
|
||||
}: GetSystemDashboardPathParameters) => {
|
||||
return [`/api/v1/system/${source}`] as const;
|
||||
};
|
||||
|
||||
export const getGetSystemDashboardQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getSystemDashboard>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ source }: GetSystemDashboardPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSystemDashboard>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetSystemDashboardQueryKey({ source });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getSystemDashboard>>
|
||||
> = ({ signal }) => getSystemDashboard({ source }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!source,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSystemDashboard>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetSystemDashboardQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getSystemDashboard>>
|
||||
>;
|
||||
export type GetSystemDashboardQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get system dashboard
|
||||
*/
|
||||
|
||||
export function useGetSystemDashboard<
|
||||
TData = Awaited<ReturnType<typeof getSystemDashboard>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ source }: GetSystemDashboardPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSystemDashboard>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetSystemDashboardQueryOptions({ source }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get system dashboard
|
||||
*/
|
||||
export const invalidateGetSystemDashboard = async (
|
||||
queryClient: QueryClient,
|
||||
{ source }: GetSystemDashboardPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetSystemDashboardQueryKey({ source }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint replaces the system dashboard for the callers org with the provided payload.
|
||||
* @summary Update system dashboard
|
||||
*/
|
||||
export const updateSystemDashboard = (
|
||||
{ source }: UpdateSystemDashboardPathParameters,
|
||||
dashboardtypesStorableDashboardDataDTO: BodyType<DashboardtypesStorableDashboardDataDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateSystemDashboard200>({
|
||||
url: `/api/v1/system/${source}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: dashboardtypesStorableDashboardDataDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateSystemDashboardMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSystemDashboardPathParameters;
|
||||
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSystemDashboardPathParameters;
|
||||
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateSystemDashboard'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>,
|
||||
{
|
||||
pathParams: UpdateSystemDashboardPathParameters;
|
||||
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateSystemDashboard(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateSystemDashboardMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>
|
||||
>;
|
||||
export type UpdateSystemDashboardMutationBody =
|
||||
BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
export type UpdateSystemDashboardMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update system dashboard
|
||||
*/
|
||||
export const useUpdateSystemDashboard = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSystemDashboardPathParameters;
|
||||
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateSystemDashboard>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSystemDashboardPathParameters;
|
||||
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateSystemDashboardMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This resets edited/updated system dashboard to default system dashboard.
|
||||
* @summary Reset system dashboard to defaults
|
||||
*/
|
||||
export const resetSystemDashboard = (
|
||||
{ source }: ResetSystemDashboardPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ResetSystemDashboard200>({
|
||||
url: `/api/v1/system/${source}/reset`,
|
||||
method: 'POST',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getResetSystemDashboardMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>,
|
||||
TError,
|
||||
{ pathParams: ResetSystemDashboardPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>,
|
||||
TError,
|
||||
{ pathParams: ResetSystemDashboardPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['resetSystemDashboard'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>,
|
||||
{ pathParams: ResetSystemDashboardPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return resetSystemDashboard(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ResetSystemDashboardMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>
|
||||
>;
|
||||
|
||||
export type ResetSystemDashboardMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Reset system dashboard to defaults
|
||||
*/
|
||||
export const useResetSystemDashboard = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>,
|
||||
TError,
|
||||
{ pathParams: ResetSystemDashboardPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof resetSystemDashboard>>,
|
||||
TError,
|
||||
{ pathParams: ResetSystemDashboardPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getResetSystemDashboardMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogBody } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@@ -217,17 +216,20 @@ function LogDetailInner({
|
||||
|
||||
const logBody = useMemo(() => {
|
||||
if (!isBodyJsonQueryEnabled) {
|
||||
return (log?.body as string) ?? '';
|
||||
return log?.body || '';
|
||||
}
|
||||
// Feature enabled: body is always a map; message is always a string
|
||||
const bodyObj = log?.body as ILogBody;
|
||||
if (!bodyObj) {
|
||||
return '';
|
||||
|
||||
try {
|
||||
const json = JSON.parse(log?.body || '');
|
||||
|
||||
if (typeof json?.message === 'string' && json.message !== '') {
|
||||
return json.message;
|
||||
}
|
||||
|
||||
return log?.body || '';
|
||||
} catch (error) {
|
||||
return log?.body || '';
|
||||
}
|
||||
if (bodyObj.message) {
|
||||
return bodyObj.message;
|
||||
}
|
||||
return JSON.stringify(bodyObj);
|
||||
}, [isBodyJsonQueryEnabled, log?.body]);
|
||||
|
||||
const htmlBody = useMemo(
|
||||
|
||||
@@ -9,10 +9,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Tooltip } from 'antd';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
// hooks
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@@ -102,7 +99,7 @@ function RawLogView({
|
||||
// Check if body is selected
|
||||
const showBody = selectedFields.some((field) => field.name === 'body');
|
||||
if (showBody) {
|
||||
parts.push(`${attributesText} ${getBodyDisplayString(data.body)}`);
|
||||
parts.push(`${attributesText} ${data.body}`);
|
||||
} else {
|
||||
parts.push(attributesText);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@ import type { ReactElement } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
@@ -90,7 +87,7 @@ export function useLogsTableColumns({
|
||||
? {
|
||||
id: 'body',
|
||||
header: 'Body',
|
||||
accessorFn: (log): string => getBodyDisplayString(log.body),
|
||||
accessorFn: (log): string => log.body,
|
||||
canBeHidden: false,
|
||||
width: { default: '100%', min: 300 },
|
||||
cell: ({ value, isActive }): ReactElement => (
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { isArray } from 'lodash-es';
|
||||
import { getBodyDisplayString } from 'container/LogDetailedView/utils';
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -174,7 +173,7 @@ export default function Events({
|
||||
(event): EventDataType => ({
|
||||
timestamp: event.timestamp,
|
||||
severity: event.data.severity_text,
|
||||
body: getBodyDisplayString(event.data.body),
|
||||
body: event.data.body,
|
||||
id: event.data.id,
|
||||
key: event.data.id,
|
||||
resources_string: event.data.resources_string,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import TableView from './TableView';
|
||||
import { getBodyDisplayString, removeEscapeCharacters } from './utils';
|
||||
import { removeEscapeCharacters } from './utils';
|
||||
|
||||
import './Overview.styles.scss';
|
||||
|
||||
@@ -112,7 +112,7 @@ function Overview({
|
||||
children: (
|
||||
<div className="logs-body-content">
|
||||
<MEditor
|
||||
value={removeEscapeCharacters(getBodyDisplayString(logData.body))}
|
||||
value={removeEscapeCharacters(logData.body)}
|
||||
language="json"
|
||||
options={options}
|
||||
onChange={(): void => {}}
|
||||
|
||||
@@ -10,7 +10,7 @@ const MAX_BODY_BYTES = 100 * 1024; // 100 KB
|
||||
|
||||
// Hook for async JSON processing
|
||||
const useAsyncJSONProcessing = (
|
||||
value: string | Record<string, unknown>,
|
||||
value: string,
|
||||
shouldProcess: boolean,
|
||||
handleChangeSelectedView?: ChangeViewFunctionType,
|
||||
): {
|
||||
@@ -40,17 +40,11 @@ const useAsyncJSONProcessing = (
|
||||
return (): void => {};
|
||||
}
|
||||
|
||||
// When value is already a parsed object skip the size check and JSON parsing
|
||||
const parseBody = (): Record<string, unknown> | null => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
const byteSize = new Blob([value as string]).size;
|
||||
if (byteSize > MAX_BODY_BYTES) {
|
||||
return null;
|
||||
}
|
||||
return recursiveParseJSON(value as string);
|
||||
};
|
||||
// Avoid processing if the json is too large
|
||||
const byteSize = new Blob([value]).size;
|
||||
if (byteSize > MAX_BODY_BYTES) {
|
||||
return (): void => {};
|
||||
}
|
||||
|
||||
processingRef.current = true;
|
||||
setJsonState({ isLoading: true, treeData: null, error: null });
|
||||
@@ -59,8 +53,8 @@ const useAsyncJSONProcessing = (
|
||||
const processAsync = (): void => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const parsedBody = parseBody();
|
||||
if (parsedBody && !isEmpty(parsedBody)) {
|
||||
const parsedBody = recursiveParseJSON(value);
|
||||
if (!isEmpty(parsedBody)) {
|
||||
const treeData = jsonToDataNodes(parsedBody, {
|
||||
isBodyJsonQueryEnabled,
|
||||
handleChangeSelectedView,
|
||||
@@ -88,8 +82,8 @@ const useAsyncJSONProcessing = (
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
(): void => {
|
||||
try {
|
||||
const parsedBody = parseBody();
|
||||
if (parsedBody && !isEmpty(parsedBody)) {
|
||||
const parsedBody = recursiveParseJSON(value);
|
||||
if (!isEmpty(parsedBody)) {
|
||||
const treeData = jsonToDataNodes(parsedBody, {
|
||||
isBodyJsonQueryEnabled,
|
||||
handleChangeSelectedView,
|
||||
|
||||
@@ -4,11 +4,7 @@ import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import dompurify from 'dompurify';
|
||||
import { uniqueId } from 'lodash-es';
|
||||
import {
|
||||
ILog,
|
||||
ILogAggregateAttributesResources,
|
||||
ILogBody,
|
||||
} from 'types/api/logs/log';
|
||||
import { ILog, ILogAggregateAttributesResources } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { FORBID_DOM_PURIFY_ATTR, FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||
|
||||
@@ -437,24 +433,3 @@ export const getSanitizedLogBody = (
|
||||
return '{}';
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a plain string for display contexts (Monaco editor, table cells, raw log row).
|
||||
export function getBodyDisplayString(body: string | ILogBody): string {
|
||||
return typeof body === 'string' ? body : JSON.stringify(body as ILogBody);
|
||||
}
|
||||
|
||||
// Returns the primary "message" text for compact log row previews.
|
||||
export function getBodyMessage(
|
||||
body: string | ILogBody,
|
||||
isBodyJsonEnabled: boolean,
|
||||
): string {
|
||||
if (!isBodyJsonEnabled) {
|
||||
return (body as string) ?? '';
|
||||
}
|
||||
// Feature enabled: body is always a map; message is always a string
|
||||
const msg = (body as ILogBody).message;
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ExpandAltOutlined } from '@ant-design/icons';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { getBodyDisplayString } from 'container/LogDetailedView/utils';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
@@ -27,9 +26,7 @@ function LogsList({ logs }: LogsListProps): JSX.Element {
|
||||
DATE_TIME_FORMATS.UTC_MONTH_SHORT,
|
||||
)}
|
||||
</div>
|
||||
<div className="logs-preview-list-item-body">
|
||||
{getBodyDisplayString(log.body)}
|
||||
</div>
|
||||
<div className="logs-preview-list-item-body">{log.body}</div>
|
||||
<div
|
||||
className="logs-preview-list-item-expand"
|
||||
onClick={makeLogDetailsHandler(log)}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
export interface ILogBody {
|
||||
message?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ILog {
|
||||
date: string;
|
||||
timestamp: number | string;
|
||||
@@ -13,7 +8,7 @@ export interface ILog {
|
||||
traceFlags: number;
|
||||
severityText: string;
|
||||
severityNumber: number;
|
||||
body: string | ILogBody;
|
||||
body: string;
|
||||
resources_string: Record<string, never>;
|
||||
scope_string: Record<string, never>;
|
||||
attributesString: Record<string, never>;
|
||||
|
||||
2
go.mod
2
go.mod
@@ -11,7 +11,6 @@ require (
|
||||
github.com/SigNoz/signoz-otel-collector v0.144.3
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
github.com/bytedance/sonic v1.14.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
@@ -113,6 +112,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -52,6 +53,7 @@ type provider struct {
|
||||
flaggerHandler flagger.Handler
|
||||
dashboardModule dashboard.Module
|
||||
dashboardHandler dashboard.Handler
|
||||
systemDashboardHandler systemdashboard.Handler
|
||||
metricsExplorerHandler metricsexplorer.Handler
|
||||
infraMonitoringHandler inframonitoring.Handler
|
||||
gatewayHandler gateway.Handler
|
||||
@@ -84,6 +86,7 @@ func NewFactory(
|
||||
flaggerHandler flagger.Handler,
|
||||
dashboardModule dashboard.Module,
|
||||
dashboardHandler dashboard.Handler,
|
||||
systemDashboardHandler systemdashboard.Handler,
|
||||
metricsExplorerHandler metricsexplorer.Handler,
|
||||
infraMonitoringHandler inframonitoring.Handler,
|
||||
gatewayHandler gateway.Handler,
|
||||
@@ -119,6 +122,7 @@ func NewFactory(
|
||||
flaggerHandler,
|
||||
dashboardModule,
|
||||
dashboardHandler,
|
||||
systemDashboardHandler,
|
||||
metricsExplorerHandler,
|
||||
infraMonitoringHandler,
|
||||
gatewayHandler,
|
||||
@@ -156,6 +160,7 @@ func newProvider(
|
||||
flaggerHandler flagger.Handler,
|
||||
dashboardModule dashboard.Module,
|
||||
dashboardHandler dashboard.Handler,
|
||||
systemDashboardHandler systemdashboard.Handler,
|
||||
metricsExplorerHandler metricsexplorer.Handler,
|
||||
infraMonitoringHandler inframonitoring.Handler,
|
||||
gatewayHandler gateway.Handler,
|
||||
@@ -191,6 +196,7 @@ func newProvider(
|
||||
flaggerHandler: flaggerHandler,
|
||||
dashboardModule: dashboardModule,
|
||||
dashboardHandler: dashboardHandler,
|
||||
systemDashboardHandler: systemDashboardHandler,
|
||||
metricsExplorerHandler: metricsExplorerHandler,
|
||||
infraMonitoringHandler: infraMonitoringHandler,
|
||||
gatewayHandler: gatewayHandler,
|
||||
@@ -260,6 +266,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addSystemDashboardRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addMetricsExplorerRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
66
pkg/apiserver/signozapiserver/systemdashboard.go
Normal file
66
pkg/apiserver/signozapiserver/systemdashboard.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
)
|
||||
|
||||
func (provider *provider) addSystemDashboardRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/system/{source}", handler.New(provider.authZ.ViewAccess(provider.systemDashboardHandler.Get), handler.OpenAPIDef{
|
||||
ID: "GetSystemDashboard",
|
||||
Tags: []string{"systemdashboard"},
|
||||
Summary: "Get system dashboard",
|
||||
Description: "This endpoint returns the system dashboard for the callers org keyed by source (e.g. ai-o11y-overview).",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(dashboardtypes.GettableDashboard),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/system/{source}", handler.New(provider.authZ.EditAccess(provider.systemDashboardHandler.Update), handler.OpenAPIDef{
|
||||
ID: "UpdateSystemDashboard",
|
||||
Tags: []string{"systemdashboard"},
|
||||
Summary: "Update system dashboard",
|
||||
Description: "This endpoint replaces the system dashboard for the callers org with the provided payload.",
|
||||
Request: new(dashboardtypes.UpdatableDashboard),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(dashboardtypes.GettableDashboard),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/system/{source}/reset", handler.New(provider.authZ.EditAccess(provider.systemDashboardHandler.Reset), handler.OpenAPIDef{
|
||||
ID: "ResetSystemDashboard",
|
||||
Tags: []string{"systemdashboard"},
|
||||
Summary: "Reset system dashboard to defaults",
|
||||
Description: "This resets edited/updated system dashboard to default system dashboard.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(dashboardtypes.GettableDashboard),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard)
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewStore(sqlstore sqlstore.SQLStore) dashboardtypes.Store {
|
||||
func (store *store) Create(ctx context.Context, storabledashboard *dashboardtypes.StorableDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(storabledashboard).
|
||||
Exec(ctx)
|
||||
@@ -55,6 +55,7 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
Model(storableDashboard).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("source = ?", "").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", id)
|
||||
@@ -63,6 +64,23 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return storableDashboard, nil
|
||||
}
|
||||
|
||||
func (store *store) GetBySource(ctx context.Context, orgID valuer.UUID, source string) (*dashboardtypes.StorableDashboard, error) {
|
||||
storableDashboard := new(dashboardtypes.StorableDashboard)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(storableDashboard).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("source = ?", source).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "system dashboard with source %s doesn't exist", source)
|
||||
}
|
||||
|
||||
return storableDashboard, nil
|
||||
}
|
||||
|
||||
func (store *store) GetPublic(ctx context.Context, dashboardID string) (*dashboardtypes.StorablePublicDashboard, error) {
|
||||
storable := new(dashboardtypes.StorablePublicDashboard)
|
||||
err := store.
|
||||
@@ -124,6 +142,7 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardty
|
||||
NewSelect().
|
||||
Model(&storableDashboards).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("source = ?", "").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -153,7 +172,7 @@ func (store *store) ListPublic(ctx context.Context, orgID valuer.UUID) ([]*dashb
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, storableDashboard *dashboardtypes.StorableDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(storableDashboard).
|
||||
WherePK().
|
||||
@@ -189,6 +208,7 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
Model(new(dashboardtypes.StorableDashboard)).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("source = ?", "").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", id)
|
||||
|
||||
@@ -6,18 +6,20 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type setter struct {
|
||||
store types.OrganizationStore
|
||||
alertmanager alertmanager.Alertmanager
|
||||
quickfilter quickfilter.Module
|
||||
store types.OrganizationStore
|
||||
alertmanager alertmanager.Alertmanager
|
||||
quickfilter quickfilter.Module
|
||||
systemDashboard systemdashboard.Module
|
||||
}
|
||||
|
||||
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module) organization.Setter {
|
||||
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter}
|
||||
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module, systemDashboard systemdashboard.Module) organization.Setter {
|
||||
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter, systemDashboard: systemDashboard}
|
||||
}
|
||||
|
||||
func (module *setter) Create(ctx context.Context, organization *types.Organization, createManagedRoles func(context.Context, valuer.UUID) error) error {
|
||||
@@ -33,6 +35,10 @@ func (module *setter) Create(ctx context.Context, organization *types.Organizati
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.systemDashboard.SetDefaultConfig(ctx, organization.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createManagedRoles(ctx, organization.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
99
pkg/modules/systemdashboard/implsystemdashboard/handler.go
Normal file
99
pkg/modules/systemdashboard/implsystemdashboard/handler.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package implsystemdashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module systemdashboard.Module
|
||||
}
|
||||
|
||||
func NewHandler(module systemdashboard.Module) systemdashboard.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), parseSource(r))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
data := dashboardtypes.UpdatableDashboard{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
render.Error(rw, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "data is required"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Update(ctx, valuer.MustNewUUID(claims.OrgID), parseSource(r),
|
||||
dashboardtypes.NewDashboardFromUpdatable(data, claims.Email))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
func (handler *handler) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Reset(ctx, valuer.MustNewUUID(claims.OrgID), parseSource(r), claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
// parseSource reads the {source} path segment.
|
||||
func parseSource(r *http.Request) dashboardtypes.Source {
|
||||
return dashboardtypes.Source(mux.Vars(r)["source"])
|
||||
}
|
||||
131
pkg/modules/systemdashboard/implsystemdashboard/module.go
Normal file
131
pkg/modules/systemdashboard/implsystemdashboard/module.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package implsystemdashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store dashboardtypes.Store
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store) systemdashboard.Module {
|
||||
return &module{store: store}
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source) (*dashboardtypes.Dashboard, error) {
|
||||
storable, err := module.store.GetBySource(ctx, orgID, string(source))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storable), nil
|
||||
}
|
||||
|
||||
// Update applies the new payload as last-writer-wins. The Get and Update run inside one transaction so a
|
||||
// concurrent Reset cannot interleave and leave the response with a stale id from before the reset.
|
||||
func (module *module) Update(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source, dashboard *dashboardtypes.Dashboard) (*dashboardtypes.Dashboard, error) {
|
||||
var updated *dashboardtypes.Dashboard
|
||||
err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
existing, err := module.store.GetBySource(ctx, orgID, string(source))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existing.Data = dashboard.Data
|
||||
existing.UpdatedBy = dashboard.UpdatedBy
|
||||
existing.UpdatedAt = time.Now()
|
||||
|
||||
if err := module.store.Update(ctx, orgID, existing); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updated = dashboardtypes.NewDashboardFromStorableDashboard(existing)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (module *module) Reset(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source, updatedBy string) (*dashboardtypes.Dashboard, error) {
|
||||
var reset *dashboardtypes.Dashboard
|
||||
err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
defaultDashboard, err := dashboardtypes.NewDefaultSystemDashboard(orgID, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existing, err := module.store.GetBySource(ctx, orgID, string(source))
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing == nil {
|
||||
storable, err := dashboardtypes.NewStorableDashboardFromDashboard(defaultDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := module.store.Create(ctx, storable); err != nil {
|
||||
return err
|
||||
}
|
||||
reset = defaultDashboard
|
||||
return nil
|
||||
}
|
||||
|
||||
existing.Data = defaultDashboard.Data
|
||||
existing.UpdatedBy = updatedBy
|
||||
existing.UpdatedAt = time.Now()
|
||||
|
||||
if err := module.store.Update(ctx, orgID, existing); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reset = dashboardtypes.NewDashboardFromStorableDashboard(existing)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reset, nil
|
||||
}
|
||||
|
||||
func (module *module) SetDefaultConfig(ctx context.Context, orgID valuer.UUID) error {
|
||||
for _, source := range dashboardtypes.SystemSources {
|
||||
if err := module.setDefaultForSource(ctx, orgID, source); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) setDefaultForSource(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source) error {
|
||||
existing, err := module.store.GetBySource(ctx, orgID, string(source))
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
if existing != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dashboard, err := dashboardtypes.NewDefaultSystemDashboard(orgID, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storable, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.Create(ctx, storable)
|
||||
}
|
||||
25
pkg/modules/systemdashboard/systemdashboard.go
Normal file
25
pkg/modules/systemdashboard/systemdashboard.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package systemdashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Get(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source) (*dashboardtypes.Dashboard, error)
|
||||
Update(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source, dashboard *dashboardtypes.Dashboard) (*dashboardtypes.Dashboard, error)
|
||||
Reset(ctx context.Context, orgID valuer.UUID, source dashboardtypes.Source, updatedBy string) (*dashboardtypes.Dashboard, error)
|
||||
SetDefaultConfig(ctx context.Context, orgID valuer.UUID) error
|
||||
}
|
||||
|
||||
// Handler defines the HTTP handler interface for system dashboard endpoints.
|
||||
// /api/v1/system/{source} — Get / Update
|
||||
// /api/v1/system/{source}/reset — Reset.
|
||||
type Handler interface {
|
||||
Get(http.ResponseWriter, *http.Request)
|
||||
Update(http.ResponseWriter, *http.Request)
|
||||
Reset(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
@@ -12,10 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,8 +22,6 @@ var (
|
||||
// written clickhouse query. The column alias indcate which value is
|
||||
// to be considered as final result (or target).
|
||||
legacyReservedColumnTargetAliases = []string{"__result", "__value", "result", "res", "value"}
|
||||
|
||||
CodeFailUnmarshalJSONColumn = errors.MustNewCode("fail_unmarshal_json_column")
|
||||
)
|
||||
|
||||
// consume reads every row and shapes it into the payload expected for the
|
||||
@@ -397,16 +393,11 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
|
||||
|
||||
// de-reference the typed pointer to any
|
||||
val := reflect.ValueOf(cellPtr).Elem().Interface()
|
||||
// Post-process JSON columns: unmarshal bytes into map[string]any
|
||||
// Post-process JSON columns: normalize into String value
|
||||
if strings.HasPrefix(strings.ToUpper(colTypes[i].DatabaseTypeName()), "JSON") {
|
||||
switch x := val.(type) {
|
||||
case []byte:
|
||||
var m map[string]any
|
||||
err := sonic.Unmarshal(x, &m)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, CodeFailUnmarshalJSONColumn, "failed to unmarshal JSON column %s", name)
|
||||
}
|
||||
val = m
|
||||
val = string(x)
|
||||
default:
|
||||
// already a structured type (map[string]any, []any, etc.)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,8 @@ import (
|
||||
"github.com/SigNoz/govaluate"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// queryInfo holds common query properties.
|
||||
@@ -52,7 +49,7 @@ func getQueryName(spec any) string {
|
||||
return getqueryInfo(spec).Name
|
||||
}
|
||||
|
||||
func (q *querier) postProcessResults(ctx context.Context, orgID valuer.UUID, results map[string]any, req *qbtypes.QueryRangeRequest) (map[string]any, error) {
|
||||
func (q *querier) postProcessResults(ctx context.Context, results map[string]any, req *qbtypes.QueryRangeRequest) (map[string]any, error) {
|
||||
// Convert results to typed format for processing
|
||||
typedResults := make(map[string]*qbtypes.Result)
|
||||
for name, result := range results {
|
||||
@@ -71,7 +68,6 @@ func (q *querier) postProcessResults(ctx context.Context, orgID valuer.UUID, res
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]:
|
||||
if result, ok := typedResults[spec.Name]; ok {
|
||||
result = postProcessBuilderQuery(q, result, spec, req)
|
||||
result = q.postProcessLogBody(ctx, orgID, result, req)
|
||||
typedResults[spec.Name] = result
|
||||
}
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
|
||||
@@ -1030,33 +1026,3 @@ func (q *querier) calculateFormulaStep(expression string, req *qbtypes.QueryRang
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// postProcessLogBody removes the "message" key from the body map when it is empty.
|
||||
// Only runs for raw list queries with the use_json_body feature enabled.
|
||||
func (q *querier) postProcessLogBody(ctx context.Context, orgID valuer.UUID, result *qbtypes.Result, req *qbtypes.QueryRangeRequest) *qbtypes.Result {
|
||||
if req.RequestType != qbtypes.RequestTypeRaw {
|
||||
return result
|
||||
}
|
||||
if !q.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
return result
|
||||
}
|
||||
rawData, ok := result.Value.(*qbtypes.RawData)
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
for _, row := range rawData.Rows {
|
||||
bodyMap, ok := row.Data["body"].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg, exists := bodyMap["message"]; exists {
|
||||
switch v := msg.(type) {
|
||||
case string:
|
||||
if v == "" {
|
||||
delete(bodyMap, "message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
@@ -36,7 +35,6 @@ var (
|
||||
|
||||
type querier struct {
|
||||
logger *slog.Logger
|
||||
fl flagger.Flagger
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
metadataStore telemetrytypes.MetadataStore
|
||||
promEngine prometheus.Prometheus
|
||||
@@ -64,12 +62,10 @@ func New(
|
||||
meterStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation],
|
||||
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder,
|
||||
bucketCache BucketCache,
|
||||
flagger flagger.Flagger,
|
||||
) *querier {
|
||||
querierSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/querier")
|
||||
return &querier{
|
||||
logger: querierSettings.Logger(),
|
||||
fl: flagger,
|
||||
telemetryStore: telemetryStore,
|
||||
metadataStore: metadataStore,
|
||||
promEngine: promEngine,
|
||||
@@ -688,7 +684,7 @@ func (q *querier) run(
|
||||
}
|
||||
|
||||
gomaps.Copy(results, preseededResults)
|
||||
processedResults, err := q.postProcessResults(ctx, orgID, results, req)
|
||||
processedResults, err := q.postProcessResults(ctx, results, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
cmock "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
||||
@@ -45,15 +44,14 @@ func TestQueryRange_MetricTypeMissing(t *testing.T) {
|
||||
providerSettings,
|
||||
nil, // telemetryStore
|
||||
metadataStore,
|
||||
nil, // prometheus
|
||||
nil, // traceStmtBuilder
|
||||
nil, // logStmtBuilder
|
||||
nil, // auditStmtBuilder
|
||||
nil, // metricStmtBuilder
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
nil, // prometheus
|
||||
nil, // traceStmtBuilder
|
||||
nil, // logStmtBuilder
|
||||
nil, // auditStmtBuilder
|
||||
nil, // metricStmtBuilder
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
@@ -118,7 +116,6 @@ func TestQueryRange_MetricTypeFromStore(t *testing.T) {
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
|
||||
@@ -186,6 +186,5 @@ func newProvider(
|
||||
meterStmtBuilder,
|
||||
traceOperatorStmtBuilder,
|
||||
bucketCache,
|
||||
flagger,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flagger,
|
||||
), metadataStore
|
||||
}
|
||||
|
||||
@@ -103,7 +102,6 @@ func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.Telemetry
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -148,6 +146,5 @@ func prepareQuerierForTraces(t *testing.T, telemetryStore telemetrystore.Telemet
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard/implsystemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail/impltracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
@@ -57,6 +59,7 @@ type Handlers struct {
|
||||
SavedView savedview.Handler
|
||||
Apdex apdex.Handler
|
||||
Dashboard dashboard.Handler
|
||||
SystemDashboard systemdashboard.Handler
|
||||
QuickFilter quickfilter.Handler
|
||||
TraceFunnel tracefunnel.Handler
|
||||
RawDataExport rawdataexport.Handler
|
||||
@@ -102,6 +105,7 @@ func NewHandlers(
|
||||
SavedView: implsavedview.NewHandler(modules.SavedView),
|
||||
Apdex: implapdex.NewHandler(modules.Apdex),
|
||||
Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings, authz),
|
||||
SystemDashboard: implsystemdashboard.NewHandler(modules.SystemDashboard),
|
||||
QuickFilter: implquickfilter.NewHandler(modules.QuickFilter),
|
||||
TraceFunnel: impltracefunnel.NewHandler(modules.TraceFunnel),
|
||||
RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport),
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/inframonitoring/implinframonitoring"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
@@ -40,6 +41,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/session/implsession"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard/implsystemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail/impltracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
@@ -66,6 +69,7 @@ type Modules struct {
|
||||
SavedView savedview.Module
|
||||
Apdex apdex.Module
|
||||
Dashboard dashboard.Module
|
||||
SystemDashboard systemdashboard.Module
|
||||
QuickFilter quickfilter.Module
|
||||
TraceFunnel tracefunnel.Module
|
||||
RawDataExport rawdataexport.Module
|
||||
@@ -106,7 +110,8 @@ func NewModules(
|
||||
fl flagger.Flagger,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
systemDashboard := implsystemdashboard.NewModule(impldashboard.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter, systemDashboard)
|
||||
userSetter := impluser.NewSetter(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, authz, analytics, config.User, userRoleStore, userGetter)
|
||||
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
|
||||
|
||||
@@ -117,6 +122,7 @@ func NewModules(
|
||||
SavedView: implsavedview.NewModule(sqlstore),
|
||||
Apdex: implapdex.NewModule(sqlstore),
|
||||
Dashboard: dashboard,
|
||||
SystemDashboard: systemDashboard,
|
||||
UserSetter: userSetter,
|
||||
UserGetter: userGetter,
|
||||
QuickFilter: quickfilter,
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/systemdashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -64,6 +65,7 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
|
||||
struct{ flagger.Handler }{},
|
||||
struct{ dashboard.Module }{},
|
||||
struct{ dashboard.Handler }{},
|
||||
struct{ systemdashboard.Handler }{},
|
||||
struct{ metricsexplorer.Handler }{},
|
||||
struct{ inframonitoring.Handler }{},
|
||||
struct{ gateway.Handler }{},
|
||||
|
||||
@@ -195,6 +195,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
|
||||
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
|
||||
sqlmigration.NewAddSystemDashboardFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -269,6 +270,7 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
|
||||
handlers.FlaggerHandler,
|
||||
modules.Dashboard,
|
||||
handlers.Dashboard,
|
||||
handlers.SystemDashboard,
|
||||
handlers.MetricsExplorer,
|
||||
handlers.InfraMonitoring,
|
||||
handlers.GatewayHandler,
|
||||
|
||||
102
pkg/sqlmigration/078_add_system_dashboard.go
Normal file
102
pkg/sqlmigration/078_add_system_dashboard.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
type addSystemDashboard struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddSystemDashboardFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_system_dashboard"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addSystemDashboard{sqlstore: sqlstore, sqlschema: sqlschema}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addSystemDashboard) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addSystemDashboard) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
// Add the `source` column directly via ALTER TABLE, not sqlschema.Operator().AddColumn.
|
||||
// when a column is not nullable the process is CREATE temp + copy + DROP original + RENAME.
|
||||
// When DROP happens it fails the foreign key from public_dashboard.dashboard_id
|
||||
if _, err := tx.ExecContext(ctx, "ALTER TABLE dashboard ADD COLUMN source TEXT NOT NULL DEFAULT ''"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We activate this part of code once we add default value for the overview page.
|
||||
// add source column to the dashboard table.
|
||||
// do not iterate over orgs.
|
||||
// var orgIDs []string
|
||||
// if err := tx.NewSelect().Model((*types.Organization)(nil)).Column("id").Scan(ctx, &orgIDs); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// for _, rawOrgID := range orgIDs {
|
||||
// orgID, err := valuer.NewUUID(rawOrgID)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// for _, source := range dashboardtypes.SystemSources {
|
||||
// count, err := tx.NewSelect().
|
||||
// Model((*dashboardtypes.StorableDashboard)(nil)).
|
||||
// Where("org_id = ?", orgID).
|
||||
// Where("source = ?", string(source)).
|
||||
// Count(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if count > 0 {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// dashboard, err := dashboardtypes.NewDefaultSystemDashboard(orgID, source)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// storable, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if _, err := tx.NewInsert().Model(storable).Exec(ctx); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addSystemDashboard) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -37,6 +37,7 @@ type StorableDashboard struct {
|
||||
Data StorableDashboardData `bun:"data,type:text,notnull"`
|
||||
Locked bool `bun:"locked,notnull,default:false"`
|
||||
OrgID valuer.UUID `bun:"org_id,notnull"`
|
||||
Source string `bun:"source,type:text,notnull"`
|
||||
}
|
||||
|
||||
type Dashboard struct {
|
||||
@@ -47,6 +48,7 @@ type Dashboard struct {
|
||||
Data StorableDashboardData `json:"data"`
|
||||
Locked bool `json:"locked"`
|
||||
OrgID valuer.UUID `json:"org_id"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type LockUnlockDashboard struct {
|
||||
@@ -86,10 +88,11 @@ func NewStorableDashboardFromDashboard(dashboard *Dashboard) (*StorableDashboard
|
||||
OrgID: dashboard.OrgID,
|
||||
Data: dashboard.Data,
|
||||
Locked: dashboard.Locked,
|
||||
Source: dashboard.Source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDashboard(orgID valuer.UUID, createdBy string, storableDashboardData StorableDashboardData) (*Dashboard, error) {
|
||||
func NewDashboard(orgID valuer.UUID, createdBy string, data StorableDashboardData, source Source) (*Dashboard, error) {
|
||||
currentTime := time.Now()
|
||||
|
||||
return &Dashboard{
|
||||
@@ -103,11 +106,20 @@ func NewDashboard(orgID valuer.UUID, createdBy string, storableDashboardData Sto
|
||||
UpdatedBy: createdBy,
|
||||
},
|
||||
OrgID: orgID,
|
||||
Data: storableDashboardData,
|
||||
Locked: false,
|
||||
Data: data,
|
||||
Source: string(source),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDashboardFromUpdatable(data UpdatableDashboard, updatedBy string) *Dashboard {
|
||||
return &Dashboard{
|
||||
Data: data,
|
||||
UserAuditable: types.UserAuditable{
|
||||
UpdatedBy: updatedBy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) *Dashboard {
|
||||
return &Dashboard{
|
||||
ID: storableDashboard.ID.StringValue(),
|
||||
@@ -122,6 +134,7 @@ func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) *Da
|
||||
OrgID: storableDashboard.OrgID,
|
||||
Data: storableDashboard.Data,
|
||||
Locked: storableDashboard.Locked,
|
||||
Source: storableDashboard.Source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +167,7 @@ func NewGettableDashboardFromDashboard(dashboard *Dashboard) (*GettableDashboard
|
||||
OrgID: dashboard.OrgID,
|
||||
Data: dashboard.Data,
|
||||
Locked: dashboard.Locked,
|
||||
Source: dashboard.Source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestCanUpdate_MultipleDeletions_ByDiff(t *testing.T) {
|
||||
initial := StorableDashboardData{
|
||||
"widgets": makeTestWidgets("a", "b", "c"),
|
||||
}
|
||||
d, err := NewDashboard(orgID, "tester", initial)
|
||||
d, err := NewDashboard(orgID, "tester", initial, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
updated := StorableDashboardData{
|
||||
|
||||
17
pkg/types/dashboardtypes/defaults.go
Normal file
17
pkg/types/dashboardtypes/defaults.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Source string
|
||||
|
||||
var SystemSources = []Source{}
|
||||
|
||||
// This will be fixed with upcoming pr of dashboard default value.
|
||||
// No issue with migration or org creation; if reset endpoint is called we get a 400
|
||||
// invalid_input response and no action is taken on dashboard.data.
|
||||
func NewDefaultSystemDashboard(orgID valuer.UUID, source Source) (*Dashboard, error) {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "no defaults registered for system dashboard source %s", source)
|
||||
}
|
||||
@@ -13,6 +13,8 @@ type Store interface {
|
||||
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableDashboard, error)
|
||||
|
||||
GetBySource(context.Context, valuer.UUID, string) (*StorableDashboard, error)
|
||||
|
||||
GetPublic(context.Context, string) (*StorablePublicDashboard, error)
|
||||
|
||||
GetDashboardByOrgsAndPublicID(context.Context, []string, string) (*StorableDashboard, error)
|
||||
|
||||
@@ -13,7 +13,7 @@ func mkASpan(id string, resource map[string]string, attributes map[string]any, s
|
||||
SpanID: id,
|
||||
Resource: resource,
|
||||
Attributes: attributes,
|
||||
TimeUnix: startNs,
|
||||
TimeUnixNano: startNs,
|
||||
DurationNano: durationNs,
|
||||
Children: make([]*WaterfallSpan, 0),
|
||||
}
|
||||
@@ -25,11 +25,11 @@ func buildTraceFromSpans(spans ...*WaterfallSpan) *WaterfallTrace {
|
||||
initialized := false
|
||||
for _, s := range spans {
|
||||
spanMap[s.SpanID] = s
|
||||
if !initialized || s.TimeUnix < startTime {
|
||||
startTime = s.TimeUnix
|
||||
if !initialized || s.TimeUnixNano < startTime {
|
||||
startTime = s.TimeUnixNano
|
||||
initialized = true
|
||||
}
|
||||
if end := s.TimeUnix + s.DurationNano; end > endTime {
|
||||
if end := s.TimeUnixNano + s.DurationNano; end > endTime {
|
||||
endTime = end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ type WaterfallSpan struct {
|
||||
ParentSpanID string `json:"parent_span_id"`
|
||||
Resource map[string]string `json:"resource"`
|
||||
SpanID string `json:"span_id"`
|
||||
TimeUnix uint64 `json:"time_unix"`
|
||||
TimeUnixNano uint64 `json:"-"`
|
||||
TraceID string `json:"trace_id"`
|
||||
TraceState string `json:"trace_state"`
|
||||
|
||||
@@ -138,7 +138,7 @@ func NewMissingWaterfallSpan(spanID, traceID string, timeUnixNano, durationNano
|
||||
SpanID: spanID,
|
||||
TraceID: traceID,
|
||||
Name: "Missing Span",
|
||||
TimeUnix: timeUnixNano,
|
||||
TimeUnixNano: timeUnixNano,
|
||||
DurationNano: durationNano,
|
||||
Events: make([]Event, 0),
|
||||
Children: make([]*WaterfallSpan, 0),
|
||||
@@ -150,10 +150,10 @@ func NewMissingWaterfallSpan(spanID, traceID string, timeUnixNano, durationNano
|
||||
// SortChildren recursively sorts children of each span by TimeUnixNano then Name.
|
||||
func (ws *WaterfallSpan) SortChildren() {
|
||||
sort.Slice(ws.Children, func(i, j int) bool {
|
||||
if ws.Children[i].TimeUnix == ws.Children[j].TimeUnix {
|
||||
if ws.Children[i].TimeUnixNano == ws.Children[j].TimeUnixNano {
|
||||
return ws.Children[i].Name < ws.Children[j].Name
|
||||
}
|
||||
return ws.Children[i].TimeUnix < ws.Children[j].TimeUnix
|
||||
return ws.Children[i].TimeUnixNano < ws.Children[j].TimeUnixNano
|
||||
})
|
||||
for _, child := range ws.Children {
|
||||
child.SortChildren()
|
||||
@@ -292,7 +292,7 @@ func (item *StorableSpan) ToWaterfallSpan() *WaterfallSpan {
|
||||
TraceID: item.TraceID,
|
||||
TraceState: item.TraceState,
|
||||
Children: make([]*WaterfallSpan, 0),
|
||||
TimeUnix: uint64(item.StartTime.UnixNano()),
|
||||
TimeUnixNano: uint64(item.StartTime.UnixNano()),
|
||||
ServiceName: item.ServiceName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func NewWaterfallTraceFromSpans(spans []StorableSpan) *WaterfallTrace {
|
||||
if parentNode, exists := spanIDToSpanNodeMap[spanNode.ParentSpanID]; exists {
|
||||
parentNode.Children = append(parentNode.Children, spanNode)
|
||||
} else {
|
||||
missingSpan := NewMissingWaterfallSpan(spanNode.ParentSpanID, spanNode.TraceID, spanNode.TimeUnix, spanNode.DurationNano)
|
||||
missingSpan := NewMissingWaterfallSpan(spanNode.ParentSpanID, spanNode.TraceID, spanNode.TimeUnixNano, spanNode.DurationNano)
|
||||
missingSpan.Children = append(missingSpan.Children, spanNode)
|
||||
spanIDToSpanNodeMap[missingSpan.SpanID] = missingSpan
|
||||
traceRoots = append(traceRoots, missingSpan)
|
||||
@@ -112,10 +112,10 @@ func NewWaterfallTraceFromSpans(spans []StorableSpan) *WaterfallTrace {
|
||||
}
|
||||
|
||||
sort.Slice(traceRoots, func(i, j int) bool {
|
||||
if traceRoots[i].TimeUnix == traceRoots[j].TimeUnix {
|
||||
if traceRoots[i].TimeUnixNano == traceRoots[j].TimeUnixNano {
|
||||
return traceRoots[i].Name < traceRoots[j].Name
|
||||
}
|
||||
return traceRoots[i].TimeUnix < traceRoots[j].TimeUnix
|
||||
return traceRoots[i].TimeUnixNano < traceRoots[j].TimeUnixNano
|
||||
})
|
||||
|
||||
return NewWaterfallTrace(
|
||||
@@ -264,7 +264,7 @@ func NewGettableWaterfallTrace(
|
||||
|
||||
// convert start timestamp to millis because client is expecting it in millis
|
||||
for _, span := range selectedSpans {
|
||||
span.TimeUnix = span.TimeUnix / 1_000_000
|
||||
span.TimeUnixNano = span.TimeUnixNano / 1_000_000
|
||||
}
|
||||
|
||||
// duration values are in nanoseconds; convert in-place to milliseconds.
|
||||
@@ -332,15 +332,15 @@ func mergeSpanIntervals(spans []*WaterfallSpan) uint64 {
|
||||
return 0
|
||||
}
|
||||
sort.Slice(spans, func(i, j int) bool {
|
||||
return spans[i].TimeUnix < spans[j].TimeUnix
|
||||
return spans[i].TimeUnixNano < spans[j].TimeUnixNano
|
||||
})
|
||||
|
||||
currentStart := spans[0].TimeUnix
|
||||
currentStart := spans[0].TimeUnixNano
|
||||
currentEnd := currentStart + spans[0].DurationNano
|
||||
total := uint64(0)
|
||||
|
||||
for _, span := range spans[1:] {
|
||||
startNano := span.TimeUnix
|
||||
startNano := span.TimeUnixNano
|
||||
endNano := startNano + span.DurationNano
|
||||
if currentEnd >= startNano {
|
||||
if endNano > currentEnd {
|
||||
|
||||
@@ -20,7 +20,7 @@ from fixtures.querier import (
|
||||
|
||||
|
||||
def _get_bodies(response: requests.Response) -> list[dict[str, Any]]:
|
||||
return [row["data"]["body"] for row in get_rows(response)]
|
||||
return [json.loads(row["data"]["body"]) for row in get_rows(response)]
|
||||
|
||||
|
||||
def _run_query_case(signoz: types.SigNoz, token: str, now: datetime, case: dict[str, Any]) -> None:
|
||||
@@ -1183,7 +1183,7 @@ def test_message_searches(
|
||||
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
|
||||
|
||||
def _body_messages(response: requests.Response) -> list[str]:
|
||||
return [row["data"]["body"].get("message", "") for row in get_rows(response)]
|
||||
return [json.loads(row["data"]["body"]).get("message", "") for row in get_rows(response)]
|
||||
|
||||
payment_messages = {
|
||||
"Payment processed successfully",
|
||||
|
||||
Reference in New Issue
Block a user