mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-12 16:00:46 +01:00
Compare commits
24 Commits
feat/updat
...
fix/uplot-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0940db0875 | ||
|
|
2616885d22 | ||
|
|
52dd57074e | ||
|
|
fe94b817db | ||
|
|
ea36032d96 | ||
|
|
6ecfa839f3 | ||
|
|
62d382b3cc | ||
|
|
cc07e2fa24 | ||
|
|
d7aa63f1bc | ||
|
|
c36b748370 | ||
|
|
89bf0f953a | ||
|
|
a850f548f8 | ||
|
|
5bf6fd9192 | ||
|
|
13f2ba7d34 | ||
|
|
848046de91 | ||
|
|
68e61af0be | ||
|
|
84780acee1 | ||
|
|
c70b2be4d5 | ||
|
|
cda2955b93 | ||
|
|
1a293652f7 | ||
|
|
0f3fb71067 | ||
|
|
b4f5b3eddf | ||
|
|
b9f4fcd681 | ||
|
|
0dd9a156b9 |
1
.github/workflows/integrationci.yaml
vendored
1
.github/workflows/integrationci.yaml
vendored
@@ -61,6 +61,7 @@ jobs:
|
||||
- querierauthz
|
||||
- role
|
||||
- rootuser
|
||||
- savedview
|
||||
- serviceaccount
|
||||
- spanmapper
|
||||
- querier_json_body
|
||||
|
||||
@@ -1499,6 +1499,7 @@ components:
|
||||
- computeengine
|
||||
- gke
|
||||
- cloudstorage
|
||||
- cloudsql_mysql
|
||||
type: string
|
||||
CloudintegrationtypesServiceMetadata:
|
||||
properties:
|
||||
@@ -7880,17 +7881,20 @@ components:
|
||||
type: string
|
||||
SavedviewtypesPostableSavedView:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
|
||||
generateName:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
schemaVersion:
|
||||
$ref: '#/components/schemas/SavedviewtypesSchemaVersion'
|
||||
source:
|
||||
$ref: '#/components/schemas/SavedviewtypesSource'
|
||||
spec:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewSpec'
|
||||
required:
|
||||
- source
|
||||
- data
|
||||
- schemaVersion
|
||||
- spec
|
||||
type: object
|
||||
SavedviewtypesSavedView:
|
||||
properties:
|
||||
@@ -7899,14 +7903,16 @@ components:
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
schemaVersion:
|
||||
$ref: '#/components/schemas/SavedviewtypesSchemaVersion'
|
||||
source:
|
||||
$ref: '#/components/schemas/SavedviewtypesSource'
|
||||
spec:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewSpec'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -7914,14 +7920,6 @@ components:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
SavedviewtypesSavedViewData:
|
||||
properties:
|
||||
schemaVersion:
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewSpec'
|
||||
required:
|
||||
- schemaVersion
|
||||
- spec
|
||||
type: object
|
||||
@@ -7936,7 +7934,10 @@ components:
|
||||
queries:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryEnvelope'
|
||||
minItems: 1
|
||||
type: array
|
||||
requestType:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5RequestType'
|
||||
selectedFields:
|
||||
items:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
@@ -7944,10 +7945,13 @@ components:
|
||||
required:
|
||||
- displayName
|
||||
- panelType
|
||||
- requestType
|
||||
- queries
|
||||
- selectedFields
|
||||
- display
|
||||
type: object
|
||||
SavedviewtypesSchemaVersion:
|
||||
enum:
|
||||
- v2
|
||||
type: string
|
||||
SavedviewtypesSource:
|
||||
enum:
|
||||
- traces
|
||||
@@ -7957,13 +7961,16 @@ components:
|
||||
type: string
|
||||
SavedviewtypesUpdatableSavedView:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewData'
|
||||
schemaVersion:
|
||||
$ref: '#/components/schemas/SavedviewtypesSchemaVersion'
|
||||
source:
|
||||
$ref: '#/components/schemas/SavedviewtypesSource'
|
||||
spec:
|
||||
$ref: '#/components/schemas/SavedviewtypesSavedViewSpec'
|
||||
required:
|
||||
- source
|
||||
- data
|
||||
- schemaVersion
|
||||
- spec
|
||||
type: object
|
||||
ServiceaccounttypesDeprecatedPostableServiceAccountRole:
|
||||
properties:
|
||||
@@ -22776,6 +22783,12 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"409":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Conflict
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
|
||||
@@ -98,14 +98,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ah.Respond(w, featureSet)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,3 @@ func GetOrDefaultEnv(key string, fallback string) string {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// constant functions that override env vars
|
||||
|
||||
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
|
||||
|
||||
var IsDotMetricsEnabled = false
|
||||
|
||||
func init() {
|
||||
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
|
||||
IsDotMetricsEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +376,23 @@ function App(): JSX.Element {
|
||||
tracesSampleRate: 0, // Ref: https://github.com/SigNoz/platform-pod/issues/2393#issuecomment-4603658055
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
beforeSend(event) {
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException as
|
||||
| { name?: string; code?: string | number }
|
||||
| undefined;
|
||||
|
||||
// Ignore benign aborted/cancelled requests (axios + fetch).
|
||||
if (error?.code === 'ERR_CANCELED' || error?.code === 'ECONNABORTED') {
|
||||
return null;
|
||||
}
|
||||
if (error?.name === 'AbortError') {
|
||||
return null;
|
||||
}
|
||||
// Ignore benign Monaco cancellation errors (name 'Canceled').
|
||||
if (error?.name === 'Canceled') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Drop the event if its level is 'warning' or 'info'
|
||||
if (event.level === 'warning' || event.level === 'info') {
|
||||
return null;
|
||||
|
||||
@@ -2818,6 +2818,7 @@ export enum CloudintegrationtypesServiceIDDTO {
|
||||
computeengine = 'computeengine',
|
||||
gke = 'gke',
|
||||
cloudstorage = 'cloudstorage',
|
||||
cloudsql_mysql = 'cloudsql_mysql',
|
||||
}
|
||||
export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
|
||||
/**
|
||||
@@ -8991,8 +8992,17 @@ export enum SavedviewtypesPanelTypeDTO {
|
||||
list = 'list',
|
||||
trace = 'trace',
|
||||
}
|
||||
export enum SavedviewtypesSchemaVersionDTO {
|
||||
v2 = 'v2',
|
||||
}
|
||||
export enum SavedviewtypesSourceDTO {
|
||||
traces = 'traces',
|
||||
logs = 'logs',
|
||||
metrics = 'metrics',
|
||||
meter = 'meter',
|
||||
}
|
||||
export interface SavedviewtypesSavedViewSpecDTO {
|
||||
display: SavedviewtypesDisplayDTO;
|
||||
display?: SavedviewtypesDisplayDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -9002,28 +9012,14 @@ export interface SavedviewtypesSavedViewSpecDTO {
|
||||
* @type array
|
||||
*/
|
||||
queries: Querybuildertypesv5QueryEnvelopeDTO[];
|
||||
requestType: Querybuildertypesv5RequestTypeDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
selectedFields: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
selectedFields?: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
}
|
||||
|
||||
export interface SavedviewtypesSavedViewDataDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
schemaVersion: string;
|
||||
spec: SavedviewtypesSavedViewSpecDTO;
|
||||
}
|
||||
|
||||
export enum SavedviewtypesSourceDTO {
|
||||
traces = 'traces',
|
||||
logs = 'logs',
|
||||
metrics = 'metrics',
|
||||
meter = 'meter',
|
||||
}
|
||||
export interface SavedviewtypesPostableSavedViewDTO {
|
||||
data: SavedviewtypesSavedViewDataDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
@@ -9032,7 +9028,9 @@ export interface SavedviewtypesPostableSavedViewDTO {
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
schemaVersion: SavedviewtypesSchemaVersionDTO;
|
||||
source: SavedviewtypesSourceDTO;
|
||||
spec: SavedviewtypesSavedViewSpecDTO;
|
||||
}
|
||||
|
||||
export interface SavedviewtypesSavedViewDTO {
|
||||
@@ -9045,7 +9043,6 @@ export interface SavedviewtypesSavedViewDTO {
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
data?: SavedviewtypesSavedViewDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -9054,7 +9051,9 @@ export interface SavedviewtypesSavedViewDTO {
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
schemaVersion: SavedviewtypesSchemaVersionDTO;
|
||||
source?: SavedviewtypesSourceDTO;
|
||||
spec: SavedviewtypesSavedViewSpecDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
@@ -9067,8 +9066,9 @@ export interface SavedviewtypesSavedViewDTO {
|
||||
}
|
||||
|
||||
export interface SavedviewtypesUpdatableSavedViewDTO {
|
||||
data: SavedviewtypesSavedViewDataDTO;
|
||||
schemaVersion: SavedviewtypesSchemaVersionDTO;
|
||||
source: SavedviewtypesSourceDTO;
|
||||
spec: SavedviewtypesSavedViewSpecDTO;
|
||||
}
|
||||
|
||||
export interface ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO {
|
||||
|
||||
@@ -24,19 +24,3 @@ export const Logout = async (): Promise<void> => {
|
||||
window.dispatchEvent(new CustomEvent('LOGOUT'));
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
export const UnderscoreToDotMap: Record<string, string> = {
|
||||
k8s_cluster_name: 'k8s.cluster.name',
|
||||
k8s_cluster_uid: 'k8s.cluster.uid',
|
||||
k8s_namespace_name: 'k8s.namespace.name',
|
||||
k8s_node_name: 'k8s.node.name',
|
||||
k8s_node_uid: 'k8s.node.uid',
|
||||
k8s_pod_name: 'k8s.pod.name',
|
||||
k8s_pod_uid: 'k8s.pod.uid',
|
||||
k8s_deployment_name: 'k8s.deployment.name',
|
||||
k8s_daemonset_name: 'k8s.daemonset.name',
|
||||
k8s_statefulset_name: 'k8s.statefulset.name',
|
||||
k8s_cronjob_name: 'k8s.cronjob.name',
|
||||
k8s_job_name: 'k8s.job.name',
|
||||
k8s_persistentvolumeclaim_name: 'k8s.persistentvolumeclaim.name',
|
||||
};
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/resetPassword';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useResetPassword` hook (or `resetPassword` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const resetPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/resetPassword`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default resetPassword;
|
||||
@@ -1,31 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/setInvite';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateInvite` hook (or `createInvite` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const sendInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/invite`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default sendInvite;
|
||||
@@ -5,10 +5,9 @@ import {
|
||||
useCreateResetPasswordToken,
|
||||
useDeleteUser,
|
||||
useGetResetPasswordToken,
|
||||
useGetRolesByUserID,
|
||||
useCreateUserRole,
|
||||
useDeleteUserRole,
|
||||
useGetUser,
|
||||
useRemoveUserRoleByUserIDAndRoleID,
|
||||
useSetRoleByUserID,
|
||||
useUpdateMyUserV2,
|
||||
useUpdateUser,
|
||||
} from 'api/generated/services/users';
|
||||
@@ -25,15 +24,14 @@ import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
useGetRolesByUserID: jest.fn(),
|
||||
useRemoveUserRoleByUserIDAndRoleID: jest.fn(),
|
||||
useDeleteUserRole: jest.fn(),
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useSetRoleByUserID: jest.fn(),
|
||||
useCreateUserRole: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
getGetRolesByUserIDQueryKey: ({ id }: { id: string }): string[] => [
|
||||
`/api/v2/users/${id}/roles`,
|
||||
getGetUserQueryKey: ({ id }: { id: string }): string[] => [
|
||||
`/api/v2/users/${id}`,
|
||||
],
|
||||
}));
|
||||
|
||||
@@ -194,11 +192,7 @@ describe('EditMemberDrawer', () => {
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
(useGetRolesByUserID as jest.Mock).mockReturnValue({
|
||||
data: { data: [managedRoles[0]] },
|
||||
isLoading: false,
|
||||
});
|
||||
(useRemoveUserRoleByUserIDAndRoleID as jest.Mock).mockReturnValue({
|
||||
(useDeleteUserRole as jest.Mock).mockReturnValue({
|
||||
mutateAsync: mockRemoveMutateAsync.mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -210,7 +204,7 @@ describe('EditMemberDrawer', () => {
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
(useSetRoleByUserID as jest.Mock).mockReturnValue({
|
||||
(useCreateUserRole as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -312,12 +306,12 @@ describe('EditMemberDrawer', () => {
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('adding a new role calls setRole without removing existing ones', async () => {
|
||||
it('adding a new role creates a user role without removing existing ones', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockSet = jest.fn().mockResolvedValue({});
|
||||
|
||||
(useSetRoleByUserID as jest.Mock).mockReturnValue({
|
||||
(useCreateUserRole as jest.Mock).mockReturnValue({
|
||||
mutateAsync: mockSet,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -334,15 +328,14 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSet).toHaveBeenCalledWith({
|
||||
pathParams: { id: 'user-1' },
|
||||
data: { name: 'signoz-editor' },
|
||||
data: { userId: 'user-1', roleId: managedRoles[1].id },
|
||||
});
|
||||
expect(mockRemoveMutateAsync).not.toHaveBeenCalled();
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('deselecting a role calls removeRole with the role id', async () => {
|
||||
it('deselecting a role deletes the user role by its assignment id', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
@@ -361,7 +354,7 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRemoveMutateAsync).toHaveBeenCalledWith({
|
||||
pathParams: { id: 'user-1', roleId: managedRoles[0].id },
|
||||
pathParams: { id: 'ur-1' },
|
||||
});
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
.highlights {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px 16px;
|
||||
padding: 12px 0;
|
||||
|
||||
// Constrain each KeyValueLabel (the grid items) to its cell.
|
||||
:global(.key-value-label) {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.valueBadge {
|
||||
--badge-font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// Truncating text inside a badge
|
||||
.badgeText {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.serviceDot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-forest);
|
||||
flex-shrink: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.traceLink {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import KeyValueLabel from 'periscope/components/KeyValueLabel';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { LOG_HIGHLIGHTS } from './config';
|
||||
import styles from './LogHighlights.module.scss';
|
||||
|
||||
interface LogHighlightsProps {
|
||||
log: ILog;
|
||||
}
|
||||
|
||||
function LogHighlights({ log }: LogHighlightsProps): JSX.Element | null {
|
||||
const fields = LOG_HIGHLIGHTS.map((field) => ({
|
||||
key: field.key,
|
||||
label: field.label,
|
||||
value: field.render(log),
|
||||
})).filter((field) => field.value != null);
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.highlights} data-testid="log-details-highlights">
|
||||
{fields.map((field) => (
|
||||
<KeyValueLabel
|
||||
key={field.key}
|
||||
badgeKey={field.label}
|
||||
badgeValue={field.value}
|
||||
direction="column"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogHighlights;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './LogHighlights.module.scss';
|
||||
|
||||
interface TraceIdFieldProps {
|
||||
traceId: string;
|
||||
}
|
||||
|
||||
function TraceIdField({ traceId }: TraceIdFieldProps): JSX.Element {
|
||||
return (
|
||||
<Link
|
||||
to={{ pathname: `/trace/${traceId}` }}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.traceLink}
|
||||
title={traceId}
|
||||
>
|
||||
{traceId}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default TraceIdField;
|
||||
102
frontend/src/components/LogDetail/LogHighlights/config.tsx
Normal file
102
frontend/src/components/LogDetail/LogHighlights/config.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Badge, BadgeColor } from '@signozhq/ui/badge';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import { getLogIndicatorType } from 'components/Logs/LogStateIndicator/utils';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import styles from './LogHighlights.module.scss';
|
||||
import TraceIdField from './TraceIdField';
|
||||
|
||||
// Severity badge color mirrors the LogStateIndicator bar
|
||||
const SEVERITY_COLOR: Record<string, BadgeColor> = {
|
||||
[LogType.TRACE]: 'forest',
|
||||
[LogType.DEBUG]: 'aqua',
|
||||
[LogType.INFO]: 'robin',
|
||||
[LogType.WARN]: 'amber',
|
||||
[LogType.ERROR]: 'cherry',
|
||||
[LogType.FATAL]: 'sakura',
|
||||
};
|
||||
|
||||
export interface LogHighlightConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
render: (log: ILog) => ReactNode | null;
|
||||
}
|
||||
|
||||
// Resource/attribute lookup (keys like `service.name` live in resources_string,
|
||||
// occasionally attributes_string). Typed loosely as these are string maps.
|
||||
const getAttr = (log: ILog, key: string): string =>
|
||||
(log.resources_string as unknown as Record<string, string>)?.[key] ||
|
||||
(log.attributes_string as unknown as Record<string, string>)?.[key] ||
|
||||
'';
|
||||
|
||||
const valueBadge = (
|
||||
value: string,
|
||||
options?: { prefix?: ReactNode; color?: BadgeColor },
|
||||
): ReactNode => (
|
||||
<Badge color={options?.color ?? 'vanilla'} className={styles.valueBadge}>
|
||||
{options?.prefix}
|
||||
<span className={styles.badgeText} title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
|
||||
export const LOG_HIGHLIGHTS: LogHighlightConfig[] = [
|
||||
{
|
||||
key: 'service',
|
||||
label: 'SERVICE',
|
||||
render: (log): ReactNode | null => {
|
||||
const value = getAttr(log, 'service.name');
|
||||
return value
|
||||
? valueBadge(value, {
|
||||
prefix: <span className={styles.serviceDot} />,
|
||||
})
|
||||
: null;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'severity',
|
||||
label: 'SEVERITY',
|
||||
render: (log): ReactNode | null => {
|
||||
if (!log.severity_text) {
|
||||
return null;
|
||||
}
|
||||
return valueBadge(log.severity_text, {
|
||||
color: SEVERITY_COLOR[getLogIndicatorType(log)] ?? 'vanilla',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'namespace',
|
||||
label: 'NAMESPACE',
|
||||
render: (log): ReactNode | null => {
|
||||
const value = getAttr(log, 'service.namespace');
|
||||
return value ? valueBadge(value) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'environment',
|
||||
label: 'ENVIRONMENT',
|
||||
render: (log): ReactNode | null => {
|
||||
const value = getAttr(log, 'deployment.environment');
|
||||
return value ? valueBadge(value) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'traceId',
|
||||
label: 'TRACE ID',
|
||||
render: (log): ReactNode | null => {
|
||||
const traceId = log.trace_id || log.traceId;
|
||||
return traceId ? <TraceIdField traceId={traceId} /> : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'spanId',
|
||||
label: 'SPAN ID',
|
||||
render: (log): ReactNode | null => {
|
||||
const spanId = log.span_id || log.spanID;
|
||||
return spanId ? valueBadge(spanId) : null;
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -115,6 +115,45 @@ describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
expect(screen.queryByText('Open in Explorer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Highlights for fields present on the log, omitting absent ones', () => {
|
||||
const logWithMeta = {
|
||||
...mockLog,
|
||||
severity_text: 'ERROR',
|
||||
trace_id: 'trace-abc',
|
||||
resources_string: {
|
||||
'service.name': 'checkout',
|
||||
'deployment.environment': 'production',
|
||||
},
|
||||
} as unknown as ILog;
|
||||
|
||||
renderDrawer({ log: logWithMeta });
|
||||
|
||||
const highlights = screen.getByTestId('log-details-highlights');
|
||||
expect(highlights).toHaveTextContent('SEVERITY');
|
||||
expect(highlights).toHaveTextContent('ERROR');
|
||||
expect(highlights).toHaveTextContent('SERVICE');
|
||||
expect(highlights).toHaveTextContent('checkout');
|
||||
expect(highlights).toHaveTextContent('ENVIRONMENT');
|
||||
expect(highlights).toHaveTextContent('production');
|
||||
expect(highlights).toHaveTextContent('TRACE ID');
|
||||
// Absent fields are omitted (no namespace / span id on this log).
|
||||
expect(highlights).not.toHaveTextContent('NAMESPACE');
|
||||
expect(highlights).not.toHaveTextContent('SPAN ID');
|
||||
});
|
||||
|
||||
it('links the trace id highlight to the trace detail in a new tab', () => {
|
||||
const logWithTrace = {
|
||||
...mockLog,
|
||||
trace_id: 'trace-abc',
|
||||
} as unknown as ILog;
|
||||
|
||||
renderDrawer({ log: logWithTrace });
|
||||
|
||||
const link = screen.getByRole('link', { name: 'trace-abc' });
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(link.getAttribute('href')).toContain('/trace/trace-abc');
|
||||
});
|
||||
|
||||
it('navigates to the next / previous log with the Down / Up arrow keys', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1'), makeLog('log-2')];
|
||||
|
||||
@@ -55,6 +55,7 @@ import { isLogDetailsV2, RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
|
||||
import LogDetailsHeader from './LogDetailsHeader/LogDetailsHeader';
|
||||
import { useLogNavigation } from './LogDetailsHeader/useLogNavigation';
|
||||
import LogHighlights from './LogHighlights/LogHighlights';
|
||||
|
||||
import './LogDetails.styles.scss';
|
||||
|
||||
@@ -399,6 +400,8 @@ function LogDetailInner({
|
||||
<div className="log-overflow-shadow"> </div>
|
||||
</div>
|
||||
|
||||
{isLogDetailsV2 && <LogHighlights log={log} />}
|
||||
|
||||
<div className="tabs-and-search">
|
||||
<ToggleGroupSimple
|
||||
type="single"
|
||||
|
||||
@@ -183,15 +183,14 @@ function QuerySearch({
|
||||
isProgrammaticChangeRef.current = true;
|
||||
}
|
||||
|
||||
const changes = view.state.changes({
|
||||
from: 0,
|
||||
to: currentValue.length,
|
||||
insert: value,
|
||||
});
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: currentValue.length,
|
||||
insert: value,
|
||||
},
|
||||
selection: {
|
||||
anchor: value.length,
|
||||
},
|
||||
changes,
|
||||
selection: { anchor: changes.newLength },
|
||||
});
|
||||
},
|
||||
[],
|
||||
|
||||
@@ -301,6 +301,66 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
dispatchSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('does not crash when the expression contains CRLF line breaks (issue #5869)', async () => {
|
||||
const dispatchSpy = jest.spyOn(EditorView.prototype, 'dispatch');
|
||||
const onChange = jest.fn() as jest.MockedFunction<(v: string) => void>;
|
||||
const initialExpression = "service.name = 'frontend'";
|
||||
// Filtering on a multi-line log value (CRLF) used to throw
|
||||
// "RangeError: Selection points outside of document".
|
||||
const crlfExpression = "body CONTAINS 'line1\r\nline2\r\nline3'";
|
||||
|
||||
const baseQueryData = {
|
||||
...initialQueriesMap.logs.builder.queryData[0],
|
||||
filter: { expression: initialExpression },
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<QuerySearch
|
||||
onChange={onChange}
|
||||
queryData={baseQueryData}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
const editorContent = document.querySelector(
|
||||
CM_EDITOR_SELECTOR,
|
||||
) as HTMLElement;
|
||||
expect(editorContent.textContent || '').toBe(initialExpression);
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
rerender(
|
||||
<QuerySearch
|
||||
onChange={onChange}
|
||||
queryData={{ ...baseQueryData, filter: { expression: crlfExpression } }}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
);
|
||||
|
||||
// The programmatic replace dispatched without throwing, and the selection anchor
|
||||
// stayed within the CRLF-normalized document (the bug set it past the end).
|
||||
await waitFor(() => {
|
||||
const spec = dispatchSpy.mock.calls
|
||||
.map(
|
||||
(call) =>
|
||||
call[0] as {
|
||||
selection?: { anchor?: number };
|
||||
changes?: { newLength?: number };
|
||||
},
|
||||
)
|
||||
.find((s) => s?.selection?.anchor != null && s?.changes?.newLength != null);
|
||||
expect(spec).toBeDefined();
|
||||
expect(spec?.selection?.anchor).toBeLessThanOrEqual(
|
||||
spec?.changes?.newLength as number,
|
||||
);
|
||||
});
|
||||
|
||||
dispatchSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('fetches key suggestions for metrics even without aggregateAttribute.key when showFilterSuggestionsWithoutMetric is true', async () => {
|
||||
const mockedGetKeys = getKeySuggestions as jest.MockedFunction<
|
||||
typeof getKeySuggestions
|
||||
|
||||
@@ -7,7 +7,6 @@ export enum FeatureKeys {
|
||||
GATEWAY = 'gateway',
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',
|
||||
|
||||
@@ -437,6 +437,17 @@ describe('Create Alert Channel', () => {
|
||||
render(<CreateAlertChannels preType={ChannelType.GoogleChat} />);
|
||||
});
|
||||
|
||||
// paste instead of type: a per-keystroke re-render of the whole form
|
||||
// pushes these tests past the 5s jest timeout on slower CI runners
|
||||
async function fillField(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
testId: string,
|
||||
value: string,
|
||||
): Promise<void> {
|
||||
await user.click(screen.getByTestId(testId));
|
||||
await user.paste(value);
|
||||
}
|
||||
|
||||
it('Should check if the selected item in the type dropdown has text "Google Chat"', () => {
|
||||
expect(screen.getByText('Google Chat')).toBeInTheDocument();
|
||||
});
|
||||
@@ -463,14 +474,8 @@ describe('Create Alert Channel', () => {
|
||||
it('Should check if saving with a webhook url outside chat.googleapis.com displays error notification', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await user.type(
|
||||
screen.getByTestId('channel-name-textbox'),
|
||||
'gchat-channel',
|
||||
);
|
||||
await user.type(
|
||||
screen.getByTestId('webhook-url-textbox'),
|
||||
'https://example.com/webhook',
|
||||
);
|
||||
await fillField(user, 'channel-name-textbox', 'gchat-channel');
|
||||
await fillField(user, 'webhook-url-textbox', 'https://example.com/webhook');
|
||||
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
@@ -496,11 +501,8 @@ describe('Create Alert Channel', () => {
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
await user.type(
|
||||
screen.getByTestId('channel-name-textbox'),
|
||||
'gchat-channel',
|
||||
);
|
||||
await user.type(screen.getByTestId('webhook-url-textbox'), validWebhookUrl);
|
||||
await fillField(user, 'channel-name-textbox', 'gchat-channel');
|
||||
await fillField(user, 'webhook-url-textbox', validWebhookUrl);
|
||||
|
||||
await user.click(screen.getByTestId('save-channel-button'));
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Exception, PayloadProps } from 'types/api/errors/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { FilterDropdownExtendsProps } from './types';
|
||||
import {
|
||||
extractFilterValues,
|
||||
@@ -418,11 +416,6 @@ function AllErrors(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const onChangeHandler: TableProps<Exception>['onChange'] = useCallback(
|
||||
(
|
||||
paginations: TablePaginationConfig,
|
||||
@@ -458,7 +451,7 @@ function AllErrors(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!isUndefined(errorCountResponse.data?.payload)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('Exception: List page visited', {
|
||||
|
||||
@@ -130,6 +130,28 @@ describe('Footer utils', () => {
|
||||
};
|
||||
expect(validateCreateAlertState(currentArgs)).toBeNull();
|
||||
});
|
||||
|
||||
it('when threshold channels are null', () => {
|
||||
const currentArgs: BuildCreateAlertRulePayloadArgs = {
|
||||
...args,
|
||||
basicAlertState: {
|
||||
...args.basicAlertState,
|
||||
name: 'test name',
|
||||
},
|
||||
thresholdState: {
|
||||
...args.thresholdState,
|
||||
thresholds: [
|
||||
{
|
||||
...args.thresholdState.thresholds[0],
|
||||
channels: null as unknown as string[],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(validateCreateAlertState(currentArgs)).toBe(
|
||||
'Please select at least one channel for each threshold or enable routing policies',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNotificationSettingsProps', () => {
|
||||
|
||||
@@ -44,7 +44,8 @@ export function validateCreateAlertState(
|
||||
if (!threshold.label) {
|
||||
return 'Please enter a label for each threshold';
|
||||
}
|
||||
if (!notificationSettings.routingPolicies && !threshold.channels.length) {
|
||||
// this runs during render, so a throw here takes down the whole page
|
||||
if (!notificationSettings.routingPolicies && !threshold.channels?.length) {
|
||||
return 'Please select at least one channel for each threshold or enable routing policies';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +316,34 @@ describe('CreateAlertV2 utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getThresholdStateFromAlertDef null channels', () => {
|
||||
it('falls back to an empty array so downstream consumers never see null', () => {
|
||||
const def: PostableAlertRuleV2 = {
|
||||
...defaultPostableAlertRuleV2,
|
||||
condition: {
|
||||
...defaultPostableAlertRuleV2.condition,
|
||||
thresholds: {
|
||||
kind: 'basic',
|
||||
spec: [
|
||||
{
|
||||
name: 'critical',
|
||||
target: 1,
|
||||
targetUnit: UniversalYAxisUnit.MINUTES,
|
||||
channels: null as unknown as string[],
|
||||
matchType: AlertThresholdMatchType.AT_LEAST_ONCE,
|
||||
op: AlertThresholdOperator.IS_ABOVE,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getThresholdStateFromAlertDef(def).thresholds[0].channels,
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeOperator', () => {
|
||||
it.each([
|
||||
['1', AlertThresholdOperator.IS_ABOVE],
|
||||
|
||||
@@ -258,7 +258,9 @@ export function getThresholdStateFromAlertDef(
|
||||
recoveryThresholdValue: null,
|
||||
unit: threshold.targetUnit,
|
||||
color: getColorForThreshold(threshold.name),
|
||||
channels: threshold.channels,
|
||||
// rules created outside the UI can come back with a null channels
|
||||
// field; drop the guard once the API enforces the schema
|
||||
channels: threshold.channels ?? [],
|
||||
})) || [],
|
||||
selectedQuery: alertDef.condition.selectedQueryName || '',
|
||||
operator:
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface BaseConfigBuilderProps {
|
||||
panelType: PANEL_TYPES;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
useExactTimeRange?: boolean;
|
||||
stepInterval?: number;
|
||||
isLogScale?: boolean;
|
||||
yAxisUnit?: string;
|
||||
@@ -46,6 +47,7 @@ export function buildBaseConfig({
|
||||
thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
useExactTimeRange,
|
||||
stepInterval,
|
||||
isLogScale,
|
||||
yAxisUnit,
|
||||
@@ -88,6 +90,7 @@ export function buildBaseConfig({
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
useExactTimeRange,
|
||||
logBase: isLogScale ? 10 : undefined,
|
||||
distribution: isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
|
||||
@@ -35,7 +35,6 @@ import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
@@ -212,19 +211,13 @@ function ServiceMetrics({
|
||||
|
||||
const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryRangeRequestData = useMemo(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
);
|
||||
|
||||
const dataQueries = useGetQueriesRange(
|
||||
|
||||
@@ -562,13 +562,9 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY})`,
|
||||
op: '=',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: `max(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY}) = 1`,
|
||||
},
|
||||
legend: `{{${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME}}}`,
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -648,13 +644,9 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY})`,
|
||||
op: '=',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: `max(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY}) = 0`,
|
||||
},
|
||||
legend: `{{${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME}}}`,
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
|
||||
@@ -22,6 +22,7 @@ import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants'
|
||||
import LoadingContainer from 'container/InfraMonitoringK8sV2/LoadingContainer';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import { ChevronDown, ChevronRight } from '@signozhq/icons';
|
||||
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
|
||||
import { useQueryState } from 'nuqs';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { parseAsJsonNoValidate } from 'utils/nuqsParsers';
|
||||
@@ -116,6 +117,7 @@ function EntityEventsContent({
|
||||
: newUserExpression || '',
|
||||
);
|
||||
if (validation.isValid) {
|
||||
saveRecentQueryByExpression(DataSource.LOGS, newUserExpression);
|
||||
querySearchOnRun(newUserExpression || '');
|
||||
|
||||
void logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { getOldLogsOperatorFromNew } from 'hooks/logs/useActiveLog';
|
||||
import useLogDetailHandlers from 'hooks/logs/useLogDetailHandlers';
|
||||
import useScrollToLog from 'hooks/logs/useScrollToLog';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { validateQuery } from 'utils/queryValidationUtils';
|
||||
@@ -132,6 +133,7 @@ function EntityLogsContent({
|
||||
);
|
||||
|
||||
if (validation.isValid) {
|
||||
saveRecentQueryByExpression(DataSource.LOGS, newUserExpression);
|
||||
querySearchOnRun(newUserExpression);
|
||||
|
||||
void logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
|
||||
@@ -121,12 +121,6 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
|
||||
plan_version: 'test-plan-version',
|
||||
},
|
||||
},
|
||||
featureFlags: [
|
||||
{
|
||||
name: 'DOT_METRICS_ENABLED',
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
const mockEntity = {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants'
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
|
||||
import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading';
|
||||
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
|
||||
import { useQueryState } from 'nuqs';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { parseAsJsonNoValidate } from 'utils/nuqsParsers';
|
||||
@@ -98,6 +99,7 @@ function EntityTracesContent({
|
||||
: newUserExpression || '',
|
||||
);
|
||||
if (validation.isValid) {
|
||||
saveRecentQueryByExpression(DataSource.TRACES, newUserExpression);
|
||||
querySearchOnRun(newUserExpression || '');
|
||||
|
||||
void logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
|
||||
@@ -1208,13 +1208,9 @@ export const getNamespaceMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_REPLICASET_DESIRED})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: `max(${INFRA_MONITORING_ATTR_KEYS.K8S_REPLICASET_DESIRED}) > 0`,
|
||||
},
|
||||
legend: 'desired',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -1261,13 +1257,9 @@ export const getNamespaceMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_REPLICASET_DESIRED})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: {
|
||||
expression: `max(${INFRA_MONITORING_ATTR_KEYS.K8S_REPLICASET_AVAILABLE}) > 0`,
|
||||
},
|
||||
legend: 'available',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
|
||||
@@ -17,8 +17,6 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
getNodeQueryPayload,
|
||||
@@ -53,23 +51,12 @@ function NodeMetrics({
|
||||
};
|
||||
}, [timestamp]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(() => {
|
||||
if (nodeName) {
|
||||
return getNodeQueryPayload(
|
||||
clusterName,
|
||||
nodeName,
|
||||
start,
|
||||
end,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
return getNodeQueryPayload(clusterName, nodeName, start, end);
|
||||
}
|
||||
return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
|
||||
}, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
|
||||
return getHostQueryPayload(hostName, start, end);
|
||||
}, [nodeName, hostName, clusterName, start, end]);
|
||||
|
||||
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -12,13 +12,11 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { getPodQueryPayload, podWidgetInfo } from './constants';
|
||||
|
||||
function PodMetrics({
|
||||
@@ -54,14 +52,9 @@ function PodMetrics({
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getPodQueryPayload(clusterName, podName, start, end, dotMetricsEnabled),
|
||||
[clusterName, end, podName, start, dotMetricsEnabled],
|
||||
() => getPodQueryPayload(clusterName, podName, start, end),
|
||||
[clusterName, end, podName, start],
|
||||
);
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
|
||||
@@ -1,56 +1,39 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import type { Having } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import type { Having as HavingV5 } from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
|
||||
const buildSumGreaterThanZeroHaving = (
|
||||
metricKey: string,
|
||||
useV5HavingFormat: boolean,
|
||||
): Having[] | HavingV5 =>
|
||||
useV5HavingFormat
|
||||
? { expression: `sum(${metricKey}) > 0` }
|
||||
: [{ columnName: `SUM(${metricKey})`, op: '>', value: 0 }];
|
||||
|
||||
export const getPodQueryPayload = (
|
||||
clusterName: string,
|
||||
podName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const containerCpuUtilKey = dotMetricsEnabled
|
||||
? 'container.cpu.usage'
|
||||
: 'container_cpu_usage';
|
||||
const containerMemUsageKey = dotMetricsEnabled
|
||||
? 'container.memory.usage'
|
||||
: 'container_memory_usage';
|
||||
const k8sContainerCpuReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sContainerMemReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sPodFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.available'
|
||||
: 'k8s_pod_filesystem_available';
|
||||
const k8sPodFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.capacity'
|
||||
: 'k8s_pod_filesystem_capacity';
|
||||
const k8sPodNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const podLegendTemplate = dotMetricsEnabled
|
||||
? '{{k8s.pod.name}}'
|
||||
: '{{k8s_pod_name}}';
|
||||
const podLegendUsage = dotMetricsEnabled
|
||||
? 'usage - {{k8s.pod.name}}'
|
||||
: 'usage - {{k8s_pod_name}}';
|
||||
const podLegendLimit = dotMetricsEnabled
|
||||
? 'limit - {{k8s.pod.name}}'
|
||||
: 'limit - {{k8s_pod_name}}';
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const containerCpuUtilKey = 'container.cpu.usage';
|
||||
const containerMemUsageKey = 'container.memory.usage';
|
||||
const k8sContainerCpuReqKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerMemReqKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sPodFsAvailKey = 'k8s.pod.filesystem.available';
|
||||
const k8sPodFsCapKey = 'k8s.pod.filesystem.capacity';
|
||||
const k8sPodNetIoKey = 'k8s.pod.network.io';
|
||||
const podLegendTemplate = '{{k8s.pod.name}}';
|
||||
const podLegendUsage = 'usage - {{k8s.pod.name}}';
|
||||
const podLegendLimit = 'limit - {{k8s.pod.name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1027,36 +1010,17 @@ export const getNodeQueryPayload = (
|
||||
nodeName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sNodeNameKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
|
||||
const k8sNodeCpuTimeKey = dotMetricsEnabled
|
||||
? 'k8s.node.cpu.time'
|
||||
: 'k8s_node_cpu_time';
|
||||
const k8sNodeAllocCpuKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_cpu'
|
||||
: 'k8s_node_allocatable_cpu';
|
||||
const k8sNodeMemWsKey = dotMetricsEnabled
|
||||
? 'k8s.node.memory.working_set'
|
||||
: 'k8s_node_memory_working_set';
|
||||
const k8sNodeAllocMemKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_memory'
|
||||
: 'k8s_node_allocatable_memory';
|
||||
const k8sNodeNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.node.network.io'
|
||||
: 'k8s_node_network_io';
|
||||
const k8sNodeFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.available'
|
||||
: 'k8s_node_filesystem_available';
|
||||
const k8sNodeFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.capacity'
|
||||
: 'k8s_node_filesystem_capacity';
|
||||
const podLegend = dotMetricsEnabled
|
||||
? '{{k8s.node.name}}'
|
||||
: '{{k8s_node_name}}';
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
const k8sNodeCpuTimeKey = 'k8s.node.cpu.time';
|
||||
const k8sNodeAllocCpuKey = 'k8s.node.allocatable_cpu';
|
||||
const k8sNodeMemWsKey = 'k8s.node.memory.working_set';
|
||||
const k8sNodeAllocMemKey = 'k8s.node.allocatable_memory';
|
||||
const k8sNodeNetIoKey = 'k8s.node.network.io';
|
||||
const k8sNodeFsAvailKey = 'k8s.node.filesystem.available';
|
||||
const k8sNodeFsCapKey = 'k8s.node.filesystem.capacity';
|
||||
const podLegend = '{{k8s.node.name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1586,48 +1550,24 @@ export const getHostQueryPayload = (
|
||||
hostName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
useV5HavingFormat = false,
|
||||
): GetQueryResultsProps[] => {
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const cpuTimeKey = dotMetricsEnabled ? 'system.cpu.time' : 'system_cpu_time';
|
||||
const memUsageKey = dotMetricsEnabled
|
||||
? 'system.memory.usage'
|
||||
: 'system_memory_usage';
|
||||
const load1mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.1m'
|
||||
: 'system_cpu_load_average_1m';
|
||||
const load5mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.5m'
|
||||
: 'system_cpu_load_average_5m';
|
||||
const load15mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
: 'system_cpu_load_average_15m';
|
||||
const netIoKey = dotMetricsEnabled ? 'system.network.io' : 'system_network_io';
|
||||
const netPktsKey = dotMetricsEnabled
|
||||
? 'system.network.packets'
|
||||
: 'system_network_packets';
|
||||
const netErrKey = dotMetricsEnabled
|
||||
? 'system.network.errors'
|
||||
: 'system_network_errors';
|
||||
const netDropKey = dotMetricsEnabled
|
||||
? 'system.network.dropped'
|
||||
: 'system_network_dropped';
|
||||
const netConnKey = dotMetricsEnabled
|
||||
? 'system.network.connections'
|
||||
: 'system_network_connections';
|
||||
const diskIoKey = dotMetricsEnabled ? 'system.disk.io' : 'system_disk_io';
|
||||
const diskOpTimeKey = dotMetricsEnabled
|
||||
? 'system.disk.operation_time'
|
||||
: 'system_disk_operation_time';
|
||||
const diskOpsKey = dotMetricsEnabled
|
||||
? 'system.disk.operations'
|
||||
: 'system_disk_operations';
|
||||
const diskPendingKey = dotMetricsEnabled
|
||||
? 'system.disk.pending_operations'
|
||||
: 'system_disk_pending_operations';
|
||||
const fsUsageKey = dotMetricsEnabled
|
||||
? 'system.filesystem.usage'
|
||||
: 'system_filesystem_usage';
|
||||
const hostNameKey = 'host.name';
|
||||
const cpuTimeKey = 'system.cpu.time';
|
||||
const memUsageKey = 'system.memory.usage';
|
||||
const load1mKey = 'system.cpu.load_average.1m';
|
||||
const load5mKey = 'system.cpu.load_average.5m';
|
||||
const load15mKey = 'system.cpu.load_average.15m';
|
||||
const netIoKey = 'system.network.io';
|
||||
const netPktsKey = 'system.network.packets';
|
||||
const netErrKey = 'system.network.errors';
|
||||
const netDropKey = 'system.network.dropped';
|
||||
const netConnKey = 'system.network.connections';
|
||||
const diskIoKey = 'system.disk.io';
|
||||
const diskOpTimeKey = 'system.disk.operation_time';
|
||||
const diskOpsKey = 'system.disk.operations';
|
||||
const diskPendingKey = 'system.disk.pending_operations';
|
||||
const fsUsageKey = 'system.filesystem.usage';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1873,13 +1813,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${fsUsageKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(fsUsageKey, useV5HavingFormat),
|
||||
legend: '{{mountpoint}}',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -1928,13 +1862,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${fsUsageKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(fsUsageKey, useV5HavingFormat),
|
||||
legend: '{{mountpoint}}',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -2160,13 +2088,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${netIoKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(netIoKey, useV5HavingFormat),
|
||||
legend: '{{device}}::{{direction}}',
|
||||
limit: 30,
|
||||
orderBy: [],
|
||||
@@ -2622,13 +2544,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${diskOpsKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(diskOpsKey, useV5HavingFormat),
|
||||
legend: '{{device}}::{{direction}}',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -2697,13 +2613,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${diskPendingKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(diskPendingKey, useV5HavingFormat),
|
||||
legend: '{{device}}',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -2779,13 +2689,7 @@ export const getHostQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
having: [
|
||||
{
|
||||
columnName: `SUM(${diskOpTimeKey})`,
|
||||
op: '>',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
having: buildSumGreaterThanZeroHaving(diskOpTimeKey, useV5HavingFormat),
|
||||
legend: '{{device}}::{{direction}}',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
|
||||
@@ -21,7 +21,6 @@ export const databaseCallsRPS = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallsRPSProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -33,7 +32,7 @@ export const databaseCallsRPS = ({
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
|
||||
key: WidgetKeys.DbSystem,
|
||||
type: 'tag',
|
||||
},
|
||||
];
|
||||
@@ -42,9 +41,7 @@ export const databaseCallsRPS = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -75,7 +72,6 @@ export const databaseCallsRPS = ({
|
||||
export const databaseCallsAvgDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozDbLatencySum,
|
||||
@@ -92,9 +88,7 @@ export const databaseCallsAvgDuration = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@ export const externalCallErrorPercent = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||
@@ -49,9 +48,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -61,7 +58,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -74,9 +71,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -120,7 +115,6 @@ export const externalCallErrorPercent = ({
|
||||
export const externalCallDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -141,9 +135,7 @@ export const externalCallDuration = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -183,7 +175,6 @@ export const externalCallRpsByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -198,9 +189,7 @@ export const externalCallRpsByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -231,7 +220,6 @@ export const externalCallDurationByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -251,9 +239,7 @@ export const externalCallDurationByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -37,15 +37,10 @@ export const latency = ({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable = false,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}: LatencyProps): QueryBuilderData => {
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
const signozLatencyBucketMetrics = WidgetKeys.SignozLatencyBucket;
|
||||
|
||||
const signozMetricsServiceName = dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm;
|
||||
const signozMetricsServiceName = WidgetKeys.OTelServiceName;
|
||||
const newAutoCompleteData: BaseAutocompleteData = {
|
||||
key: isSpanMetricEnable
|
||||
? signozLatencyBucketMetrics
|
||||
@@ -287,28 +282,21 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
threashold,
|
||||
delta,
|
||||
metricsBuckets,
|
||||
dotMetricsEnabled,
|
||||
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -317,9 +305,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -343,7 +329,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -363,9 +349,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -399,7 +383,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -409,9 +393,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -474,13 +456,10 @@ export const operationPerSec = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
},
|
||||
@@ -491,9 +470,7 @@ export const operationPerSec = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -534,7 +511,6 @@ export const errorPercentage = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozCallsTotal,
|
||||
@@ -553,9 +529,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -575,7 +549,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -589,9 +563,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -21,12 +21,9 @@ import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
|
||||
|
||||
export const topOperationQueries = ({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}: TopOperationQueryFactoryProps): QueryBuilderData => {
|
||||
const latencyAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -38,9 +35,7 @@ export const topOperationQueries = ({
|
||||
};
|
||||
|
||||
const numOfCallAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -49,9 +44,7 @@ export const topOperationQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -65,9 +58,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -77,7 +68,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -28,8 +28,6 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
GraphTitle,
|
||||
MENU_ITEMS,
|
||||
@@ -89,12 +87,7 @@ function DBCall(): JSX.Element {
|
||||
[queries],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
|
||||
const legend = '{{db.system}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
() =>
|
||||
@@ -106,7 +99,6 @@ function DBCall(): JSX.Element {
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -117,7 +109,7 @@ function DBCall(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.dbCallsRPS,
|
||||
fillSpans: false,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled, legend],
|
||||
[servicename, tagFilterItems, legend],
|
||||
);
|
||||
const databaseCallsAverageDurationWidget = useMemo(
|
||||
() =>
|
||||
@@ -128,7 +120,6 @@ function DBCall(): JSX.Element {
|
||||
builder: databaseCallsAvgDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -139,7 +130,7 @@ function DBCall(): JSX.Element {
|
||||
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
@@ -157,7 +148,7 @@ function DBCall(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
|
||||
@@ -30,8 +30,6 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
GraphTitle,
|
||||
legend,
|
||||
@@ -84,10 +82,6 @@ function External(): JSX.Element {
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const externalCallErrorWidget = useMemo(
|
||||
() =>
|
||||
@@ -99,7 +93,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -109,7 +102,7 @@ function External(): JSX.Element {
|
||||
yAxisUnit: '%',
|
||||
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
@@ -126,7 +119,6 @@ function External(): JSX.Element {
|
||||
builder: externalCallDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -137,7 +129,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const errorApmToTraceQuery = useGetAPMToTracesQueries({
|
||||
@@ -171,7 +163,7 @@ function External(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -194,7 +186,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -205,7 +196,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const externalCallDurationAddressWidget = useMemo(
|
||||
@@ -218,7 +209,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -229,7 +219,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const apmToTraceQuery = useGetAPMToTracesQueries({
|
||||
|
||||
@@ -93,15 +93,12 @@ function Application(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const logEventCalledRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -159,7 +156,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -169,7 +165,7 @@ function Application(): JSX.Element {
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const errorPercentageWidget = useMemo(
|
||||
@@ -182,7 +178,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -193,7 +188,7 @@ function Application(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
|
||||
@@ -22,8 +22,6 @@ import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/Me
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { IServiceName } from '../../types';
|
||||
import { ApDexMetricsProps } from './types';
|
||||
|
||||
@@ -38,10 +36,6 @@ function ApDexMetrics({
|
||||
}: ApDexMetricsProps): JSX.Element {
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
const apDexMetricsWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -55,7 +49,6 @@ function ApDexMetrics({
|
||||
threashold: thresholdValue || 0,
|
||||
delta: delta || false,
|
||||
metricsBuckets: metricsBuckets || [],
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -81,7 +74,6 @@ function ApDexMetrics({
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import Spinner from 'components/Spinner';
|
||||
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { WidgetKeys } from '../../../constant';
|
||||
import { IServiceName } from '../../types';
|
||||
import ApDexMetrics from './ApDexMetrics';
|
||||
@@ -20,17 +18,8 @@ function ApDexMetricsApplication({
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
|
||||
const { data, isLoading, error } = useGetMetricMeta(
|
||||
signozLatencyBucketMetrics,
|
||||
WidgetKeys.SignozLatencyBucket,
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
@@ -56,10 +56,6 @@ function ServiceOverview({
|
||||
[isSpanMetricEnable, queries],
|
||||
);
|
||||
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const latencyWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -71,7 +67,6 @@ function ServiceOverview({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -81,13 +76,7 @@ function ServiceOverview({
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
}),
|
||||
[
|
||||
isSpanMetricEnable,
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
[isSpanMetricEnable, servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const isQueryEnabled =
|
||||
|
||||
@@ -19,8 +19,6 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { IServiceName } from '../types';
|
||||
import { title } from './config';
|
||||
import ColumnWithLink from './TableRenderer/ColumnWithLink';
|
||||
@@ -44,11 +42,6 @@ function TopOperationMetrics(): JSX.Element {
|
||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const keyOperationWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -57,14 +50,13 @@ function TopOperationMetrics(): JSX.Element {
|
||||
promql: [],
|
||||
builder: topOperationQueries({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
panelTypes: PANEL_TYPES.TABLE,
|
||||
}),
|
||||
[servicename, dotMetricsEnabled],
|
||||
[servicename],
|
||||
);
|
||||
|
||||
const updatedQuery = updateStepInterval(keyOperationWidget.query);
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface IServiceName {
|
||||
|
||||
export interface TopOperationQueryFactoryProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
@@ -20,7 +19,6 @@ export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
export interface ExternalCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderQueriesProps {
|
||||
@@ -52,7 +50,6 @@ export interface OperationPerSecProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
topLevelOperations: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface LatencyProps {
|
||||
@@ -60,7 +57,6 @@ export interface LatencyProps {
|
||||
tagFilterItems: TagFilterItem[];
|
||||
isSpanMetricEnable?: boolean;
|
||||
topLevelOperationsRoute: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ApDexProps {
|
||||
@@ -78,5 +74,4 @@ export interface TableRendererProps {
|
||||
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||
delta: boolean;
|
||||
metricsBuckets: number[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,11 @@ export enum WidgetKeys {
|
||||
HasError = 'hasError',
|
||||
Address = 'address',
|
||||
DurationNano = 'durationNano',
|
||||
StatusCodeNorm = 'status_code',
|
||||
StatusCode = 'status.code',
|
||||
Operation = 'operation',
|
||||
OperationName = 'operationName',
|
||||
Service_name_norm = 'service_name',
|
||||
Service_name = 'service.name',
|
||||
OTelServiceName = 'service.name',
|
||||
ServiceName = 'serviceName',
|
||||
SignozLatencyCountNorm = 'signoz_latency_count',
|
||||
SignozLatencyCount = 'signoz_latency.count',
|
||||
SignozDBLatencyCount = 'signoz_db_latency_count',
|
||||
DatabaseCallCount = 'signoz_database_call_count',
|
||||
@@ -101,10 +98,8 @@ export enum WidgetKeys {
|
||||
SignozCallsTotal = 'signoz_calls_total',
|
||||
SignozExternalCallLatencyCount = 'signoz_external_call_latency_count',
|
||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||
Signoz_latency_bucket_norm = 'signoz_latency_bucket',
|
||||
Signoz_latency_bucket = 'signoz_latency.bucket',
|
||||
Db_system = 'db.system',
|
||||
Db_system_norm = 'db_system',
|
||||
SignozLatencyBucket = 'signoz_latency.bucket',
|
||||
DbSystem = 'db.system',
|
||||
}
|
||||
|
||||
export const topOperationMetricsDownloadOptions: DownloadOptions = {
|
||||
|
||||
@@ -32,5 +32,4 @@ export interface DatabaseCallsRPSProps extends DatabaseCallProps {
|
||||
export interface DatabaseCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { MetricsSearchProps } from './types';
|
||||
@@ -23,12 +24,14 @@ function MetricsSearch({
|
||||
);
|
||||
|
||||
const handleStageAndRunQuery = useCallback(() => {
|
||||
saveRecentQueryByExpression(DataSource.METRICS, currentQueryFilterExpression);
|
||||
onChange(currentQueryFilterExpression);
|
||||
onRunQuery?.();
|
||||
}, [currentQueryFilterExpression, onChange, onRunQuery]);
|
||||
|
||||
const handleRunQuery = useCallback(
|
||||
(expression: string): void => {
|
||||
saveRecentQueryByExpression(DataSource.METRICS, expression);
|
||||
setCurrentQueryFilterExpression(expression);
|
||||
onChange(expression);
|
||||
},
|
||||
|
||||
@@ -387,4 +387,42 @@ describe('useOptionsMenu', () => {
|
||||
expect(remaining).toHaveLength(seedColumns.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fieldsSelector.value drops legacy columns without a name', () => {
|
||||
it('excludes entries missing name while keeping valid columns', () => {
|
||||
(useGetQueryKeySuggestions as jest.Mock).mockReturnValue({
|
||||
data: { data: { data: { keys: {} } } },
|
||||
isFetching: false,
|
||||
});
|
||||
(usePreferenceContext as jest.Mock).mockReturnValue({
|
||||
traces: {
|
||||
preferences: {
|
||||
columns: [
|
||||
{ name: 'body', fieldContext: 'log' },
|
||||
{ key: 'legacy-key-no-name', fieldContext: 'log' },
|
||||
{ name: 'timestamp', fieldContext: 'log' },
|
||||
],
|
||||
formatting: { format: 'table', maxLines: 1, fontSize: 'small' },
|
||||
},
|
||||
updateColumns: mockUpdateColumns,
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
},
|
||||
logs: {
|
||||
preferences: { columns: [], formatting: {} },
|
||||
updateColumns: mockUpdateColumns,
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useOptionsMenu({
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateOperator: 'count',
|
||||
}),
|
||||
);
|
||||
|
||||
const fields = result.current.config.fieldsSelector?.value ?? [];
|
||||
expect(fields.map((f) => f.name)).toStrictEqual(['body', 'timestamp']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -399,7 +399,7 @@ const useOptionsMenu = ({
|
||||
onReorder: reorderSelectColumns,
|
||||
},
|
||||
fieldsSelector: {
|
||||
value: preferences?.columns ?? [],
|
||||
value: preferences?.columns?.filter((item) => has(item, 'name')) ?? [],
|
||||
onFieldsChange: updateColumns,
|
||||
},
|
||||
format: {
|
||||
|
||||
@@ -53,8 +53,6 @@ import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
|
||||
import { useSelectPopupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { selectStyle } from './config';
|
||||
import { PLACEHOLDER } from './constant';
|
||||
import ExampleQueriesRendererForLogs from './ExampleQueriesRendererForLogs';
|
||||
@@ -104,11 +102,6 @@ function QueryBuilderSearch({
|
||||
|
||||
const [isEditingTag, setIsEditingTag] = useState(false);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const {
|
||||
updateTag,
|
||||
handleClearTag,
|
||||
@@ -128,7 +121,6 @@ function QueryBuilderSearch({
|
||||
exampleQueries,
|
||||
} = useAutoComplete(
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
whereClauseConfig,
|
||||
isLogsExplorerPage,
|
||||
isInfraMonitoring,
|
||||
@@ -146,7 +138,6 @@ function QueryBuilderSearch({
|
||||
const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues(
|
||||
searchValue,
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
searchKey,
|
||||
isLogsExplorerPage,
|
||||
isInfraMonitoring,
|
||||
|
||||
@@ -33,7 +33,7 @@ jest.mock('hooks/useNotifications', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const RESET_PASSWORD_ENDPOINT = '*/resetPassword';
|
||||
const RESET_PASSWORD_ENDPOINT = '*/api/v2/factor_password/reset';
|
||||
|
||||
const mockHistoryPush = history.push as jest.MockedFunction<
|
||||
typeof history.push
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-use';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Callout } from '@signozhq/ui/callout';
|
||||
import { Form, Input as AntdInput } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import resetPasswordApi from 'api/v1/factor_password/resetPassword';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { useResetPassword } from 'api/generated/services/users';
|
||||
import AuthError from 'components/AuthError/AuthError';
|
||||
import AuthPageContainer from 'components/AuthPageContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -14,7 +15,6 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { ArrowRight, CircleAlert, KeyRound } from '@signozhq/icons';
|
||||
import { Label } from 'pages/SignUp/styles';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { FormContainer } from './styles';
|
||||
|
||||
@@ -26,40 +26,41 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
const [confirmPasswordError, setConfirmPasswordError] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<APIError | null>();
|
||||
|
||||
const [isValidPassword, setIsValidPassword] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation(['common']);
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const token = params.get('token');
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const {
|
||||
mutate: resetPassword,
|
||||
isLoading,
|
||||
error: mutationError,
|
||||
} = useResetPassword();
|
||||
|
||||
const errorMessage = useMemo(
|
||||
() => convertToApiError(mutationError),
|
||||
[mutationError],
|
||||
);
|
||||
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const handleFormSubmit: () => Promise<void> = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setErrorMessage(null);
|
||||
const { password } = form.getFieldsValue();
|
||||
const handleFormSubmit = (): void => {
|
||||
const { password } = form.getFieldsValue();
|
||||
|
||||
await resetPasswordApi({
|
||||
password,
|
||||
token: token || '',
|
||||
});
|
||||
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
history.push(ROUTES.LOGIN);
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
setErrorMessage(error as APIError);
|
||||
}
|
||||
resetPassword(
|
||||
{ data: { password, token: token || '' } },
|
||||
{
|
||||
onSuccess: (): void => {
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
history.push(ROUTES.LOGIN);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const validatePassword = (): boolean => {
|
||||
@@ -222,7 +223,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-attr="reset-password"
|
||||
disabled={!isValidPassword || loading}
|
||||
disabled={!isValidPassword || isLoading}
|
||||
className="reset-password-submit-button"
|
||||
suffix={<ArrowRight size={16} />}
|
||||
>
|
||||
|
||||
@@ -14,8 +14,6 @@ import { SelectOption } from 'types/common/select';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import QueryChip from './components/QueryChip';
|
||||
import { QueryChipItem, SearchContainer } from './styles';
|
||||
|
||||
@@ -42,12 +40,7 @@ function ResourceAttributesFilter({
|
||||
SelectOption<string, string>[]
|
||||
>([]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys(dotMetricsEnabled);
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys();
|
||||
|
||||
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>([]);
|
||||
|
||||
@@ -73,21 +66,20 @@ function ResourceAttributesFilter({
|
||||
}, [queries, resourceDeploymentKey]);
|
||||
|
||||
useEffect(() => {
|
||||
getEnvironmentTagKeys(dotMetricsEnabled).then((tagKeys) => {
|
||||
getEnvironmentTagKeys().then((tagKeys) => {
|
||||
if (tagKeys && Array.isArray(tagKeys) && tagKeys.length > 0) {
|
||||
getEnvironmentTagValues(dotMetricsEnabled).then((tagValues) => {
|
||||
getEnvironmentTagValues().then((tagValues) => {
|
||||
setEnvironments(tagValues);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [dotMetricsEnabled]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="resourceAttributesFilter-container">
|
||||
<div className="environment-selector">
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
key={selectedEnvironments.join('')}
|
||||
showSearch
|
||||
mode="multiple"
|
||||
value={selectedEnvironments}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
|
||||
import { encode } from 'js-base64';
|
||||
|
||||
import ResourceAttributesFilter from '../ResourceAttributesFilter';
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
push: jest.fn(),
|
||||
location: { search: '', pathname: '/' },
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('api/metrics/getResourceAttributes', () => ({
|
||||
getResourceAttributesTagKeys: jest.fn(),
|
||||
getResourceAttributesTagValues: jest.fn(),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line import/first, import/order
|
||||
import {
|
||||
getResourceAttributesTagKeys,
|
||||
getResourceAttributesTagValues,
|
||||
// eslint-disable-next-line import/newline-after-import
|
||||
} from 'api/metrics/getResourceAttributes';
|
||||
// eslint-disable-next-line import/first, import/order
|
||||
import history from 'lib/history';
|
||||
|
||||
const mockTagKeys = getResourceAttributesTagKeys as jest.MockedFunction<
|
||||
typeof getResourceAttributesTagKeys
|
||||
>;
|
||||
const mockTagValues = getResourceAttributesTagValues as jest.MockedFunction<
|
||||
typeof getResourceAttributesTagValues
|
||||
>;
|
||||
|
||||
function tagKeysPayload(keys: string[]): never {
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'ok',
|
||||
payload: {
|
||||
data: {
|
||||
attributeKeys: keys.map((key) => ({
|
||||
key,
|
||||
dataType: 'string',
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
})),
|
||||
},
|
||||
},
|
||||
} as unknown as never;
|
||||
}
|
||||
|
||||
function tagValuesPayload(values: string[]): never {
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'ok',
|
||||
payload: { data: { stringAttributeValues: values } },
|
||||
} as unknown as never;
|
||||
}
|
||||
|
||||
function seedUrl(queries: IResourceAttribute[], pathname: string): void {
|
||||
const location = history.location as { search: string; pathname: string };
|
||||
location.search = queries.length
|
||||
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
|
||||
: '';
|
||||
location.pathname = pathname;
|
||||
}
|
||||
|
||||
function renderFilter(pathname: string): MemoryHistory {
|
||||
const routerHistory = createMemoryHistory({
|
||||
initialEntries: [`${pathname}${history.location.search}`],
|
||||
});
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
|
||||
function Wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router history={routerHistory}>
|
||||
<ResourceProvider>{children}</ResourceProvider>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<ResourceAttributesFilter />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
return routerHistory;
|
||||
}
|
||||
|
||||
describe('ResourceAttributesFilter', () => {
|
||||
beforeEach(() => {
|
||||
mockTagKeys.mockReset();
|
||||
mockTagValues.mockReset();
|
||||
mockTagKeys.mockResolvedValue(
|
||||
tagKeysPayload(['resource_deployment.environment']),
|
||||
);
|
||||
mockTagValues.mockResolvedValue(tagValuesPayload(['production', 'staging']));
|
||||
seedUrl([], '/');
|
||||
});
|
||||
|
||||
it('shows every applied filter on the service map, including ones it cannot apply', async () => {
|
||||
seedUrl(
|
||||
[
|
||||
{
|
||||
id: 'svc',
|
||||
tagKey: 'resource_service_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['frontend'],
|
||||
},
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
],
|
||||
ROUTES.SERVICE_MAP,
|
||||
);
|
||||
|
||||
renderFilter(ROUTES.SERVICE_MAP);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/service\.name/)).toBeInTheDocument(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('resource-environment-filter')
|
||||
.querySelector('.ant-select-selection-item'),
|
||||
).toHaveTextContent('production'),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the environment dropdown open so more than one environment can be picked', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderFilter('/services');
|
||||
|
||||
const environmentFilter = screen.getByTestId('resource-environment-filter');
|
||||
await user.click(
|
||||
environmentFilter.querySelector('input') as HTMLInputElement,
|
||||
);
|
||||
|
||||
await user.click(await screen.findByTitle('production'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByTitle('staging').closest('.ant-select-dropdown'),
|
||||
).not.toHaveClass('ant-select-dropdown-hidden'),
|
||||
);
|
||||
|
||||
await user.click(screen.getByTitle('staging'));
|
||||
|
||||
await waitFor(() => {
|
||||
const selected = Array.from(
|
||||
environmentFilter.querySelectorAll('.ant-select-selection-item-content'),
|
||||
).map((node) => node.textContent);
|
||||
expect(selected).toStrictEqual(['production', 'staging']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
getResourceDeploymentKeys,
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { QueryChipContainer, QueryChipItem } from '../../styles';
|
||||
import { IQueryChipProps } from './types';
|
||||
|
||||
@@ -13,13 +11,7 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
|
||||
onClose(queryData.id);
|
||||
};
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const isClosable =
|
||||
queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled);
|
||||
const isClosable = queryData.tagKey !== getResourceDeploymentKeys();
|
||||
|
||||
return (
|
||||
<QueryChipContainer>
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { ServiceMetricsProps } from '../types';
|
||||
import { getQueryRangeRequestData } from '../utils';
|
||||
import ServiceMetricTable from './ServiceMetricTable';
|
||||
@@ -18,19 +16,13 @@ function ServiceMetricsApplication({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryRangeRequestData = useMemo(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
);
|
||||
return (
|
||||
<ServiceMetricTable
|
||||
|
||||
@@ -19,13 +19,10 @@ import {
|
||||
|
||||
export const serviceMetricsQuery = (
|
||||
topLevelOperation: [keyof ServiceDataProps, string[]],
|
||||
dotMetricsEnabled: boolean,
|
||||
): QueryBuilderData => {
|
||||
const p99AutoCompleteData: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
type: '',
|
||||
};
|
||||
|
||||
@@ -53,9 +50,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -78,9 +73,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -90,7 +83,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -113,9 +106,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -138,9 +129,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -193,9 +182,7 @@ export const serviceMetricsQuery = (
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,8 +17,6 @@ import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import SkipOnBoardingModal from '../SkipOnBoardModal';
|
||||
import ServiceTraceTable from './ServiceTracesTable';
|
||||
|
||||
@@ -40,11 +38,6 @@ function ServiceTraces(): JSX.Element {
|
||||
selectedTags,
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const services = data || [];
|
||||
@@ -62,7 +55,7 @@ function ServiceTraces(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && !isUndefined(data)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
const rps = data.reduce((total, service) => total + service.callRate, 0);
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface ServiceMetricsTableProps {
|
||||
export interface GetQueryRangeRequestDataProps {
|
||||
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||
globalSelectedInterval: Time | CustomTimeType;
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface GetServiceListFromQueryProps {
|
||||
|
||||
@@ -26,7 +26,6 @@ export function getSeriesValue(
|
||||
export const getQueryRangeRequestData = ({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}: GetQueryRangeRequestDataProps): GetQueryResultsProps[] => {
|
||||
const requestData: GetQueryResultsProps[] = [];
|
||||
topLevelOperations.forEach((operation) => {
|
||||
@@ -34,7 +33,7 @@ export const getQueryRangeRequestData = ({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: serviceMetricsQuery(operation, dotMetricsEnabled),
|
||||
builder: serviceMetricsQuery(operation),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import type { AuthtypesGettableRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import type {
|
||||
AuthtypesGettableRoleDTO,
|
||||
AuthtypesUserRoleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
getGetRolesByUserIDQueryKey,
|
||||
useGetRolesByUserID,
|
||||
useRemoveUserRoleByUserIDAndRoleID,
|
||||
useSetRoleByUserID,
|
||||
useCreateUserRole,
|
||||
useDeleteUserRole,
|
||||
useGetUser,
|
||||
} from 'api/generated/services/users';
|
||||
import { retryOn429 } from 'utils/errorUtils';
|
||||
|
||||
const enum PromiseStatus {
|
||||
Fulfilled = 'fulfilled',
|
||||
Rejected = 'rejected',
|
||||
}
|
||||
|
||||
// Stable identity so the memos below do not recompute on every render.
|
||||
const EMPTY_USER_ROLES: AuthtypesUserRoleDTO[] = [];
|
||||
|
||||
export interface MemberRoleUpdateFailure {
|
||||
roleName: string;
|
||||
error: unknown;
|
||||
@@ -33,31 +36,31 @@ export function useMemberRoleManager(
|
||||
userId: string,
|
||||
enabled: boolean,
|
||||
): UseMemberRoleManagerResult {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading } = useGetRolesByUserID(
|
||||
const { data, isLoading } = useGetUser(
|
||||
{ id: userId },
|
||||
{ query: { enabled: !!userId && enabled } },
|
||||
);
|
||||
|
||||
const userRoles = data?.data?.userRoles ?? EMPTY_USER_ROLES;
|
||||
|
||||
const currentRoles = useMemo<AuthtypesGettableRoleDTO[]>(
|
||||
() => data?.data ?? [],
|
||||
[data?.data],
|
||||
() => userRoles.map((userRole) => userRole.role),
|
||||
[userRoles],
|
||||
);
|
||||
|
||||
const { mutateAsync: setRole } = useSetRoleByUserID({
|
||||
mutation: { retry: retryOn429 },
|
||||
});
|
||||
const { mutateAsync: removeRole } = useRemoveUserRoleByUserIDAndRoleID({
|
||||
mutation: { retry: retryOn429 },
|
||||
});
|
||||
|
||||
const invalidateRoles = useCallback(
|
||||
() =>
|
||||
queryClient.invalidateQueries(getGetRolesByUserIDQueryKey({ id: userId })),
|
||||
[userId, queryClient],
|
||||
// DELETE /api/v2/user_roles/{id} is keyed by the user_role join row, not the role.
|
||||
const assignmentIdByRoleId = useMemo(
|
||||
() => new Map(userRoles.map((userRole) => [userRole.roleId, userRole.id])),
|
||||
[userRoles],
|
||||
);
|
||||
|
||||
const { mutateAsync: createUserRole } = useCreateUserRole({
|
||||
mutation: { retry: retryOn429 },
|
||||
});
|
||||
const { mutateAsync: deleteUserRole } = useDeleteUserRole({
|
||||
mutation: { retry: retryOn429 },
|
||||
});
|
||||
|
||||
const applyDiff = useCallback(
|
||||
async (
|
||||
localRoleIds: string[],
|
||||
@@ -80,30 +83,33 @@ export function useMemberRoleManager(
|
||||
const allOperations = [
|
||||
...addedRoles.map((role) => ({
|
||||
role,
|
||||
run: (): ReturnType<typeof setRole> =>
|
||||
setRole({
|
||||
pathParams: { id: userId },
|
||||
data: { name: role.name ?? '' },
|
||||
}),
|
||||
})),
|
||||
...removedRoles.map((role) => ({
|
||||
role,
|
||||
run: (): ReturnType<typeof removeRole> =>
|
||||
removeRole({ pathParams: { id: userId, roleId: role.id ?? '' } }),
|
||||
run: (): ReturnType<typeof createUserRole> =>
|
||||
createUserRole({ data: { userId, roleId: role.id ?? '' } }),
|
||||
})),
|
||||
...removedRoles
|
||||
.map((role) => ({
|
||||
role,
|
||||
assignmentId: assignmentIdByRoleId.get(role.id ?? ''),
|
||||
}))
|
||||
.filter(
|
||||
(
|
||||
entry,
|
||||
): entry is {
|
||||
role: AuthtypesGettableRoleDTO;
|
||||
assignmentId: string;
|
||||
} => !!entry.assignmentId,
|
||||
)
|
||||
.map(({ role, assignmentId }) => ({
|
||||
role,
|
||||
run: (): ReturnType<typeof deleteUserRole> =>
|
||||
deleteUserRole({ pathParams: { id: assignmentId } }),
|
||||
})),
|
||||
];
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
allOperations.map((op) => op.run()),
|
||||
);
|
||||
|
||||
const successCount = results.filter(
|
||||
(r) => r.status === PromiseStatus.Fulfilled,
|
||||
).length;
|
||||
if (successCount > 0) {
|
||||
await invalidateRoles();
|
||||
}
|
||||
|
||||
const failures: MemberRoleUpdateFailure[] = [];
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === PromiseStatus.Rejected) {
|
||||
@@ -113,7 +119,6 @@ export function useMemberRoleManager(
|
||||
error: result.reason,
|
||||
onRetry: async (): Promise<void> => {
|
||||
await run();
|
||||
await invalidateRoles();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -121,7 +126,7 @@ export function useMemberRoleManager(
|
||||
|
||||
return failures;
|
||||
},
|
||||
[userId, currentRoles, setRole, removeRole, invalidateRoles],
|
||||
[userId, currentRoles, assignmentIdByRoleId, createUserRole, deleteUserRole],
|
||||
);
|
||||
|
||||
return { currentRoles, isLoading, applyDiff };
|
||||
|
||||
@@ -27,7 +27,6 @@ export type WhereClauseConfig = {
|
||||
|
||||
export const useAutoComplete = (
|
||||
query: IBuilderQuery,
|
||||
dotMetricsEnabled: boolean,
|
||||
whereClauseConfig?: WhereClauseConfig,
|
||||
shouldUseSuggestions?: boolean,
|
||||
isInfraMonitoring?: boolean,
|
||||
@@ -40,7 +39,6 @@ export const useAutoComplete = (
|
||||
const { keys, results, isFetching, exampleQueries } = useFetchKeysAndValues(
|
||||
searchValue,
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
searchKey,
|
||||
shouldUseSuggestions,
|
||||
isInfraMonitoring,
|
||||
|
||||
@@ -48,7 +48,6 @@ type IuseFetchKeysAndValues = {
|
||||
export const useFetchKeysAndValues = (
|
||||
searchValue: string,
|
||||
query: IBuilderQuery,
|
||||
dotMetricsEnabled: boolean,
|
||||
searchKey: string,
|
||||
shouldUseSuggestions?: boolean,
|
||||
isInfraMonitoring?: boolean,
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { encode } from 'js-base64';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { whilelistedKeys } from './config';
|
||||
import { ResourceContext } from './context';
|
||||
import {
|
||||
IResourceAttribute,
|
||||
@@ -58,11 +54,6 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const dispatchQueries = useCallback(
|
||||
(queries: IResourceAttribute[]): void => {
|
||||
urlQuery.set(
|
||||
@@ -78,7 +69,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
const loadTagKeys = (): void => {
|
||||
handleLoading(true);
|
||||
GetTagKeys(dotMetricsEnabled)
|
||||
GetTagKeys()
|
||||
.then((tagKeys) => {
|
||||
const options = mappingWithRoutesAndKeys(pathname, tagKeys);
|
||||
setOptionsData({ options, mode: undefined });
|
||||
@@ -161,15 +152,15 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
setSelectedQueries([...value]);
|
||||
},
|
||||
[optionsData.mode, step, staging, dotMetricsEnabled, pathname],
|
||||
[optionsData.mode, step, staging, pathname],
|
||||
);
|
||||
|
||||
const handleEnvironmentChange = useCallback(
|
||||
(environments: string[]): void => {
|
||||
const staging = [getResourceDeploymentKeys(dotMetricsEnabled), 'IN'];
|
||||
const staging = [getResourceDeploymentKeys(), 'IN'];
|
||||
|
||||
const queriesCopy = queries.filter(
|
||||
(query) => query.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(query) => query.tagKey !== getResourceDeploymentKeys(),
|
||||
);
|
||||
|
||||
if (environments && Array.isArray(environments) && environments.length > 0) {
|
||||
@@ -184,7 +175,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
setStep('Idle');
|
||||
},
|
||||
[dispatchQueries, dotMetricsEnabled, queries],
|
||||
[dispatchQueries, queries],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(
|
||||
@@ -202,16 +193,9 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
setOptionsData({ mode: undefined, options: [] });
|
||||
}, [dispatchQueries]);
|
||||
|
||||
const getVisibleQueries = useMemo(() => {
|
||||
if (pathname === ROUTES.SERVICE_MAP) {
|
||||
return queries.filter((query) => whilelistedKeys.includes(query.tagKey));
|
||||
}
|
||||
return queries;
|
||||
}, [queries, pathname]);
|
||||
|
||||
const value: IResourceAttributeProps = useMemo(
|
||||
() => ({
|
||||
queries: getVisibleQueries,
|
||||
queries,
|
||||
staging,
|
||||
handleClearAll,
|
||||
handleClose,
|
||||
@@ -234,7 +218,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
staging,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
getVisibleQueries,
|
||||
queries,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -2,13 +2,9 @@ import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import { encode } from 'js-base64';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
|
||||
import ResourceProvider from '../ResourceProvider';
|
||||
import useResourceAttribute from '../useResourceAttribute';
|
||||
@@ -55,10 +51,8 @@ const mockTagValues = getResourceAttributesTagValues as jest.MockedFunction<
|
||||
|
||||
function createWrapper({
|
||||
routerHistory,
|
||||
appContextOverrides,
|
||||
}: {
|
||||
routerHistory: MemoryHistory;
|
||||
appContextOverrides?: Partial<IAppContext>;
|
||||
}): ({ children }: { children: ReactNode }) => JSX.Element {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
@@ -66,13 +60,9 @@ function createWrapper({
|
||||
return function Wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppContext.Provider
|
||||
value={getAppContextMock('ADMIN', appContextOverrides)}
|
||||
>
|
||||
<Router history={routerHistory}>
|
||||
<ResourceProvider>{children}</ResourceProvider>
|
||||
</Router>
|
||||
</AppContext.Provider>
|
||||
<Router history={routerHistory}>
|
||||
<ResourceProvider>{children}</ResourceProvider>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
@@ -411,7 +401,7 @@ describe('ResourceProvider', () => {
|
||||
});
|
||||
|
||||
describe('handleEnvironmentChange', () => {
|
||||
it('adds an environment query when envs are provided', async () => {
|
||||
it('adds a dotted environment query when envs are provided', async () => {
|
||||
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
|
||||
const { result } = renderHook(() => useResourceAttribute(), {
|
||||
wrapper: createWrapper({ routerHistory }),
|
||||
@@ -424,7 +414,7 @@ describe('ResourceProvider', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries).toHaveLength(1);
|
||||
expect(result.current.queries[0]).toMatchObject({
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
});
|
||||
@@ -435,7 +425,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -459,7 +449,7 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const tagKeys = result.current.queries.map((q) => q.tagKey);
|
||||
expect(tagKeys).not.toContain('resource_deployment_environment');
|
||||
expect(tagKeys).not.toContain('resource_deployment.environment');
|
||||
expect(tagKeys).toContain('resource_service_name');
|
||||
});
|
||||
});
|
||||
@@ -468,7 +458,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -486,43 +476,13 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const envQueries = result.current.queries.filter(
|
||||
(q) => q.tagKey === 'resource_deployment_environment',
|
||||
(q) => q.tagKey === 'resource_deployment.environment',
|
||||
);
|
||||
expect(envQueries).toHaveLength(1);
|
||||
expect(envQueries[0].tagValue).toStrictEqual(['staging']);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the dotted deployment env key when DOT_METRICS_ENABLED is active', async () => {
|
||||
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
|
||||
const { result } = renderHook(() => useResourceAttribute(), {
|
||||
wrapper: createWrapper({
|
||||
routerHistory,
|
||||
appContextOverrides: {
|
||||
featureFlags: [
|
||||
{
|
||||
name: FeatureKeys.DOT_METRICS_ENABLED,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleEnvironmentChange(['production']);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries[0].tagKey).toBe(
|
||||
'resource_deployment.environment',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves unrelated query params when dispatching', async () => {
|
||||
const routerHistory = createMemoryHistory({
|
||||
initialEntries: ['/?tab=overview'],
|
||||
@@ -544,22 +504,23 @@ describe('ResourceProvider', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVisibleQueries (SERVICE_MAP filtering)', () => {
|
||||
it('filters queries down to whitelisted keys on SERVICE_MAP', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'a',
|
||||
tagKey: 'resource_service_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['frontend'],
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
tagKey: 'resource_k8s_cluster_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['prod'],
|
||||
},
|
||||
];
|
||||
describe('SERVICE_MAP', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'a',
|
||||
tagKey: 'resource_service_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['frontend'],
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
tagKey: 'resource_k8s_cluster_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['prod'],
|
||||
},
|
||||
];
|
||||
|
||||
it('exposes every query from the URL, including ones the map cannot apply', () => {
|
||||
mockLibHistory(
|
||||
`?resourceAttribute=${encode(JSON.stringify(seeded))}`,
|
||||
ROUTES.SERVICE_MAP,
|
||||
@@ -572,24 +533,10 @@ describe('ResourceProvider', () => {
|
||||
wrapper: createWrapper({ routerHistory }),
|
||||
});
|
||||
|
||||
expect(result.current.queries).toStrictEqual([seeded[1]]);
|
||||
expect(result.current.queries).toStrictEqual(seeded);
|
||||
});
|
||||
|
||||
it('returns all queries on non-SERVICE_MAP routes', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'a',
|
||||
tagKey: 'resource_service_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['frontend'],
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
tagKey: 'resource_k8s_cluster_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['prod'],
|
||||
},
|
||||
];
|
||||
mockLibHistory(
|
||||
`?resourceAttribute=${encode(JSON.stringify(seeded))}`,
|
||||
'/services',
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import { whilelistedKeys } from '../config';
|
||||
import { mappingWithRoutesAndKeys } from '../utils';
|
||||
import {
|
||||
filterServiceMapSupportedQueries,
|
||||
mappingWithRoutesAndKeys,
|
||||
} from '../utils';
|
||||
|
||||
describe('useResourceAttribute config', () => {
|
||||
describe('whilelistedKeys', () => {
|
||||
it('should include underscore-notation keys (DOT_METRICS_ENABLED=false)', () => {
|
||||
it('should include underscore-notation keys', () => {
|
||||
expect(whilelistedKeys).toContain('resource_deployment_environment');
|
||||
expect(whilelistedKeys).toContain('resource_k8s_cluster_name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s_cluster_namespace');
|
||||
});
|
||||
|
||||
it('should include dot-notation keys (DOT_METRICS_ENABLED=true)', () => {
|
||||
it('should include dot-notation keys', () => {
|
||||
expect(whilelistedKeys).toContain('resource_deployment.environment');
|
||||
expect(whilelistedKeys).toContain('resource_k8s.cluster.name');
|
||||
expect(whilelistedKeys).toContain('resource_k8s.cluster.namespace');
|
||||
@@ -74,4 +77,29 @@ describe('useResourceAttribute config', () => {
|
||||
expect(result).toStrictEqual(allFilters);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterServiceMapSupportedQueries', () => {
|
||||
const environmentQuery = {
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
};
|
||||
const serviceQuery = {
|
||||
id: 'svc',
|
||||
tagKey: 'resource_service_name',
|
||||
operator: 'IN',
|
||||
tagValue: ['frontend'],
|
||||
};
|
||||
|
||||
it('should keep only the queries the service map can filter on', () => {
|
||||
expect(
|
||||
filterServiceMapSupportedQueries([environmentQuery, serviceQuery]),
|
||||
).toStrictEqual([environmentQuery]);
|
||||
});
|
||||
|
||||
it('should return an empty list when no query is supported', () => {
|
||||
expect(filterServiceMapSupportedQueries([serviceQuery])).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,19 +144,11 @@ export const OperatorSchema: IOption[] = OperatorConversions.map(
|
||||
}),
|
||||
);
|
||||
|
||||
export const getResourceDeploymentKeys = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): string => {
|
||||
if (dotMetricsEnabled) {
|
||||
return 'resource_deployment.environment';
|
||||
}
|
||||
return 'resource_deployment_environment';
|
||||
};
|
||||
export const getResourceDeploymentKeys = (): string =>
|
||||
'resource_deployment.environment';
|
||||
|
||||
export const GetTagKeys = async (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Promise<IOption[]> => {
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys(dotMetricsEnabled);
|
||||
export const GetTagKeys = async (): Promise<IOption[]> => {
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys();
|
||||
const { payload } = await getResourceAttributesTagKeys({
|
||||
metricName: 'signoz_calls_total',
|
||||
match: 'resource_',
|
||||
@@ -176,12 +168,10 @@ export const GetTagKeys = async (
|
||||
}));
|
||||
};
|
||||
|
||||
export const getEnvironmentTagKeys = async (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Promise<IOption[]> => {
|
||||
export const getEnvironmentTagKeys = async (): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagKeys({
|
||||
metricName: 'signoz_calls_total',
|
||||
match: getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
match: getResourceDeploymentKeys(),
|
||||
});
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
@@ -194,11 +184,9 @@ export const getEnvironmentTagKeys = async (
|
||||
}));
|
||||
};
|
||||
|
||||
export const getEnvironmentTagValues = async (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Promise<IOption[]> => {
|
||||
export const getEnvironmentTagValues = async (): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagValues({
|
||||
tagKey: getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
tagKey: getResourceDeploymentKeys(),
|
||||
metricName: 'signoz_calls_total',
|
||||
});
|
||||
|
||||
@@ -293,3 +281,8 @@ export const mappingWithRoutesAndKeys = (
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
|
||||
export const filterServiceMapSupportedQueries = (
|
||||
queries: IResourceAttribute[],
|
||||
): IResourceAttribute[] =>
|
||||
queries.filter((query) => whilelistedKeys.includes(query.tagKey));
|
||||
|
||||
@@ -19,6 +19,30 @@ type CompositeWithBuilder = {
|
||||
builder?: { queryData?: IBuilderQuery[] };
|
||||
};
|
||||
|
||||
export function saveRecentQueryByExpression(
|
||||
dataSource: IBuilderQuery['dataSource'],
|
||||
expression: string | null | undefined,
|
||||
source = '',
|
||||
): void {
|
||||
const trimmed = expression?.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
const validation = validateQuery(trimmed);
|
||||
if (!validation.isValid) {
|
||||
return;
|
||||
}
|
||||
const signal = toSignal(dataSource);
|
||||
if (!signal) {
|
||||
return;
|
||||
}
|
||||
store.save({
|
||||
signal,
|
||||
source,
|
||||
filter: { expression: trimmed },
|
||||
});
|
||||
}
|
||||
|
||||
// Persists each builder query in the composite as a recent entry. Call this
|
||||
// only from explicit user-driven Run triggers — reacting to stagedQuery or any
|
||||
// other derived state pollutes recents with navigation/refresh/go-to traffic.
|
||||
@@ -31,22 +55,10 @@ export function saveRecentQuery(
|
||||
}
|
||||
|
||||
queryData.forEach((q) => {
|
||||
const expression = q.filter?.expression?.trim();
|
||||
if (!expression) {
|
||||
return;
|
||||
}
|
||||
const validation = validateQuery(expression);
|
||||
if (!validation.isValid) {
|
||||
return;
|
||||
}
|
||||
const signal = toSignal(q.dataSource);
|
||||
if (!signal) {
|
||||
return;
|
||||
}
|
||||
store.save({
|
||||
signal,
|
||||
source: q.source ?? '',
|
||||
filter: q.filter ?? { expression: '' },
|
||||
});
|
||||
saveRecentQueryByExpression(
|
||||
q.dataSource,
|
||||
q.filter?.expression,
|
||||
q.source ?? '',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export class UPlotScaleBuilder extends ConfigBuilder<
|
||||
logBase = 10,
|
||||
padMinBy = 0,
|
||||
padMaxBy = 0.05,
|
||||
useExactTimeRange = false,
|
||||
} = this.props;
|
||||
|
||||
// Special handling for time scales (X axis)
|
||||
@@ -58,14 +59,20 @@ export class UPlotScaleBuilder extends ConfigBuilder<
|
||||
|
||||
// Align max time to "endTime - 1 minute", rounded down to minute precision
|
||||
// This matches legacy getXAxisScale behavior and avoids empty space at the right edge
|
||||
const oneMinuteAgoTimestamp = (maxTime - 60) * 1000;
|
||||
const currentDate = new Date(oneMinuteAgoTimestamp);
|
||||
if (!useExactTimeRange) {
|
||||
const oneMinuteAgoTimestamp = (maxTime - 60) * 1000;
|
||||
const currentDate = new Date(oneMinuteAgoTimestamp);
|
||||
|
||||
currentDate.setSeconds(0);
|
||||
currentDate.setMilliseconds(0);
|
||||
currentDate.setSeconds(0);
|
||||
currentDate.setMilliseconds(0);
|
||||
|
||||
const unixTimestampSeconds = Math.floor(currentDate.getTime() / 1000);
|
||||
maxTime = unixTimestampSeconds;
|
||||
const unixTimestampSeconds = Math.floor(currentDate.getTime() / 1000);
|
||||
|
||||
// Trimming past min inverts the range, which uPlot draws as an empty plot.
|
||||
if (unixTimestampSeconds > minTime) {
|
||||
maxTime = unixTimestampSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[scaleKey]: {
|
||||
|
||||
@@ -79,6 +79,44 @@ describe('UPlotScaleBuilder', () => {
|
||||
expect(resolvedMax).toBe(expectedMax);
|
||||
});
|
||||
|
||||
it('plots min/max as given when useExactTimeRange is set', () => {
|
||||
const min = 1_700_000_000;
|
||||
const max = 1_700_000_630;
|
||||
|
||||
const builder = new UPlotScaleBuilder(
|
||||
createScaleProps({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min,
|
||||
max,
|
||||
useExactTimeRange: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const config = builder.getConfig();
|
||||
|
||||
expect(config.x.range).toStrictEqual([min, max]);
|
||||
});
|
||||
|
||||
it('keeps the requested end when the window is shorter than the trim', () => {
|
||||
// 23 second window: trimming a minute off the end would put max before min.
|
||||
const min = 1_786_527_160;
|
||||
const max = 1_786_527_183;
|
||||
|
||||
const builder = new UPlotScaleBuilder(
|
||||
createScaleProps({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min,
|
||||
max,
|
||||
}),
|
||||
);
|
||||
|
||||
const config = builder.getConfig();
|
||||
|
||||
expect(config.x.range).toStrictEqual([min, max]);
|
||||
});
|
||||
|
||||
it('falls back to getFallbackMinMaxTimeStamp when time scale has no min/max', () => {
|
||||
getFallbackMinMaxSpy.mockReturnValue({
|
||||
fallbackMin: 100,
|
||||
|
||||
@@ -97,6 +97,8 @@ export interface ScaleProps {
|
||||
auto?: boolean;
|
||||
logBase?: uPlot.Scale.LogBase;
|
||||
distribution?: DistributionType;
|
||||
/** Plots a time scale's `min`/`max` as given, skipping the trim below. */
|
||||
useExactTimeRange?: boolean;
|
||||
}
|
||||
|
||||
export enum DisconnectedValuesMode {
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
export const membersResponse = [
|
||||
{
|
||||
id: '3223a874-5678458745786',
|
||||
name: 'John Doe',
|
||||
email: 'firstUser@test.io',
|
||||
createdAt: 1666357530,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '5e9681b1-5678458745786',
|
||||
name: 'Jane Doe',
|
||||
email: 'johndoe2@test.io',
|
||||
createdAt: 1666365394,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '11e8c55d-5678458745786',
|
||||
name: 'Alex',
|
||||
email: 'blah@test.io',
|
||||
createdAt: 1666366317,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: 'd878012367813286731aab62',
|
||||
role: 'VIEWER',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '2ad2e404-5678458745786',
|
||||
name: 'Tom',
|
||||
email: 'johndoe4@test.io',
|
||||
createdAt: 1673441483,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '6f532456-5678458745786',
|
||||
name: 'Harry',
|
||||
email: 'harry@test.io',
|
||||
createdAt: 1691551672,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: 'ae22fa73-5678458745786',
|
||||
name: 'Ron',
|
||||
email: 'ron@test.io',
|
||||
createdAt: 1691668239,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '3223a874-5678458745786',
|
||||
name: 'John Doe',
|
||||
email: 'johndoe@test.io',
|
||||
createdAt: 1666357530,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '5e9681b1-5678458745786',
|
||||
name: 'Jane Doe',
|
||||
email: 'johndoe2@test.io',
|
||||
createdAt: 1666365394,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '11e8c55d-5678458745786',
|
||||
name: 'Alex',
|
||||
email: 'blah@test.io',
|
||||
createdAt: 1666366317,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: 'd878012367813286731aab62',
|
||||
role: 'VIEWER',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '2ad2e404-5678458745786',
|
||||
name: 'Tom',
|
||||
email: 'johndoe4@test.io',
|
||||
createdAt: 1673441483,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '6f532456-5678458745786',
|
||||
name: 'Harry',
|
||||
email: 'harry@test.io',
|
||||
createdAt: 1691551672,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: 'ae22fa73-5678458745786',
|
||||
name: 'Ron',
|
||||
email: 'ron@test.io',
|
||||
createdAt: 1691668239,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '3223a874-5678458745786',
|
||||
name: 'John Doe',
|
||||
email: 'johndoe@test.io',
|
||||
createdAt: 1666357530,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '5e9681b1-5678458745786',
|
||||
name: 'Jane Doe',
|
||||
email: 'johndoe2@test.io',
|
||||
createdAt: 1666365394,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '11e8c55d-5678458745786',
|
||||
name: 'Alex',
|
||||
email: 'blah@test.io',
|
||||
createdAt: 1666366317,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: 'd878012367813286731aab62',
|
||||
role: 'VIEWER',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '2ad2e404-5678458745786',
|
||||
name: 'Tom',
|
||||
email: 'johndoe4@test.io',
|
||||
createdAt: 1673441483,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: '6f532456-5678458745786',
|
||||
name: 'Harry',
|
||||
email: 'harry@test.io',
|
||||
createdAt: 1691551672,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
{
|
||||
id: 'ae22fa73-5678458745786',
|
||||
name: 'Ron',
|
||||
email: 'lastUser@test.io',
|
||||
createdAt: 1691668239,
|
||||
profilePictureURL: '',
|
||||
orgId: '1287612376312867312867',
|
||||
groupId: '5678458745786',
|
||||
role: 'ADMIN',
|
||||
organization: 'Test Inc',
|
||||
flags: null,
|
||||
},
|
||||
];
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from './__mockdata__/dashboards';
|
||||
import { explorerView } from './__mockdata__/explorer_views';
|
||||
import { licensesSuccessResponse } from './__mockdata__/licenses';
|
||||
import { membersResponse } from './__mockdata__/members';
|
||||
import { queryRangeSuccessResponse } from './__mockdata__/query_range';
|
||||
import { serviceSuccessResponse } from './__mockdata__/services';
|
||||
import { topLevelOperationSuccessResponse } from './__mockdata__/top_level_operations';
|
||||
@@ -40,9 +39,6 @@ export const handlers = [
|
||||
res(ctx.status(200), ctx.json(topLevelOperationSuccessResponse)),
|
||||
),
|
||||
|
||||
rest.get('http://localhost/api/v1/user', (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: '200', data: membersResponse })),
|
||||
),
|
||||
rest.get(
|
||||
'http://localhost/api/v3/autocomplete/attribute_keys',
|
||||
(req, res, ctx) => {
|
||||
@@ -164,15 +160,6 @@ export const handlers = [
|
||||
),
|
||||
),
|
||||
|
||||
rest.post('http://localhost/api/v1/invite', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: 'invite sent successfully',
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'http://localhost/api/v3/autocomplete/aggregate_attributes',
|
||||
(req, res, ctx) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@ts-nocheck
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
@@ -11,6 +11,7 @@ import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { whilelistedKeys } from 'hooks/useResourceAttribute/config';
|
||||
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
|
||||
import { filterServiceMapSupportedQueries } from 'hooks/useResourceAttribute/utils';
|
||||
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
@@ -70,32 +71,37 @@ function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
|
||||
const { queries } = useResourceAttribute();
|
||||
|
||||
const supportedQueries = useMemo(
|
||||
() => filterServiceMapSupportedQueries(queries),
|
||||
[queries],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
Call the apis only when the route is loaded.
|
||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||
*/
|
||||
getDetailedServiceMapItems(globalTime, queries);
|
||||
}, [globalTime, getDetailedServiceMapItems, queries]);
|
||||
getDetailedServiceMapItems(globalTime, supportedQueries);
|
||||
}, [globalTime, getDetailedServiceMapItems, supportedQueries]);
|
||||
|
||||
useEffect(() => {
|
||||
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
||||
});
|
||||
|
||||
if (serviceMap.loading) {
|
||||
return <Spinner size="large" tip="Loading..." />;
|
||||
}
|
||||
const renderBody = (): JSX.Element => {
|
||||
if (serviceMap.loading) {
|
||||
return <Spinner size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (serviceMap.items.length === 0) {
|
||||
return <Card>No Service Found</Card>;
|
||||
}
|
||||
|
||||
return <Map fgRef={fgRef} serviceMap={serviceMap} />;
|
||||
};
|
||||
|
||||
if (!serviceMap.loading && serviceMap.items.length === 0) {
|
||||
return (
|
||||
<Container>
|
||||
<ResourceAttributesFilter />
|
||||
<Card>No Service Found</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="service-map-container">
|
||||
<Container className="service-map-container">
|
||||
<ResourceAttributesFilter
|
||||
suffixIcon={
|
||||
<TextToolTip
|
||||
@@ -108,8 +114,8 @@ function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
}
|
||||
/>
|
||||
|
||||
<Map fgRef={fgRef} serviceMap={serviceMap} />
|
||||
</div>
|
||||
{renderBody()}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,4 +182,56 @@ describe('ValueSelector', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('opening and closing without touching the list', () => {
|
||||
function renderWith(
|
||||
selection: VariableSelection,
|
||||
options: string[],
|
||||
): jest.Mock {
|
||||
const onChange = jest.fn();
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<ValueSelector
|
||||
options={options}
|
||||
variableType="dynamic"
|
||||
multiSelect
|
||||
showAllOption
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
emptyFallback={{ value: [], allSelected: false }}
|
||||
testId="variable-select-env"
|
||||
/>
|
||||
</TooltipProvider>,
|
||||
);
|
||||
return onChange;
|
||||
}
|
||||
|
||||
async function openThenClose(): Promise<void> {
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
const control = screen.getByTestId('variable-select-env');
|
||||
await user.click(control.querySelector('input') as HTMLInputElement);
|
||||
await user.keyboard('{Escape}');
|
||||
}
|
||||
|
||||
it('does not promote a pick that covers every available option to ALL', async () => {
|
||||
// A narrow time range can leave only the selected value in the list. That is
|
||||
// still an explicit pick, not "everything, always".
|
||||
const onChange = renderWith(
|
||||
{ value: ['checkout-service-prod'], allSelected: false },
|
||||
['checkout-service-prod'],
|
||||
);
|
||||
|
||||
await openThenClose();
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not rewrite a dynamic ALL into concrete values', async () => {
|
||||
const onChange = renderWith({ value: null, allSelected: true }, OPTIONS);
|
||||
|
||||
await openThenClose();
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,6 +145,133 @@ describe('reconcileWithOptions', () => {
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
describe('preserveSelection (options moved on their own — time range, reload)', () => {
|
||||
const multi = model({
|
||||
type: 'DYNAMIC',
|
||||
multiSelect: true,
|
||||
showAllOption: true,
|
||||
dynamicAttribute: 'service.name',
|
||||
});
|
||||
|
||||
it('keeps a multi-select pick the new option list no longer offers', () => {
|
||||
expect(
|
||||
reconcileWithOptions(multi, { value: ['frontend'], allSelected: false }, [
|
||||
'backend',
|
||||
'cart',
|
||||
]),
|
||||
).toStrictEqual({ value: null, allSelected: true });
|
||||
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{ value: ['frontend'], allSelected: false },
|
||||
['backend', 'cart'],
|
||||
{ preserveSelection: true },
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('still materializes ALL, which must track the option list', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
model({ type: 'QUERY', multiSelect: true, showAllOption: true }),
|
||||
{ value: ['a'], allSelected: true },
|
||||
['a', 'b'],
|
||||
{ preserveSelection: true },
|
||||
),
|
||||
).toStrictEqual({ value: ['a', 'b'], allSelected: true });
|
||||
});
|
||||
|
||||
it('still fills the default when nothing is selected yet', () => {
|
||||
expect(
|
||||
reconcileWithOptions(multi, { value: [], allSelected: false }, ['a', 'b'], {
|
||||
preserveSelection: true,
|
||||
}),
|
||||
).toStrictEqual({ value: null, allSelected: true });
|
||||
});
|
||||
});
|
||||
|
||||
// A typed value is in no option list, so no refetch can invalidate it.
|
||||
describe('customValues (typed in, never offered by the data)', () => {
|
||||
const multi = model({
|
||||
type: 'DYNAMIC',
|
||||
multiSelect: true,
|
||||
showAllOption: true,
|
||||
dynamicAttribute: 'service.name',
|
||||
});
|
||||
|
||||
it('keeps them through a re-scope that drops a fetched value', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{
|
||||
value: ['frontend', 'typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
},
|
||||
['backend', 'cart'],
|
||||
),
|
||||
).toStrictEqual({
|
||||
value: ['typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
});
|
||||
});
|
||||
|
||||
it('never re-defaults a selection made only of them', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{ value: ['typed-in'], allSelected: false, customValues: ['typed-in'] },
|
||||
['backend', 'cart'],
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
// An inert marker is not worth a store write + dependent refetch to prune.
|
||||
it('leaves a stale marker alone when it drops nothing', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{
|
||||
value: ['frontend', 'typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in', 'removed-earlier'],
|
||||
},
|
||||
['frontend'],
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('prunes markers for values it does drop', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{
|
||||
value: ['stale', 'typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
},
|
||||
['frontend'],
|
||||
),
|
||||
).toStrictEqual({
|
||||
value: ['typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
});
|
||||
});
|
||||
|
||||
it('still drops an unmarked value the list no longer offers', () => {
|
||||
expect(
|
||||
reconcileWithOptions(
|
||||
multi,
|
||||
{ value: ['frontend', 'stale'], allSelected: false },
|
||||
['frontend'],
|
||||
),
|
||||
).toStrictEqual({ value: ['frontend'], allSelected: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('configuredDefaultValue', () => {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { VariableSelection } from '../selectionTypes';
|
||||
import { selectionFromCommittedValues } from '../utils/selectionUtils';
|
||||
|
||||
const OPTIONS = ['checkout', 'payments', 'cart'];
|
||||
const FALLBACK: VariableSelection = { value: null, allSelected: true };
|
||||
|
||||
function commit(
|
||||
values: string[],
|
||||
overrides: Partial<Parameters<typeof selectionFromCommittedValues>[0]> = {},
|
||||
): VariableSelection {
|
||||
return selectionFromCommittedValues({
|
||||
values,
|
||||
options: OPTIONS,
|
||||
showAllOption: true,
|
||||
emptyFallback: FALLBACK,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
// What a multi-select commit resolves to. The option list is known only here, so this
|
||||
// is the one place a typed value can be recognised.
|
||||
describe('selectionFromCommittedValues', () => {
|
||||
it('marks values the option list did not offer as typed in', () => {
|
||||
expect(commit(['checkout', 'typed-in'])).toStrictEqual({
|
||||
value: ['checkout', 'typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
});
|
||||
});
|
||||
|
||||
it('marks a selection made only of typed-in values', () => {
|
||||
expect(commit(['a', 'b'])).toStrictEqual({
|
||||
value: ['a', 'b'],
|
||||
allSelected: false,
|
||||
customValues: ['a', 'b'],
|
||||
});
|
||||
});
|
||||
|
||||
it('records no marker when every pick came from the list', () => {
|
||||
expect(commit(['checkout', 'cart'])).toStrictEqual({
|
||||
value: ['checkout', 'cart'],
|
||||
allSelected: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('reads a set covering every option as ALL', () => {
|
||||
expect(commit(OPTIONS)).toStrictEqual({
|
||||
value: OPTIONS,
|
||||
allSelected: true,
|
||||
});
|
||||
});
|
||||
|
||||
// ALL re-materializes to the option set, so recording this as ALL would drop the
|
||||
// typed value on the next refetch.
|
||||
it('does not read every option PLUS a typed value as ALL', () => {
|
||||
expect(commit([...OPTIONS, 'typed-in'])).toStrictEqual({
|
||||
value: [...OPTIONS, 'typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
});
|
||||
});
|
||||
|
||||
// Derived from the values + options at commit time, never from the old selection.
|
||||
it('recomputes the marker: a typed value the data now offers is a normal pick', () => {
|
||||
expect(
|
||||
commit(['checkout', 'was-typed'], {
|
||||
options: [...OPTIONS, 'was-typed'],
|
||||
}),
|
||||
).toStrictEqual({ value: ['checkout', 'was-typed'], allSelected: false });
|
||||
});
|
||||
|
||||
it('does not read it as ALL when the variable offers no ALL', () => {
|
||||
expect(commit(OPTIONS, { showAllOption: false })).toStrictEqual({
|
||||
value: OPTIONS,
|
||||
allSelected: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves an empty commit to the variable fallback', () => {
|
||||
expect(commit([])).toBe(FALLBACK);
|
||||
});
|
||||
|
||||
it('marks everything while the options have not arrived', () => {
|
||||
// Nothing to judge against yet; erring this way keeps a value rather than dropping it.
|
||||
expect(commit(['typed-in'], { options: [] })).toStrictEqual({
|
||||
value: ['typed-in'],
|
||||
allSelected: false,
|
||||
customValues: ['typed-in'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
emptyVariableFormModel,
|
||||
type VariableFormModel,
|
||||
} from '../../DashboardSettings/Variables/variableFormModel';
|
||||
import { VariableCycleReason } from '../../store/slices/variableFetchSlice';
|
||||
import { useDashboardStore } from '../../store/useDashboardStore';
|
||||
import type { VariableSelection } from '../selectionTypes';
|
||||
import { useAutoSelect } from '../hooks/useAutoSelect';
|
||||
|
||||
@@ -15,7 +17,11 @@ function run(
|
||||
variable: VariableFormModel,
|
||||
options: string[],
|
||||
selection: VariableSelection,
|
||||
cycleReason?: VariableCycleReason,
|
||||
): VariableSelection | undefined {
|
||||
useDashboardStore.setState({
|
||||
variableCycleReasons: cycleReason ? { [variable.name]: cycleReason } : {},
|
||||
});
|
||||
const onAutoSelect = jest.fn();
|
||||
renderHook(() => useAutoSelect(variable, options, selection, onAutoSelect));
|
||||
return onAutoSelect.mock.calls[0]?.[0];
|
||||
@@ -70,11 +76,13 @@ describe('useAutoSelect', () => {
|
||||
expect(next).toStrictEqual({ value: ['a', 'b'], allSelected: true });
|
||||
});
|
||||
|
||||
it('falls back to ALL, not the first option, when every selected value is gone', () => {
|
||||
// Re-scoped options only — a time-range refetch must NOT re-default; see below.
|
||||
it('re-scoped: falls back to ALL, not the first option, when every selected value is gone', () => {
|
||||
const next = run(
|
||||
model({ type: 'QUERY', multiSelect: true, showAllOption: true }),
|
||||
['x', 'y'],
|
||||
{ value: ['a', 'b'], allSelected: false },
|
||||
VariableCycleReason.ValueCascade,
|
||||
);
|
||||
expect(next).toStrictEqual({ value: ['x', 'y'], allSelected: true });
|
||||
});
|
||||
@@ -102,20 +110,23 @@ describe('useAutoSelect', () => {
|
||||
expect(next).toStrictEqual({ value: ['b'], allSelected: false });
|
||||
});
|
||||
|
||||
it('keeps the still-valid subset of a multi-select when options re-scope', () => {
|
||||
it('re-scoped: keeps the still-valid subset of a multi-select', () => {
|
||||
const next = run(
|
||||
model({ type: 'QUERY', multiSelect: true }),
|
||||
['a', 'b', 'd'],
|
||||
{ value: ['a', 'b', 'c'], allSelected: false },
|
||||
VariableCycleReason.ValueCascade,
|
||||
);
|
||||
expect(next).toStrictEqual({ value: ['a', 'b'], allSelected: false });
|
||||
});
|
||||
|
||||
it('re-defaults a multi-select when none of the selected values remain', () => {
|
||||
const next = run(model({ type: 'QUERY', multiSelect: true }), ['x', 'y'], {
|
||||
value: ['a', 'b'],
|
||||
allSelected: false,
|
||||
});
|
||||
it('re-scoped: re-defaults a multi-select when none of the selected values remain', () => {
|
||||
const next = run(
|
||||
model({ type: 'QUERY', multiSelect: true }),
|
||||
['x', 'y'],
|
||||
{ value: ['a', 'b'], allSelected: false },
|
||||
VariableCycleReason.ValueCascade,
|
||||
);
|
||||
expect(next).toStrictEqual({ value: ['x'], allSelected: false });
|
||||
});
|
||||
|
||||
@@ -151,4 +162,45 @@ describe('useAutoSelect', () => {
|
||||
});
|
||||
expect(next).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('by cycle reason', () => {
|
||||
const service = model({
|
||||
name: 'service',
|
||||
type: 'DYNAMIC',
|
||||
multiSelect: true,
|
||||
showAllOption: true,
|
||||
dynamicAttribute: 'service.name',
|
||||
});
|
||||
const gone: VariableSelection = { value: ['frontend'], allSelected: false };
|
||||
|
||||
it('keeps the selection when a full cycle refetched the options', () => {
|
||||
// The new window has no data for the selected service — no reason to widen to ALL.
|
||||
const next = run(
|
||||
service,
|
||||
['backend', 'cart'],
|
||||
gone,
|
||||
VariableCycleReason.FullCycle,
|
||||
);
|
||||
expect(next).toBeUndefined();
|
||||
});
|
||||
|
||||
it('re-scopes the selection when a value cascade refetched the options', () => {
|
||||
const next = run(
|
||||
service,
|
||||
['backend', 'cart'],
|
||||
gone,
|
||||
VariableCycleReason.ValueCascade,
|
||||
);
|
||||
expect(next).toStrictEqual({ value: null, allSelected: true });
|
||||
});
|
||||
|
||||
it('reconciles a variable with no cycle of its own (custom definition change)', () => {
|
||||
const next = run(
|
||||
model({ name: 'env', type: 'CUSTOM', multiSelect: true }),
|
||||
['staging', 'prod'],
|
||||
{ value: ['dev'], allSelected: false },
|
||||
);
|
||||
expect(next).toStrictEqual({ value: ['staging'], allSelected: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,11 +13,11 @@ jest.mock('nuqs', () => ({
|
||||
useQueryState: (): unknown => [null, jest.fn()],
|
||||
}));
|
||||
|
||||
const mockGlobalTime = { minTime: 1, maxTime: 2, selectedTime: '5m' };
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
useSelector: (selector: (state: unknown) => unknown): unknown =>
|
||||
selector({
|
||||
globalTime: { minTime: 1, maxTime: 2, selectedTime: '5m' },
|
||||
}),
|
||||
selector({ globalTime: mockGlobalTime }),
|
||||
}));
|
||||
|
||||
jest.mock('../../DashboardSettings/Variables/variableAdapters', () => ({
|
||||
@@ -150,3 +150,57 @@ describe('useVariableSelection — setSelection', () => {
|
||||
expect(svcCycleId()).toBe(before + 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useVariableSelection — what a time-range change enqueues', () => {
|
||||
// Longer than FETCH_CYCLE_DEBOUNCE_MS, which the hook keeps private.
|
||||
const PAST_DEBOUNCE = 400;
|
||||
|
||||
function reasons(): Record<string, string> {
|
||||
return useDashboardStore.getState().variableCycleReasons;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
mockGlobalTime.selectedTime = '5m';
|
||||
useDashboardStore.setState({
|
||||
variableValues: {},
|
||||
variableFetchStates: {},
|
||||
variableLastUpdated: {},
|
||||
variableCycleIds: {},
|
||||
variableCycleReasons: {},
|
||||
variableResolvedEmpty: {},
|
||||
variableFetchContext: null,
|
||||
lastFetchAllKey: null,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
// The tag is what stops the reconcile re-defaulting a user's selection.
|
||||
it('tags every variable as a full cycle, overriding an earlier cascade tag', () => {
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useVariableSelection(dashboard),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(PAST_DEBOUNCE);
|
||||
});
|
||||
expect(reasons()).toStrictEqual({ env: 'full-cycle', svc: 'full-cycle' });
|
||||
|
||||
// A value change re-scopes the dependent's options: it may drop what no longer applies.
|
||||
act(() => {
|
||||
result.current.setSelection('env', { value: ['prod'], allSelected: false });
|
||||
});
|
||||
expect(reasons().svc).toBe('value-cascade');
|
||||
|
||||
mockGlobalTime.selectedTime = '30m';
|
||||
rerender();
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(PAST_DEBOUNCE);
|
||||
});
|
||||
|
||||
expect(reasons()).toStrictEqual({ env: 'full-cycle', svc: 'full-cycle' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DashboardDetailEvents } from 'pages/DashboardPageV2/constants/events';
|
||||
|
||||
import type { VariableSelection } from '../../selectionTypes';
|
||||
import { areSelectionsEqual } from '../../utils/resolveVariableSelection';
|
||||
import { selectionFromCommittedValues } from '../../utils/selectionUtils';
|
||||
import OverflowValuesTooltip from './OverflowValuesTooltip';
|
||||
import styles from '../../VariablesBar.module.scss';
|
||||
|
||||
@@ -75,13 +76,23 @@ function ValueSelector({
|
||||
options.every((option) => draft.includes(option));
|
||||
|
||||
const commit = (values: string[]): void => {
|
||||
// CustomMultiSelect emits the full value set when ALL is picked.
|
||||
const isAll =
|
||||
showAllOption &&
|
||||
options.length > 0 &&
|
||||
options.every((option) => values.includes(option));
|
||||
const next: VariableSelection =
|
||||
values.length === 0 ? emptyFallback : { value: values, allSelected: isAll };
|
||||
// A close that left the list as it opened commits nothing — else a pick covering
|
||||
// every option this window offers would be promoted to a standing ALL.
|
||||
if (
|
||||
areSelectionsEqual(
|
||||
{ value: values, allSelected: false },
|
||||
{ value: committedValues, allSelected: false },
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = selectionFromCommittedValues({
|
||||
values,
|
||||
options,
|
||||
showAllOption,
|
||||
emptyFallback,
|
||||
});
|
||||
|
||||
// Closing without actually changing the selection must not re-fire onChange —
|
||||
// that would needlessly re-cascade to dependent variables/panels.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import type { VariableFormModel } from '../../DashboardSettings/Variables/variableFormModel';
|
||||
import {
|
||||
selectVariableCycleReason,
|
||||
VariableCycleReason,
|
||||
} from '../../store/slices/variableFetchSlice';
|
||||
import { useDashboardStore } from '../../store/useDashboardStore';
|
||||
import { reconcileWithOptions } from '../utils/resolveVariableSelection';
|
||||
import type { VariableSelection } from '../selectionTypes';
|
||||
|
||||
@@ -9,6 +14,9 @@ import type { VariableSelection } from '../selectionTypes';
|
||||
* `onAutoSelect` only when the value must change. The reconcile rule lives in
|
||||
* {@link reconcileWithOptions} (shared with seed + payload defaulting) so the bar
|
||||
* and the panel query can never disagree about a variable's default.
|
||||
*
|
||||
* Only a value cascade may re-default the selection; a full cycle (time range,
|
||||
* reload) leaves the user's pick alone. Types with no cycle of their own reconcile.
|
||||
*/
|
||||
export function useAutoSelect(
|
||||
variable: VariableFormModel,
|
||||
@@ -16,8 +24,14 @@ export function useAutoSelect(
|
||||
selection: VariableSelection,
|
||||
onAutoSelect: (selection: VariableSelection) => void,
|
||||
): void {
|
||||
const cycleReason = useDashboardStore(
|
||||
selectVariableCycleReason(variable.name),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const next = reconcileWithOptions(variable, selection, options);
|
||||
const next = reconcileWithOptions(variable, selection, options, {
|
||||
preserveSelection: cycleReason === VariableCycleReason.FullCycle,
|
||||
});
|
||||
if (next) {
|
||||
onAutoSelect(next);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ export interface VariableSelection {
|
||||
value: SelectedVariableValue;
|
||||
/** True when every option is selected ("ALL"); for dynamic vars value may be null. */
|
||||
allSelected: boolean;
|
||||
/**
|
||||
* Entries of `value` the user typed rather than picked. Never in any option list,
|
||||
* so the reconcile keeps them instead of reading them as invalid.
|
||||
*/
|
||||
customValues?: string[];
|
||||
}
|
||||
|
||||
/** Selected values for a dashboard's variables, keyed by variable name. */
|
||||
|
||||
@@ -134,12 +134,23 @@ export function resolveDefaultSelection(
|
||||
return { value: model.multiSelect ? [] : '', allSelected: false };
|
||||
}
|
||||
|
||||
interface ReconcileOptions {
|
||||
/**
|
||||
* Set when no other variable caused this refetch (time-range change, reload): the
|
||||
* selection then outranks the options and is kept as-is. Leave false for a
|
||||
* dependency cascade, where a selection that no longer applies must give way.
|
||||
*/
|
||||
preserveSelection?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciles a variable's current selection against its freshly-fetched options.
|
||||
* Returns the next selection, or null when nothing should change (a valid pick is
|
||||
* left untouched — local-first). Behaviour, in order:
|
||||
* - materialize ALL to the full option set (query/custom);
|
||||
* - keep a still-valid multi-select subset, dropping only invalid entries;
|
||||
* - keep a multi-select selection outright when `preserveSelection` is set;
|
||||
* - keep a still-valid multi-select subset, dropping only entries the list no longer
|
||||
* offers and the user did not type in (`customValues`);
|
||||
* - otherwise auto-pick the default (or first option) so dependent variables and
|
||||
* panels always resolve against a usable value.
|
||||
*/
|
||||
@@ -147,6 +158,7 @@ export function reconcileWithOptions(
|
||||
model: VariableFormModel,
|
||||
current: VariableSelection,
|
||||
options: string[],
|
||||
{ preserveSelection = false }: ReconcileOptions = {},
|
||||
): VariableSelection | null {
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
@@ -161,13 +173,31 @@ export function reconcileWithOptions(
|
||||
Array.isArray(current.value) &&
|
||||
current.value.length > 0
|
||||
) {
|
||||
const valid = current.value.map(String).filter((c) => options.includes(c));
|
||||
// A pick this window has no data for is still the user's filter; re-defaulting it
|
||||
// here is what widened a single pick to ALL on every time-range change.
|
||||
if (preserveSelection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// A typed value is in no option list, so it is never "no longer offered".
|
||||
const custom = new Set(current.customValues ?? []);
|
||||
const valid = current.value
|
||||
.map(String)
|
||||
.filter((c) => options.includes(c) || custom.has(c));
|
||||
|
||||
if (valid.length === current.value.length) {
|
||||
return null;
|
||||
}
|
||||
return valid.length > 0
|
||||
? { value: valid, allSelected: false }
|
||||
: fillDefault(model, options);
|
||||
if (valid.length === 0) {
|
||||
return fillDefault(model, options);
|
||||
}
|
||||
|
||||
const customValues = valid.filter((v) => custom.has(v));
|
||||
return {
|
||||
value: valid,
|
||||
allSelected: false,
|
||||
...(customValues.length > 0 && { customValues }),
|
||||
};
|
||||
}
|
||||
|
||||
if (!model.multiSelect) {
|
||||
|
||||
@@ -47,6 +47,43 @@ export function hasUsableValue(
|
||||
return value !== '' && value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
interface CommittedValues {
|
||||
values: string[];
|
||||
options: string[];
|
||||
showAllOption: boolean;
|
||||
emptyFallback: VariableSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* The selection a multi-select commit resolves to. Options are known only here, so
|
||||
* this is where a value the list never offered is recorded as typed in.
|
||||
*/
|
||||
export function selectionFromCommittedValues({
|
||||
values,
|
||||
options,
|
||||
showAllOption,
|
||||
emptyFallback,
|
||||
}: CommittedValues): VariableSelection {
|
||||
if (values.length === 0) {
|
||||
return emptyFallback;
|
||||
}
|
||||
|
||||
const customValues = values.filter((value) => !options.includes(value));
|
||||
// ALL re-materializes to the option set, so a set carrying a typed value is not ALL
|
||||
// — the next refetch would expand it back and drop what the user typed.
|
||||
const allSelected =
|
||||
showAllOption &&
|
||||
options.length > 0 &&
|
||||
customValues.length === 0 &&
|
||||
options.every((option) => values.includes(option));
|
||||
|
||||
return {
|
||||
value: values,
|
||||
allSelected,
|
||||
...(customValues.length > 0 && { customValues }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Flatten the selection map into the `{ name: value }` payload a query expects. */
|
||||
export function selectionToPayload(
|
||||
selection: VariableSelectionMap,
|
||||
|
||||
@@ -34,6 +34,7 @@ function reset(names: string[], context: VariableFetchContext): void {
|
||||
variableFetchStates: {},
|
||||
variableLastUpdated: {},
|
||||
variableCycleIds: {},
|
||||
variableCycleReasons: {},
|
||||
variableFetchContext: null,
|
||||
});
|
||||
store().initVariableFetch(names, context);
|
||||
@@ -133,6 +134,33 @@ describe('variableFetchSlice', () => {
|
||||
expect(states().q1).toBe('error');
|
||||
expect(states().q2).toBe('idle');
|
||||
});
|
||||
|
||||
// The reason is what tells the post-fetch reconcile whether it may re-default a
|
||||
// selection: a full cycle must not, a value cascade must.
|
||||
it('tags a full cycle, then re-tags only the cascaded variables', () => {
|
||||
store().enqueueFetchAll();
|
||||
expect(store().variableCycleReasons).toStrictEqual({
|
||||
q1: 'full-cycle',
|
||||
q2: 'full-cycle',
|
||||
d1: 'full-cycle',
|
||||
d2: 'full-cycle',
|
||||
});
|
||||
|
||||
resolve('q1');
|
||||
store().enqueueDescendants('q1');
|
||||
expect(store().variableCycleReasons).toStrictEqual({
|
||||
q1: 'full-cycle',
|
||||
q2: 'value-cascade',
|
||||
d1: 'full-cycle',
|
||||
d2: 'full-cycle',
|
||||
});
|
||||
});
|
||||
|
||||
it('drops the reason for a variable that no longer exists', () => {
|
||||
store().enqueueFetchAll();
|
||||
store().initVariableFetch(['q1'], context);
|
||||
expect(store().variableCycleReasons).toStrictEqual({ q1: 'full-cycle' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('variableFetchSlice — query depends on a dynamic', () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type FetchMaps,
|
||||
isVariableInActiveFetchState,
|
||||
resolveFetchState,
|
||||
VariableCycleReason,
|
||||
VariableFetchState,
|
||||
} from './variableFetchSlice.utils';
|
||||
|
||||
@@ -30,7 +31,10 @@ function queryParentsHaveValues(
|
||||
);
|
||||
}
|
||||
|
||||
export { VariableFetchState } from './variableFetchSlice.utils';
|
||||
export {
|
||||
VariableCycleReason,
|
||||
VariableFetchState,
|
||||
} from './variableFetchSlice.utils';
|
||||
|
||||
/**
|
||||
* Runtime fetch orchestration for dashboard variables — native port of V1's
|
||||
@@ -45,6 +49,8 @@ export interface VariableFetchSlice {
|
||||
variableFetchStates: Record<string, VariableFetchState>;
|
||||
variableLastUpdated: Record<string, number>;
|
||||
variableCycleIds: Record<string, number>;
|
||||
/** Why each variable's current cycle was enqueued, read by the post-fetch reconcile. */
|
||||
variableCycleReasons: Record<string, VariableCycleReason>;
|
||||
/**
|
||||
* Whether a QUERY/DYNAMIC variable settled its fetch with zero options (so it
|
||||
* will never get a value). Lets a dependent panel fall through to "no data"
|
||||
@@ -106,6 +112,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
variableFetchStates: {},
|
||||
variableLastUpdated: {},
|
||||
variableCycleIds: {},
|
||||
variableCycleReasons: {},
|
||||
variableResolvedEmpty: {},
|
||||
variableFetchContext: null,
|
||||
lastFetchAllKey: null,
|
||||
@@ -115,6 +122,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
variableFetchStates: {},
|
||||
variableLastUpdated: {},
|
||||
variableCycleIds: {},
|
||||
variableCycleReasons: {},
|
||||
variableResolvedEmpty: {},
|
||||
variableFetchContext: null,
|
||||
lastFetchAllKey: null,
|
||||
@@ -132,6 +140,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
initVariableFetch: (names, context): void => {
|
||||
const maps = cloneMaps(get());
|
||||
const resolvedEmpty = { ...get().variableResolvedEmpty };
|
||||
const reasons = { ...get().variableCycleReasons };
|
||||
names.forEach((name) => {
|
||||
if (!maps.states[name]) {
|
||||
maps.states[name] = VariableFetchState.Idle;
|
||||
@@ -144,12 +153,14 @@ export const createVariableFetchSlice: StateCreator<
|
||||
delete maps.lastUpdated[name];
|
||||
delete maps.cycleIds[name];
|
||||
delete resolvedEmpty[name];
|
||||
delete reasons[name];
|
||||
}
|
||||
});
|
||||
set({
|
||||
variableFetchStates: maps.states,
|
||||
variableLastUpdated: maps.lastUpdated,
|
||||
variableCycleIds: maps.cycleIds,
|
||||
variableCycleReasons: reasons,
|
||||
variableResolvedEmpty: resolvedEmpty,
|
||||
variableFetchContext: context,
|
||||
});
|
||||
@@ -171,6 +182,11 @@ export const createVariableFetchSlice: StateCreator<
|
||||
dynamicVariableOrder,
|
||||
} = variableFetchContext;
|
||||
const maps = cloneMaps(get());
|
||||
const reasons = { ...get().variableCycleReasons };
|
||||
const bump = (name: string): void => {
|
||||
maps.cycleIds[name] = (maps.cycleIds[name] || 0) + 1;
|
||||
reasons[name] = VariableCycleReason.FullCycle;
|
||||
};
|
||||
|
||||
// Query variables wait only for their QUERY parents. A DYNAMIC parent does not
|
||||
// gate: its option fetch feeds only its own dropdown, while its selected value
|
||||
@@ -178,7 +194,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
// dependent query substitutes it immediately and refetches via the cascade if
|
||||
// it later changes. Text/custom parents resolve synchronously, so nothing waits.
|
||||
queryVariableOrder.forEach((name) => {
|
||||
maps.cycleIds[name] = (maps.cycleIds[name] || 0) + 1;
|
||||
bump(name);
|
||||
const parents = dependencyData.parentGraph[name] || [];
|
||||
const hasQueryParents = parents.some((p) => variableTypes[p] === 'QUERY');
|
||||
maps.states[name] = hasQueryParents
|
||||
@@ -192,7 +208,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
const orderedQuery = new Set(queryVariableOrder);
|
||||
Object.keys(variableTypes).forEach((name) => {
|
||||
if (variableTypes[name] === 'QUERY' && !orderedQuery.has(name)) {
|
||||
maps.cycleIds[name] = (maps.cycleIds[name] || 0) + 1;
|
||||
bump(name);
|
||||
maps.states[name] = resolveFetchState(maps, name);
|
||||
}
|
||||
});
|
||||
@@ -203,7 +219,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
// populate fast even when query variables are slow; a sibling selection change
|
||||
// later refetches them via `enqueueDescendantsBatch`.
|
||||
dynamicVariableOrder.forEach((name) => {
|
||||
maps.cycleIds[name] = (maps.cycleIds[name] || 0) + 1;
|
||||
bump(name);
|
||||
maps.states[name] = resolveFetchState(maps, name);
|
||||
});
|
||||
|
||||
@@ -211,6 +227,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
variableFetchStates: maps.states,
|
||||
variableLastUpdated: maps.lastUpdated,
|
||||
variableCycleIds: maps.cycleIds,
|
||||
variableCycleReasons: reasons,
|
||||
lastFetchAllKey: key ?? get().lastFetchAllKey,
|
||||
});
|
||||
},
|
||||
@@ -290,6 +307,11 @@ export const createVariableFetchSlice: StateCreator<
|
||||
const { dependencyData, variableTypes, dynamicVariableOrder } =
|
||||
variableFetchContext;
|
||||
const maps = cloneMaps(get());
|
||||
const reasons = { ...get().variableCycleReasons };
|
||||
const bump = (name: string): void => {
|
||||
maps.cycleIds[name] = (maps.cycleIds[name] || 0) + 1;
|
||||
reasons[name] = VariableCycleReason.ValueCascade;
|
||||
};
|
||||
const changed = new Set(names);
|
||||
// Callers commit values before this runs, so the gate sees the new parent values.
|
||||
const selection = selectVariableValues(get().dashboardId)(get());
|
||||
@@ -305,7 +327,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
});
|
||||
});
|
||||
queryDescendants.forEach((desc) => {
|
||||
maps.cycleIds[desc] = (maps.cycleIds[desc] || 0) + 1;
|
||||
bump(desc);
|
||||
maps.states[desc] = queryParentsHaveValues(
|
||||
desc,
|
||||
variableFetchContext,
|
||||
@@ -322,7 +344,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
dynamicVariableOrder
|
||||
.filter((dynName) => !changed.has(dynName))
|
||||
.forEach((dynName) => {
|
||||
maps.cycleIds[dynName] = (maps.cycleIds[dynName] || 0) + 1;
|
||||
bump(dynName);
|
||||
maps.states[dynName] = resolveFetchState(maps, dynName);
|
||||
});
|
||||
}
|
||||
@@ -331,6 +353,7 @@ export const createVariableFetchSlice: StateCreator<
|
||||
variableFetchStates: maps.states,
|
||||
variableLastUpdated: maps.lastUpdated,
|
||||
variableCycleIds: maps.cycleIds,
|
||||
variableCycleReasons: reasons,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -347,6 +370,12 @@ export const selectVariableCycleId =
|
||||
(state: DashboardStore): number =>
|
||||
state.variableCycleIds[name] ?? 0;
|
||||
|
||||
/** Selector: why a variable's cycle was enqueued. Undefined for types that never fetch. */
|
||||
export const selectVariableCycleReason =
|
||||
(name: string) =>
|
||||
(state: DashboardStore): VariableCycleReason | undefined =>
|
||||
state.variableCycleReasons[name];
|
||||
|
||||
/** Selector: whether a variable has completed at least one fetch. */
|
||||
export const selectVariableFetchedOnce =
|
||||
(name: string) =>
|
||||
|
||||
@@ -7,6 +7,14 @@ export enum VariableFetchState {
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
/** Why a cycle was started — only a cascade may re-default a user's selection. */
|
||||
export enum VariableCycleReason {
|
||||
/** `enqueueFetchAll`: load, time-range or variable-order change. */
|
||||
FullCycle = 'full-cycle',
|
||||
/** `enqueueDescendantsBatch`: a parent or sibling variable's value changed. */
|
||||
ValueCascade = 'value-cascade',
|
||||
}
|
||||
|
||||
/** Mutable clones a fetch action works over before committing back in one `set`. */
|
||||
export interface FetchMaps {
|
||||
states: Record<string, VariableFetchState>;
|
||||
|
||||
@@ -4,8 +4,6 @@ import { CardContainer } from 'container/GridCardLayout/styles';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import MetricPageGridGraph from './MetricPageGraph';
|
||||
import {
|
||||
getAverageRequestLatencyWidgetData,
|
||||
@@ -73,20 +71,15 @@ function MetricColumnGraphs({
|
||||
}): JSX.Element {
|
||||
const { t } = useTranslation('messagingQueues');
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const metricsData = [
|
||||
{
|
||||
title: t('metricGraphCategory.brokerMetrics.title'),
|
||||
description: t('metricGraphCategory.brokerMetrics.description'),
|
||||
graphCount: [
|
||||
getBrokerCountWidgetData(dotMetricsEnabled),
|
||||
getRequestTimesWidgetData(dotMetricsEnabled),
|
||||
getProducerFetchRequestPurgatoryWidgetData(dotMetricsEnabled),
|
||||
getBrokerNetworkThroughputWidgetData(dotMetricsEnabled),
|
||||
getBrokerCountWidgetData(),
|
||||
getRequestTimesWidgetData(),
|
||||
getProducerFetchRequestPurgatoryWidgetData(),
|
||||
getBrokerNetworkThroughputWidgetData(),
|
||||
],
|
||||
id: 'broker-metrics',
|
||||
},
|
||||
@@ -94,11 +87,11 @@ function MetricColumnGraphs({
|
||||
title: t('metricGraphCategory.producerMetrics.title'),
|
||||
description: t('metricGraphCategory.producerMetrics.description'),
|
||||
graphCount: [
|
||||
getIoWaitTimeWidgetData(dotMetricsEnabled),
|
||||
getRequestResponseWidgetData(dotMetricsEnabled),
|
||||
getAverageRequestLatencyWidgetData(dotMetricsEnabled),
|
||||
getKafkaProducerByteRateWidgetData(dotMetricsEnabled),
|
||||
getBytesConsumedWidgetData(dotMetricsEnabled),
|
||||
getIoWaitTimeWidgetData(),
|
||||
getRequestResponseWidgetData(),
|
||||
getAverageRequestLatencyWidgetData(),
|
||||
getKafkaProducerByteRateWidgetData(),
|
||||
getBytesConsumedWidgetData(),
|
||||
],
|
||||
id: 'producer-metrics',
|
||||
},
|
||||
@@ -106,11 +99,11 @@ function MetricColumnGraphs({
|
||||
title: t('metricGraphCategory.consumerMetrics.title'),
|
||||
description: t('metricGraphCategory.consumerMetrics.description'),
|
||||
graphCount: [
|
||||
getConsumerOffsetWidgetData(dotMetricsEnabled),
|
||||
getConsumerGroupMemberWidgetData(dotMetricsEnabled),
|
||||
getConsumerLagByGroupWidgetData(dotMetricsEnabled),
|
||||
getConsumerFetchRateWidgetData(dotMetricsEnabled),
|
||||
getMessagesConsumedWidgetData(dotMetricsEnabled),
|
||||
getConsumerOffsetWidgetData(),
|
||||
getConsumerGroupMemberWidgetData(),
|
||||
getConsumerLagByGroupWidgetData(),
|
||||
getConsumerFetchRateWidgetData(),
|
||||
getMessagesConsumedWidgetData(),
|
||||
],
|
||||
id: 'consumer-metrics',
|
||||
},
|
||||
|
||||
@@ -8,8 +8,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { ChevronDown, ChevronUp } from '@signozhq/icons';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import MetricColumnGraphs from './MetricColumnGraphs';
|
||||
import MetricPageGridGraph from './MetricPageGraph';
|
||||
import {
|
||||
@@ -97,11 +95,6 @@ function MetricPage(): JSX.Element {
|
||||
}));
|
||||
};
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const { t } = useTranslation('messagingQueues');
|
||||
|
||||
const metricSections = [
|
||||
@@ -110,10 +103,10 @@ function MetricPage(): JSX.Element {
|
||||
title: t('metricGraphCategory.brokerJVMMetrics.title'),
|
||||
description: t('metricGraphCategory.brokerJVMMetrics.description'),
|
||||
graphCount: [
|
||||
getJvmGCCountWidgetData(dotMetricsEnabled),
|
||||
getJvmGcCollectionsElapsedWidgetData(dotMetricsEnabled),
|
||||
getCpuRecentUtilizationWidgetData(dotMetricsEnabled),
|
||||
getJvmMemoryHeapWidgetData(dotMetricsEnabled),
|
||||
getJvmGCCountWidgetData(),
|
||||
getJvmGcCollectionsElapsedWidgetData(),
|
||||
getCpuRecentUtilizationWidgetData(),
|
||||
getJvmMemoryHeapWidgetData(),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -121,10 +114,10 @@ function MetricPage(): JSX.Element {
|
||||
title: t('metricGraphCategory.partitionMetrics.title'),
|
||||
description: t('metricGraphCategory.partitionMetrics.description'),
|
||||
graphCount: [
|
||||
getPartitionCountPerTopicWidgetData(dotMetricsEnabled),
|
||||
getCurrentOffsetPartitionWidgetData(dotMetricsEnabled),
|
||||
getOldestOffsetWidgetData(dotMetricsEnabled),
|
||||
getInsyncReplicasWidgetData(dotMetricsEnabled),
|
||||
getPartitionCountPerTopicWidgetData(),
|
||||
getCurrentOffsetPartitionWidgetData(),
|
||||
getOldestOffsetWidgetData(),
|
||||
getInsyncReplicasWidgetData(),
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -138,7 +131,7 @@ function MetricPage(): JSX.Element {
|
||||
|
||||
// Only log when first graph has rendered and we haven't logged yet
|
||||
if (renderedGraphCountRef.current === 1 && !hasLoggedRef.current) {
|
||||
logEvent('MQ Kafka: Metric view', {
|
||||
void logEvent('MQ Kafka: Metric view', {
|
||||
graphRendered: true,
|
||||
});
|
||||
hasLoggedRef.current = true;
|
||||
|
||||
@@ -78,21 +78,15 @@ export function getWidgetQuery(
|
||||
};
|
||||
}
|
||||
|
||||
export const getRequestTimesWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getRequestTimesWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
// choose key based on flag
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.request.time.avg'
|
||||
: 'kafka_request_time_avg',
|
||||
// mirror into the id as well
|
||||
id: 'kafka_request_time_avg--float64--Gauge--true',
|
||||
key: 'kafka.request.time.avg',
|
||||
id: 'kafka.request.time.avg--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -122,15 +116,15 @@ export const getRequestTimesWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getBrokerCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
export const getBrokerCountWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled ? 'kafka.brokers' : 'kafka_brokers',
|
||||
id: 'kafka_brokers--float64--Gauge--true',
|
||||
key: 'kafka.brokers',
|
||||
id: 'kafka.brokers--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'sum',
|
||||
@@ -156,20 +150,15 @@ export const getBrokerCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
}),
|
||||
);
|
||||
|
||||
export const getProducerFetchRequestPurgatoryWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getProducerFetchRequestPurgatoryWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
// inline ternary based on dotMetricsEnabled
|
||||
key: dotMetricsEnabled ? 'kafka.purgatory.size' : 'kafka_purgatory_size',
|
||||
id: `${
|
||||
dotMetricsEnabled ? 'kafka.purgatory.size' : 'kafka_purgatory_size'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.purgatory.size',
|
||||
id: 'kafka.purgatory.size--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -196,24 +185,15 @@ export const getProducerFetchRequestPurgatoryWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getBrokerNetworkThroughputWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getBrokerNetworkThroughputWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
// inline ternary based on dotMetricsEnabled
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate'
|
||||
: 'kafka_server_brokertopicmetrics_bytesoutpersec_oneminuterate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate'
|
||||
: 'kafka_server_brokertopicmetrics_bytesoutpersec_oneminuterate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate',
|
||||
id: 'kafka_server_brokertopicmetrics_total_replicationbytesinpersec_oneminuterate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -240,22 +220,15 @@ export const getBrokerNetworkThroughputWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getIoWaitTimeWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
export const getIoWaitTimeWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
// inline ternary based on dotMetricsEnabled
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.producer.io_waittime_total'
|
||||
: 'kafka_producer_io_waittime_total',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.producer.io_waittime_total'
|
||||
: 'kafka_producer_io_waittime_total'
|
||||
}--float64--Sum--true`,
|
||||
key: 'kafka.producer.io_waittime_total',
|
||||
id: 'kafka.producer.io_waittime_total--float64--Sum--true',
|
||||
type: 'Sum',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
@@ -282,23 +255,15 @@ export const getIoWaitTimeWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
}),
|
||||
);
|
||||
|
||||
export const getRequestResponseWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getRequestResponseWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.producer.request_rate'
|
||||
: 'kafka_producer_request_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.producer.request_rate'
|
||||
: 'kafka_producer_request_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.producer.request_rate',
|
||||
id: 'kafka.producer.request_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -321,14 +286,8 @@ export const getRequestResponseWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.producer.response_rate'
|
||||
: 'kafka_producer_response_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.producer.response_rate'
|
||||
: 'kafka_producer_response_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.producer.response_rate',
|
||||
id: 'kafka.producer.response_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -355,23 +314,15 @@ export const getRequestResponseWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getAverageRequestLatencyWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getAverageRequestLatencyWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.producer.request_latency_avg'
|
||||
: 'kafka_producer_request_latency_avg',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.producer.request_latency_avg'
|
||||
: 'kafka_producer_request_latency_avg'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.producer.request_latency_avg',
|
||||
id: 'kafka.producer.request_latency_avg--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -398,23 +349,15 @@ export const getAverageRequestLatencyWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getKafkaProducerByteRateWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getKafkaProducerByteRateWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.producer.byte_rate'
|
||||
: 'kafka_producer_byte_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.producer.byte_rate'
|
||||
: 'kafka_producer_byte_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.producer.byte_rate',
|
||||
id: 'kafka.producer.byte_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -442,31 +385,21 @@ export const getKafkaProducerByteRateWidgetData = (
|
||||
timeAggregation: 'avg',
|
||||
},
|
||||
],
|
||||
title: dotMetricsEnabled
|
||||
? 'kafka.producer.byte_rate'
|
||||
: 'kafka_producer_byte_rate',
|
||||
title: 'kafka.producer.byte_rate',
|
||||
description:
|
||||
'Helps measure the data output rate from the producer, indicating the load a producer is placing on Kafka brokers.',
|
||||
}),
|
||||
);
|
||||
|
||||
export const getBytesConsumedWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getBytesConsumedWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer.bytes_consumed_rate'
|
||||
: 'kafka_consumer_bytes_consumed_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer.bytes_consumed_rate'
|
||||
: 'kafka_consumer_bytes_consumed_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer.bytes_consumed_rate',
|
||||
id: 'kafka.consumer.bytes_consumed_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -494,23 +427,15 @@ export const getBytesConsumedWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getConsumerOffsetWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getConsumerOffsetWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer_group.offset'
|
||||
: 'kafka_consumer_group_offset',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer_group.offset'
|
||||
: 'kafka_consumer_group_offset'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer_group.offset',
|
||||
id: 'kafka.consumer_group.offset--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -556,23 +481,15 @@ export const getConsumerOffsetWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getConsumerGroupMemberWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getConsumerGroupMemberWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer_group.members'
|
||||
: 'kafka_consumer_group_members',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer_group.members'
|
||||
: 'kafka_consumer_group_members'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer_group.members',
|
||||
id: 'kafka.consumer_group.members--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'sum',
|
||||
@@ -605,23 +522,15 @@ export const getConsumerGroupMemberWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getConsumerLagByGroupWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getConsumerLagByGroupWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer_group.lag'
|
||||
: 'kafka_consumer_group_lag',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer_group.lag'
|
||||
: 'kafka_consumer_group_lag'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer_group.lag',
|
||||
id: 'kafka.consumer_group.lag--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -667,23 +576,15 @@ export const getConsumerLagByGroupWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getConsumerFetchRateWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getConsumerFetchRateWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer.fetch_rate'
|
||||
: 'kafka_consumer_fetch_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer.fetch_rate'
|
||||
: 'kafka_consumer_fetch_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer.fetch_rate',
|
||||
id: 'kafka.consumer.fetch_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -696,7 +597,7 @@ export const getConsumerFetchRateWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'service_name--string--tag--false',
|
||||
key: dotMetricsEnabled ? 'service.name' : 'service_name',
|
||||
key: 'service.name',
|
||||
type: 'tag',
|
||||
},
|
||||
],
|
||||
@@ -717,23 +618,15 @@ export const getConsumerFetchRateWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getMessagesConsumedWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getMessagesConsumedWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer.records_consumed_rate'
|
||||
: 'kafka_consumer_records_consumed_rate',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer.records_consumed_rate'
|
||||
: 'kafka_consumer_records_consumed_rate'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.consumer.records_consumed_rate',
|
||||
id: 'kafka.consumer.records_consumed_rate--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -760,21 +653,15 @@ export const getMessagesConsumedWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getJvmGCCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
export const getJvmGCCountWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'jvm.gc.collections.count'
|
||||
: 'jvm_gc_collections_count',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'jvm.gc.collections.count'
|
||||
: 'jvm_gc_collections_count'
|
||||
}--float64--Sum--true`,
|
||||
key: 'jvm.gc.collections.count',
|
||||
id: 'jvm.gc.collections.count--float64--Sum--true',
|
||||
type: 'Sum',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
@@ -801,23 +688,15 @@ export const getJvmGCCountWidgetData = (dotMetricsEnabled: boolean): Widgets =>
|
||||
}),
|
||||
);
|
||||
|
||||
export const getJvmGcCollectionsElapsedWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getJvmGcCollectionsElapsedWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'jvm.gc.collections.elapsed'
|
||||
: 'jvm_gc_collections_elapsed',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'jvm.gc.collections.elapsed'
|
||||
: 'jvm_gc_collections_elapsed'
|
||||
}--float64--Sum--true`,
|
||||
key: 'jvm.gc.collections.elapsed',
|
||||
id: 'jvm.gc.collections.elapsed--float64--Sum--true',
|
||||
type: 'Sum',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
@@ -838,31 +717,21 @@ export const getJvmGcCollectionsElapsedWidgetData = (
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
title: dotMetricsEnabled
|
||||
? 'jvm.gc.collections.elapsed'
|
||||
: 'jvm_gc_collections_elapsed',
|
||||
title: 'jvm.gc.collections.elapsed',
|
||||
description:
|
||||
'Measures the total time (usually in milliseconds) spent on garbage collection (GC) events in the Java Virtual Machine (JVM).',
|
||||
}),
|
||||
);
|
||||
|
||||
export const getCpuRecentUtilizationWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getCpuRecentUtilizationWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'jvm.cpu.recent_utilization'
|
||||
: 'jvm_cpu_recent_utilization',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'jvm.cpu.recent_utilization'
|
||||
: 'jvm_cpu_recent_utilization'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'jvm.cpu.recent_utilization',
|
||||
id: 'jvm.cpu.recent_utilization--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -889,19 +758,15 @@ export const getCpuRecentUtilizationWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getJvmMemoryHeapWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getJvmMemoryHeapWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled ? 'jvm.memory.heap.max' : 'jvm_memory_heap_max',
|
||||
id: `${
|
||||
dotMetricsEnabled ? 'jvm.memory.heap.max' : 'jvm_memory_heap_max'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'jvm.memory.heap.max',
|
||||
id: 'jvm.memory.heap.max--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -928,21 +793,15 @@ export const getJvmMemoryHeapWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getPartitionCountPerTopicWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getPartitionCountPerTopicWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.topic.partitions'
|
||||
: 'kafka_topic_partitions',
|
||||
id: `${
|
||||
dotMetricsEnabled ? 'kafka.topic.partitions' : 'kafka_topic_partitions'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.topic.partitions',
|
||||
id: 'kafka.topic.partitions--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'sum',
|
||||
@@ -975,23 +834,15 @@ export const getPartitionCountPerTopicWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getCurrentOffsetPartitionWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getCurrentOffsetPartitionWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.partition.current_offset'
|
||||
: 'kafka_partition_current_offset',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.partition.current_offset'
|
||||
: 'kafka_partition_current_offset'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.partition.current_offset',
|
||||
id: 'kafka.partition.current_offset--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -1031,23 +882,15 @@ export const getCurrentOffsetPartitionWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getOldestOffsetWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getOldestOffsetWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.partition.oldest_offset'
|
||||
: 'kafka_partition_oldest_offset',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.partition.oldest_offset'
|
||||
: 'kafka_partition_oldest_offset'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.partition.oldest_offset',
|
||||
id: 'kafka.partition.oldest_offset--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
@@ -1087,23 +930,15 @@ export const getOldestOffsetWidgetData = (
|
||||
}),
|
||||
);
|
||||
|
||||
export const getInsyncReplicasWidgetData = (
|
||||
dotMetricsEnabled: boolean,
|
||||
): Widgets =>
|
||||
export const getInsyncReplicasWidgetData = (): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.partition.replicas_in_sync'
|
||||
: 'kafka_partition_replicas_in_sync',
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.partition.replicas_in_sync'
|
||||
: 'kafka_partition_replicas_in_sync'
|
||||
}--float64--Gauge--true`,
|
||||
key: 'kafka.partition.replicas_in_sync',
|
||||
id: 'kafka.partition.replicas_in_sync--float64--Gauge--true',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'avg',
|
||||
|
||||
@@ -11,8 +11,6 @@ import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Check, Share2 } from '@signozhq/icons';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { useGetAllConfigOptions } from './useGetAllConfigOptions';
|
||||
|
||||
import './MQConfigOptions.styles.scss';
|
||||
@@ -40,19 +38,11 @@ const useConfigOptions = (
|
||||
isFetching: boolean;
|
||||
options: DefaultOptionType[];
|
||||
} => {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const { isFetching, options } = useGetAllConfigOptions(
|
||||
{
|
||||
attributeKey: type,
|
||||
searchText,
|
||||
},
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
const { isFetching, options } = useGetAllConfigOptions({
|
||||
attributeKey: type,
|
||||
searchText,
|
||||
});
|
||||
const handleDebouncedSearch = useDebouncedFn((searchText): void => {
|
||||
setSearchText(searchText as string);
|
||||
}, 500);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/GridCardLayout/config';
|
||||
@@ -12,7 +11,6 @@ import { Card } from 'container/GridCardLayout/styles';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
|
||||
import {
|
||||
@@ -34,15 +32,9 @@ function MessagingQueuesGraph(): JSX.Element {
|
||||
[consumerGrp, topic, partition],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const widgetData = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder(getWidgetQuery({ filterItems, dotMetricsEnabled })),
|
||||
[filterItems, dotMetricsEnabled],
|
||||
() => getWidgetQueryBuilder(getWidgetQuery({ filterItems })),
|
||||
[filterItems],
|
||||
);
|
||||
|
||||
const history = useHistory();
|
||||
@@ -81,7 +73,7 @@ function MessagingQueuesGraph(): JSX.Element {
|
||||
const checkIfDataExists = (isDataAvailable: boolean): void => {
|
||||
if (!isLogEventCalled.current) {
|
||||
isLogEventCalled.current = true;
|
||||
logEvent('Messaging Queues: Graph data fetched', {
|
||||
void logEvent('Messaging Queues: Graph data fetched', {
|
||||
isDataAvailable,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ export interface GetAllConfigOptionsResponse {
|
||||
|
||||
export function useGetAllConfigOptions(
|
||||
props: ConfigOptions,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetAllConfigOptionsResponse {
|
||||
const { attributeKey, searchText } = props;
|
||||
|
||||
@@ -26,9 +25,7 @@ export function useGetAllConfigOptions(
|
||||
const { payload } = await getAttributesValues({
|
||||
aggregateOperator: 'avg',
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateAttribute: dotMetricsEnabled
|
||||
? 'kafka.consumer_group.lag'
|
||||
: 'kafka_consumer_group_lag',
|
||||
aggregateAttribute: 'kafka.consumer_group.lag',
|
||||
attributeKey,
|
||||
searchText: searchText ?? '',
|
||||
filterAttributeKeyDataType: DataTypes.String,
|
||||
|
||||
@@ -94,10 +94,8 @@ export function getFiltersFromConfigOptions(
|
||||
|
||||
export function getWidgetQuery({
|
||||
filterItems,
|
||||
dotMetricsEnabled,
|
||||
}: {
|
||||
filterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}): GetWidgetQueryBuilderProps {
|
||||
return {
|
||||
title: 'Consumer Lag',
|
||||
@@ -112,14 +110,8 @@ export function getWidgetQuery({
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: `${
|
||||
dotMetricsEnabled
|
||||
? 'kafka.consumer_group.lag'
|
||||
: 'kafka_consumer_group_lag'
|
||||
}--float64--Gauge--true`,
|
||||
key: dotMetricsEnabled
|
||||
? 'kafka.consumer_group.lag'
|
||||
: 'kafka_consumer_group_lag',
|
||||
id: 'kafka.consumer_group.lag--float64--Gauge--true',
|
||||
key: 'kafka.consumer_group.lag',
|
||||
type: 'Gauge',
|
||||
},
|
||||
aggregateOperator: 'max',
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface Props {
|
||||
token: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
export interface Props {
|
||||
name: User['displayName'];
|
||||
email: User['email'];
|
||||
role: ROLES;
|
||||
frontendBaseUrl: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user