mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-12 12:32:04 +00:00
Compare commits
14 Commits
v0.111.0
...
test/uplot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c266fa0087 | ||
|
|
54655c6ab2 | ||
|
|
68ea28cf6b | ||
|
|
3726c0aac1 | ||
|
|
dae2d3239b | ||
|
|
0c660f8618 | ||
|
|
97cffbc20a | ||
|
|
8317eb1735 | ||
|
|
b1789ea3f7 | ||
|
|
3b41d0a731 | ||
|
|
a171f7122f | ||
|
|
4a20e93b20 | ||
|
|
d4dc709aa5 | ||
|
|
cd014652a1 |
@@ -4,7 +4,6 @@ services:
|
||||
container_name: signoz-otel-collector-dev
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
environment:
|
||||
|
||||
10
.github/workflows/goci.yaml
vendored
10
.github/workflows/goci.yaml
vendored
@@ -93,3 +93,13 @@ jobs:
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate openapi
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1)
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: install-frontend
|
||||
run: cd frontend && yarn install
|
||||
- name: generate-api-clients
|
||||
run: |
|
||||
cd frontend && yarn generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run yarn generate:api in frontend/ locally and commit."; exit 1)
|
||||
|
||||
@@ -214,7 +214,6 @@ services:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
|
||||
@@ -155,7 +155,6 @@ services:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
configs:
|
||||
- source: otel-collector-config
|
||||
target: /etc/otel-collector-config.yaml
|
||||
|
||||
@@ -219,7 +219,6 @@ services:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
|
||||
@@ -150,7 +150,6 @@ services:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
|
||||
@@ -4355,6 +4355,8 @@ components:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
Querybuildertypesv5QueryData:
|
||||
properties:
|
||||
@@ -4427,6 +4429,9 @@ components:
|
||||
type: array
|
||||
nullable: true
|
||||
type: object
|
||||
required:
|
||||
- keys
|
||||
- complete
|
||||
type: object
|
||||
TelemetrytypesGettableFieldValues:
|
||||
properties:
|
||||
@@ -4434,6 +4439,9 @@ components:
|
||||
type: boolean
|
||||
values:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldValues'
|
||||
required:
|
||||
- values
|
||||
- complete
|
||||
type: object
|
||||
TelemetrytypesTelemetryFieldKey:
|
||||
properties:
|
||||
@@ -4449,6 +4457,8 @@ components:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
TelemetrytypesTelemetryFieldValues:
|
||||
properties:
|
||||
|
||||
@@ -17,6 +17,8 @@ const config: Config.InitialOptions = {
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^@signozhq/icons$':
|
||||
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
|
||||
},
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
"test:changedsince": "jest --changedSince=main --coverage --silent",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh && prettier --write src/api/generated && (eslint --fix src/api/generated || true)"
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
@@ -52,6 +52,7 @@
|
||||
"@signozhq/combobox": "0.0.2",
|
||||
"@signozhq/command": "0.0.0",
|
||||
"@signozhq/design-tokens": "2.1.1",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
"@signozhq/resizable": "0.0.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"FORGOT_PASSWORD": "SigNoz | Forgot Password",
|
||||
"HOME": "SigNoz | Home",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "\n\n---\nRenamed tag files to index.ts...\n"
|
||||
# Rename tag files to index.ts in services directories
|
||||
# tags-split creates: services/tagName/tagName.ts -> rename to services/tagName/index.ts
|
||||
find src/api/generated/services -mindepth 1 -maxdepth 1 -type d | while read -r dir; do
|
||||
@@ -11,4 +12,33 @@ find src/api/generated/services -mindepth 1 -maxdepth 1 -type d | while read -r
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Tag files renamed to index.ts"
|
||||
echo "\n✅ Tag files renamed to index.ts"
|
||||
|
||||
# Format generated files
|
||||
echo "\n\n---\nRunning prettier...\n"
|
||||
if ! prettier --write src/api/generated; then
|
||||
echo "Prettier formatting failed!"
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ Prettier formatting successful"
|
||||
|
||||
|
||||
# Fix linting issues
|
||||
echo "\n\n---\nRunning eslint...\n"
|
||||
if ! yarn lint --fix --quiet src/api/generated; then
|
||||
echo "ESLint check failed! Please fix linting errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ ESLint check successful"
|
||||
|
||||
|
||||
# Check for type errors
|
||||
echo "\n\n---\nChecking for type errors...\n"
|
||||
if ! tsc --noEmit; then
|
||||
echo "Type check failed! Please fix type errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ Type check successful"
|
||||
|
||||
|
||||
echo "\n\n---\n ✅✅✅ API generation complete!"
|
||||
|
||||
@@ -194,6 +194,10 @@ export const Login = Loadable(
|
||||
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
|
||||
);
|
||||
|
||||
export const ForgotPassword = Loadable(
|
||||
() => import(/* webpackChunkName: "ForgotPassword" */ 'pages/ForgotPassword'),
|
||||
);
|
||||
|
||||
export const UnAuthorized = Loadable(
|
||||
() => import(/* webpackChunkName: "UnAuthorized" */ 'pages/UnAuthorized'),
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DashboardWidget,
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
ForgotPassword,
|
||||
Home,
|
||||
InfrastructureMonitoring,
|
||||
InstalledIntegrations,
|
||||
@@ -339,6 +340,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: false,
|
||||
key: 'LOGIN',
|
||||
},
|
||||
{
|
||||
path: ROUTES.FORGOT_PASSWORD,
|
||||
exact: true,
|
||||
component: ForgotPassword,
|
||||
isPrivate: false,
|
||||
key: 'FORGOT_PASSWORD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.UN_AUTHORIZED,
|
||||
exact: true,
|
||||
|
||||
222
frontend/src/api/generated/services/fields/index.ts
Normal file
222
frontend/src/api/generated/services/fields/index.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../index';
|
||||
import type {
|
||||
GetFieldsKeys200,
|
||||
GetFieldsKeysParams,
|
||||
GetFieldsValues200,
|
||||
GetFieldsValuesParams,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
|
||||
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* This endpoint returns field keys
|
||||
* @summary Get field keys
|
||||
*/
|
||||
export const getFieldsKeys = (
|
||||
params?: GetFieldsKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetFieldsKeys200>({
|
||||
url: `/api/v1/fields/keys`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetFieldsKeysQueryKey = (params?: GetFieldsKeysParams) => {
|
||||
return ['getFieldsKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetFieldsKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetFieldsKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getFieldsKeys>>> = ({
|
||||
signal,
|
||||
}) => getFieldsKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetFieldsKeysQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>
|
||||
>;
|
||||
export type GetFieldsKeysQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Get field keys
|
||||
*/
|
||||
|
||||
export function useGetFieldsKeys<
|
||||
TData = Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetFieldsKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get field keys
|
||||
*/
|
||||
export const invalidateGetFieldsKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetFieldsKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint returns field values
|
||||
* @summary Get field values
|
||||
*/
|
||||
export const getFieldsValues = (
|
||||
params?: GetFieldsValuesParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetFieldsValues200>({
|
||||
url: `/api/v1/fields/values`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetFieldsValuesQueryKey = (params?: GetFieldsValuesParams) => {
|
||||
return ['getFieldsValues', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetFieldsValuesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetFieldsValuesQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getFieldsValues>>> = ({
|
||||
signal,
|
||||
}) => getFieldsValues(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetFieldsValuesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>
|
||||
>;
|
||||
export type GetFieldsValuesQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Get field values
|
||||
*/
|
||||
|
||||
export function useGetFieldsValues<
|
||||
TData = Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetFieldsValuesQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get field values
|
||||
*/
|
||||
export const invalidateGetFieldsValues = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetFieldsValuesQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
@@ -1049,7 +1049,7 @@ export interface Querybuildertypesv5OrderByKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -1141,6 +1141,79 @@ export interface RoletypesRoleDTO {
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys = {
|
||||
[key: string]: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
} | null;
|
||||
|
||||
export interface TelemetrytypesGettableFieldKeysDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
complete: boolean;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
keys: TelemetrytypesGettableFieldKeysDTOKeys;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesGettableFieldValuesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
complete: boolean;
|
||||
values: TelemetrytypesTelemetryFieldValuesDTO;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesTelemetryFieldKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesTelemetryFieldValuesDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
boolValues?: boolean[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
numberValues?: number[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
relatedValues?: string[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
stringValues?: string[];
|
||||
}
|
||||
|
||||
export interface TypesChangePasswordRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1588,6 +1661,132 @@ export type DeleteAuthDomainPathParameters = {
|
||||
export type UpdateAuthDomainPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetFieldsKeysParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
searchText?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsKeys200 = {
|
||||
data?: TelemetrytypesGettableFieldKeysDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsValuesParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
searchText?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
existingQuery?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsValues200 = {
|
||||
data?: TelemetrytypesGettableFieldValuesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetResetPasswordTokenPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
1
frontend/src/auto-import-registry.d.ts
vendored
1
frontend/src/auto-import-registry.d.ts
vendored
@@ -18,6 +18,7 @@ import '@signozhq/checkbox';
|
||||
import '@signozhq/combobox';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/resizable';
|
||||
|
||||
@@ -648,7 +648,13 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
) : (
|
||||
<Typography.Text
|
||||
className="value-string"
|
||||
ellipsis={{ tooltip: { placement: 'top' } }}
|
||||
ellipsis={{
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
mouseEnterDelay: 0.2,
|
||||
mouseLeaveDelay: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{String(value)}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const ROUTES = {
|
||||
SIGN_UP: '/signup',
|
||||
LOGIN: '/login',
|
||||
FORGOT_PASSWORD: '/forgot-password',
|
||||
HOME: '/home',
|
||||
SERVICE_METRICS: '/services/:servicename',
|
||||
SERVICE_TOP_LEVEL_OPERATIONS: '/services/:servicename/top-level-operations',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Spin, Table, Typography } from 'antd';
|
||||
import { Spin, Table } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
@@ -14,11 +14,13 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useListOverview } from 'hooks/thirdPartyApis/useListOverview';
|
||||
import { get } from 'lodash-es';
|
||||
import { MoveUpRight } from 'lucide-react';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { HandleChangeQueryDataV5 } from 'types/common/operations.types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
|
||||
import { ApiMonitoringHardcodedAttributeKeys } from '../../constants';
|
||||
import { DEFAULT_PARAMS, useApiMonitoringParams } from '../../queryParams';
|
||||
@@ -125,51 +127,67 @@ function DomainList(): JSX.Element {
|
||||
hardcodedAttributeKeys={ApiMonitoringHardcodedAttributeKeys}
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
className={cx('api-monitoring-domain-list-table')}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
|
||||
columns={columnsConfig}
|
||||
loading={{
|
||||
spinning: isFetching || isLoading,
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
locale={{
|
||||
emptyText:
|
||||
isFetching || isLoading ? null : (
|
||||
<div className="no-filtered-domains-message-container">
|
||||
<div className="no-filtered-domains-message-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
{!isFetching && !isLoading && formattedDataForTable.length === 0 && (
|
||||
<div className="no-filtered-domains-message-container">
|
||||
<div className="no-filtered-domains-message-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-domains-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="no-filtered-domains-message">
|
||||
<div className="no-domain-title">
|
||||
No External API calls detected with applied filters.
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
onRow={(record, index): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
if (index !== undefined) {
|
||||
const dataIndex = formattedDataForTable.findIndex(
|
||||
(item) => item.key === record.key,
|
||||
);
|
||||
setSelectedDomainIndex(dataIndex);
|
||||
setParams({ selectedDomain: record.domainName });
|
||||
logEvent('API Monitoring: Domain name row clicked', {});
|
||||
}
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
||||
}
|
||||
/>
|
||||
<div className="no-domain-subtitle">
|
||||
Ensure all HTTP client spans are being sent with kind as{' '}
|
||||
<span className="attribute">Client</span> and url set in{' '}
|
||||
<span className="attribute">url.full</span> or{' '}
|
||||
<span className="attribute">http.url</span> attribute.
|
||||
</div>
|
||||
<a
|
||||
href={DOCLINKS.EXTERNAL_API_MONITORING}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="external-api-doc-link"
|
||||
>
|
||||
Learn how External API monitoring works in SigNoz{' '}
|
||||
<MoveUpRight size={14} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(isFetching || isLoading || formattedDataForTable.length > 0) && (
|
||||
<Table
|
||||
className="api-monitoring-domain-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
|
||||
columns={columnsConfig}
|
||||
loading={{
|
||||
spinning: isFetching || isLoading,
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
onRow={(record, index): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
if (index !== undefined) {
|
||||
const dataIndex = formattedDataForTable.findIndex(
|
||||
(item) => item.key === record.key,
|
||||
);
|
||||
setSelectedDomainIndex(dataIndex);
|
||||
setParams({ selectedDomain: record.domainName });
|
||||
logEvent('API Monitoring: Domain name row clicked', {});
|
||||
}
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{selectedDomainIndex !== -1 && (
|
||||
<DomainDetails
|
||||
domainData={formattedDataForTable[selectedDomainIndex]}
|
||||
|
||||
@@ -180,10 +180,59 @@
|
||||
|
||||
.no-filtered-domains-message {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
|
||||
.no-domain-title {
|
||||
color: var(--bg-vanilla-100, #fff);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.no-domain-subtitle {
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
|
||||
.attribute {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
|
||||
.external-api-doc-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.no-filtered-domains-message-container {
|
||||
.no-filtered-domains-message-content {
|
||||
.no-filtered-domains-message {
|
||||
.no-domain-title {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
|
||||
.no-domain-subtitle {
|
||||
color: var(--text-ink-400);
|
||||
|
||||
.attribute {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.api-monitoring-domain-list-table {
|
||||
.ant-table {
|
||||
.ant-table-thead > tr > th {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import ChartLayout from 'container/DashboardContainer/visualization/layout/ChartLayout/ChartLayout';
|
||||
import Legend from 'lib/uPlotV2/components/Legend/Legend';
|
||||
import {
|
||||
LegendPosition,
|
||||
TooltipRenderArgs,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import UPlotChart from 'lib/uPlotV2/components/UPlotChart';
|
||||
import { PlotContextProvider } from 'lib/uPlotV2/context/PlotContext';
|
||||
import TooltipPlugin from 'lib/uPlotV2/plugins/TooltipPlugin/TooltipPlugin';
|
||||
import noop from 'lodash-es/noop';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { ChartProps } from '../types';
|
||||
|
||||
const TOOLTIP_WIDTH_PADDING = 60;
|
||||
const TOOLTIP_MIN_WIDTH = 200;
|
||||
|
||||
export default function ChartWrapper({
|
||||
legendConfig = { position: LegendPosition.BOTTOM },
|
||||
config,
|
||||
data,
|
||||
width: containerWidth,
|
||||
height: containerHeight,
|
||||
showTooltip = true,
|
||||
canPinTooltip = false,
|
||||
syncMode,
|
||||
syncKey,
|
||||
onDestroy = noop,
|
||||
children,
|
||||
layoutChildren,
|
||||
renderTooltip,
|
||||
'data-testid': testId,
|
||||
}: ChartProps): JSX.Element {
|
||||
const plotInstanceRef = useRef<uPlot | null>(null);
|
||||
|
||||
const legendComponent = useCallback(
|
||||
(averageLegendWidth: number): React.ReactNode => {
|
||||
return (
|
||||
<Legend
|
||||
config={config}
|
||||
position={legendConfig.position}
|
||||
averageLegendWidth={averageLegendWidth}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[config, legendConfig.position],
|
||||
);
|
||||
|
||||
const renderTooltipCallback = useCallback(
|
||||
(args: TooltipRenderArgs): React.ReactNode => {
|
||||
if (renderTooltip) {
|
||||
return renderTooltip(args);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[renderTooltip],
|
||||
);
|
||||
|
||||
return (
|
||||
<PlotContextProvider>
|
||||
<ChartLayout
|
||||
config={config}
|
||||
containerWidth={containerWidth}
|
||||
containerHeight={containerHeight}
|
||||
legendConfig={legendConfig}
|
||||
legendComponent={legendComponent}
|
||||
layoutChildren={layoutChildren}
|
||||
>
|
||||
{({ chartWidth, chartHeight, averageLegendWidth }): JSX.Element => (
|
||||
<UPlotChart
|
||||
config={config}
|
||||
data={data}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
plotRef={(plot): void => {
|
||||
plotInstanceRef.current = plot;
|
||||
}}
|
||||
onDestroy={(plot: uPlot): void => {
|
||||
plotInstanceRef.current = null;
|
||||
onDestroy(plot);
|
||||
}}
|
||||
data-testid={testId}
|
||||
>
|
||||
{children}
|
||||
{showTooltip && (
|
||||
<TooltipPlugin
|
||||
config={config}
|
||||
canPinTooltip={canPinTooltip}
|
||||
syncMode={syncMode}
|
||||
maxWidth={Math.max(
|
||||
TOOLTIP_MIN_WIDTH,
|
||||
averageLegendWidth + TOOLTIP_WIDTH_PADDING,
|
||||
)}
|
||||
syncKey={syncKey}
|
||||
render={renderTooltipCallback}
|
||||
/>
|
||||
)}
|
||||
</UPlotChart>
|
||||
)}
|
||||
</ChartLayout>
|
||||
</PlotContextProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,104 +1,46 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import ChartLayout from 'container/DashboardContainer/visualization/layout/ChartLayout/ChartLayout';
|
||||
import Legend from 'lib/uPlotV2/components/Legend/Legend';
|
||||
import Tooltip from 'lib/uPlotV2/components/Tooltip/Tooltip';
|
||||
import { useCallback } from 'react';
|
||||
import ChartWrapper from 'container/DashboardContainer/visualization/charts/ChartWrapper/ChartWrapper';
|
||||
import TimeSeriesTooltip from 'lib/uPlotV2/components/Tooltip/TimeSeriesTooltip';
|
||||
import { buildTooltipContent } from 'lib/uPlotV2/components/Tooltip/utils';
|
||||
import {
|
||||
LegendPosition,
|
||||
TimeSeriesTooltipProps,
|
||||
TooltipRenderArgs,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import UPlotChart from 'lib/uPlotV2/components/UPlotChart';
|
||||
import { PlotContextProvider } from 'lib/uPlotV2/context/PlotContext';
|
||||
import TooltipPlugin from 'lib/uPlotV2/plugins/TooltipPlugin/TooltipPlugin';
|
||||
import _noop from 'lodash-es/noop';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { ChartProps } from '../types';
|
||||
import { TimeSeriesChartProps } from '../types';
|
||||
|
||||
const TOOLTIP_WIDTH_PADDING = 60;
|
||||
const TOOLTIP_MIN_WIDTH = 200;
|
||||
export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
const { children, renderTooltip: customRenderTooltip, ...rest } = props;
|
||||
|
||||
export default function TimeSeries({
|
||||
legendConfig = { position: LegendPosition.BOTTOM },
|
||||
config,
|
||||
data,
|
||||
width: containerWidth,
|
||||
height: containerHeight,
|
||||
disableTooltip = false,
|
||||
canPinTooltip = false,
|
||||
timezone,
|
||||
yAxisUnit,
|
||||
decimalPrecision,
|
||||
syncMode,
|
||||
syncKey,
|
||||
onDestroy = _noop,
|
||||
children,
|
||||
layoutChildren,
|
||||
'data-testid': testId,
|
||||
}: ChartProps): JSX.Element {
|
||||
const plotInstanceRef = useRef<uPlot | null>(null);
|
||||
|
||||
const legendComponent = useCallback(
|
||||
(averageLegendWidth: number): React.ReactNode => {
|
||||
return (
|
||||
<Legend
|
||||
config={config}
|
||||
position={legendConfig.position}
|
||||
averageLegendWidth={averageLegendWidth}
|
||||
/>
|
||||
);
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customRenderTooltip) {
|
||||
return customRenderTooltip(props);
|
||||
}
|
||||
const content = buildTooltipContent({
|
||||
data: props.uPlotInstance.data,
|
||||
series: props.uPlotInstance.series,
|
||||
dataIndexes: props.dataIndexes,
|
||||
activeSeriesIndex: props.seriesIndex,
|
||||
uPlotInstance: props.uPlotInstance,
|
||||
yAxisUnit: rest.yAxisUnit ?? '',
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
});
|
||||
const tooltipProps: TimeSeriesTooltipProps = {
|
||||
...props,
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
content,
|
||||
};
|
||||
return <TimeSeriesTooltip {...tooltipProps} />;
|
||||
},
|
||||
[config, legendConfig.position],
|
||||
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
<PlotContextProvider>
|
||||
<ChartLayout
|
||||
config={config}
|
||||
containerWidth={containerWidth}
|
||||
containerHeight={containerHeight}
|
||||
legendConfig={legendConfig}
|
||||
legendComponent={legendComponent}
|
||||
layoutChildren={layoutChildren}
|
||||
>
|
||||
{({ chartWidth, chartHeight, averageLegendWidth }): JSX.Element => (
|
||||
<UPlotChart
|
||||
config={config}
|
||||
data={data}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
plotRef={(plot): void => {
|
||||
plotInstanceRef.current = plot;
|
||||
}}
|
||||
onDestroy={(plot: uPlot): void => {
|
||||
plotInstanceRef.current = null;
|
||||
onDestroy(plot);
|
||||
}}
|
||||
data-testid={testId}
|
||||
>
|
||||
{children}
|
||||
{!disableTooltip && (
|
||||
<TooltipPlugin
|
||||
config={config}
|
||||
canPinTooltip={canPinTooltip}
|
||||
syncMode={syncMode}
|
||||
maxWidth={Math.max(
|
||||
TOOLTIP_MIN_WIDTH,
|
||||
averageLegendWidth + TOOLTIP_WIDTH_PADDING,
|
||||
)}
|
||||
syncKey={syncKey}
|
||||
render={(props: TooltipRenderArgs): React.ReactNode => (
|
||||
<Tooltip
|
||||
{...props}
|
||||
timezone={timezone}
|
||||
yAxisUnit={yAxisUnit}
|
||||
decimalPrecision={decimalPrecision}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</UPlotChart>
|
||||
)}
|
||||
</ChartLayout>
|
||||
</PlotContextProvider>
|
||||
<ChartWrapper {...rest} renderTooltip={renderTooltip}>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { LegendConfig } from 'lib/uPlotV2/components/types';
|
||||
import { LegendConfig, TooltipRenderArgs } from 'lib/uPlotV2/components/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
|
||||
interface BaseChartProps {
|
||||
width: number;
|
||||
height: number;
|
||||
disableTooltip?: boolean;
|
||||
showTooltip?: boolean;
|
||||
timezone: string;
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
canPinTooltip?: boolean;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
renderTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
interface TimeSeriesChartProps extends BaseChartProps {
|
||||
interface UPlotBasedChartProps {
|
||||
config: UPlotConfigBuilder;
|
||||
legendConfig: LegendConfig;
|
||||
data: uPlot.AlignedData;
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
plotRef?: (plot: uPlot | null) => void;
|
||||
onDestroy?: (plot: uPlot) => void;
|
||||
children?: React.ReactNode;
|
||||
layoutChildren?: React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
export type ChartProps = TimeSeriesChartProps;
|
||||
export interface TimeSeriesChartProps
|
||||
extends BaseChartProps,
|
||||
UPlotBasedChartProps {
|
||||
legendConfig: LegendConfig;
|
||||
}
|
||||
|
||||
export interface BarChartProps extends BaseChartProps, UPlotBasedChartProps {
|
||||
legendConfig: LegendConfig;
|
||||
isStackedBarChart?: boolean;
|
||||
}
|
||||
|
||||
export type ChartProps = TimeSeriesChartProps | BarChartProps;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { LineInterpolation } from 'lib/uPlotV2/config/types';
|
||||
import { ContextMenu } from 'periscope/components/ContextMenu';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
@@ -73,55 +72,28 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
}, [queryResponse?.data?.payload]);
|
||||
|
||||
const config = useMemo(() => {
|
||||
const tzDate = (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
|
||||
|
||||
return prepareUPlotConfig({
|
||||
widgetId: widget.id || '',
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
tzDate,
|
||||
minTimeScale: minTimeScale,
|
||||
maxTimeScale: maxTimeScale,
|
||||
isLogScale: widget?.isLogScale ?? false,
|
||||
thresholds: {
|
||||
scaleKey: 'y',
|
||||
thresholds: (widget.thresholds || []).map((threshold) => ({
|
||||
thresholdValue: threshold.thresholdValue ?? 0,
|
||||
thresholdColor: threshold.thresholdColor,
|
||||
thresholdUnit: threshold.thresholdUnit,
|
||||
thresholdLabel: threshold.thresholdLabel,
|
||||
})),
|
||||
yAxisUnit: widget.yAxisUnit,
|
||||
},
|
||||
yAxisUnit: widget.yAxisUnit || '',
|
||||
softMin: widget.softMin === undefined ? null : widget.softMin,
|
||||
softMax: widget.softMax === undefined ? null : widget.softMax,
|
||||
spanGaps: false,
|
||||
colorMapping: widget.customLegendColors ?? {},
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
widget,
|
||||
isDarkMode,
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
currentQuery: widget.query,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
maxTimeScale: maxTimeScale,
|
||||
});
|
||||
}, [
|
||||
widget.id,
|
||||
maxTimeScale,
|
||||
minTimeScale,
|
||||
timezone.value,
|
||||
widget.customLegendColors,
|
||||
widget.isLogScale,
|
||||
widget.softMax,
|
||||
widget.softMin,
|
||||
widget,
|
||||
isDarkMode,
|
||||
queryResponse?.data?.payload,
|
||||
widget.query,
|
||||
widget.thresholds,
|
||||
widget.yAxisUnit,
|
||||
panelMode,
|
||||
clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
queryResponse?.data?.payload,
|
||||
panelMode,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
timezone,
|
||||
]);
|
||||
|
||||
const layoutChildren = useMemo(() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
fillMissingXAxisTimestamps,
|
||||
@@ -5,23 +6,20 @@ import {
|
||||
} from 'container/DashboardContainer/visualization/panels/utils';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import onClickPlugin, {
|
||||
OnClickPluginOpts,
|
||||
} from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import {
|
||||
DistributionType,
|
||||
DrawStyle,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
SelectionPreferencesSource,
|
||||
VisibilityMode,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { ThresholdsDrawHookOptions } from 'lib/uPlotV2/hooks/types';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
import { buildBaseConfig } from '../utils/baseConfigBuilder';
|
||||
|
||||
export const prepareChartData = (
|
||||
apiResponse: MetricRangePayloadProps,
|
||||
@@ -34,112 +32,39 @@ export const prepareChartData = (
|
||||
};
|
||||
|
||||
export const prepareUPlotConfig = ({
|
||||
widgetId,
|
||||
apiResponse,
|
||||
tzDate,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isLogScale,
|
||||
thresholds,
|
||||
softMin,
|
||||
softMax,
|
||||
spanGaps,
|
||||
colorMapping,
|
||||
lineInterpolation,
|
||||
widget,
|
||||
isDarkMode,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
onClick,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}: {
|
||||
widgetId: string;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
tzDate: uPlot.LocalDateFromUnix;
|
||||
minTimeScale: number | undefined;
|
||||
maxTimeScale: number | undefined;
|
||||
isLogScale: boolean;
|
||||
softMin: number | null;
|
||||
softMax: number | null;
|
||||
spanGaps: boolean;
|
||||
colorMapping: Record<string, string>;
|
||||
lineInterpolation: LineInterpolation;
|
||||
widget: Widgets;
|
||||
isDarkMode: boolean;
|
||||
thresholds: ThresholdsDrawHookOptions;
|
||||
currentQuery: Query;
|
||||
yAxisUnit: string;
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
}): UPlotConfigBuilder => {
|
||||
const builder = new UPlotConfigBuilder({
|
||||
const builder = buildBaseConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
onClick,
|
||||
onDragSelect,
|
||||
widgetId,
|
||||
tzDate,
|
||||
shouldSaveSelectionPreference: panelMode === PanelMode.DASHBOARD_VIEW,
|
||||
selectionPreferencesSource: [
|
||||
PanelMode.DASHBOARD_VIEW,
|
||||
PanelMode.STANDALONE_VIEW,
|
||||
].includes(panelMode)
|
||||
? SelectionPreferencesSource.LOCAL_STORAGE
|
||||
: SelectionPreferencesSource.IN_MEMORY,
|
||||
});
|
||||
|
||||
// X scale – time axis
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
logBase: isLogScale ? 10 : undefined,
|
||||
distribution: isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
: DistributionType.Linear,
|
||||
});
|
||||
|
||||
// Y scale – value axis, driven primarily by softMin/softMax and data
|
||||
builder.addScale({
|
||||
scaleKey: 'y',
|
||||
time: false,
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
softMin: softMin ?? undefined,
|
||||
softMax: softMax ?? undefined,
|
||||
thresholds,
|
||||
logBase: isLogScale ? 10 : undefined,
|
||||
distribution: isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
: DistributionType.Linear,
|
||||
});
|
||||
|
||||
builder.addThresholds(thresholds);
|
||||
|
||||
if (typeof onClick === 'function') {
|
||||
builder.addPlugin(
|
||||
onClickPlugin({
|
||||
onClick,
|
||||
apiResponse,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
isLogScale: false,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'y',
|
||||
show: true,
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
isLogScale: false,
|
||||
yAxisUnit,
|
||||
apiResponse,
|
||||
timezone,
|
||||
panelMode,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
});
|
||||
|
||||
apiResponse.data?.result?.forEach((series) => {
|
||||
@@ -157,14 +82,16 @@ export const prepareUPlotConfig = ({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Line,
|
||||
label: label,
|
||||
colorMapping,
|
||||
spanGaps,
|
||||
colorMapping: widget.customLegendColors ?? {},
|
||||
spanGaps: false,
|
||||
lineStyle: LineStyle.Solid,
|
||||
lineInterpolation,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
showPoints: VisibilityMode.Never,
|
||||
pointSize: 5,
|
||||
isDarkMode,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
import type { GraphVisibilityState } from '../../types';
|
||||
import {
|
||||
getStoredSeriesVisibility,
|
||||
updateSeriesVisibilityToLocalStorage,
|
||||
} from '../legendVisibilityUtils';
|
||||
|
||||
describe('legendVisibilityUtils', () => {
|
||||
const storageKey = LOCALSTORAGE.GRAPH_VISIBILITY_STATES;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
jest.spyOn(window.localStorage.__proto__, 'getItem');
|
||||
jest.spyOn(window.localStorage.__proto__, 'setItem');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('getStoredSeriesVisibility', () => {
|
||||
it('returns null when there is no stored visibility state', () => {
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(localStorage.getItem).toHaveBeenCalledWith(storageKey);
|
||||
});
|
||||
|
||||
it('returns null when widget has no stored dataIndex', () => {
|
||||
const stored: GraphVisibilityState[] = [
|
||||
{
|
||||
name: 'widget-1',
|
||||
dataIndex: [],
|
||||
},
|
||||
];
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify(stored));
|
||||
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns a Map of label to visibility when widget state exists', () => {
|
||||
const stored: GraphVisibilityState[] = [
|
||||
{
|
||||
name: 'widget-1',
|
||||
dataIndex: [
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'widget-2',
|
||||
dataIndex: [{ label: 'Errors', show: true }],
|
||||
},
|
||||
];
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify(stored));
|
||||
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result instanceof Map).toBe(true);
|
||||
expect(result?.get('CPU')).toBe(true);
|
||||
expect(result?.get('Memory')).toBe(false);
|
||||
expect(result?.get('Errors')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns null on malformed JSON in localStorage', () => {
|
||||
localStorage.setItem(storageKey, '{invalid-json');
|
||||
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when widget id is not found', () => {
|
||||
const stored: GraphVisibilityState[] = [
|
||||
{
|
||||
name: 'another-widget',
|
||||
dataIndex: [{ label: 'CPU', show: true }],
|
||||
},
|
||||
];
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify(stored));
|
||||
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSeriesVisibilityToLocalStorage', () => {
|
||||
it('creates new visibility state when none exists', () => {
|
||||
const seriesVisibility = [
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
];
|
||||
|
||||
updateSeriesVisibilityToLocalStorage('widget-1', seriesVisibility);
|
||||
|
||||
const stored = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored!.get('CPU')).toBe(true);
|
||||
expect(stored!.get('Memory')).toBe(false);
|
||||
});
|
||||
|
||||
it('adds a new widget entry when other widgets already exist', () => {
|
||||
const existing: GraphVisibilityState[] = [
|
||||
{
|
||||
name: 'widget-existing',
|
||||
dataIndex: [{ label: 'Errors', show: true }],
|
||||
},
|
||||
];
|
||||
localStorage.setItem(storageKey, JSON.stringify(existing));
|
||||
|
||||
const newVisibility = [{ label: 'CPU', show: false }];
|
||||
|
||||
updateSeriesVisibilityToLocalStorage('widget-new', newVisibility);
|
||||
|
||||
const stored = getStoredSeriesVisibility('widget-new');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored!.get('CPU')).toBe(false);
|
||||
});
|
||||
|
||||
it('updates existing widget visibility when entry already exists', () => {
|
||||
const initialVisibility: GraphVisibilityState[] = [
|
||||
{
|
||||
name: 'widget-1',
|
||||
dataIndex: [
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify(initialVisibility));
|
||||
|
||||
const updatedVisibility = [
|
||||
{ label: 'CPU', show: false },
|
||||
{ label: 'Memory', show: true },
|
||||
];
|
||||
|
||||
updateSeriesVisibilityToLocalStorage('widget-1', updatedVisibility);
|
||||
|
||||
const stored = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored!.get('CPU')).toBe(false);
|
||||
expect(stored!.get('Memory')).toBe(true);
|
||||
});
|
||||
|
||||
it('silently handles malformed existing JSON without throwing', () => {
|
||||
localStorage.setItem(storageKey, '{invalid-json');
|
||||
|
||||
expect(() =>
|
||||
updateSeriesVisibilityToLocalStorage('widget-1', [
|
||||
{ label: 'CPU', show: true },
|
||||
]),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import onClickPlugin, {
|
||||
OnClickPluginOpts,
|
||||
} from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import {
|
||||
DistributionType,
|
||||
SelectionPreferencesSource,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { ThresholdsDrawHookOptions } from 'lib/uPlotV2/hooks/types';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
|
||||
export interface BaseConfigBuilderProps {
|
||||
widget: Widgets;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
isDarkMode: boolean;
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
panelType: PANEL_TYPES;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
}
|
||||
|
||||
export function buildBaseConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
onClick,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
panelMode,
|
||||
panelType,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}: BaseConfigBuilderProps): UPlotConfigBuilder {
|
||||
const tzDate = (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
|
||||
|
||||
const builder = new UPlotConfigBuilder({
|
||||
onDragSelect,
|
||||
widgetId: widget.id,
|
||||
tzDate,
|
||||
shouldSaveSelectionPreference: panelMode === PanelMode.DASHBOARD_VIEW,
|
||||
selectionPreferencesSource: [
|
||||
PanelMode.DASHBOARD_VIEW,
|
||||
PanelMode.STANDALONE_VIEW,
|
||||
].includes(panelMode)
|
||||
? SelectionPreferencesSource.LOCAL_STORAGE
|
||||
: SelectionPreferencesSource.IN_MEMORY,
|
||||
});
|
||||
|
||||
const thresholdOptions: ThresholdsDrawHookOptions = {
|
||||
scaleKey: 'y',
|
||||
thresholds: (widget.thresholds || []).map((threshold) => ({
|
||||
thresholdValue: threshold.thresholdValue ?? 0,
|
||||
thresholdColor: threshold.thresholdColor,
|
||||
thresholdUnit: threshold.thresholdUnit,
|
||||
thresholdLabel: threshold.thresholdLabel,
|
||||
})),
|
||||
yAxisUnit: widget.yAxisUnit,
|
||||
};
|
||||
|
||||
builder.addThresholds(thresholdOptions);
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
logBase: widget.isLogScale ? 10 : undefined,
|
||||
distribution: widget.isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
: DistributionType.Linear,
|
||||
});
|
||||
|
||||
// Y scale – value axis, driven primarily by softMin/softMax and data
|
||||
builder.addScale({
|
||||
scaleKey: 'y',
|
||||
time: false,
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
softMin: widget.softMin ?? undefined,
|
||||
softMax: widget.softMax ?? undefined,
|
||||
// thresholds,
|
||||
logBase: widget.isLogScale ? 10 : undefined,
|
||||
distribution: widget.isLogScale
|
||||
? DistributionType.Logarithmic
|
||||
: DistributionType.Linear,
|
||||
});
|
||||
|
||||
if (typeof onClick === 'function') {
|
||||
builder.addPlugin(
|
||||
onClickPlugin({
|
||||
onClick,
|
||||
apiResponse,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
isLogScale: widget.isLogScale,
|
||||
panelType,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'y',
|
||||
show: true,
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
isLogScale: widget.isLogScale,
|
||||
yAxisUnit: widget.yAxisUnit,
|
||||
panelType,
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
.forgot-password-title {
|
||||
font-family: var(--label-large-600-font-family);
|
||||
font-size: var(--label-large-600-font-size);
|
||||
font-weight: var(--label-large-600-font-weight);
|
||||
letter-spacing: var(--label-large-600-letter-spacing);
|
||||
line-height: 1.45;
|
||||
color: var(--l1-foreground);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.forgot-password-description {
|
||||
font-family: var(--paragraph-base-400-font-family);
|
||||
font-size: var(--paragraph-base-400-font-size);
|
||||
font-weight: var(--paragraph-base-400-font-weight);
|
||||
line-height: var(--paragraph-base-400-line-height);
|
||||
letter-spacing: -0.065px;
|
||||
color: var(--l2-foreground);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
max-width: 317px;
|
||||
}
|
||||
|
||||
.forgot-password-form {
|
||||
width: 100%;
|
||||
|
||||
// Label styling
|
||||
.forgot-password-label {
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.065px;
|
||||
color: var(--l1-foreground);
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
|
||||
.lightMode & {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
}
|
||||
|
||||
// Parent container for fields
|
||||
.forgot-password-field {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.ant-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
> .forgot-password-back-button,
|
||||
> .login-submit-btn {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-back-button {
|
||||
height: 32px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 2px;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background: var(--l3-background);
|
||||
border: 1px solid var(--l3-border);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--l3-border);
|
||||
border-color: var(--l3-border);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
41
frontend/src/container/ForgotPassword/SuccessScreen.tsx
Normal file
41
frontend/src/container/ForgotPassword/SuccessScreen.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { ArrowLeft, Mail } from '@signozhq/icons';
|
||||
|
||||
interface SuccessScreenProps {
|
||||
onBackToLogin: () => void;
|
||||
}
|
||||
|
||||
function SuccessScreen({ onBackToLogin }: SuccessScreenProps): JSX.Element {
|
||||
return (
|
||||
<div className="login-form-container">
|
||||
<div className="forgot-password-form">
|
||||
<div className="login-form-header">
|
||||
<div className="login-form-emoji">
|
||||
<Mail size={32} />
|
||||
</div>
|
||||
<h4 className="forgot-password-title">Check your email</h4>
|
||||
<p className="forgot-password-description">
|
||||
We've sent a password reset link to your email. Please check your
|
||||
inbox and follow the instructions to reset your password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="login-form-actions forgot-password-actions">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
type="button"
|
||||
data-testid="back-to-login"
|
||||
className="login-submit-btn"
|
||||
onClick={onBackToLogin}
|
||||
prefixIcon={<ArrowLeft size={12} />}
|
||||
>
|
||||
Back to login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SuccessScreen;
|
||||
@@ -0,0 +1,402 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleInternalServerError,
|
||||
rest,
|
||||
server,
|
||||
} from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
|
||||
|
||||
import ForgotPassword, { ForgotPasswordRouteState } from '../index';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
push: jest.fn(),
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const mockHistoryPush = history.push as jest.MockedFunction<
|
||||
typeof history.push
|
||||
>;
|
||||
|
||||
const FORGOT_PASSWORD_ENDPOINT = '*/api/v2/factor_password/forgot';
|
||||
|
||||
// Mock data
|
||||
const mockSingleOrg: OrgSessionContext[] = [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Test Organization',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockMultipleOrgs: OrgSessionContext[] = [
|
||||
{
|
||||
id: 'org-1',
|
||||
name: 'Organization One',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'org-2',
|
||||
name: 'Organization Two',
|
||||
authNSupport: {
|
||||
password: [{ provider: 'email_password' }],
|
||||
callback: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const TEST_EMAIL = 'jest.test@signoz.io';
|
||||
|
||||
const defaultProps: ForgotPasswordRouteState = {
|
||||
email: TEST_EMAIL,
|
||||
orgs: mockSingleOrg,
|
||||
};
|
||||
|
||||
const multiOrgProps: ForgotPasswordRouteState = {
|
||||
email: TEST_EMAIL,
|
||||
orgs: mockMultipleOrgs,
|
||||
};
|
||||
|
||||
describe('ForgotPassword Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('Initial Render', () => {
|
||||
it('renders forgot password form with all required elements', () => {
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(/forgot your password\?/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/send a reset link to your inbox/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('email')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('forgot-password-submit')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('forgot-password-back')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('pre-fills email from props', () => {
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const emailInput = screen.getByTestId('email');
|
||||
expect(emailInput).toHaveValue(TEST_EMAIL);
|
||||
});
|
||||
|
||||
it('disables email input field', () => {
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const emailInput = screen.getByTestId('email');
|
||||
expect(emailInput).toBeDisabled();
|
||||
});
|
||||
|
||||
it('does not show organization dropdown for single org', () => {
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByTestId('orgId')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Organization Name')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('enables submit button when email is provided with single org', () => {
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Organizations', () => {
|
||||
it('shows organization dropdown when multiple orgs exist', () => {
|
||||
render(<ForgotPassword {...multiOrgProps} />);
|
||||
|
||||
expect(screen.getByTestId('orgId')).toBeInTheDocument();
|
||||
expect(screen.getByText('Organization Name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables submit button when org is not selected', () => {
|
||||
const propsWithoutOrgId: ForgotPasswordRouteState = {
|
||||
email: TEST_EMAIL,
|
||||
orgs: mockMultipleOrgs,
|
||||
};
|
||||
|
||||
render(<ForgotPassword {...propsWithoutOrgId} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables submit button after selecting an organization', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<ForgotPassword {...multiOrgProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// Click on the dropdown to reveal the options
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
await user.click(screen.getByText('Organization One'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('pre-selects organization when orgId is provided', () => {
|
||||
const propsWithOrgId: ForgotPasswordRouteState = {
|
||||
email: TEST_EMAIL,
|
||||
orgId: 'org-1',
|
||||
orgs: mockMultipleOrgs,
|
||||
};
|
||||
|
||||
render(<ForgotPassword {...propsWithOrgId} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Submission - Success', () => {
|
||||
it('successfully submits forgot password request and shows success screen', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(await screen.findByText(/check your email/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/we've sent a password reset link/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows back to login button on success screen', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(await screen.findByTestId('back-to-login')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('redirects to login when clicking back to login on success screen', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(await screen.findByTestId('back-to-login')).toBeInTheDocument();
|
||||
|
||||
const backToLoginButton = screen.getByTestId('back-to-login');
|
||||
await user.click(backToLoginButton);
|
||||
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Submission - Error Handling', () => {
|
||||
it('displays error message when forgot password API fails', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(
|
||||
FORGOT_PASSWORD_ENDPOINT,
|
||||
createErrorResponse(400, 'USER_NOT_FOUND', 'User not found'),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(await screen.findByText(/user not found/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays error message when API returns server error', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(rest.post(FORGOT_PASSWORD_ENDPOINT, handleInternalServerError));
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/internal server error occurred/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clears error message on new submission attempt', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
let requestCount = 0;
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) => {
|
||||
requestCount += 1;
|
||||
if (requestCount === 1) {
|
||||
return res(
|
||||
ctx.status(400),
|
||||
ctx.json({
|
||||
error: {
|
||||
code: 'USER_NOT_FOUND',
|
||||
message: 'User not found',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
return res(ctx.status(200), ctx.json({ status: 'success' }));
|
||||
}),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
expect(await screen.findByText(/user not found/i)).toBeInTheDocument();
|
||||
|
||||
// Click submit again
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/user not found/i)).not.toBeInTheDocument();
|
||||
});
|
||||
expect(await screen.findByText(/check your email/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('redirects to login when clicking back button on form', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const backButton = screen.getByTestId('forgot-password-back');
|
||||
await user.click(backButton);
|
||||
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Loading States', () => {
|
||||
it('shows loading state during API call', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.delay(100), ctx.status(200), ctx.json({ status: 'success' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
// Button should show loading state
|
||||
expect(await screen.findByText(/sending\.\.\./i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables submit button during loading', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.delay(100), ctx.status(200), ctx.json({ status: 'success' })),
|
||||
),
|
||||
);
|
||||
|
||||
render(<ForgotPassword {...defaultProps} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('handles empty email gracefully', () => {
|
||||
const propsWithEmptyEmail: ForgotPasswordRouteState = {
|
||||
email: '',
|
||||
orgs: mockSingleOrg,
|
||||
};
|
||||
|
||||
render(<ForgotPassword {...propsWithEmptyEmail} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('handles whitespace-only email', () => {
|
||||
const propsWithWhitespaceEmail: ForgotPasswordRouteState = {
|
||||
email: ' ',
|
||||
orgs: mockSingleOrg,
|
||||
};
|
||||
|
||||
render(<ForgotPassword {...propsWithWhitespaceEmail} />);
|
||||
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('handles empty orgs array by disabling submission', () => {
|
||||
const propsWithNoOrgs: ForgotPasswordRouteState = {
|
||||
email: TEST_EMAIL,
|
||||
orgs: [],
|
||||
};
|
||||
|
||||
render(<ForgotPassword {...propsWithNoOrgs} />);
|
||||
|
||||
// Should not show org dropdown
|
||||
expect(screen.queryByTestId('orgId')).not.toBeInTheDocument();
|
||||
// Submit should be disabled because no orgId can be determined
|
||||
const submitButton = screen.getByTestId('forgot-password-submit');
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
217
frontend/src/container/ForgotPassword/index.tsx
Normal file
217
frontend/src/container/ForgotPassword/index.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { ArrowLeft, ArrowRight } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Form, Select } from 'antd';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { useForgotPassword } from 'api/generated/services/users';
|
||||
import { AxiosError } from 'axios';
|
||||
import AuthError from 'components/AuthError/AuthError';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { ErrorV2Resp } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
|
||||
|
||||
import SuccessScreen from './SuccessScreen';
|
||||
|
||||
import './ForgotPassword.styles.scss';
|
||||
import 'container/Login/Login.styles.scss';
|
||||
|
||||
type FormValues = {
|
||||
email: string;
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type ForgotPasswordRouteState = {
|
||||
email: string;
|
||||
orgId?: string;
|
||||
orgs: OrgSessionContext[];
|
||||
};
|
||||
|
||||
function ForgotPassword({
|
||||
email,
|
||||
orgId,
|
||||
orgs,
|
||||
}: ForgotPasswordRouteState): JSX.Element {
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const {
|
||||
mutate: forgotPasswordMutate,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
error: mutationError,
|
||||
} = useForgotPassword();
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
if (!mutationError) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
ErrorResponseHandlerV2(mutationError as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
return apiError as APIError;
|
||||
}
|
||||
}, [mutationError]);
|
||||
|
||||
const initialOrgId = useMemo((): string | undefined => {
|
||||
if (orgId) {
|
||||
return orgId;
|
||||
}
|
||||
|
||||
if (orgs.length === 1) {
|
||||
return orgs[0]?.id;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [orgId, orgs]);
|
||||
|
||||
const watchedEmail = Form.useWatch('email', form);
|
||||
const selectedOrgId = Form.useWatch('orgId', form);
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
email,
|
||||
orgId: initialOrgId,
|
||||
});
|
||||
}, [email, form, initialOrgId]);
|
||||
|
||||
const hasMultipleOrgs = orgs.length > 1;
|
||||
|
||||
const isSubmitEnabled = useMemo((): boolean => {
|
||||
if (isLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!watchedEmail?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure we have an orgId (either selected from dropdown or the initial one)
|
||||
const currentOrgId = hasMultipleOrgs ? selectedOrgId : initialOrgId;
|
||||
return Boolean(currentOrgId);
|
||||
}, [watchedEmail, selectedOrgId, isLoading, initialOrgId, hasMultipleOrgs]);
|
||||
|
||||
const handleSubmit = useCallback((): void => {
|
||||
const values = form.getFieldsValue();
|
||||
const currentOrgId = hasMultipleOrgs ? values.orgId : initialOrgId;
|
||||
|
||||
if (!currentOrgId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the forgot password API
|
||||
forgotPasswordMutate({
|
||||
data: {
|
||||
email: values.email,
|
||||
orgId: currentOrgId,
|
||||
frontendBaseURL: window.location.origin,
|
||||
},
|
||||
});
|
||||
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);
|
||||
|
||||
const handleBackToLogin = useCallback((): void => {
|
||||
history.push(ROUTES.LOGIN);
|
||||
}, []);
|
||||
|
||||
// Success screen
|
||||
if (isSuccess) {
|
||||
return <SuccessScreen onBackToLogin={handleBackToLogin} />;
|
||||
}
|
||||
|
||||
// Form screen
|
||||
return (
|
||||
<div className="login-form-container">
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
className="forgot-password-form"
|
||||
initialValues={{
|
||||
email,
|
||||
orgId: initialOrgId,
|
||||
}}
|
||||
>
|
||||
<div className="login-form-header">
|
||||
<div className="login-form-emoji">
|
||||
<img src="/svgs/tv.svg" alt="TV" width="32" height="32" />
|
||||
</div>
|
||||
<h4 className="forgot-password-title">Forgot your password?</h4>
|
||||
<p className="forgot-password-description">
|
||||
Send a reset link to your inbox and get back to monitoring.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="login-form-card">
|
||||
<div className="forgot-password-field">
|
||||
<label className="forgot-password-label" htmlFor="forgotPasswordEmail">
|
||||
Email address
|
||||
</label>
|
||||
<Form.Item name="email">
|
||||
<Input
|
||||
type="email"
|
||||
id="forgotPasswordEmail"
|
||||
data-testid="email"
|
||||
required
|
||||
disabled
|
||||
className="login-form-input"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{hasMultipleOrgs && (
|
||||
<div className="forgot-password-field">
|
||||
<label className="forgot-password-label" htmlFor="orgId">
|
||||
Organization Name
|
||||
</label>
|
||||
<Form.Item
|
||||
name="orgId"
|
||||
rules={[{ required: true, message: 'Please select your organization' }]}
|
||||
>
|
||||
<Select
|
||||
id="orgId"
|
||||
data-testid="orgId"
|
||||
className="login-form-input login-form-select-no-border"
|
||||
placeholder="Select your organization"
|
||||
options={orgs.map((org) => ({
|
||||
value: org.id,
|
||||
label: org.name || 'default',
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{errorMessage && <AuthError error={errorMessage} />}
|
||||
|
||||
<div className="login-form-actions forgot-password-actions">
|
||||
<Button
|
||||
variant="solid"
|
||||
type="button"
|
||||
data-testid="forgot-password-back"
|
||||
className="forgot-password-back-button"
|
||||
onClick={handleBackToLogin}
|
||||
prefixIcon={<ArrowLeft size={12} />}
|
||||
>
|
||||
Back to login
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={!isSubmitEnabled}
|
||||
loading={isLoading}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="forgot-password-submit"
|
||||
className="login-submit-btn"
|
||||
suffixIcon={<ArrowRight size={12} />}
|
||||
>
|
||||
{isLoading ? 'Sending...' : 'Send reset link'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -2,7 +2,6 @@
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
@@ -27,12 +26,20 @@ import {
|
||||
} from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { CollapseProps } from 'antd/lib';
|
||||
import createIngestionKeyApi from 'api/IngestionKeys/createIngestionKey';
|
||||
import deleteIngestionKey from 'api/IngestionKeys/deleteIngestionKey';
|
||||
import createLimitForIngestionKeyApi from 'api/IngestionKeys/limits/createLimitsForKey';
|
||||
import deleteLimitsForIngestionKey from 'api/IngestionKeys/limits/deleteLimitsForIngestionKey';
|
||||
import updateLimitForIngestionKeyApi from 'api/IngestionKeys/limits/updateLimitsForIngestionKey';
|
||||
import updateIngestionKey from 'api/IngestionKeys/updateIngestionKey';
|
||||
import {
|
||||
useCreateIngestionKey,
|
||||
useCreateIngestionKeyLimit,
|
||||
useDeleteIngestionKey,
|
||||
useDeleteIngestionKeyLimit,
|
||||
useGetIngestionKeys,
|
||||
useSearchIngestionKeys,
|
||||
useUpdateIngestionKey,
|
||||
useUpdateIngestionKeyLimit,
|
||||
} from 'api/generated/services/gateway';
|
||||
import {
|
||||
GatewaytypesIngestionKeyDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import Tags from 'components/Tags/Tags';
|
||||
@@ -44,7 +51,6 @@ import ROUTES from 'constants/routes';
|
||||
import { INITIAL_ALERT_THRESHOLD_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
|
||||
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { cloneDeep, isNil, isUndefined } from 'lodash-es';
|
||||
@@ -66,16 +72,12 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ErrorResponse } from 'types/api';
|
||||
import {
|
||||
AddLimitProps,
|
||||
LimitProps,
|
||||
UpdateLimitProps,
|
||||
} from 'types/api/ingestionKeys/limits/types';
|
||||
import {
|
||||
IngestionKeyProps,
|
||||
PaginationProps,
|
||||
} from 'types/api/ingestionKeys/types';
|
||||
import { PaginationProps } from 'types/api/ingestionKeys/types';
|
||||
import { MeterAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { getDaysUntilExpiry } from 'utils/timeUtils';
|
||||
@@ -86,6 +88,10 @@ const { Option } = Select;
|
||||
|
||||
const BYTES = 1073741824;
|
||||
|
||||
const INITIAL_PAGE_SIZE = 10;
|
||||
const SEARCH_PAGE_SIZE = 100;
|
||||
const FIRST_PAGE = 1;
|
||||
|
||||
const COUNT_MULTIPLIER = {
|
||||
thousand: 1000,
|
||||
million: 1000000,
|
||||
@@ -111,6 +117,8 @@ export const showErrorNotification = (
|
||||
): void => {
|
||||
notifications.error({
|
||||
message: err.message || SOMETHING_WENT_WRONG,
|
||||
description: (err as AxiosError<RenderErrorResponseDTO>).response?.data?.error
|
||||
?.message,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -163,15 +171,20 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const [updatedTags, setUpdatedTags] = useState<string[]>([]);
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [isEditAddLimitOpen, setIsEditAddLimitOpen] = useState(false);
|
||||
const [activeAPIKey, setActiveAPIKey] = useState<IngestionKeyProps | null>();
|
||||
const [
|
||||
activeAPIKey,
|
||||
setActiveAPIKey,
|
||||
] = useState<GatewaytypesIngestionKeyDTO | null>(null);
|
||||
const [activeSignal, setActiveSignal] = useState<LimitProps | null>(null);
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [dataSource, setDataSource] = useState<IngestionKeyProps[]>([]);
|
||||
const [dataSource, setDataSource] = useState<GatewaytypesIngestionKeyDTO[]>(
|
||||
[],
|
||||
);
|
||||
const [paginationParams, setPaginationParams] = useState<PaginationProps>({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
page: FIRST_PAGE,
|
||||
per_page: INITIAL_PAGE_SIZE,
|
||||
});
|
||||
|
||||
const [totalIngestionKeys, setTotalIngestionKeys] = useState(0);
|
||||
@@ -186,7 +199,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const [
|
||||
createLimitForIngestionKeyError,
|
||||
setCreateLimitForIngestionKeyError,
|
||||
] = useState<ErrorResponse | null>(null);
|
||||
] = useState<string | null>(null);
|
||||
|
||||
const [
|
||||
hasUpdateLimitForIngestionKeyError,
|
||||
@@ -196,7 +209,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const [
|
||||
updateLimitForIngestionKeyError,
|
||||
setUpdateLimitForIngestionKeyError,
|
||||
] = useState<ErrorResponse | null>(null);
|
||||
] = useState<string | null>(null);
|
||||
|
||||
const { t } = useTranslation(['ingestionKeys']);
|
||||
|
||||
@@ -216,7 +229,11 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
handleFormReset();
|
||||
};
|
||||
|
||||
const showDeleteModal = (apiKey: IngestionKeyProps): void => {
|
||||
const showDeleteModal = (apiKey: GatewaytypesIngestionKeyDTO): void => {
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
setCreateLimitForIngestionKeyError(null);
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
setUpdateLimitForIngestionKeyError(null);
|
||||
setActiveAPIKey(apiKey);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
@@ -233,7 +250,11 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
setIsAddModalOpen(false);
|
||||
};
|
||||
|
||||
const showEditModal = (apiKey: IngestionKeyProps): void => {
|
||||
const showEditModal = (apiKey: GatewaytypesIngestionKeyDTO): void => {
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
setCreateLimitForIngestionKeyError(null);
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
setUpdateLimitForIngestionKeyError(null);
|
||||
setActiveAPIKey(apiKey);
|
||||
handleFormReset();
|
||||
setUpdatedTags(apiKey.tags || []);
|
||||
@@ -248,6 +269,10 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
};
|
||||
|
||||
const showAddModal = (): void => {
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
setCreateLimitForIngestionKeyError(null);
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
setUpdateLimitForIngestionKeyError(null);
|
||||
setUpdatedTags([]);
|
||||
setActiveAPIKey(null);
|
||||
setIsAddModalOpen(true);
|
||||
@@ -258,27 +283,62 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
setActiveSignal(null);
|
||||
};
|
||||
|
||||
// Use search API when searchText is present, otherwise use normal get API
|
||||
const isSearching = searchText.length > 0;
|
||||
|
||||
const {
|
||||
data: IngestionKeys,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
refetch: refetchAPIKeys,
|
||||
error,
|
||||
isError,
|
||||
} = useGetAllIngestionsKeys({
|
||||
search: searchText,
|
||||
...paginationParams,
|
||||
});
|
||||
data: ingestionKeysData,
|
||||
isLoading: isLoadingGet,
|
||||
isRefetching: isRefetchingGet,
|
||||
refetch: refetchGetAPIKeys,
|
||||
error: getError,
|
||||
isError: isGetError,
|
||||
} = useGetIngestionKeys(
|
||||
{
|
||||
...paginationParams,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
enabled: !isSearching,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: searchIngestionKeysData,
|
||||
isLoading: isLoadingSearch,
|
||||
isRefetching: isRefetchingSearch,
|
||||
refetch: refetchSearchAPIKeys,
|
||||
error: searchError,
|
||||
isError: isSearchError,
|
||||
} = useSearchIngestionKeys(
|
||||
{
|
||||
page: FIRST_PAGE,
|
||||
per_page: SEARCH_PAGE_SIZE,
|
||||
name: searchText,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
enabled: isSearching,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Use the appropriate data based on which API is active
|
||||
const ingestionKeys = isSearching
|
||||
? searchIngestionKeysData
|
||||
: ingestionKeysData;
|
||||
const isLoading = isSearching ? isLoadingSearch : isLoadingGet;
|
||||
const isRefetching = isSearching ? isRefetchingSearch : isRefetchingGet;
|
||||
const refetchAPIKeys = isSearching ? refetchSearchAPIKeys : refetchGetAPIKeys;
|
||||
const error = isSearching ? searchError : getError;
|
||||
const isError = isSearching ? isSearchError : isGetError;
|
||||
|
||||
useEffect(() => {
|
||||
setActiveAPIKey(IngestionKeys?.data.data[0]);
|
||||
}, [IngestionKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
setDataSource(IngestionKeys?.data.data || []);
|
||||
setTotalIngestionKeys(IngestionKeys?.data?._pagination?.total || 0);
|
||||
setDataSource(ingestionKeys?.data.data?.keys || []);
|
||||
setTotalIngestionKeys(ingestionKeys?.data?.data?._pagination?.total || 0);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [IngestionKeys?.data?.data]);
|
||||
}, [ingestionKeys?.data?.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
@@ -297,6 +357,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
const clearSearch = (): void => {
|
||||
setSearchValue('');
|
||||
setSearchText('');
|
||||
};
|
||||
|
||||
const {
|
||||
@@ -309,101 +370,54 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const {
|
||||
mutate: createIngestionKey,
|
||||
isLoading: isLoadingCreateAPIKey,
|
||||
} = useMutation(createIngestionKeyApi, {
|
||||
onSuccess: (data) => {
|
||||
setActiveAPIKey(data.payload);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
});
|
||||
} = useCreateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const { mutate: updateAPIKey, isLoading: isLoadingUpdateAPIKey } = useMutation(
|
||||
updateIngestionKey,
|
||||
{
|
||||
onSuccess: () => {
|
||||
refetchAPIKeys();
|
||||
setIsEditModalOpen(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
const {
|
||||
mutate: updateAPIKey,
|
||||
isLoading: isLoadingUpdateAPIKey,
|
||||
} = useUpdateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const { mutate: deleteAPIKey, isLoading: isDeleteingAPIKey } = useMutation(
|
||||
deleteIngestionKey,
|
||||
{
|
||||
onSuccess: () => {
|
||||
refetchAPIKeys();
|
||||
setIsDeleteModalOpen(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
const {
|
||||
mutate: deleteAPIKey,
|
||||
isLoading: isDeleteingAPIKey,
|
||||
} = useDeleteIngestionKey<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const {
|
||||
mutate: createLimitForIngestionKey,
|
||||
isLoading: isLoadingLimitForKey,
|
||||
} = useMutation(createLimitForIngestionKeyApi, {
|
||||
onSuccess: () => {
|
||||
setActiveSignal(null);
|
||||
setActiveAPIKey(null);
|
||||
setIsEditAddLimitOpen(false);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
setHasCreateLimitForIngestionKeyError(true);
|
||||
setCreateLimitForIngestionKeyError(error);
|
||||
},
|
||||
});
|
||||
} = useCreateIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const {
|
||||
mutate: updateLimitForIngestionKey,
|
||||
isLoading: isLoadingUpdatedLimitForKey,
|
||||
} = useMutation(updateLimitForIngestionKeyApi, {
|
||||
onSuccess: () => {
|
||||
setActiveSignal(null);
|
||||
setActiveAPIKey(null);
|
||||
setIsEditAddLimitOpen(false);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
},
|
||||
onError: (error: ErrorResponse) => {
|
||||
setHasUpdateLimitForIngestionKeyError(true);
|
||||
setUpdateLimitForIngestionKeyError(error);
|
||||
},
|
||||
});
|
||||
} = useUpdateIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const { mutate: deleteLimitForKey, isLoading: isDeletingLimit } = useMutation(
|
||||
deleteLimitsForIngestionKey,
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsDeleteModalOpen(false);
|
||||
setIsDeleteLimitModalOpen(false);
|
||||
refetchAPIKeys();
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
const {
|
||||
mutate: deleteLimitForKey,
|
||||
isLoading: isDeletingLimit,
|
||||
} = useDeleteIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const onDeleteHandler = (): void => {
|
||||
clearSearch();
|
||||
|
||||
if (activeAPIKey) {
|
||||
deleteAPIKey(activeAPIKey.id);
|
||||
if (activeAPIKey && activeAPIKey.id) {
|
||||
deleteAPIKey(
|
||||
{
|
||||
pathParams: { keyId: activeAPIKey.id },
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Ingestion key deleted successfully',
|
||||
});
|
||||
refetchAPIKeys();
|
||||
setIsDeleteModalOpen(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -411,15 +425,31 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
editForm
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
if (activeAPIKey) {
|
||||
updateAPIKey({
|
||||
id: activeAPIKey.id,
|
||||
data: {
|
||||
name: values.name,
|
||||
tags: updatedTags,
|
||||
expires_at: dayjs(values.expires_at).endOf('day').toISOString(),
|
||||
if (activeAPIKey && activeAPIKey.id) {
|
||||
updateAPIKey(
|
||||
{
|
||||
pathParams: { keyId: activeAPIKey.id },
|
||||
data: {
|
||||
name: values.name,
|
||||
tags: updatedTags,
|
||||
expires_at: new Date(
|
||||
dayjs(values.expires_at).endOf('day').toISOString(),
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Ingestion key updated successfully',
|
||||
});
|
||||
refetchAPIKeys();
|
||||
setIsEditModalOpen(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
@@ -435,10 +465,30 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const requestPayload = {
|
||||
name: values.name,
|
||||
tags: updatedTags,
|
||||
expires_at: dayjs(values.expires_at).endOf('day').toISOString(),
|
||||
expires_at: new Date(dayjs(values.expires_at).endOf('day').toISOString()),
|
||||
};
|
||||
|
||||
createIngestionKey(requestPayload);
|
||||
createIngestionKey(
|
||||
{
|
||||
data: requestPayload,
|
||||
},
|
||||
{
|
||||
onSuccess: (_data) => {
|
||||
notifications.success({
|
||||
message: 'Ingestion key created successfully',
|
||||
});
|
||||
// The new API returns GatewaytypesGettableCreatedIngestionKeyDTO with only id and value
|
||||
// We rely on refetchAPIKeys to get the full key object
|
||||
setActiveAPIKey(null);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
@@ -465,7 +515,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
formatTimezoneAdjustedTimestamp(date, DATE_TIME_FORMATS.UTC_MONTH_COMPACT);
|
||||
|
||||
const showDeleteLimitModal = (
|
||||
APIKey: IngestionKeyProps,
|
||||
APIKey: GatewaytypesIngestionKeyDTO,
|
||||
limit: LimitProps,
|
||||
): void => {
|
||||
setActiveAPIKey(APIKey);
|
||||
@@ -489,9 +539,17 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
const handleAddLimit = (
|
||||
APIKey: IngestionKeyProps,
|
||||
APIKey: GatewaytypesIngestionKeyDTO,
|
||||
signalName: string,
|
||||
): void => {
|
||||
if (!APIKey.id) {
|
||||
notifications.error({
|
||||
message: 'Invalid ingestion key',
|
||||
description: 'Cannot create limit for ingestion key without a valid ID',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
dailyLimit,
|
||||
secondsLimit,
|
||||
@@ -576,13 +634,49 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
createLimitForIngestionKey(payload);
|
||||
createLimitForIngestionKey(
|
||||
{
|
||||
pathParams: { keyId: payload.keyID },
|
||||
data: {
|
||||
signal: payload.signal,
|
||||
config: payload.config,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Limit created successfully',
|
||||
});
|
||||
setActiveSignal(null);
|
||||
setActiveAPIKey(null);
|
||||
setIsEditAddLimitOpen(false);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
},
|
||||
onError: (error: AxiosError<RenderErrorResponseDTO>) => {
|
||||
setHasCreateLimitForIngestionKeyError(true);
|
||||
setCreateLimitForIngestionKeyError(
|
||||
error.response?.data?.error?.message || 'Failed to create limit',
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleUpdateLimit = (
|
||||
APIKey: IngestionKeyProps,
|
||||
APIKey: GatewaytypesIngestionKeyDTO,
|
||||
signal: LimitProps,
|
||||
): void => {
|
||||
if (!signal.id) {
|
||||
notifications.error({
|
||||
message: 'Invalid limit',
|
||||
description: 'Cannot update limit without a valid ID',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
dailyLimit,
|
||||
secondsLimit,
|
||||
@@ -644,7 +738,34 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
updateLimitForIngestionKey(payload);
|
||||
updateLimitForIngestionKey(
|
||||
{
|
||||
pathParams: { limitId: payload.limitID },
|
||||
data: {
|
||||
config: payload.config,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Limit updated successfully',
|
||||
});
|
||||
setActiveSignal(null);
|
||||
setActiveAPIKey(null);
|
||||
setIsEditAddLimitOpen(false);
|
||||
setUpdatedTags([]);
|
||||
hideAddViewModal();
|
||||
refetchAPIKeys();
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
},
|
||||
onError: (error: AxiosError<RenderErrorResponseDTO>) => {
|
||||
setHasUpdateLimitForIngestionKeyError(true);
|
||||
setUpdateLimitForIngestionKeyError(
|
||||
error.response?.data?.error?.message || 'Failed to update limit',
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
/* eslint-enable sonarjs/cognitive-complexity */
|
||||
|
||||
@@ -656,7 +777,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
};
|
||||
|
||||
const enableEditLimitMode = (
|
||||
APIKey: IngestionKeyProps,
|
||||
APIKey: GatewaytypesIngestionKeyDTO,
|
||||
signal: LimitProps,
|
||||
): void => {
|
||||
const dayCount = signal?.config?.day?.count;
|
||||
@@ -665,6 +786,11 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const dayCountConverted = countToUnit(dayCount || 0);
|
||||
const secondCountConverted = countToUnit(secondCount || 0);
|
||||
|
||||
setHasCreateLimitForIngestionKeyError(false);
|
||||
setCreateLimitForIngestionKeyError(null);
|
||||
setHasUpdateLimitForIngestionKeyError(false);
|
||||
setUpdateLimitForIngestionKeyError(null);
|
||||
|
||||
setActiveAPIKey(APIKey);
|
||||
setActiveSignal({
|
||||
...signal,
|
||||
@@ -703,14 +829,31 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
const onDeleteLimitHandler = (): void => {
|
||||
if (activeSignal && activeSignal.id) {
|
||||
deleteLimitForKey(activeSignal.id);
|
||||
deleteLimitForKey(
|
||||
{
|
||||
pathParams: { limitId: activeSignal.id },
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Limit deleted successfully',
|
||||
});
|
||||
setIsDeleteModalOpen(false);
|
||||
setIsDeleteLimitModalOpen(false);
|
||||
refetchAPIKeys();
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const handleCreateAlert = (
|
||||
APIKey: IngestionKeyProps,
|
||||
APIKey: GatewaytypesIngestionKeyDTO,
|
||||
signal: LimitProps,
|
||||
): void => {
|
||||
let metricName = '';
|
||||
@@ -771,31 +914,61 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
history.push(URL);
|
||||
};
|
||||
|
||||
const columns: AntDTableProps<IngestionKeyProps>['columns'] = [
|
||||
const columns: AntDTableProps<GatewaytypesIngestionKeyDTO>['columns'] = [
|
||||
{
|
||||
title: 'Ingestion Key',
|
||||
key: 'ingestion-key',
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
render: (APIKey: IngestionKeyProps): JSX.Element => {
|
||||
const createdOn = getFormattedTime(
|
||||
APIKey.created_at,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
render: (APIKey: GatewaytypesIngestionKeyDTO): JSX.Element => {
|
||||
const createdOn = APIKey?.created_at
|
||||
? getFormattedTime(
|
||||
dayjs(APIKey.created_at).toISOString(),
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
)
|
||||
: '';
|
||||
|
||||
const expiresOn =
|
||||
!APIKey?.expires_at || APIKey?.expires_at === '0001-01-01T00:00:00Z'
|
||||
!APIKey?.expires_at ||
|
||||
dayjs(APIKey?.expires_at).toISOString() === '0001-01-01T00:00:00.000Z'
|
||||
? 'No Expiry'
|
||||
: getFormattedTime(APIKey?.expires_at, formatTimezoneAdjustedTimestamp);
|
||||
: getFormattedTime(
|
||||
dayjs(APIKey?.expires_at).toISOString(),
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
|
||||
const updatedOn = getFormattedTime(
|
||||
APIKey?.updated_at,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const updatedOn = APIKey?.updated_at
|
||||
? getFormattedTime(
|
||||
dayjs(APIKey.updated_at).toISOString(),
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
)
|
||||
: '';
|
||||
|
||||
const onCopyKey = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (APIKey?.value) {
|
||||
handleCopyKey(APIKey.value);
|
||||
}
|
||||
};
|
||||
|
||||
const onEditKey = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showEditModal(APIKey);
|
||||
};
|
||||
|
||||
const onDeleteKey = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showDeleteModal(APIKey);
|
||||
};
|
||||
|
||||
// Convert array of limits to a dictionary for quick access
|
||||
const limitsDict: Record<string, LimitProps> = {};
|
||||
APIKey.limits?.forEach((limitItem: LimitProps) => {
|
||||
limitsDict[limitItem.signal] = limitItem;
|
||||
APIKey.limits?.forEach((limitItem) => {
|
||||
if (limitItem.signal && limitItem.id) {
|
||||
limitsDict[limitItem.signal] = limitItem as LimitProps;
|
||||
}
|
||||
});
|
||||
|
||||
const hasLimits = (signalName: string): boolean => !!limitsDict[signalName];
|
||||
@@ -812,39 +985,25 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
<div className="ingestion-key-value">
|
||||
<Typography.Text>
|
||||
{APIKey?.value.substring(0, 2)}********
|
||||
{APIKey?.value.substring(APIKey.value.length - 2).trim()}
|
||||
{APIKey?.value?.substring(0, 2)}********
|
||||
{APIKey?.value
|
||||
?.substring(APIKey?.value?.length ? APIKey.value.length - 2 : 0)
|
||||
?.trim()}
|
||||
</Typography.Text>
|
||||
|
||||
<Copy
|
||||
className="copy-key-btn"
|
||||
size={12}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleCopyKey(APIKey.value);
|
||||
}}
|
||||
/>
|
||||
<Copy className="copy-key-btn" size={12} onClick={onCopyKey} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="action-btn">
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
icon={<PenLine size={14} />}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showEditModal(APIKey);
|
||||
}}
|
||||
onClick={onEditKey}
|
||||
/>
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showDeleteModal(APIKey);
|
||||
}}
|
||||
onClick={onDeleteKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -854,7 +1013,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Row>
|
||||
<Col span={6}> ID </Col>
|
||||
<Col span={12}>
|
||||
<Typography.Text>{APIKey.id}</Typography.Text>
|
||||
<Typography.Text>{APIKey?.id}</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -906,6 +1065,39 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
limit?.config?.second?.size !== undefined ||
|
||||
limit?.config?.second?.count !== undefined;
|
||||
|
||||
const onEditSignalLimit = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
enableEditLimitMode(APIKey, limit);
|
||||
};
|
||||
|
||||
const onDeleteSignalLimit = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showDeleteLimitModal(APIKey, limit);
|
||||
};
|
||||
|
||||
const onAddSignalLimit = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
enableEditLimitMode(APIKey, {
|
||||
id: signalName,
|
||||
signal: signalName,
|
||||
config: {},
|
||||
});
|
||||
};
|
||||
|
||||
const onSaveSignalLimit = (): void => {
|
||||
if (!hasLimits(signalName)) {
|
||||
handleAddLimit(APIKey, signalName);
|
||||
} else {
|
||||
handleUpdateLimit(APIKey, limitsDict[signalName]);
|
||||
}
|
||||
};
|
||||
|
||||
const onCreateSignalAlert = (): void =>
|
||||
handleCreateAlert(APIKey, limitsDict[signalName]);
|
||||
|
||||
return (
|
||||
<div className="signal" key={signalName}>
|
||||
<div className="header">
|
||||
@@ -916,22 +1108,18 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
icon={<PenLine size={14} />}
|
||||
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
enableEditLimitMode(APIKey, limit);
|
||||
}}
|
||||
disabled={
|
||||
!!(activeAPIKey?.id === APIKey?.id && activeSignal)
|
||||
}
|
||||
onClick={onEditSignalLimit}
|
||||
/>
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
||||
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showDeleteLimitModal(APIKey, limit);
|
||||
}}
|
||||
disabled={
|
||||
!!(activeAPIKey?.id === APIKey?.id && activeSignal)
|
||||
}
|
||||
onClick={onDeleteSignalLimit}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -940,16 +1128,8 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
size="small"
|
||||
shape="round"
|
||||
icon={<PlusIcon size={14} />}
|
||||
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
enableEditLimitMode(APIKey, {
|
||||
id: signalName,
|
||||
signal: signalName,
|
||||
config: {},
|
||||
});
|
||||
}}
|
||||
disabled={!!(activeAPIKey?.id === APIKey?.id && activeSignal)}
|
||||
onClick={onAddSignalLimit}
|
||||
>
|
||||
Limits
|
||||
</Button>
|
||||
@@ -958,7 +1138,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="signal-limit-values">
|
||||
{activeAPIKey?.id === APIKey.id &&
|
||||
{activeAPIKey?.id === APIKey?.id &&
|
||||
activeSignal?.signal === signalName &&
|
||||
isEditAddLimitOpen ? (
|
||||
<Form
|
||||
@@ -1154,27 +1334,27 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeAPIKey?.id === APIKey.id &&
|
||||
{activeAPIKey?.id === APIKey?.id &&
|
||||
activeSignal.signal === signalName &&
|
||||
!isLoadingLimitForKey &&
|
||||
hasCreateLimitForIngestionKeyError &&
|
||||
createLimitForIngestionKeyError?.error && (
|
||||
createLimitForIngestionKeyError && (
|
||||
<div className="error">
|
||||
{createLimitForIngestionKeyError?.error}
|
||||
{createLimitForIngestionKeyError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeAPIKey?.id === APIKey.id &&
|
||||
{activeAPIKey?.id === APIKey?.id &&
|
||||
activeSignal.signal === signalName &&
|
||||
!isLoadingLimitForKey &&
|
||||
hasUpdateLimitForIngestionKeyError &&
|
||||
updateLimitForIngestionKeyError?.error && (
|
||||
updateLimitForIngestionKeyError && (
|
||||
<div className="error">
|
||||
{updateLimitForIngestionKeyError?.error}
|
||||
{updateLimitForIngestionKeyError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeAPIKey?.id === APIKey.id &&
|
||||
{activeAPIKey?.id === APIKey?.id &&
|
||||
activeSignal.signal === signalName &&
|
||||
isEditAddLimitOpen && (
|
||||
<div className="signal-limit-save-discard">
|
||||
@@ -1188,13 +1368,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
loading={
|
||||
isLoadingLimitForKey || isLoadingUpdatedLimitForKey
|
||||
}
|
||||
onClick={(): void => {
|
||||
if (!hasLimits(signalName)) {
|
||||
handleAddLimit(APIKey, signalName);
|
||||
} else {
|
||||
handleUpdateLimit(APIKey, limitsDict[signalName]);
|
||||
}
|
||||
}}
|
||||
onClick={onSaveSignalLimit}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
@@ -1275,9 +1449,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
className="set-alert-btn periscope-btn ghost"
|
||||
type="text"
|
||||
data-testid={`set-alert-btn-${signalName}`}
|
||||
onClick={(): void =>
|
||||
handleCreateAlert(APIKey, limitsDict[signalName])
|
||||
}
|
||||
onClick={onCreateSignalAlert}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -1392,7 +1564,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
const handleTableChange = (pagination: TablePaginationConfig): void => {
|
||||
setPaginationParams({
|
||||
page: pagination?.current || 1,
|
||||
per_page: 10,
|
||||
per_page: INITIAL_PAGE_SIZE,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1490,7 +1662,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
showHeader={false}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
pageSize: paginationParams?.per_page,
|
||||
pageSize: isSearching ? SEARCH_PAGE_SIZE : paginationParams?.per_page,
|
||||
hideOnSinglePage: true,
|
||||
showTotal: (total: number, range: number[]): string =>
|
||||
`${range[0]}-${range[1]} of ${total} Ingestion keys`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { GatewaytypesGettableIngestionKeysDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
@@ -18,6 +19,12 @@ interface TestAllIngestionKeyProps extends Omit<AllIngestionKeyProps, 'data'> {
|
||||
data: TestIngestionKeyProps[];
|
||||
}
|
||||
|
||||
// Gateway API response type (uses actual schema types for contract safety)
|
||||
interface TestGatewayIngestionKeysResponse {
|
||||
status: string;
|
||||
data: GatewaytypesGettableIngestionKeysDTO;
|
||||
}
|
||||
|
||||
// Mock useHistory.push to capture navigation URL used by MultiIngestionSettings
|
||||
const mockPush = jest.fn() as jest.MockedFunction<(path: string) => void>;
|
||||
jest.mock('react-router-dom', () => {
|
||||
@@ -86,32 +93,34 @@ describe('MultiIngestionSettings Page', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
// Arrange API response with a metrics daily count limit so the alert button is visible
|
||||
const response: TestAllIngestionKeyProps = {
|
||||
const response: TestGatewayIngestionKeysResponse = {
|
||||
status: 'success',
|
||||
data: [
|
||||
{
|
||||
name: 'Key One',
|
||||
expires_at: TEST_EXPIRES_AT,
|
||||
value: 'secret',
|
||||
workspace_id: TEST_WORKSPACE_ID,
|
||||
id: 'k1',
|
||||
created_at: TEST_CREATED_UPDATED,
|
||||
updated_at: TEST_CREATED_UPDATED,
|
||||
tags: [],
|
||||
limits: [
|
||||
{
|
||||
id: 'l1',
|
||||
signal: 'metrics',
|
||||
config: { day: { count: 1000 } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
|
||||
data: {
|
||||
keys: [
|
||||
{
|
||||
name: 'Key One',
|
||||
expires_at: new Date(TEST_EXPIRES_AT),
|
||||
value: 'secret',
|
||||
workspace_id: TEST_WORKSPACE_ID,
|
||||
id: 'k1',
|
||||
created_at: new Date(TEST_CREATED_UPDATED),
|
||||
updated_at: new Date(TEST_CREATED_UPDATED),
|
||||
tags: [],
|
||||
limits: [
|
||||
{
|
||||
id: 'l1',
|
||||
signal: 'metrics',
|
||||
config: { day: { count: 1000 } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
server.use(
|
||||
rest.get('*/workspaces/me/keys*', (_req, res, ctx) =>
|
||||
rest.get('*/api/v2/gateway/ingestion_keys*', (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(response)),
|
||||
),
|
||||
);
|
||||
@@ -257,4 +266,95 @@ describe('MultiIngestionSettings Page', () => {
|
||||
'signoz.meter.log.size',
|
||||
);
|
||||
});
|
||||
|
||||
it('switches to search API when search text is entered', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
const getResponse: TestGatewayIngestionKeysResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
keys: [
|
||||
{
|
||||
name: 'Key Regular',
|
||||
expires_at: new Date(TEST_EXPIRES_AT),
|
||||
value: 'secret1',
|
||||
workspace_id: TEST_WORKSPACE_ID,
|
||||
id: 'k1',
|
||||
created_at: new Date(TEST_CREATED_UPDATED),
|
||||
updated_at: new Date(TEST_CREATED_UPDATED),
|
||||
tags: [],
|
||||
limits: [],
|
||||
},
|
||||
],
|
||||
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
const searchResponse: TestGatewayIngestionKeysResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
keys: [
|
||||
{
|
||||
name: 'Key Search Result',
|
||||
expires_at: new Date(TEST_EXPIRES_AT),
|
||||
value: 'secret2',
|
||||
workspace_id: TEST_WORKSPACE_ID,
|
||||
id: 'k2',
|
||||
created_at: new Date(TEST_CREATED_UPDATED),
|
||||
updated_at: new Date(TEST_CREATED_UPDATED),
|
||||
tags: [],
|
||||
limits: [],
|
||||
},
|
||||
],
|
||||
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
const getHandler = jest.fn();
|
||||
const searchHandler = jest.fn();
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/v2/gateway/ingestion_keys', (req, res, ctx) => {
|
||||
if (req.url.pathname.endsWith('/search')) {
|
||||
return undefined;
|
||||
}
|
||||
getHandler();
|
||||
return res(ctx.status(200), ctx.json(getResponse));
|
||||
}),
|
||||
rest.get('*/api/v2/gateway/ingestion_keys/search', (_req, res, ctx) => {
|
||||
searchHandler();
|
||||
return res(ctx.status(200), ctx.json(searchResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(<MultiIngestionSettings />, undefined, {
|
||||
initialRoute: INGESTION_SETTINGS_ROUTE,
|
||||
});
|
||||
|
||||
await screen.findByText('Key Regular');
|
||||
expect(getHandler).toHaveBeenCalled();
|
||||
expect(searchHandler).not.toHaveBeenCalled();
|
||||
|
||||
// Reset getHandler count to verify it's not called again during search
|
||||
getHandler.mockClear();
|
||||
|
||||
// Type in search box
|
||||
const searchInput = screen.getByPlaceholderText(
|
||||
'Search for ingestion key...',
|
||||
);
|
||||
await user.type(searchInput, 'test');
|
||||
|
||||
await screen.findByText('Key Search Result');
|
||||
expect(searchHandler).toHaveBeenCalled();
|
||||
expect(getHandler).not.toHaveBeenCalled();
|
||||
|
||||
// Clear search
|
||||
searchHandler.mockClear();
|
||||
getHandler.mockClear();
|
||||
await user.clear(searchInput);
|
||||
|
||||
await screen.findByText('Key Regular');
|
||||
// Search API should be disabled when not searching
|
||||
expect(searchHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 0px;
|
||||
.group-by-clause {
|
||||
.more-filter-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.group-by-clause:hover {
|
||||
.more-filter-actions:hover {
|
||||
background-color: unset !important;
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
border: 1px solid var(--bg-vanilla-400);
|
||||
background: var(--bg-vanilla-100) !important;
|
||||
|
||||
.group-by-clause {
|
||||
.more-filter-actions {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
@@ -16,7 +17,12 @@ import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
|
||||
import {
|
||||
ArrowDownToDot,
|
||||
ArrowUpFromDot,
|
||||
Ellipsis,
|
||||
RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import { ExplorerViews } from 'pages/LogsExplorer/utils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
@@ -205,6 +211,70 @@ export default function TableViewActions(
|
||||
viewName,
|
||||
]);
|
||||
|
||||
const handleReplaceFilter = useCallback((): void => {
|
||||
if (!stagedQuery) {
|
||||
return;
|
||||
}
|
||||
const normalizedDataType: DataTypes | undefined =
|
||||
dataType && Object.values(DataTypes).includes(dataType as DataTypes)
|
||||
? (dataType as DataTypes)
|
||||
: undefined;
|
||||
|
||||
const updatedQuery = updateQueriesData(
|
||||
stagedQuery,
|
||||
'queryData',
|
||||
(item, index) => {
|
||||
// Only replace filters for index 0
|
||||
if (index === 0) {
|
||||
const newFilterItem: BaseAutocompleteData = {
|
||||
key: fieldFilterKey,
|
||||
type: fieldType || '',
|
||||
dataType: normalizedDataType,
|
||||
};
|
||||
|
||||
// Create new filter items array with single IN filter
|
||||
const newFilters = {
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
key: newFilterItem,
|
||||
op: OPERATORS.IN,
|
||||
value: [parseFieldValue(fieldData.value)],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
// Clear the expression and update filters
|
||||
return {
|
||||
...item,
|
||||
filters: newFilters,
|
||||
filter: { expression: '' },
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
},
|
||||
);
|
||||
|
||||
const queryData: ICurrentQueryData = {
|
||||
name: viewName,
|
||||
id: updatedQuery.id,
|
||||
query: updatedQuery,
|
||||
};
|
||||
|
||||
handleChangeSelectedView?.(ExplorerViews.LIST, queryData);
|
||||
}, [
|
||||
stagedQuery,
|
||||
updateQueriesData,
|
||||
fieldFilterKey,
|
||||
fieldType,
|
||||
dataType,
|
||||
fieldData,
|
||||
handleChangeSelectedView,
|
||||
viewName,
|
||||
]);
|
||||
|
||||
// Memoize textToCopy computation
|
||||
const textToCopy = useMemo(() => {
|
||||
let text = fieldData.value;
|
||||
@@ -327,13 +397,21 @@ export default function TableViewActions(
|
||||
content={
|
||||
<div>
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
className="more-filter-actions"
|
||||
type="text"
|
||||
icon={<GroupByIcon />}
|
||||
onClick={handleGroupByAttribute}
|
||||
>
|
||||
Group By Attribute
|
||||
</Button>
|
||||
<Button
|
||||
className="more-filter-actions"
|
||||
type="text"
|
||||
icon={<RefreshCw size={14} />}
|
||||
onClick={handleReplaceFilter}
|
||||
>
|
||||
Replace filters with this value
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
rootClassName="table-view-actions-content"
|
||||
@@ -405,13 +483,21 @@ export default function TableViewActions(
|
||||
content={
|
||||
<div>
|
||||
<Button
|
||||
className="group-by-clause"
|
||||
className="more-filter-actions"
|
||||
type="text"
|
||||
icon={<GroupByIcon />}
|
||||
onClick={handleGroupByAttribute}
|
||||
>
|
||||
Group By Attribute
|
||||
</Button>
|
||||
<Button
|
||||
className="more-filter-actions"
|
||||
type="text"
|
||||
icon={<RefreshCw size={14} />}
|
||||
onClick={handleReplaceFilter}
|
||||
>
|
||||
Replace filters with this value
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
rootClassName="table-view-actions-content"
|
||||
|
||||
@@ -407,6 +407,10 @@
|
||||
color: var(--text-neutral-light-200) !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--text-ink-500) !important;
|
||||
}
|
||||
|
||||
&:hover .ant-select-selector {
|
||||
border-color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Form, Input, Select, Tooltip, Typography } from 'antd';
|
||||
import { Form, Input, Select, Typography } from 'antd';
|
||||
import getVersion from 'api/v1/version/get';
|
||||
import get from 'api/v2/sessions/context/get';
|
||||
import post from 'api/v2/sessions/email_password/post';
|
||||
@@ -220,6 +220,20 @@ function Login(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const handleForgotPasswordClick = useCallback((): void => {
|
||||
const email = form.getFieldValue('email');
|
||||
|
||||
if (!email || !sessionsContext || !sessionsContext?.orgs?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(ROUTES.FORGOT_PASSWORD, {
|
||||
email,
|
||||
orgId: sessionsOrgId,
|
||||
orgs: sessionsContext.orgs,
|
||||
});
|
||||
}, [form, sessionsContext, sessionsOrgId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (callbackAuthError) {
|
||||
setErrorMessage(
|
||||
@@ -345,11 +359,16 @@ function Login(): JSX.Element {
|
||||
<ParentContainer>
|
||||
<div className="password-label-container">
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Tooltip title="Ask your admin to reset your password and send you a new invite link">
|
||||
<Typography.Link className="forgot-password-link">
|
||||
Forgot password?
|
||||
</Typography.Link>
|
||||
</Tooltip>
|
||||
<Typography.Link
|
||||
className="forgot-password-link"
|
||||
href="#"
|
||||
onClick={(event): void => {
|
||||
event.preventDefault();
|
||||
handleForgotPasswordClick();
|
||||
}}
|
||||
>
|
||||
Forgot password?
|
||||
</Typography.Link>
|
||||
</div>
|
||||
<FormContainer.Item name="password">
|
||||
<Input.Password
|
||||
|
||||
@@ -2,13 +2,16 @@ import { useEffect, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useGetIngestionKeys } from 'api/generated/services/gateway';
|
||||
import {
|
||||
GatewaytypesIngestionKeyDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import { DOCS_BASE_URL } from 'constants/app';
|
||||
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
|
||||
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
|
||||
import { IngestionKeyProps } from 'types/api/ingestionKeys/types';
|
||||
|
||||
import './IngestionDetails.styles.scss';
|
||||
|
||||
@@ -39,17 +42,17 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const [, handleCopyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const [firstIngestionKey, setFirstIngestionKey] = useState<IngestionKeyProps>(
|
||||
{} as IngestionKeyProps,
|
||||
);
|
||||
const [
|
||||
firstIngestionKey,
|
||||
setFirstIngestionKey,
|
||||
] = useState<GatewaytypesIngestionKeyDTO>({} as GatewaytypesIngestionKeyDTO);
|
||||
|
||||
const {
|
||||
data: ingestionKeys,
|
||||
isLoading: isIngestionKeysLoading,
|
||||
error,
|
||||
isError,
|
||||
} = useGetAllIngestionsKeys({
|
||||
search: '',
|
||||
} = useGetIngestionKeys({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
});
|
||||
@@ -69,8 +72,11 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (ingestionKeys?.data.data && ingestionKeys?.data.data.length > 0) {
|
||||
setFirstIngestionKey(ingestionKeys?.data.data[0]);
|
||||
if (
|
||||
ingestionKeys?.data?.data?.keys &&
|
||||
ingestionKeys?.data.data.keys.length > 0
|
||||
) {
|
||||
setFirstIngestionKey(ingestionKeys?.data.data.keys[0]);
|
||||
}
|
||||
}, [ingestionKeys]);
|
||||
|
||||
@@ -80,7 +86,10 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
<div className="ingestion-endpoint-section-error-container">
|
||||
<Typography.Text className="ingestion-endpoint-section-error-text error">
|
||||
<TriangleAlert size={14} />{' '}
|
||||
{(error as AxiosError)?.message || 'Something went wrong'}
|
||||
{(error as AxiosError<RenderErrorResponseDTO>)?.response?.data?.error
|
||||
?.message ||
|
||||
(error as AxiosError)?.message ||
|
||||
'Something went wrong'}
|
||||
</Typography.Text>
|
||||
|
||||
<div className="ingestion-setup-details-links">
|
||||
@@ -176,7 +185,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text className="ingestion-key-value-copy">
|
||||
{maskKey(firstIngestionKey?.value)}
|
||||
{maskKey(firstIngestionKey?.value || '')}
|
||||
|
||||
<Copy
|
||||
size={14}
|
||||
@@ -186,7 +195,9 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INGESTION_KEY_COPIED}`,
|
||||
{},
|
||||
);
|
||||
handleCopyKey(firstIngestionKey?.value);
|
||||
if (firstIngestionKey?.value) {
|
||||
handleCopyKey(firstIngestionKey.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Typography.Text>
|
||||
|
||||
@@ -32,6 +32,7 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.LOGIN]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.FORGOT_PASSWORD]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.LOGS]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.LOGS_BASE]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.MY_SETTINGS]: [QueryParams.resourceAttributes],
|
||||
|
||||
@@ -5,8 +5,8 @@ import cx from 'classnames';
|
||||
import { LegendItem } from 'lib/uPlotV2/config/types';
|
||||
import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync';
|
||||
|
||||
import { useLegendActions } from '../../hooks/useLegendActions';
|
||||
import { LegendPosition, LegendProps } from '../types';
|
||||
import { useLegendActions } from './useLegendActions';
|
||||
|
||||
import './Legend.styles.scss';
|
||||
|
||||
@@ -106,6 +106,7 @@ export default function Legend({
|
||||
placeholder="Search..."
|
||||
value={legendSearchQuery}
|
||||
onChange={(e): void => setLegendSearchQuery(e.target.value)}
|
||||
data-testid="legend-search-input"
|
||||
className="legend-search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { TimeSeriesTooltipProps } from '../types';
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
export default function TimeSeriesTooltip(
|
||||
props: TimeSeriesTooltipProps,
|
||||
): JSX.Element {
|
||||
return <Tooltip {...props} />;
|
||||
}
|
||||
@@ -5,8 +5,7 @@ import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import { TooltipContentItem, TooltipProps } from '../types';
|
||||
import { buildTooltipContent } from './utils';
|
||||
import { TooltipProps } from '../types';
|
||||
|
||||
import './Tooltip.styles.scss';
|
||||
|
||||
@@ -14,14 +13,14 @@ const TOOLTIP_LIST_MAX_HEIGHT = 330;
|
||||
const TOOLTIP_ITEM_HEIGHT = 38;
|
||||
|
||||
export default function Tooltip({
|
||||
seriesIndex,
|
||||
dataIndexes,
|
||||
uPlotInstance,
|
||||
timezone,
|
||||
yAxisUnit = '',
|
||||
decimalPrecision,
|
||||
content,
|
||||
}: TooltipProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const tooltipContent = content ?? [];
|
||||
|
||||
const headerTitle = useMemo(() => {
|
||||
const data = uPlotInstance.data;
|
||||
const cursorIdx = uPlotInstance.cursor.idx;
|
||||
@@ -33,20 +32,6 @@ export default function Tooltip({
|
||||
.format(DATE_TIME_FORMATS.MONTH_DATETIME_SECONDS);
|
||||
}, [timezone, uPlotInstance.data, uPlotInstance.cursor.idx]);
|
||||
|
||||
const content = useMemo(
|
||||
(): TooltipContentItem[] =>
|
||||
buildTooltipContent({
|
||||
data: uPlotInstance.data,
|
||||
series: uPlotInstance.series,
|
||||
dataIndexes,
|
||||
activeSeriesIndex: seriesIndex,
|
||||
uPlotInstance,
|
||||
yAxisUnit,
|
||||
decimalPrecision,
|
||||
}),
|
||||
[uPlotInstance, seriesIndex, dataIndexes, yAxisUnit, decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
@@ -60,16 +45,16 @@ export default function Tooltip({
|
||||
<div
|
||||
style={{
|
||||
height: Math.min(
|
||||
content.length * TOOLTIP_ITEM_HEIGHT,
|
||||
tooltipContent.length * TOOLTIP_ITEM_HEIGHT,
|
||||
TOOLTIP_LIST_MAX_HEIGHT,
|
||||
),
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{content.length > 0 ? (
|
||||
{tooltipContent.length > 0 ? (
|
||||
<Virtuoso
|
||||
className="uplot-tooltip-list"
|
||||
data={content}
|
||||
data={tooltipContent}
|
||||
defaultItemHeight={TOOLTIP_ITEM_HEIGHT}
|
||||
itemContent={(_, item): JSX.Element => (
|
||||
<div className="uplot-tooltip-item">
|
||||
|
||||
@@ -20,6 +20,27 @@ export function resolveSeriesColor(
|
||||
return FALLBACK_SERIES_COLOR;
|
||||
}
|
||||
|
||||
export function getTooltipBaseValue({
|
||||
data,
|
||||
index,
|
||||
dataIndex,
|
||||
isStackedBarChart,
|
||||
}: {
|
||||
data: AlignedData;
|
||||
index: number;
|
||||
dataIndex: number;
|
||||
isStackedBarChart?: boolean;
|
||||
}): number | null {
|
||||
let baseValue = data[index][dataIndex] ?? null;
|
||||
if (isStackedBarChart && index + 1 < data.length && baseValue !== null) {
|
||||
const nextValue = data[index + 1][dataIndex] ?? null;
|
||||
if (nextValue !== null) {
|
||||
baseValue = baseValue - nextValue;
|
||||
}
|
||||
}
|
||||
return baseValue;
|
||||
}
|
||||
|
||||
export function buildTooltipContent({
|
||||
data,
|
||||
series,
|
||||
@@ -28,6 +49,7 @@ export function buildTooltipContent({
|
||||
uPlotInstance,
|
||||
yAxisUnit,
|
||||
decimalPrecision,
|
||||
isStackedBarChart,
|
||||
}: {
|
||||
data: AlignedData;
|
||||
series: Series[];
|
||||
@@ -36,6 +58,7 @@ export function buildTooltipContent({
|
||||
uPlotInstance: uPlot;
|
||||
yAxisUnit: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
isStackedBarChart?: boolean;
|
||||
}): TooltipContentItem[] {
|
||||
const active: TooltipContentItem[] = [];
|
||||
const rest: TooltipContentItem[] = [];
|
||||
@@ -52,23 +75,29 @@ export function buildTooltipContent({
|
||||
continue;
|
||||
}
|
||||
|
||||
const raw = data[index]?.[dataIndex];
|
||||
const value = Number(raw);
|
||||
const displayValue = Number.isNaN(value) ? 0 : value;
|
||||
const baseValue = getTooltipBaseValue({
|
||||
data,
|
||||
index,
|
||||
dataIndex,
|
||||
isStackedBarChart,
|
||||
});
|
||||
|
||||
const isActive = index === activeSeriesIndex;
|
||||
|
||||
const item: TooltipContentItem = {
|
||||
label: String(s.label ?? ''),
|
||||
value: displayValue,
|
||||
tooltipValue: getToolTipValue(displayValue, yAxisUnit, decimalPrecision),
|
||||
color: resolveSeriesColor(s.stroke, uPlotInstance, index),
|
||||
isActive,
|
||||
};
|
||||
if (Number.isFinite(baseValue) && baseValue !== null) {
|
||||
const item: TooltipContentItem = {
|
||||
label: String(s.label ?? ''),
|
||||
value: baseValue,
|
||||
tooltipValue: getToolTipValue(baseValue, yAxisUnit, decimalPrecision),
|
||||
color: resolveSeriesColor(s.stroke, uPlotInstance, index),
|
||||
isActive,
|
||||
};
|
||||
|
||||
if (isActive) {
|
||||
active.push(item);
|
||||
} else {
|
||||
rest.push(item);
|
||||
if (isActive) {
|
||||
active.push(item);
|
||||
} else {
|
||||
rest.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
213
frontend/src/lib/uPlotV2/components/__tests__/Legend.test.tsx
Normal file
213
frontend/src/lib/uPlotV2/components/__tests__/Legend.test.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import React from 'react';
|
||||
import { render, RenderResult, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { LegendItem } from 'lib/uPlotV2/config/types';
|
||||
import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync';
|
||||
|
||||
import { useLegendActions } from '../../hooks/useLegendActions';
|
||||
import Legend from '../Legend/Legend';
|
||||
import { LegendPosition } from '../types';
|
||||
|
||||
jest.mock('react-virtuoso', () => ({
|
||||
VirtuosoGrid: ({
|
||||
data,
|
||||
itemContent,
|
||||
className,
|
||||
}: {
|
||||
data: LegendItem[];
|
||||
itemContent: (index: number, item: LegendItem) => React.ReactNode;
|
||||
className?: string;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="virtuoso-grid" className={className}>
|
||||
{data.map((item, index) => (
|
||||
<div key={item.seriesIndex ?? index} data-testid="legend-item-wrapper">
|
||||
{itemContent(index, item)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('lib/uPlotV2/hooks/useLegendsSync');
|
||||
jest.mock('lib/uPlotV2/hooks/useLegendActions');
|
||||
|
||||
const mockUseLegendsSync = useLegendsSync as jest.MockedFunction<
|
||||
typeof useLegendsSync
|
||||
>;
|
||||
const mockUseLegendActions = useLegendActions as jest.MockedFunction<
|
||||
typeof useLegendActions
|
||||
>;
|
||||
|
||||
describe('Legend', () => {
|
||||
const baseLegendItemsMap = {
|
||||
0: {
|
||||
seriesIndex: 0,
|
||||
label: 'A',
|
||||
show: true,
|
||||
color: '#ff0000',
|
||||
},
|
||||
1: {
|
||||
seriesIndex: 1,
|
||||
label: 'B',
|
||||
show: false,
|
||||
color: '#00ff00',
|
||||
},
|
||||
2: {
|
||||
seriesIndex: 2,
|
||||
label: 'C',
|
||||
show: true,
|
||||
color: '#0000ff',
|
||||
},
|
||||
};
|
||||
|
||||
let onLegendClick: jest.Mock;
|
||||
let onLegendMouseMove: jest.Mock;
|
||||
let onLegendMouseLeave: jest.Mock;
|
||||
let onFocusSeries: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
onLegendClick = jest.fn();
|
||||
onLegendMouseMove = jest.fn();
|
||||
onLegendMouseLeave = jest.fn();
|
||||
onFocusSeries = jest.fn();
|
||||
|
||||
mockUseLegendsSync.mockReturnValue({
|
||||
legendItemsMap: baseLegendItemsMap,
|
||||
focusedSeriesIndex: 1,
|
||||
setFocusedSeriesIndex: jest.fn(),
|
||||
});
|
||||
|
||||
mockUseLegendActions.mockReturnValue({
|
||||
onLegendClick,
|
||||
onLegendMouseMove,
|
||||
onLegendMouseLeave,
|
||||
onFocusSeries,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderLegend = (position?: LegendPosition): RenderResult =>
|
||||
render(
|
||||
<Legend
|
||||
position={position}
|
||||
// config is not used directly in the component, it's consumed by the mocked hook
|
||||
config={{} as any}
|
||||
/>,
|
||||
);
|
||||
|
||||
describe('layout and position', () => {
|
||||
it('renders search input when position is RIGHT', () => {
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
expect(screen.getByTestId('legend-search-input')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the marker with the correct color', () => {
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const legendMarker = document.querySelector(
|
||||
'[data-legend-item-id="0"] [data-is-legend-marker="true"]',
|
||||
) as HTMLElement;
|
||||
|
||||
expect(legendMarker).toHaveStyle({
|
||||
'border-color': '#ff0000',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render search input when position is BOTTOM (default)', () => {
|
||||
renderLegend();
|
||||
|
||||
expect(screen.queryByTestId('legend-search-input')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders all legend items in the grid by default', () => {
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
expect(screen.getByTestId('virtuoso-grid')).toBeInTheDocument();
|
||||
expect(screen.getByText('A')).toBeInTheDocument();
|
||||
expect(screen.getByText('B')).toBeInTheDocument();
|
||||
expect(screen.getByText('C')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('search behavior (RIGHT position)', () => {
|
||||
it('filters legend items based on search query (case-insensitive)', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const searchInput = screen.getByTestId('legend-search-input');
|
||||
await user.type(searchInput, 'A');
|
||||
|
||||
expect(screen.getByText('A')).toBeInTheDocument();
|
||||
expect(screen.queryByText('B')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('C')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows empty state when no legend items match the search query', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const searchInput = screen.getByTestId('legend-search-input');
|
||||
await user.type(searchInput, 'network');
|
||||
|
||||
expect(
|
||||
screen.getByText(/No series found matching "network"/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('virtuoso-grid')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not filter or show empty state when search query is empty or only whitespace', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const searchInput = screen.getByTestId('legend-search-input');
|
||||
await user.type(searchInput, ' ');
|
||||
|
||||
expect(
|
||||
screen.queryByText(/No series found matching/i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('A')).toBeInTheDocument();
|
||||
expect(screen.getByText('B')).toBeInTheDocument();
|
||||
expect(screen.getByText('C')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('legend actions', () => {
|
||||
it('calls onLegendClick when a legend item is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
await user.click(screen.getByText('A'));
|
||||
|
||||
expect(onLegendClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls mouseMove when the mouse moves over a legend item', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const legendItem = document.querySelector(
|
||||
'[data-legend-item-id="0"]',
|
||||
) as HTMLElement;
|
||||
|
||||
await user.hover(legendItem);
|
||||
|
||||
expect(onLegendMouseMove).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onLegendMouseLeave when the mouse leaves the legend container', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderLegend(LegendPosition.RIGHT);
|
||||
|
||||
const container = document.querySelector('.legend-container') as HTMLElement;
|
||||
|
||||
await user.hover(container);
|
||||
await user.unhover(container);
|
||||
|
||||
expect(onLegendMouseLeave).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -59,11 +59,22 @@ export interface TooltipRenderArgs {
|
||||
viaSync: boolean;
|
||||
}
|
||||
|
||||
export type TooltipProps = TooltipRenderArgs & {
|
||||
export interface BaseTooltipProps {
|
||||
timezone: string;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
};
|
||||
content?: TooltipContentItem[];
|
||||
}
|
||||
|
||||
export interface TimeSeriesTooltipProps
|
||||
extends BaseTooltipProps,
|
||||
TooltipRenderArgs {}
|
||||
|
||||
export interface BarTooltipProps extends BaseTooltipProps, TooltipRenderArgs {
|
||||
isStackedBarChart?: boolean;
|
||||
}
|
||||
|
||||
export type TooltipProps = TimeSeriesTooltipProps | BarTooltipProps;
|
||||
|
||||
export enum LegendPosition {
|
||||
BOTTOM = 'bottom',
|
||||
|
||||
@@ -110,6 +110,7 @@ export enum LineStyle {
|
||||
export enum DrawStyle {
|
||||
Line = 'line',
|
||||
Points = 'points',
|
||||
Bar = 'bar',
|
||||
}
|
||||
|
||||
export enum LineInterpolation {
|
||||
@@ -128,7 +129,7 @@ export enum VisibilityMode {
|
||||
export interface SeriesProps {
|
||||
scaleKey: string;
|
||||
label?: string;
|
||||
|
||||
panelType: PANEL_TYPES;
|
||||
colorMapping: Record<string, string>;
|
||||
drawStyle: DrawStyle;
|
||||
pathBuilder?: Series.PathBuilder;
|
||||
|
||||
318
frontend/src/lib/uPlotV2/context/__tests__/PlotContext.test.tsx
Normal file
318
frontend/src/lib/uPlotV2/context/__tests__/PlotContext.test.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { updateSeriesVisibilityToLocalStorage } from 'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils';
|
||||
import {
|
||||
PlotContextProvider,
|
||||
usePlotContext,
|
||||
} from 'lib/uPlotV2/context/PlotContext';
|
||||
import type uPlot from 'uplot';
|
||||
|
||||
jest.mock(
|
||||
'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils',
|
||||
() => ({
|
||||
updateSeriesVisibilityToLocalStorage: jest.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
const mockUpdateSeriesVisibilityToLocalStorage = updateSeriesVisibilityToLocalStorage as jest.MockedFunction<
|
||||
typeof updateSeriesVisibilityToLocalStorage
|
||||
>;
|
||||
|
||||
interface MockSeries extends Partial<uPlot.Series> {
|
||||
label?: string;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
const createMockPlot = (series: MockSeries[] = []): uPlot =>
|
||||
(({
|
||||
series,
|
||||
batch: jest.fn((fn: () => void) => fn()),
|
||||
setSeries: jest.fn(),
|
||||
} as unknown) as uPlot);
|
||||
|
||||
const getPlotContext = (): ReturnType<typeof usePlotContext> => {
|
||||
let ctx: ReturnType<typeof usePlotContext> | null = null;
|
||||
|
||||
const Consumer = (): JSX.Element => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
ctx = usePlotContext();
|
||||
return <div data-testid="consumer" />;
|
||||
};
|
||||
|
||||
render(
|
||||
<PlotContextProvider>
|
||||
<Consumer />
|
||||
</PlotContextProvider>,
|
||||
);
|
||||
|
||||
if (!ctx) {
|
||||
throw new Error('Context was not captured');
|
||||
}
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
describe('PlotContext', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('throws when usePlotContext is used outside provider', () => {
|
||||
const Consumer = (): JSX.Element => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
usePlotContext();
|
||||
return <div />;
|
||||
};
|
||||
|
||||
expect(() => render(<Consumer />)).toThrow(
|
||||
'Should be used inside the context',
|
||||
);
|
||||
});
|
||||
|
||||
it('syncSeriesVisibilityToLocalStorage does nothing without plot or widgetId', () => {
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.syncSeriesVisibilityToLocalStorage();
|
||||
});
|
||||
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('syncSeriesVisibilityToLocalStorage serializes series visibility to localStorage helper', () => {
|
||||
const plot = createMockPlot([
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
]);
|
||||
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-123',
|
||||
shouldSaveSelectionPreference: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.syncSeriesVisibilityToLocalStorage();
|
||||
});
|
||||
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledWith(
|
||||
'widget-123',
|
||||
[
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
describe('onToggleSeriesVisibility', () => {
|
||||
it('does nothing when plot instance is not set', () => {
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesVisibility(1);
|
||||
});
|
||||
|
||||
// No errors and no calls to localStorage helper
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('highlights a single series and saves visibility when preferences are enabled', () => {
|
||||
const series: MockSeries[] = [
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: true },
|
||||
];
|
||||
const plot = createMockPlot(series);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-visibility',
|
||||
shouldSaveSelectionPreference: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesVisibility(1);
|
||||
});
|
||||
|
||||
const setSeries = (plot.setSeries as jest.Mock).mock.calls;
|
||||
|
||||
// index 0 is skipped, so we expect calls for 1 and 2
|
||||
expect(setSeries).toEqual([
|
||||
[1, { show: true }],
|
||||
[2, { show: false }],
|
||||
]);
|
||||
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledWith(
|
||||
'widget-visibility',
|
||||
[
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: true },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('resets visibility for all series when toggling the same index again', () => {
|
||||
const series: MockSeries[] = [
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: true },
|
||||
];
|
||||
const plot = createMockPlot(series);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-reset',
|
||||
shouldSaveSelectionPreference: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesVisibility(1);
|
||||
});
|
||||
|
||||
(plot.setSeries as jest.Mock).mockClear();
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesVisibility(1);
|
||||
});
|
||||
|
||||
const setSeries = (plot.setSeries as jest.Mock).mock.calls;
|
||||
|
||||
// After reset, all non-zero series should be shown
|
||||
expect(setSeries).toEqual([
|
||||
[1, { show: true }],
|
||||
[2, { show: true }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onToggleSeriesOnOff', () => {
|
||||
it('does nothing when plot instance is not set', () => {
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesOnOff(1);
|
||||
});
|
||||
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggles series show flag and saves visibility when preferences are enabled', () => {
|
||||
const series: MockSeries[] = [
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
];
|
||||
const plot = createMockPlot(series);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-toggle',
|
||||
shouldSaveSelectionPreference: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesOnOff(1);
|
||||
});
|
||||
|
||||
expect(plot.setSeries).toHaveBeenCalledWith(1, { show: false });
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).toHaveBeenCalledWith(
|
||||
'widget-toggle',
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not toggle when target series does not exist', () => {
|
||||
const series: MockSeries[] = [{ label: 'x-axis', show: true }];
|
||||
const plot = createMockPlot(series);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-missing-series',
|
||||
shouldSaveSelectionPreference: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesOnOff(5);
|
||||
});
|
||||
|
||||
expect(plot.setSeries).not.toHaveBeenCalled();
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not persist visibility when preferences flag is disabled', () => {
|
||||
const series: MockSeries[] = [
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
];
|
||||
const plot = createMockPlot(series);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-no-persist',
|
||||
shouldSaveSelectionPreference: false,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onToggleSeriesOnOff(1);
|
||||
});
|
||||
|
||||
expect(plot.setSeries).toHaveBeenCalledWith(1, { show: false });
|
||||
expect(mockUpdateSeriesVisibilityToLocalStorage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onFocusSeries', () => {
|
||||
it('does nothing when plot instance is not set', () => {
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.onFocusSeries(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets focus on the given series index', () => {
|
||||
const plot = createMockPlot([
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: true },
|
||||
]);
|
||||
const ctx = getPlotContext();
|
||||
|
||||
act(() => {
|
||||
ctx.setPlotContextInitialState({
|
||||
uPlotInstance: plot,
|
||||
widgetId: 'widget-focus',
|
||||
shouldSaveSelectionPreference: false,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
ctx.onFocusSeries(1);
|
||||
});
|
||||
|
||||
expect(plot.setSeries).toHaveBeenCalledWith(1, { focus: true }, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
|
||||
import { usePlotContext } from 'lib/uPlotV2/context/PlotContext';
|
||||
import { useLegendActions } from 'lib/uPlotV2/hooks/useLegendActions';
|
||||
|
||||
jest.mock('lib/uPlotV2/context/PlotContext');
|
||||
|
||||
const mockUsePlotContext = usePlotContext as jest.MockedFunction<
|
||||
typeof usePlotContext
|
||||
>;
|
||||
|
||||
describe('useLegendActions', () => {
|
||||
let onToggleSeriesVisibility: jest.Mock;
|
||||
let onToggleSeriesOnOff: jest.Mock;
|
||||
let onFocusSeriesPlot: jest.Mock;
|
||||
let setPlotContextInitialState: jest.Mock;
|
||||
let syncSeriesVisibilityToLocalStorage: jest.Mock;
|
||||
let setFocusedSeriesIndexMock: jest.Mock;
|
||||
let cancelAnimationFrameMock: jest.Mock;
|
||||
let user: UserEvent;
|
||||
|
||||
beforeAll(() => {
|
||||
user = userEvent.setup();
|
||||
cancelAnimationFrameMock = jest.fn();
|
||||
(global as any).cancelAnimationFrame = cancelAnimationFrameMock;
|
||||
(global as any).requestAnimationFrame = (
|
||||
cb: FrameRequestCallback,
|
||||
): number => {
|
||||
cb(0);
|
||||
return 1;
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
onToggleSeriesVisibility = jest.fn();
|
||||
onToggleSeriesOnOff = jest.fn();
|
||||
onFocusSeriesPlot = jest.fn();
|
||||
setPlotContextInitialState = jest.fn();
|
||||
syncSeriesVisibilityToLocalStorage = jest.fn();
|
||||
setFocusedSeriesIndexMock = jest.fn();
|
||||
|
||||
mockUsePlotContext.mockReturnValue({
|
||||
onToggleSeriesVisibility,
|
||||
onToggleSeriesOnOff,
|
||||
onFocusSeries: onFocusSeriesPlot,
|
||||
setPlotContextInitialState,
|
||||
syncSeriesVisibilityToLocalStorage,
|
||||
});
|
||||
|
||||
cancelAnimationFrameMock.mockClear();
|
||||
});
|
||||
|
||||
const TestLegendActionsComponent = ({
|
||||
focusedSeriesIndex,
|
||||
}: {
|
||||
focusedSeriesIndex: number | null;
|
||||
}): JSX.Element => {
|
||||
const {
|
||||
onLegendClick,
|
||||
onLegendMouseMove,
|
||||
onLegendMouseLeave,
|
||||
} = useLegendActions({
|
||||
setFocusedSeriesIndex: setFocusedSeriesIndexMock,
|
||||
focusedSeriesIndex,
|
||||
});
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
'data-testid': 'legend-container',
|
||||
onClick: onLegendClick,
|
||||
onMouseMove: onLegendMouseMove,
|
||||
onMouseLeave: onLegendMouseLeave,
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ 'data-testid': 'no-legend-target' },
|
||||
'No legend',
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ 'data-legend-item-id': '0' },
|
||||
React.createElement('div', {
|
||||
'data-testid': 'marker-0',
|
||||
'data-is-legend-marker': 'true',
|
||||
} as any),
|
||||
React.createElement('div', { 'data-testid': 'label-0' }, 'Series 0'),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ 'data-legend-item-id': '1' },
|
||||
React.createElement('div', {
|
||||
'data-testid': 'marker-1',
|
||||
'data-is-legend-marker': 'true',
|
||||
} as any),
|
||||
React.createElement('div', { 'data-testid': 'label-1' }, 'Series 1'),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
describe('onLegendClick', () => {
|
||||
it('toggles series visibility when clicking on legend label', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('label-0'));
|
||||
|
||||
expect(onToggleSeriesVisibility).toHaveBeenCalledTimes(1);
|
||||
expect(onToggleSeriesVisibility).toHaveBeenCalledWith(0);
|
||||
expect(onToggleSeriesOnOff).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggles series on/off when clicking on marker', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('marker-0'));
|
||||
|
||||
expect(onToggleSeriesOnOff).toHaveBeenCalledTimes(1);
|
||||
expect(onToggleSeriesOnOff).toHaveBeenCalledWith(0);
|
||||
expect(onToggleSeriesVisibility).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing when click target is not inside a legend item', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('no-legend-target'));
|
||||
|
||||
expect(onToggleSeriesOnOff).not.toHaveBeenCalled();
|
||||
expect(onToggleSeriesVisibility).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onFocusSeries', () => {
|
||||
it('schedules focus update and calls plot focus handler via mouse move', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.hover(screen.getByTestId('label-0'));
|
||||
|
||||
expect(setFocusedSeriesIndexMock).toHaveBeenCalledWith(0);
|
||||
expect(onFocusSeriesPlot).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it('cancels previous animation frame before scheduling new one on subsequent mouse moves', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.hover(screen.getByTestId('label-0'));
|
||||
await user.hover(screen.getByTestId('label-1'));
|
||||
|
||||
expect(cancelAnimationFrameMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onLegendMouseMove', () => {
|
||||
it('focuses new series when hovering over different legend item', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.hover(screen.getByTestId('label-1'));
|
||||
|
||||
expect(setFocusedSeriesIndexMock).toHaveBeenCalledWith(1);
|
||||
expect(onFocusSeriesPlot).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('does nothing when hovering over already focused series', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.hover(screen.getByTestId('label-1'));
|
||||
|
||||
expect(setFocusedSeriesIndexMock).not.toHaveBeenCalled();
|
||||
expect(onFocusSeriesPlot).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onLegendMouseLeave', () => {
|
||||
it('cancels pending animation frame and clears focus state', async () => {
|
||||
render(
|
||||
React.createElement(TestLegendActionsComponent, {
|
||||
focusedSeriesIndex: null,
|
||||
}),
|
||||
);
|
||||
|
||||
await user.hover(screen.getByTestId('label-0'));
|
||||
await user.unhover(screen.getByTestId('legend-container'));
|
||||
|
||||
expect(cancelAnimationFrameMock).toHaveBeenCalled();
|
||||
expect(setFocusedSeriesIndexMock).toHaveBeenCalledWith(null);
|
||||
expect(onFocusSeriesPlot).toHaveBeenCalledWith(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
235
frontend/src/lib/uPlotV2/hooks/__tests__/useLegendsSync.test.ts
Normal file
235
frontend/src/lib/uPlotV2/hooks/__tests__/useLegendsSync.test.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { act, cleanup, render } from '@testing-library/react';
|
||||
import type { LegendItem } from 'lib/uPlotV2/config/types';
|
||||
import type { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync';
|
||||
|
||||
describe('useLegendsSync', () => {
|
||||
let requestAnimationFrameSpy: jest.Mock<
|
||||
number,
|
||||
[callback: FrameRequestCallback]
|
||||
>;
|
||||
let cancelAnimationFrameSpy: jest.Mock<void, [handle: number]>;
|
||||
|
||||
beforeAll(() => {
|
||||
requestAnimationFrameSpy = jest.fn((cb: FrameRequestCallback): number => {
|
||||
cb(0);
|
||||
return 1;
|
||||
});
|
||||
|
||||
cancelAnimationFrameSpy = jest.fn();
|
||||
|
||||
(global as any).requestAnimationFrame = requestAnimationFrameSpy;
|
||||
(global as any).cancelAnimationFrame = cancelAnimationFrameSpy;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
type HookResult = ReturnType<typeof useLegendsSync>;
|
||||
let latestHookValue: HookResult;
|
||||
|
||||
const createMockConfig = (
|
||||
legendItems: Record<number, LegendItem>,
|
||||
): {
|
||||
config: UPlotConfigBuilder;
|
||||
invokeSetSeries: (
|
||||
seriesIndex: number | null,
|
||||
opts: { show?: boolean; focus?: boolean },
|
||||
fireHook?: boolean,
|
||||
) => void;
|
||||
} => {
|
||||
let setSeriesHandler:
|
||||
| ((u: uPlot, seriesIndex: number | null, opts: uPlot.Series) => void)
|
||||
| null = null;
|
||||
|
||||
const config = ({
|
||||
getLegendItems: jest.fn(() => legendItems),
|
||||
addHook: jest.fn(
|
||||
(
|
||||
hookName: string,
|
||||
handler: (
|
||||
u: uPlot,
|
||||
seriesIndex: number | null,
|
||||
opts: uPlot.Series,
|
||||
) => void,
|
||||
) => {
|
||||
if (hookName === 'setSeries') {
|
||||
setSeriesHandler = handler;
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
setSeriesHandler = null;
|
||||
};
|
||||
},
|
||||
),
|
||||
} as unknown) as UPlotConfigBuilder;
|
||||
|
||||
const invokeSetSeries = (
|
||||
seriesIndex: number | null,
|
||||
opts: { show?: boolean; focus?: boolean },
|
||||
): void => {
|
||||
if (setSeriesHandler) {
|
||||
setSeriesHandler({} as uPlot, seriesIndex, { ...opts });
|
||||
}
|
||||
};
|
||||
|
||||
return { config, invokeSetSeries };
|
||||
};
|
||||
|
||||
const TestComponent = ({
|
||||
config,
|
||||
subscribeToFocusChange,
|
||||
}: {
|
||||
config: UPlotConfigBuilder;
|
||||
subscribeToFocusChange?: boolean;
|
||||
}): JSX.Element => {
|
||||
const value = useLegendsSync({ config, subscribeToFocusChange });
|
||||
latestHookValue = value;
|
||||
|
||||
useEffect(() => {
|
||||
latestHookValue = value;
|
||||
}, [value]);
|
||||
|
||||
return React.createElement('div', { 'data-testid': 'hook-container' });
|
||||
};
|
||||
|
||||
it('initializes legend items from config', () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
2: { seriesIndex: 2, label: 'Memory', show: false, color: '#0f0' },
|
||||
};
|
||||
|
||||
const { config } = createMockConfig(initialItems);
|
||||
|
||||
render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(config.getLegendItems).toHaveBeenCalledTimes(1);
|
||||
expect(config.addHook).toHaveBeenCalledWith(
|
||||
'setSeries',
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
expect(latestHookValue.legendItemsMap).toEqual(initialItems);
|
||||
});
|
||||
|
||||
it('updates focusedSeriesIndex when a series gains focus via setSeries by default', async () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
};
|
||||
|
||||
const { config, invokeSetSeries } = createMockConfig(initialItems);
|
||||
|
||||
render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(latestHookValue.focusedSeriesIndex).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
invokeSetSeries(1, { focus: true });
|
||||
});
|
||||
|
||||
expect(latestHookValue.focusedSeriesIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('does not update focusedSeriesIndex when subscribeToFocusChange is false', () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
};
|
||||
|
||||
const { config, invokeSetSeries } = createMockConfig(initialItems);
|
||||
|
||||
render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
subscribeToFocusChange: false,
|
||||
}),
|
||||
);
|
||||
|
||||
invokeSetSeries(1, { focus: true });
|
||||
|
||||
expect(latestHookValue.focusedSeriesIndex).toBeNull();
|
||||
});
|
||||
|
||||
it('updates legendItemsMap visibility when show changes for a series', async () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
0: { seriesIndex: 0, label: 'x-axis', show: true, color: '#000' },
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
};
|
||||
|
||||
const { config, invokeSetSeries } = createMockConfig(initialItems);
|
||||
|
||||
render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
}),
|
||||
);
|
||||
|
||||
// Toggle visibility of series 1
|
||||
await act(async () => {
|
||||
invokeSetSeries(1, { show: false });
|
||||
});
|
||||
|
||||
expect(latestHookValue.legendItemsMap[1].show).toBe(false);
|
||||
});
|
||||
|
||||
it('ignores visibility updates for unknown legend items or unchanged show values', () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
};
|
||||
|
||||
const { config, invokeSetSeries } = createMockConfig(initialItems);
|
||||
|
||||
render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
}),
|
||||
);
|
||||
|
||||
const before = latestHookValue;
|
||||
|
||||
// Unknown series index
|
||||
invokeSetSeries(5, { show: false });
|
||||
// Unchanged visibility for existing item
|
||||
invokeSetSeries(1, { show: true });
|
||||
|
||||
const after = latestHookValue;
|
||||
expect(after.legendItemsMap).toEqual(before.legendItemsMap);
|
||||
});
|
||||
|
||||
it('cancels pending visibility RAF on unmount', () => {
|
||||
const initialItems: Record<number, LegendItem> = {
|
||||
1: { seriesIndex: 1, label: 'CPU', show: true, color: '#f00' },
|
||||
};
|
||||
|
||||
const { config, invokeSetSeries } = createMockConfig(initialItems);
|
||||
|
||||
// Override RAF to not immediately invoke callback so we can assert cancellation
|
||||
requestAnimationFrameSpy.mockImplementationOnce(() => 42);
|
||||
|
||||
const { unmount } = render(
|
||||
React.createElement(TestComponent, {
|
||||
config,
|
||||
}),
|
||||
);
|
||||
|
||||
invokeSetSeries(1, { show: false });
|
||||
|
||||
unmount();
|
||||
|
||||
expect(cancelAnimationFrameSpy).toHaveBeenCalledWith(42);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,13 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import type {
|
||||
CSSProperties,
|
||||
MutableRefObject,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
} from 'react';
|
||||
import type uPlot from 'uplot';
|
||||
|
||||
import { TooltipRenderArgs } from '../../components/types';
|
||||
import { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
import type { TooltipRenderArgs } from '../../components/types';
|
||||
import type { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
|
||||
export const TOOLTIP_OFFSET = 10;
|
||||
|
||||
@@ -17,7 +23,7 @@ export interface TooltipViewState {
|
||||
isHovering: boolean;
|
||||
isPinned: boolean;
|
||||
dismiss: () => void;
|
||||
contents?: React.ReactNode;
|
||||
contents?: ReactNode;
|
||||
}
|
||||
|
||||
export interface TooltipLayoutInfo {
|
||||
@@ -31,7 +37,7 @@ export interface TooltipPluginProps {
|
||||
canPinTooltip?: boolean;
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
render: (args: TooltipRenderArgs) => React.ReactNode;
|
||||
render: (args: TooltipRenderArgs) => ReactNode;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
@@ -75,13 +81,11 @@ export interface TooltipControllerState {
|
||||
*/
|
||||
export interface TooltipControllerContext {
|
||||
controller: TooltipControllerState;
|
||||
layoutRef: React.MutableRefObject<TooltipLayoutInfo | undefined>;
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
rafId: React.MutableRefObject<number | null>;
|
||||
layoutRef: MutableRefObject<TooltipLayoutInfo | undefined>;
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
rafId: MutableRefObject<number | null>;
|
||||
updateState: (updates: Partial<TooltipViewState>) => void;
|
||||
renderRef: React.MutableRefObject<
|
||||
(args: TooltipRenderArgs) => React.ReactNode
|
||||
>;
|
||||
renderRef: MutableRefObject<(args: TooltipRenderArgs) => ReactNode>;
|
||||
syncMode: DashboardCursorSync;
|
||||
syncKey: string;
|
||||
canPinTooltip: boolean;
|
||||
|
||||
484
frontend/src/lib/uPlotV2/plugins/__tests__/TooltipPlugin.test.ts
Normal file
484
frontend/src/lib/uPlotV2/plugins/__tests__/TooltipPlugin.test.ts
Normal file
@@ -0,0 +1,484 @@
|
||||
import React from 'react';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'tests/test-utils';
|
||||
import type uPlot from 'uplot';
|
||||
|
||||
import { TooltipRenderArgs } from '../../components/types';
|
||||
import { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
import TooltipPlugin from '../TooltipPlugin/TooltipPlugin';
|
||||
import { DashboardCursorSync } from '../TooltipPlugin/types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type HookHandler = (...args: unknown[]) => void;
|
||||
|
||||
class TestConfigBuilder extends UPlotConfigBuilder {
|
||||
public registeredHooks: { type: string; handler: HookHandler }[] = [];
|
||||
|
||||
public removeCallbacks: jest.Mock[] = [];
|
||||
|
||||
// Override addHook so we can:
|
||||
// - capture handlers by hook name for tests
|
||||
// - return removable jest mocks to assert cleanup
|
||||
public addHook<T extends keyof uPlot.Hooks.Defs>(
|
||||
type: T,
|
||||
hook: uPlot.Hooks.Defs[T],
|
||||
): () => void {
|
||||
this.registeredHooks.push({
|
||||
type: String(type),
|
||||
handler: hook as HookHandler,
|
||||
});
|
||||
const remove = jest.fn();
|
||||
this.removeCallbacks.push(remove);
|
||||
return remove;
|
||||
}
|
||||
}
|
||||
|
||||
type ConfigMock = TestConfigBuilder;
|
||||
|
||||
function createConfigMock(): ConfigMock {
|
||||
return new TestConfigBuilder();
|
||||
}
|
||||
|
||||
function getHandler(config: ConfigMock, hookName: string): HookHandler {
|
||||
const entry = config.registeredHooks.find((h) => h.type === hookName);
|
||||
if (!entry) {
|
||||
throw new Error(`Hook "${hookName}" was not registered on config`);
|
||||
}
|
||||
return entry.handler;
|
||||
}
|
||||
|
||||
function createFakePlot(): {
|
||||
over: HTMLDivElement;
|
||||
setCursor: jest.Mock<void, [uPlot.Cursor]>;
|
||||
cursor: { event: Record<string, unknown> };
|
||||
} {
|
||||
return {
|
||||
over: document.createElement('div'),
|
||||
setCursor: jest.fn(),
|
||||
cursor: { event: {} },
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('TooltipPlugin', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
|
||||
(callback as FrameRequestCallback)(0);
|
||||
return 0;
|
||||
});
|
||||
jest
|
||||
.spyOn(window, 'cancelAnimationFrame')
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
/**
|
||||
* Shorthand: render the plugin, initialise a fake plot, and trigger a
|
||||
* series focus so the tooltip becomes visible. Returns the fake plot
|
||||
* instance for further interaction (e.g. clicking the overlay).
|
||||
*/
|
||||
function renderAndActivateHover(
|
||||
config: ConfigMock,
|
||||
renderFn: (
|
||||
args: TooltipRenderArgs,
|
||||
) => React.ReactNode = (): React.ReactNode =>
|
||||
React.createElement('div', null, 'tooltip-body'),
|
||||
extraProps: Partial<React.ComponentProps<typeof TooltipPlugin>> = {},
|
||||
): ReturnType<typeof createFakePlot> {
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: renderFn,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
...extraProps,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
const initHandler = getHandler(config, 'init');
|
||||
const setSeriesHandler = getHandler(config, 'setSeries');
|
||||
|
||||
act(() => {
|
||||
initHandler(fakePlot);
|
||||
setSeriesHandler(fakePlot, 1, { focus: true });
|
||||
});
|
||||
|
||||
return fakePlot;
|
||||
}
|
||||
|
||||
// ---- Initial state --------------------------------------------------------
|
||||
|
||||
describe('before any interaction', () => {
|
||||
it('does not render anything when there is no active hover', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => React.createElement('div', null, 'tooltip-body'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(document.querySelector('.tooltip-plugin-container')).toBeNull();
|
||||
});
|
||||
|
||||
it('registers all required uPlot hooks on mount', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
}),
|
||||
);
|
||||
|
||||
const registered = config.registeredHooks.map((h) => h.type);
|
||||
expect(registered).toContain('ready');
|
||||
expect(registered).toContain('init');
|
||||
expect(registered).toContain('setData');
|
||||
expect(registered).toContain('setSeries');
|
||||
expect(registered).toContain('setLegend');
|
||||
expect(registered).toContain('setCursor');
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Tooltip rendering ------------------------------------------------------
|
||||
|
||||
describe('tooltip rendering', () => {
|
||||
it('renders contents into a portal on document.body when hover is active', () => {
|
||||
const config = createConfigMock();
|
||||
const renderTooltip = jest.fn(() =>
|
||||
React.createElement('div', null, 'tooltip-body'),
|
||||
);
|
||||
|
||||
renderAndActivateHover(config, renderTooltip);
|
||||
|
||||
expect(renderTooltip).toHaveBeenCalled();
|
||||
expect(screen.getByText('tooltip-body')).toBeInTheDocument();
|
||||
|
||||
const container = document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement;
|
||||
expect(container).not.toBeNull();
|
||||
expect(container.parentElement).toBe(document.body);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Pin behaviour ----------------------------------------------------------
|
||||
|
||||
describe('pin behaviour', () => {
|
||||
it('pins the tooltip when canPinTooltip is true and overlay is clicked', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
const fakePlot = renderAndActivateHover(config, undefined, {
|
||||
canPinTooltip: true,
|
||||
});
|
||||
|
||||
const container = document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement;
|
||||
expect(container.classList.contains('pinned')).toBe(false);
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
return waitFor(() => {
|
||||
const updated = document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement | null;
|
||||
expect(updated).not.toBeNull();
|
||||
expect(updated?.classList.contains('pinned')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('dismisses a pinned tooltip via the dismiss callback', async () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: (args: TooltipRenderArgs) =>
|
||||
React.createElement(
|
||||
'button',
|
||||
{ type: 'button', onClick: args.dismiss },
|
||||
'Dismiss',
|
||||
),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
|
||||
act(() => {
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
});
|
||||
|
||||
// Pin the tooltip.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
// Wait until the tooltip is actually pinned (pointer events enabled)
|
||||
await waitFor(() => {
|
||||
const container = document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement | null;
|
||||
expect(container).not.toBeNull();
|
||||
expect(container?.classList.contains('pinned')).toBe(true);
|
||||
});
|
||||
|
||||
const button = await screen.findByRole('button', { name: 'Dismiss' });
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('.tooltip-plugin-container')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('drops a pinned tooltip when the underlying data changes', () => {
|
||||
jest.useFakeTimers();
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config: config,
|
||||
render: () => React.createElement('div', null, 'tooltip-body'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
|
||||
act(() => {
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Pin.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(
|
||||
(document.querySelector(
|
||||
'.tooltip-plugin-container',
|
||||
) as HTMLElement)?.classList.contains('pinned'),
|
||||
).toBe(true);
|
||||
|
||||
// Simulate data update – should dismiss the pinned tooltip.
|
||||
act(() => {
|
||||
getHandler(config, 'setData')(fakePlot);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelector('.tooltip-plugin-container')).toBeNull();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('unpins the tooltip on outside mousedown', () => {
|
||||
jest.useFakeTimers();
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => React.createElement('div', null, 'pinned content'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
|
||||
act(() => {
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(
|
||||
document
|
||||
.querySelector('.tooltip-plugin-container')
|
||||
?.classList.contains('pinned'),
|
||||
).toBe(true);
|
||||
|
||||
// Click outside the tooltip container.
|
||||
act(() => {
|
||||
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelector('.tooltip-plugin-container')).toBeNull();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('unpins the tooltip on outside keydown', () => {
|
||||
jest.useFakeTimers();
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => React.createElement('div', null, 'pinned content'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
|
||||
act(() => {
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(
|
||||
document
|
||||
.querySelector('.tooltip-plugin-container')
|
||||
?.classList.contains('pinned'),
|
||||
).toBe(true);
|
||||
|
||||
// Press a key outside the tooltip.
|
||||
act(() => {
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),
|
||||
);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelector('.tooltip-plugin-container')).toBeNull();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Cursor sync ------------------------------------------------------------
|
||||
|
||||
describe('cursor sync', () => {
|
||||
it('enables uPlot cursor sync for time-based scales when mode is Tooltip', () => {
|
||||
const config = createConfigMock();
|
||||
const setCursorSpy = jest.spyOn(config, 'setCursor');
|
||||
config.addScale({ scaleKey: 'x', time: true });
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.Tooltip,
|
||||
syncKey: 'dashboard-sync',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(setCursorSpy).toHaveBeenCalledWith({
|
||||
sync: { key: 'dashboard-sync', scales: ['x', null] },
|
||||
});
|
||||
});
|
||||
|
||||
it('does not enable cursor sync when mode is None', () => {
|
||||
const config = createConfigMock();
|
||||
const setCursorSpy = jest.spyOn(config, 'setCursor');
|
||||
config.addScale({ scaleKey: 'x', time: true });
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(setCursorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not enable cursor sync when scale is not time-based', () => {
|
||||
const config = createConfigMock();
|
||||
const setCursorSpy = jest.spyOn(config, 'setCursor');
|
||||
config.addScale({ scaleKey: 'x', time: false });
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.Tooltip,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(setCursorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Cleanup ----------------------------------------------------------------
|
||||
|
||||
describe('cleanup on unmount', () => {
|
||||
it('removes window event listeners and all uPlot hooks', () => {
|
||||
const config = createConfigMock();
|
||||
const addSpy = jest.spyOn(window, 'addEventListener');
|
||||
const removeSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
const { unmount } = render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
}),
|
||||
);
|
||||
|
||||
const resizeCall = addSpy.mock.calls.find(([type]) => type === 'resize');
|
||||
const scrollCall = addSpy.mock.calls.find(([type]) => type === 'scroll');
|
||||
|
||||
expect(resizeCall).toBeDefined();
|
||||
expect(scrollCall).toBeDefined();
|
||||
|
||||
const resizeListener = resizeCall?.[1] as EventListener;
|
||||
const scrollListener = scrollCall?.[1] as EventListener;
|
||||
const scrollOptions = scrollCall?.[2];
|
||||
|
||||
unmount();
|
||||
|
||||
config.removeCallbacks.forEach((removeFn) => {
|
||||
expect(removeFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(removeSpy).toHaveBeenCalledWith('resize', resizeListener);
|
||||
expect(removeSpy).toHaveBeenCalledWith(
|
||||
'scroll',
|
||||
scrollListener,
|
||||
scrollOptions,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
62
frontend/src/lib/uPlotV2/utils/__tests__/dataUtils.test.ts
Normal file
62
frontend/src/lib/uPlotV2/utils/__tests__/dataUtils.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { isInvalidPlotValue, normalizePlotValue } from '../dataUtils';
|
||||
|
||||
describe('dataUtils', () => {
|
||||
describe('isInvalidPlotValue', () => {
|
||||
it('treats null and undefined as invalid', () => {
|
||||
expect(isInvalidPlotValue(null)).toBe(true);
|
||||
expect(isInvalidPlotValue(undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('treats finite numbers as valid and non-finite as invalid', () => {
|
||||
expect(isInvalidPlotValue(0)).toBe(false);
|
||||
expect(isInvalidPlotValue(123.45)).toBe(false);
|
||||
expect(isInvalidPlotValue(Number.NaN)).toBe(true);
|
||||
expect(isInvalidPlotValue(Infinity)).toBe(true);
|
||||
expect(isInvalidPlotValue(-Infinity)).toBe(true);
|
||||
});
|
||||
|
||||
it('treats well-formed numeric strings as valid', () => {
|
||||
expect(isInvalidPlotValue('0')).toBe(false);
|
||||
expect(isInvalidPlotValue('123.45')).toBe(false);
|
||||
expect(isInvalidPlotValue('-1')).toBe(false);
|
||||
});
|
||||
|
||||
it('treats Infinity/NaN string variants and non-numeric strings as invalid', () => {
|
||||
expect(isInvalidPlotValue('+Inf')).toBe(true);
|
||||
expect(isInvalidPlotValue('-Inf')).toBe(true);
|
||||
expect(isInvalidPlotValue('Infinity')).toBe(true);
|
||||
expect(isInvalidPlotValue('-Infinity')).toBe(true);
|
||||
expect(isInvalidPlotValue('NaN')).toBe(true);
|
||||
expect(isInvalidPlotValue('not-a-number')).toBe(true);
|
||||
});
|
||||
|
||||
it('treats non-number, non-string values as valid (left to caller)', () => {
|
||||
expect(isInvalidPlotValue({})).toBe(false);
|
||||
expect(isInvalidPlotValue([])).toBe(false);
|
||||
expect(isInvalidPlotValue(true)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizePlotValue', () => {
|
||||
it('returns null for invalid values detected by isInvalidPlotValue', () => {
|
||||
expect(normalizePlotValue(null)).toBeNull();
|
||||
expect(normalizePlotValue(undefined)).toBeNull();
|
||||
expect(normalizePlotValue(NaN)).toBeNull();
|
||||
expect(normalizePlotValue(Infinity)).toBeNull();
|
||||
expect(normalizePlotValue('-Infinity')).toBeNull();
|
||||
expect(normalizePlotValue('not-a-number')).toBeNull();
|
||||
});
|
||||
|
||||
it('parses valid numeric strings into numbers', () => {
|
||||
expect(normalizePlotValue('0')).toBe(0);
|
||||
expect(normalizePlotValue('123.45')).toBe(123.45);
|
||||
expect(normalizePlotValue('-1')).toBe(-1);
|
||||
});
|
||||
|
||||
it('passes through valid numbers unchanged', () => {
|
||||
expect(normalizePlotValue(0)).toBe(0);
|
||||
expect(normalizePlotValue(123)).toBe(123);
|
||||
expect(normalizePlotValue(42.5)).toBe(42.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
201
frontend/src/lib/uPlotV2/utils/__tests__/scale.test.ts
Normal file
201
frontend/src/lib/uPlotV2/utils/__tests__/scale.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { DistributionType } from '../../config/types';
|
||||
import * as scaleUtils from '../scale';
|
||||
|
||||
describe('scale utils', () => {
|
||||
describe('normalizeLogScaleLimits', () => {
|
||||
it('returns limits unchanged when distribution is not logarithmic', () => {
|
||||
const limits = {
|
||||
min: 1,
|
||||
max: 100,
|
||||
softMin: 5,
|
||||
softMax: 50,
|
||||
};
|
||||
|
||||
const result = scaleUtils.normalizeLogScaleLimits({
|
||||
distr: DistributionType.Linear,
|
||||
logBase: 10,
|
||||
limits,
|
||||
});
|
||||
|
||||
expect(result).toEqual(limits);
|
||||
});
|
||||
|
||||
it('snaps positive limits to powers of the log base when distribution is logarithmic', () => {
|
||||
const result = scaleUtils.normalizeLogScaleLimits({
|
||||
distr: DistributionType.Logarithmic,
|
||||
logBase: 10,
|
||||
limits: {
|
||||
min: 3,
|
||||
max: 900,
|
||||
softMin: 12,
|
||||
softMax: 85,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.min).toBe(1); // 10^0
|
||||
expect(result.max).toBe(1000); // 10^3
|
||||
expect(result.softMin).toBe(10); // 10^1
|
||||
expect(result.softMax).toBe(100); // 10^2
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDistributionConfig', () => {
|
||||
it('returns empty config for time scales', () => {
|
||||
const config = scaleUtils.getDistributionConfig({
|
||||
time: true,
|
||||
distr: DistributionType.Linear,
|
||||
logBase: 2,
|
||||
});
|
||||
|
||||
expect(config).toEqual({});
|
||||
});
|
||||
|
||||
it('returns linear distribution settings for non-time scales', () => {
|
||||
const config = scaleUtils.getDistributionConfig({
|
||||
time: false,
|
||||
distr: DistributionType.Linear,
|
||||
logBase: 2,
|
||||
});
|
||||
|
||||
expect(config.distr).toBe(1);
|
||||
expect(config.log).toBe(2);
|
||||
});
|
||||
|
||||
it('returns log distribution settings for non-time scales', () => {
|
||||
const config = scaleUtils.getDistributionConfig({
|
||||
time: false,
|
||||
distr: DistributionType.Logarithmic,
|
||||
logBase: 10,
|
||||
});
|
||||
|
||||
expect(config.distr).toBe(3);
|
||||
expect(config.log).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRangeConfig', () => {
|
||||
it('computes range config and fixed range flags correctly', () => {
|
||||
const {
|
||||
rangeConfig,
|
||||
hardMinOnly,
|
||||
hardMaxOnly,
|
||||
hasFixedRange,
|
||||
} = scaleUtils.getRangeConfig(0, 100, null, null, 0.1, 0.2);
|
||||
|
||||
expect(rangeConfig.min).toEqual({
|
||||
pad: 0.1,
|
||||
hard: 0,
|
||||
soft: undefined,
|
||||
mode: 3,
|
||||
});
|
||||
expect(rangeConfig.max).toEqual({
|
||||
pad: 0.2,
|
||||
hard: 100,
|
||||
soft: undefined,
|
||||
mode: 3,
|
||||
});
|
||||
expect(hardMinOnly).toBe(true);
|
||||
expect(hardMaxOnly).toBe(true);
|
||||
expect(hasFixedRange).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRangeFunction', () => {
|
||||
it('returns [dataMin, dataMax] when no fixed range and no data', () => {
|
||||
const params = {
|
||||
rangeConfig: {} as uPlot.Range.Config,
|
||||
hardMinOnly: false,
|
||||
hardMaxOnly: false,
|
||||
hasFixedRange: false,
|
||||
min: null,
|
||||
max: null,
|
||||
};
|
||||
|
||||
const rangeFn = scaleUtils.createRangeFunction(params);
|
||||
|
||||
const u = ({
|
||||
scales: {
|
||||
y: {
|
||||
distr: 1,
|
||||
log: 10,
|
||||
},
|
||||
},
|
||||
} as unknown) as uPlot;
|
||||
|
||||
const result = rangeFn(
|
||||
u,
|
||||
(null as unknown) as number,
|
||||
(null as unknown) as number,
|
||||
'y',
|
||||
);
|
||||
|
||||
expect(result).toEqual([null, null]);
|
||||
});
|
||||
|
||||
it('applies hard min/max for linear scale when only hard limits are set', () => {
|
||||
const params = {
|
||||
rangeConfig: {} as uPlot.Range.Config,
|
||||
hardMinOnly: true,
|
||||
hardMaxOnly: true,
|
||||
hasFixedRange: true,
|
||||
min: 0,
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const rangeFn = scaleUtils.createRangeFunction(params);
|
||||
|
||||
// Use an undefined distr so the range function skips calling uPlot.rangeNum
|
||||
// and we can focus on the behavior of applyHardLimits.
|
||||
const u = ({
|
||||
scales: {
|
||||
y: {
|
||||
distr: undefined,
|
||||
log: 10,
|
||||
},
|
||||
},
|
||||
} as unknown) as uPlot;
|
||||
|
||||
const result = rangeFn(u, 10, 20, 'y');
|
||||
|
||||
// After applyHardLimits, the returned range should respect configured min/max
|
||||
expect(result).toEqual([0, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adjustSoftLimitsWithThresholds', () => {
|
||||
it('returns original soft limits when there are no thresholds', () => {
|
||||
const result = scaleUtils.adjustSoftLimitsWithThresholds(1, 5, [], 'ms');
|
||||
|
||||
expect(result).toEqual({ softMin: 1, softMax: 5 });
|
||||
});
|
||||
|
||||
it('expands soft limits to include threshold min/max values', () => {
|
||||
const result = scaleUtils.adjustSoftLimitsWithThresholds(
|
||||
3,
|
||||
6,
|
||||
[{ thresholdValue: 2 }, { thresholdValue: 8 }],
|
||||
'ms',
|
||||
);
|
||||
|
||||
// min should be pulled down to the smallest threshold value
|
||||
expect(result.softMin).toBe(2);
|
||||
// max should be pushed up to the largest threshold value
|
||||
expect(result.softMax).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFallbackMinMaxTimeStamp', () => {
|
||||
it('returns a 24-hour window ending at approximately now', () => {
|
||||
const { fallbackMin, fallbackMax } = scaleUtils.getFallbackMinMaxTimeStamp();
|
||||
|
||||
// Difference should be exactly one day in seconds
|
||||
expect(fallbackMax - fallbackMin).toBe(86400);
|
||||
|
||||
// Both should be reasonable timestamps (not NaN or negative)
|
||||
expect(fallbackMin).toBeGreaterThan(0);
|
||||
expect(fallbackMax).toBeGreaterThan(fallbackMin);
|
||||
});
|
||||
});
|
||||
});
|
||||
34
frontend/src/lib/uPlotV2/utils/__tests__/threshold.test.ts
Normal file
34
frontend/src/lib/uPlotV2/utils/__tests__/threshold.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { findMinMaxThresholdValues } from '../threshold';
|
||||
|
||||
describe('findMinMaxThresholdValues', () => {
|
||||
it('returns [null, null] when thresholds array is empty or missing', () => {
|
||||
expect(findMinMaxThresholdValues([], 'ms')).toEqual([null, null]);
|
||||
});
|
||||
|
||||
it('returns min and max from thresholdValue when units are not provided', () => {
|
||||
const thresholds = [
|
||||
{ thresholdValue: 5 },
|
||||
{ thresholdValue: 1 },
|
||||
{ thresholdValue: 10 },
|
||||
];
|
||||
|
||||
const [min, max] = findMinMaxThresholdValues(thresholds);
|
||||
|
||||
expect(min).toBe(1);
|
||||
expect(max).toBe(10);
|
||||
});
|
||||
|
||||
it('ignores thresholds without a value or with unconvertible units', () => {
|
||||
const thresholds = [
|
||||
// Should be ignored: convertValue returns null for unknown unit
|
||||
{ thresholdValue: 100, thresholdUnit: 'unknown-unit' },
|
||||
// Should be used
|
||||
{ thresholdValue: 4 },
|
||||
];
|
||||
|
||||
const [min, max] = findMinMaxThresholdValues(thresholds, 'ms');
|
||||
|
||||
expect(min).toBe(4);
|
||||
expect(max).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -7,4 +7,6 @@ import { handlers } from './handlers';
|
||||
// This configures a request mocking server with the given request handlers.
|
||||
export const server = setupServer(...handlers);
|
||||
|
||||
export * from './utils';
|
||||
|
||||
export { rest };
|
||||
|
||||
26
frontend/src/mocks-server/utils.ts
Normal file
26
frontend/src/mocks-server/utils.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ResponseResolver, restContext, RestRequest } from 'msw';
|
||||
|
||||
export const createErrorResponse = (
|
||||
status: number,
|
||||
code: string,
|
||||
message: string,
|
||||
): ResponseResolver<RestRequest, typeof restContext> => (
|
||||
_req,
|
||||
res,
|
||||
ctx,
|
||||
): ReturnType<ResponseResolver<RestRequest, typeof restContext>> =>
|
||||
res(
|
||||
ctx.status(status),
|
||||
ctx.json({
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const handleInternalServerError = createErrorResponse(
|
||||
500,
|
||||
'INTERNAL_SERVER_ERROR',
|
||||
'Internal server error occurred',
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { render, waitFor } from 'tests/test-utils';
|
||||
|
||||
import ForgotPassword from '../index';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
push: jest.fn(),
|
||||
location: {
|
||||
search: '',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const mockHistoryPush = history.push as jest.MockedFunction<
|
||||
typeof history.push
|
||||
>;
|
||||
|
||||
describe('ForgotPassword Page', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Route State Handling', () => {
|
||||
it('redirects to login when route state is missing', async () => {
|
||||
render(<ForgotPassword />, undefined, {
|
||||
initialRoute: '/forgot-password',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null when route state is missing', () => {
|
||||
const { container } = render(<ForgotPassword />, undefined, {
|
||||
initialRoute: '/forgot-password',
|
||||
});
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
39
frontend/src/pages/ForgotPassword/index.tsx
Normal file
39
frontend/src/pages/ForgotPassword/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import AuthPageContainer from 'components/AuthPageContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ForgotPasswordContainer, {
|
||||
ForgotPasswordRouteState,
|
||||
} from 'container/ForgotPassword';
|
||||
import history from 'lib/history';
|
||||
|
||||
import '../Login/Login.styles.scss';
|
||||
|
||||
function ForgotPassword(): JSX.Element | null {
|
||||
const location = useLocation<ForgotPasswordRouteState | undefined>();
|
||||
const routeState = location.state;
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeState?.email) {
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
}, [routeState]);
|
||||
|
||||
if (!routeState?.email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthPageContainer>
|
||||
<div className="auth-form-card">
|
||||
<ForgotPasswordContainer
|
||||
email={routeState.email}
|
||||
orgId={routeState.orgId}
|
||||
orgs={routeState.orgs}
|
||||
/>
|
||||
</div>
|
||||
</AuthPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -6,6 +6,8 @@ const DOCLINKS = {
|
||||
'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=traces-explorer-trace-tab#traces-view',
|
||||
METRICS_EXPLORER_EMPTY_STATE:
|
||||
'https://signoz.io/docs/userguide/send-metrics-cloud/',
|
||||
EXTERNAL_API_MONITORING:
|
||||
'https://signoz.io/docs/external-api-monitoring/overview/',
|
||||
};
|
||||
|
||||
export default DOCLINKS;
|
||||
|
||||
@@ -68,6 +68,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
ALERT_HISTORY: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
ALERT_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGIN: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
FORGOT_PASSWORD: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR'],
|
||||
PASSWORD_RESET: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SERVICE_METRICS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
|
||||
@@ -5038,7 +5038,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-2.1.1.tgz#9c36d433fd264410713cc0c5ebdd75ce0ebecba3"
|
||||
integrity sha512-SdziCHg5Lwj+6oY6IRUPplaKZ+kTHjbrlhNj//UoAJ8aQLnRdR2F/miPzfSi4vrYw88LtXxNA9J9iJyacCp37A==
|
||||
|
||||
"@signozhq/icons@^0.1.0":
|
||||
"@signozhq/icons@0.1.0", "@signozhq/icons@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@signozhq/icons/-/icons-0.1.0.tgz#00dfb430dbac423bfff715876f91a7b8a72509e4"
|
||||
integrity sha512-kGWDhCpQkFWaNwyWfy88AIbg902wBbgTFTBAtmo6DkHyLGoqWAf0Jcq8BX+7brFqJF9PnLoSJDj1lvCpUsI/Ig==
|
||||
|
||||
@@ -278,7 +278,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
var metricTemporality map[string]metrictypes.Temporality
|
||||
if len(metricNames) > 0 {
|
||||
var err error
|
||||
metricTemporality, err = q.metadataStore.FetchTemporalityMulti(ctx, metricNames...)
|
||||
metricTemporality, err = q.metadataStore.FetchTemporalityMulti(ctx, req.Start, req.End, metricNames...)
|
||||
if err != nil {
|
||||
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
|
||||
// Continue without temporality - statement builder will handle unspecified
|
||||
|
||||
@@ -1597,12 +1597,12 @@ func (t *telemetryMetaStore) GetAllValues(ctx context.Context, fieldValueSelecto
|
||||
return values, complete, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error) {
|
||||
func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error) {
|
||||
if metricName == "" {
|
||||
return metrictypes.Unknown, errors.Newf(errors.TypeInternal, errors.CodeInternal, "metric name cannot be empty")
|
||||
}
|
||||
|
||||
temporalityMap, err := t.FetchTemporalityMulti(ctx, metricName)
|
||||
temporalityMap, err := t.FetchTemporalityMulti(ctx, queryTimeRangeStartTs, queryTimeRangeEndTs, metricName)
|
||||
if err != nil {
|
||||
return metrictypes.Unknown, err
|
||||
}
|
||||
@@ -1615,13 +1615,13 @@ func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, metricName st
|
||||
return temporality, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
|
||||
func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error) {
|
||||
if len(metricNames) == 0 {
|
||||
return make(map[string]metrictypes.Temporality), nil
|
||||
}
|
||||
|
||||
result := make(map[string]metrictypes.Temporality)
|
||||
metricsTemporality, err := t.fetchMetricsTemporality(ctx, metricNames...)
|
||||
metricsTemporality, err := t.fetchMetricsTemporality(ctx, queryTimeRangeStartTs, queryTimeRangeEndTs, metricNames...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1630,8 +1630,12 @@ func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, metricNa
|
||||
|
||||
// For metrics not found in the database, set to Unknown
|
||||
for _, metricName := range metricNames {
|
||||
if temporality, exists := metricsTemporality[metricName]; exists {
|
||||
result[metricName] = temporality
|
||||
if temporality, exists := metricsTemporality[metricName]; exists && len(temporality) > 0 {
|
||||
if len(temporality) > 1 {
|
||||
result[metricName] = metrictypes.Multiple
|
||||
} else {
|
||||
result[metricName] = temporality[0]
|
||||
}
|
||||
continue
|
||||
}
|
||||
if temporality, exists := meterMetricsTemporality[metricName]; exists {
|
||||
@@ -1644,8 +1648,10 @@ func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, metricNa
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
|
||||
result := make(map[string]metrictypes.Temporality)
|
||||
func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string][]metrictypes.Temporality, error) {
|
||||
result := make(map[string][]metrictypes.Temporality)
|
||||
|
||||
adjustedStartTs, adjustedEndTs, tsTableName, _ := telemetrymetrics.WhichTSTableToUse(queryTimeRangeStartTs, queryTimeRangeEndTs, nil)
|
||||
|
||||
// Build query to fetch temporality for all metrics
|
||||
// We use attr_string_value where attr_name = '__temporality__'
|
||||
@@ -1653,14 +1659,18 @@ func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, metric
|
||||
// and metric_name column contains temporality value, so we use the correct mapping
|
||||
sb := sqlbuilder.Select(
|
||||
"metric_name",
|
||||
"argMax(temporality, last_reported_unix_milli) as temporality",
|
||||
).From(t.metricsDBName + "." + t.metricsFieldsTblName)
|
||||
"temporality",
|
||||
).
|
||||
From(t.metricsDBName + "." + tsTableName)
|
||||
|
||||
// Filter by metric names (in the temporality column due to data mix-up)
|
||||
sb.Where(sb.In("metric_name", metricNames))
|
||||
sb.Where(
|
||||
sb.In("metric_name", metricNames),
|
||||
sb.GTE("unix_milli", adjustedStartTs),
|
||||
sb.LT("unix_milli", adjustedEndTs),
|
||||
)
|
||||
|
||||
// Group by metric name to get one temporality per metric
|
||||
sb.GroupBy("metric_name")
|
||||
sb.GroupBy("metric_name", "temporality")
|
||||
|
||||
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
|
||||
@@ -1692,8 +1702,12 @@ func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, metric
|
||||
// Unknown or empty temporality
|
||||
temporality = metrictypes.Unknown
|
||||
}
|
||||
|
||||
result[metricName] = temporality
|
||||
if temporality != metrictypes.Unknown {
|
||||
result[metricName] = append(result[metricName], temporality)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error iterating over metrics temporality rows")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
@@ -21,6 +21,10 @@ const (
|
||||
RateWithoutNegative = `If((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) < 0, per_series_value / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window), (per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))`
|
||||
IncreaseWithoutNegative = `If((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) < 0, per_series_value, ((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window)) * (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))`
|
||||
|
||||
RateWithoutNegativeMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, IF((%s - lagInFrame(%s, 1, 0) OVER rate_window) < 0, %s / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window), (%s - lagInFrame(%s, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))) AS per_series_value`
|
||||
IncreaseWithoutNegativeMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, IF((%s - lagInFrame(%s, 1, 0) OVER rate_window) < 0, %s, ((%s - lagInFrame(%s, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window)) * (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))) AS per_series_value`
|
||||
OthersMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, %s) AS per_series_value`
|
||||
|
||||
RateWithInterpolation = `
|
||||
CASE
|
||||
WHEN row_number() OVER rate_window = 1 THEN
|
||||
@@ -377,7 +381,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
|
||||
sb.LTE("unix_milli", end),
|
||||
)
|
||||
|
||||
if query.Aggregations[0].Temporality != metrictypes.Unknown {
|
||||
if query.Aggregations[0].Temporality != metrictypes.Multiple && query.Aggregations[0].Temporality != metrictypes.Unknown {
|
||||
sb.Where(sb.ILike("temporality", query.Aggregations[0].Temporality.StringValue()))
|
||||
}
|
||||
|
||||
@@ -407,8 +411,10 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggregationCTE(
|
||||
) (string, []any, error) {
|
||||
if query.Aggregations[0].Temporality == metrictypes.Delta {
|
||||
return b.buildTemporalAggDelta(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
} else if query.Aggregations[0].Temporality != metrictypes.Multiple {
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
return b.buildTemporalAggForMultipleTemporalities(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildTemporalAggDelta(
|
||||
@@ -528,6 +534,58 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
|
||||
}
|
||||
}
|
||||
|
||||
// because RateInterpolation is not enabled anywhere due to some gaps in the logic wrt cache handling, it hasn't been considered for the multi temporality
|
||||
func (b *MetricQueryStatementBuilder) buildTemporalAggForMultipleTemporalities(
|
||||
_ context.Context,
|
||||
start, end uint64,
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
|
||||
timeSeriesCTE string,
|
||||
timeSeriesCTEArgs []any,
|
||||
) (string, []any, error) {
|
||||
stepSec := int64(query.StepInterval.Seconds())
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
sb.SelectMore(fmt.Sprintf(
|
||||
"toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), toIntervalSecond(%d)) AS ts",
|
||||
stepSec,
|
||||
))
|
||||
for _, g := range query.GroupBy {
|
||||
sb.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
|
||||
}
|
||||
|
||||
aggForDeltaTemporality := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, metrictypes.Delta, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
aggForCumulativeTemporality := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, metrictypes.Cumulative, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
if query.Aggregations[0].TimeAggregation == metrictypes.TimeAggregationRate {
|
||||
aggForDeltaTemporality = fmt.Sprintf("%s/%d", aggForDeltaTemporality, stepSec)
|
||||
}
|
||||
|
||||
switch query.Aggregations[0].TimeAggregation {
|
||||
case metrictypes.TimeAggregationRate:
|
||||
rateExpr := fmt.Sprintf(RateWithoutNegativeMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, start, aggForCumulativeTemporality, aggForCumulativeTemporality, start)
|
||||
sb.SelectMore(rateExpr)
|
||||
case metrictypes.TimeAggregationIncrease:
|
||||
increaseExpr := fmt.Sprintf(IncreaseWithoutNegativeMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, start, start)
|
||||
sb.SelectMore(increaseExpr)
|
||||
default:
|
||||
expr := fmt.Sprintf(OthersMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality)
|
||||
sb.SelectMore(expr)
|
||||
}
|
||||
|
||||
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
|
||||
sb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
|
||||
sb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
|
||||
sb.Where(
|
||||
sb.In("metric_name", query.Aggregations[0].MetricName),
|
||||
sb.GTE("unix_milli", start),
|
||||
sb.LT("unix_milli", end),
|
||||
)
|
||||
sb.GroupBy("fingerprint", "ts", "temporality")
|
||||
sb.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
|
||||
queryWithoutWindow, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse, timeSeriesCTEArgs...)
|
||||
queryWithWindowAndOrder := queryWithoutWindow + " WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint ASC, ts ASC) ORDER BY ts"
|
||||
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", queryWithWindowAndOrder), args, nil
|
||||
}
|
||||
|
||||
func (b *MetricQueryStatementBuilder) buildSpatialAggregationCTE(
|
||||
_ context.Context,
|
||||
_ uint64,
|
||||
|
||||
@@ -151,7 +151,8 @@ func (c *AccountConfig) Value() (driver.Value, error) {
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize cloud account config to JSON")
|
||||
}
|
||||
return serialized, nil
|
||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
||||
return string(serialized), nil
|
||||
}
|
||||
|
||||
type AgentReport struct {
|
||||
@@ -186,7 +187,8 @@ func (r *AgentReport) Value() (driver.Value, error) {
|
||||
err, errors.CodeInternal, "couldn't serialize agent report to JSON",
|
||||
)
|
||||
}
|
||||
return serialized, nil
|
||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
||||
return string(serialized), nil
|
||||
}
|
||||
|
||||
type CloudIntegrationService struct {
|
||||
@@ -240,5 +242,6 @@ func (c *CloudServiceConfig) Value() (driver.Value, error) {
|
||||
err, errors.CodeInternal, "couldn't serialize cloud service config to JSON",
|
||||
)
|
||||
}
|
||||
return serialized, nil
|
||||
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
|
||||
return string(serialized), nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ var (
|
||||
Cumulative = Temporality{valuer.NewString("cumulative")}
|
||||
Unspecified = Temporality{valuer.NewString("unspecified")}
|
||||
Unknown = Temporality{valuer.NewString("")}
|
||||
Multiple = Temporality{valuer.NewString("__multiple__")}
|
||||
)
|
||||
|
||||
func (t Temporality) Value() (driver.Value, error) {
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestScalarData_MarshalJSON(t *testing.T) {
|
||||
{4.0, 5.0, 6.0},
|
||||
},
|
||||
},
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"value","signal":"","fieldContext":"","fieldDataType":"","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[1,2,3],[4,5,6]]}`,
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"value","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[1,2,3],[4,5,6]]}`,
|
||||
},
|
||||
{
|
||||
name: "scalar data with NaN",
|
||||
@@ -149,7 +149,7 @@ func TestScalarData_MarshalJSON(t *testing.T) {
|
||||
{math.Inf(1), 5.0, math.Inf(-1)},
|
||||
},
|
||||
},
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"value","signal":"","fieldContext":"","fieldDataType":"","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[1,"NaN",3],["Inf",5,"-Inf"]]}`,
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"value","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[1,"NaN",3],["Inf",5,"-Inf"]]}`,
|
||||
},
|
||||
{
|
||||
name: "scalar data with mixed types",
|
||||
@@ -168,7 +168,7 @@ func TestScalarData_MarshalJSON(t *testing.T) {
|
||||
{nil, math.Inf(1), 3.14, false},
|
||||
},
|
||||
},
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"mixed","signal":"","fieldContext":"","fieldDataType":"","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[["string",42,"NaN",true],[null,"Inf",3.14,false]]}`,
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"mixed","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[["string",42,"NaN",true],[null,"Inf",3.14,false]]}`,
|
||||
},
|
||||
{
|
||||
name: "scalar data with nested structures",
|
||||
@@ -189,7 +189,7 @@ func TestScalarData_MarshalJSON(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"nested","signal":"","fieldContext":"","fieldDataType":"","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[{"count":10,"value":"NaN"},[1,"Inf",3]]]}`,
|
||||
expected: `{"queryName":"test_query","columns":[{"name":"nested","queryName":"test_query","aggregationIndex":0,"meta":{},"columnType":"aggregation"}],"data":[[{"count":10,"value":"NaN"},[1,"Inf",3]]]}`,
|
||||
},
|
||||
{
|
||||
name: "empty scalar data",
|
||||
|
||||
@@ -28,12 +28,12 @@ const (
|
||||
)
|
||||
|
||||
type TelemetryFieldKey struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name" required:"true"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Unit string `json:"unit,omitempty"`
|
||||
Signal Signal `json:"signal,omitempty"`
|
||||
FieldContext FieldContext `json:"fieldContext,omitempty"`
|
||||
FieldDataType FieldDataType `json:"fieldDataType,omitempty"`
|
||||
Signal Signal `json:"signal,omitzero"`
|
||||
FieldContext FieldContext `json:"fieldContext,omitzero"`
|
||||
FieldDataType FieldDataType `json:"fieldDataType,omitzero"`
|
||||
|
||||
JSONDataType *JSONDataType `json:"-"`
|
||||
JSONPlan JSONAccessPlan `json:"-"`
|
||||
@@ -268,8 +268,8 @@ type FieldValueSelector struct {
|
||||
}
|
||||
|
||||
type GettableFieldKeys struct {
|
||||
Keys map[string][]*TelemetryFieldKey `json:"keys"`
|
||||
Complete bool `json:"complete"`
|
||||
Keys map[string][]*TelemetryFieldKey `json:"keys" required:"true"`
|
||||
Complete bool `json:"complete" required:"true"`
|
||||
}
|
||||
|
||||
type PostableFieldKeysParams struct {
|
||||
@@ -285,8 +285,8 @@ type PostableFieldKeysParams struct {
|
||||
}
|
||||
|
||||
type GettableFieldValues struct {
|
||||
Values *TelemetryFieldValues `json:"values"`
|
||||
Complete bool `json:"complete"`
|
||||
Values *TelemetryFieldValues `json:"values" required:"true"`
|
||||
Complete bool `json:"complete" required:"true"`
|
||||
}
|
||||
|
||||
type PostableFieldValueParams struct {
|
||||
|
||||
@@ -27,10 +27,10 @@ type MetadataStore interface {
|
||||
GetAllValues(ctx context.Context, fieldValueSelector *FieldValueSelector) (*TelemetryFieldValues, bool, error)
|
||||
|
||||
// FetchTemporality fetches the temporality for metric
|
||||
FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error)
|
||||
FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error)
|
||||
|
||||
// FetchTemporalityMulti fetches the temporality for multiple metrics
|
||||
FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error)
|
||||
FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error)
|
||||
|
||||
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
|
||||
ListLogsJSONIndexes(ctx context.Context, filters ...string) (map[string][]schemamigrator.Index, error)
|
||||
|
||||
@@ -265,7 +265,7 @@ func (m *MockMetadataStore) SetAllValues(lookupKey string, values *telemetrytype
|
||||
}
|
||||
|
||||
// FetchTemporality fetches the temporality for a metric
|
||||
func (m *MockMetadataStore) FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error) {
|
||||
func (m *MockMetadataStore) FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error) {
|
||||
if temporality, exists := m.TemporalityMap[metricName]; exists {
|
||||
return temporality, nil
|
||||
}
|
||||
@@ -273,7 +273,7 @@ func (m *MockMetadataStore) FetchTemporality(ctx context.Context, metricName str
|
||||
}
|
||||
|
||||
// FetchTemporalityMulti fetches the temporality for multiple metrics
|
||||
func (m *MockMetadataStore) FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
|
||||
func (m *MockMetadataStore) FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error) {
|
||||
result := make(map[string]metrictypes.Temporality)
|
||||
|
||||
for _, metricName := range metricNames {
|
||||
|
||||
@@ -20,6 +20,7 @@ pytest_plugins = [
|
||||
"fixtures.idputils",
|
||||
"fixtures.notification_channel",
|
||||
"fixtures.alerts",
|
||||
"fixtures.cloudintegrations",
|
||||
]
|
||||
|
||||
|
||||
|
||||
88
tests/integration/fixtures/cloudintegrations.py
Normal file
88
tests/integration/fixtures/cloudintegrations.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Fixtures for cloud integration tests."""
|
||||
from typing import Callable, Optional
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def create_cloud_integration_account(
|
||||
request: pytest.FixtureRequest,
|
||||
signoz: types.SigNoz,
|
||||
) -> Callable[[str, str], dict]:
|
||||
created_account_id: Optional[str] = None
|
||||
cloud_provider_used: Optional[str] = None
|
||||
|
||||
def _create(
|
||||
admin_token: str,
|
||||
cloud_provider: str = "aws",
|
||||
) -> dict:
|
||||
nonlocal created_account_id, cloud_provider_used
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
|
||||
)
|
||||
|
||||
request_payload = {
|
||||
"account_config": {"regions": ["us-east-1"]},
|
||||
"agent_config": {
|
||||
"region": "us-east-1",
|
||||
"ingestion_url": "https://ingest.test.signoz.cloud",
|
||||
"ingestion_key": "test-ingestion-key-123456",
|
||||
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
|
||||
"signoz_api_key": "test-api-key-789",
|
||||
"version": "v0.0.8",
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=request_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Failed to create test account: {response.status_code}"
|
||||
|
||||
data = response.json().get("data", response.json())
|
||||
created_account_id = data.get("account_id")
|
||||
cloud_provider_used = cloud_provider
|
||||
|
||||
return data
|
||||
|
||||
def _disconnect(admin_token: str, cloud_provider: str) -> requests.Response:
|
||||
assert created_account_id
|
||||
disconnect_endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{created_account_id}/disconnect"
|
||||
)
|
||||
return requests.post(
|
||||
signoz.self.host_configs["8080"].get(disconnect_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
# Yield factory to the test
|
||||
yield _create
|
||||
|
||||
# Post-test cleanup: generate admin token and disconnect the created account
|
||||
if created_account_id and cloud_provider_used:
|
||||
get_token = request.getfixturevalue("get_token")
|
||||
try:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
r = _disconnect(admin_token, cloud_provider_used)
|
||||
if r.status_code != HTTPStatus.OK:
|
||||
logger.info(
|
||||
"Disconnect cleanup returned %s for account %s",
|
||||
r.status_code,
|
||||
created_account_id,
|
||||
)
|
||||
logger.info("Cleaned up test account: %s", created_account_id)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.info("Post-test disconnect cleanup failed: %s", exc)
|
||||
46
tests/integration/fixtures/cloudintegrationsutils.py
Normal file
46
tests/integration/fixtures/cloudintegrationsutils.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Fixtures for cloud integration tests."""
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def simulate_agent_checkin(
|
||||
signoz: types.SigNoz,
|
||||
admin_token: str,
|
||||
cloud_provider: str,
|
||||
account_id: str,
|
||||
cloud_account_id: str,
|
||||
) -> dict:
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/agent-check-in"
|
||||
|
||||
checkin_payload = {
|
||||
"account_id": account_id,
|
||||
"cloud_account_id": cloud_account_id,
|
||||
"data": {},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=checkin_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error(
|
||||
"Agent check-in failed: %s, response: %s",
|
||||
response.status_code,
|
||||
response.text,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Agent check-in failed: {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
return response_data.get("data", response_data)
|
||||
@@ -52,7 +52,7 @@ def build_builder_query(
|
||||
time_aggregation: str,
|
||||
space_aggregation: str,
|
||||
*,
|
||||
temporality: str = "cumulative",
|
||||
temporality: Optional[str] = None,
|
||||
step_interval: int = DEFAULT_STEP_INTERVAL,
|
||||
group_by: Optional[List[str]] = None,
|
||||
filter_expression: Optional[str] = None,
|
||||
@@ -65,7 +65,6 @@ def build_builder_query(
|
||||
"aggregations": [
|
||||
{
|
||||
"metricName": metric_name,
|
||||
"temporality": temporality,
|
||||
"timeAggregation": time_aggregation,
|
||||
"spaceAggregation": space_aggregation,
|
||||
}
|
||||
@@ -73,6 +72,8 @@ def build_builder_query(
|
||||
"stepInterval": step_interval,
|
||||
"disabled": disabled,
|
||||
}
|
||||
if temporality:
|
||||
spec["aggregations"][0]["temporality"] = temporality
|
||||
|
||||
if group_by:
|
||||
spec["groupBy"] = [
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
from http import HTTPStatus
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_generate_connection_url(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test to generate connection URL for AWS CloudFormation stack deployment."""
|
||||
|
||||
# Get authentication token for admin user
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
cloud_provider = "aws"
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
|
||||
|
||||
# Prepare request payload
|
||||
request_payload = {
|
||||
"account_config": {"regions": ["us-east-1", "us-west-2"]},
|
||||
"agent_config": {
|
||||
"region": "us-east-1",
|
||||
"ingestion_url": "https://ingest.test.signoz.cloud",
|
||||
"ingestion_key": "test-ingestion-key-123456",
|
||||
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
|
||||
"signoz_api_key": "test-api-key-789",
|
||||
"version": "v0.0.8",
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=request_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
# Assert successful response
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}: {response.text}"
|
||||
|
||||
# Parse response JSON
|
||||
response_data = response.json()
|
||||
|
||||
# Assert response structure contains expected data
|
||||
assert "data" in response_data, "Response should contain 'data' field"
|
||||
|
||||
# Assert required fields in the response data
|
||||
expected_fields = ["account_id", "connection_url"]
|
||||
|
||||
for field in expected_fields:
|
||||
assert (
|
||||
field in response_data["data"]
|
||||
), f"Response data should contain '{field}' field"
|
||||
|
||||
data = response_data["data"]
|
||||
|
||||
# Assert account_id is a valid UUID format
|
||||
assert (
|
||||
len(data["account_id"]) > 0
|
||||
), "account_id should be a non-empty string (UUID)"
|
||||
|
||||
# Assert connection_url contains expected CloudFormation parameters
|
||||
connection_url = data["connection_url"]
|
||||
|
||||
# Verify it's an AWS CloudFormation URL
|
||||
assert (
|
||||
"console.aws.amazon.com/cloudformation" in connection_url
|
||||
), "connection_url should be an AWS CloudFormation URL"
|
||||
|
||||
# Verify region is included
|
||||
assert (
|
||||
"region=us-east-1" in connection_url
|
||||
), "connection_url should contain the specified region"
|
||||
|
||||
# Verify required parameters are in the URL
|
||||
required_params = [
|
||||
"param_SigNozIntegrationAgentVersion=v0.0.8",
|
||||
"param_SigNozApiUrl=https%3A%2F%2Ftest-deployment.test.signoz.cloud",
|
||||
"param_SigNozApiKey=test-api-key-789",
|
||||
"param_SigNozAccountId=", # Will be a UUID
|
||||
"param_IngestionUrl=https%3A%2F%2Fingest.test.signoz.cloud",
|
||||
"param_IngestionKey=test-ingestion-key-123456",
|
||||
"stackName=signoz-integration",
|
||||
"templateURL=https%3A%2F%2Fsignoz-integrations.s3.us-east-1.amazonaws.com%2Faws-quickcreate-template-v0.0.8.json",
|
||||
]
|
||||
|
||||
for param in required_params:
|
||||
assert (
|
||||
param in connection_url
|
||||
), f"connection_url should contain parameter: {param}"
|
||||
|
||||
|
||||
def test_generate_connection_url_unsupported_provider(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test that unsupported cloud providers return an error."""
|
||||
# Get authentication token for admin user
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
# Try with GCP (unsupported)
|
||||
cloud_provider = "gcp"
|
||||
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
|
||||
|
||||
request_payload = {
|
||||
"account_config": {"regions": ["us-central1"]},
|
||||
"agent_config": {
|
||||
"region": "us-central1",
|
||||
"ingestion_url": "https://ingest.test.signoz.cloud",
|
||||
"ingestion_key": "test-ingestion-key-123456",
|
||||
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
|
||||
"signoz_api_key": "test-api-key-789",
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=request_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
# Should return Bad Request for unsupported provider
|
||||
assert (
|
||||
response.status_code == HTTPStatus.BAD_REQUEST
|
||||
), f"Expected 400 for unsupported provider, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
assert "error" in response_data, "Response should contain 'error' field"
|
||||
assert (
|
||||
"unsupported cloud provider" in response_data["error"].lower()
|
||||
), "Error message should indicate unsupported provider"
|
||||
316
tests/integration/src/cloudintegrations/03_accounts.py
Normal file
316
tests/integration/src/cloudintegrations/03_accounts.py
Normal file
@@ -0,0 +1,316 @@
|
||||
from http import HTTPStatus
|
||||
from typing import Callable
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.cloudintegrations import (
|
||||
create_cloud_integration_account,
|
||||
)
|
||||
from fixtures.cloudintegrationsutils import simulate_agent_checkin
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_list_connected_accounts_empty(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test listing connected accounts when there are none."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
cloud_provider = "aws"
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "accounts" in data, "Response should contain 'accounts' field"
|
||||
assert isinstance(data["accounts"], list), "Accounts should be a list"
|
||||
assert len(data["accounts"]) == 0, "Accounts list should be empty when no accounts are connected"
|
||||
|
||||
|
||||
|
||||
def test_list_connected_accounts_with_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test listing connected accounts after creating one."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
# Simulate agent check-in to mark as connected
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# List accounts
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "accounts" in data, "Response should contain 'accounts' field"
|
||||
assert isinstance(data["accounts"], list), "Accounts should be a list"
|
||||
|
||||
# Find our account in the list (there may be leftover accounts from previous test runs)
|
||||
account = next((a for a in data["accounts"] if a["id"] == account_id), None)
|
||||
assert account is not None, f"Account {account_id} should be found in list"
|
||||
assert account["id"] == account_id, "Account ID should match"
|
||||
assert "config" in account, "Account should have config field"
|
||||
assert "status" in account, "Account should have status field"
|
||||
|
||||
|
||||
|
||||
def test_get_account_status(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test getting the status of a specific account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
# Create a test account (no check-in needed for status check)
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
# Get account status
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/status"
|
||||
)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "id" in data, "Response should contain 'id' field"
|
||||
assert data["id"] == account_id, "Account ID should match"
|
||||
assert "status" in data, "Response should contain 'status' field"
|
||||
assert "integration" in data["status"], "Status should contain 'integration' field"
|
||||
|
||||
|
||||
def test_get_account_status_not_found(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test getting status for a non-existent account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
cloud_provider = "aws"
|
||||
fake_account_id = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{fake_account_id}/status"
|
||||
)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.NOT_FOUND
|
||||
), f"Expected 404, got {response.status_code}"
|
||||
|
||||
|
||||
def test_update_account_config(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test updating account configuration."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
# Simulate agent check-in to mark as connected
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Update account configuration
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/config"
|
||||
)
|
||||
|
||||
updated_config = {
|
||||
"config": {"regions": ["us-east-1", "us-west-2", "eu-west-1"]}
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=updated_config,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "id" in data, "Response should contain 'id' field"
|
||||
assert data["id"] == account_id, "Account ID should match"
|
||||
|
||||
# Verify the update by listing accounts
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
|
||||
list_response_data = list_response.json()
|
||||
list_data = list_response_data.get("data", list_response_data)
|
||||
account = next((a for a in list_data["accounts"] if a["id"] == account_id), None)
|
||||
assert account is not None, "Account should be found in list"
|
||||
assert "config" in account, "Account should have config"
|
||||
assert "regions" in account["config"], "Config should have regions"
|
||||
assert len(account["config"]["regions"]) == 3, "Should have 3 regions"
|
||||
assert set(account["config"]["regions"]) == {
|
||||
"us-east-1",
|
||||
"us-west-2",
|
||||
"eu-west-1",
|
||||
}, "Regions should match updated config"
|
||||
|
||||
|
||||
|
||||
def test_disconnect_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test disconnecting an account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
# Simulate agent check-in to mark as connected
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Disconnect the account
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/disconnect"
|
||||
)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
# Verify our specific account is no longer in the connected list
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_response_data = list_response.json()
|
||||
list_data = list_response_data.get("data", list_response_data)
|
||||
|
||||
# Check that our specific account is not in the list
|
||||
disconnected_account = next(
|
||||
(a for a in list_data["accounts"] if a["id"] == account_id), None
|
||||
)
|
||||
assert disconnected_account is None, f"Account {account_id} should be removed from connected accounts"
|
||||
|
||||
|
||||
|
||||
def test_disconnect_account_not_found(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test disconnecting a non-existent account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "aws"
|
||||
fake_account_id = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
endpoint = (
|
||||
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{fake_account_id}/disconnect"
|
||||
)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.NOT_FOUND
|
||||
), f"Expected 404, got {response.status_code}"
|
||||
|
||||
|
||||
|
||||
def test_list_accounts_unsupported_provider(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test listing accounts for an unsupported cloud provider."""
|
||||
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "gcp" # Unsupported provider
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.BAD_REQUEST
|
||||
), f"Expected 400, got {response.status_code}"
|
||||
468
tests/integration/src/cloudintegrations/04_services.py
Normal file
468
tests/integration/src/cloudintegrations/04_services.py
Normal file
@@ -0,0 +1,468 @@
|
||||
from http import HTTPStatus
|
||||
from typing import Callable
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.logger import setup_logger
|
||||
from fixtures.cloudintegrations import (
|
||||
create_cloud_integration_account,
|
||||
)
|
||||
from fixtures.cloudintegrationsutils import simulate_agent_checkin
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def test_list_services_without_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test listing available services without specifying an account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "aws"
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "services" in data, "Response should contain 'services' field"
|
||||
assert isinstance(data["services"], list), "Services should be a list"
|
||||
assert len(data["services"]) > 0, "Should have at least one service available"
|
||||
|
||||
# Verify service structure
|
||||
service = data["services"][0]
|
||||
assert "id" in service, "Service should have 'id' field"
|
||||
assert "title" in service, "Service should have 'title' field"
|
||||
assert "icon" in service, "Service should have 'icon' field"
|
||||
|
||||
|
||||
|
||||
def test_list_services_with_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test listing services for a specific connected account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account and do check-in
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# List services for the account
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services?cloud_account_id={cloud_account_id}"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
assert "services" in data, "Response should contain 'services' field"
|
||||
assert isinstance(data["services"], list), "Services should be a list"
|
||||
assert len(data["services"]) > 0, "Should have at least one service available"
|
||||
|
||||
# Services should include config field (may be null if not configured)
|
||||
service = data["services"][0]
|
||||
assert "id" in service, "Service should have 'id' field"
|
||||
assert "title" in service, "Service should have 'title' field"
|
||||
assert "icon" in service, "Service should have 'icon' field"
|
||||
|
||||
|
||||
|
||||
def test_get_service_details_without_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test getting service details without specifying an account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "aws"
|
||||
# First get the list of services to get a valid service ID
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_data = list_response.json().get("data", list_response.json())
|
||||
assert len(list_data["services"]) > 0, "Should have at least one service"
|
||||
service_id = list_data["services"][0]["id"]
|
||||
|
||||
# Get service details
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}"
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
|
||||
# Verify service details structure
|
||||
assert "id" in data, "Service details should have 'id' field"
|
||||
assert data["id"] == service_id, "Service ID should match requested ID"
|
||||
assert "title" in data, "Service details should have 'title' field"
|
||||
assert "overview" in data, "Service details should have 'overview' field"
|
||||
# assert assets to had list of dashboards
|
||||
assert "assets" in data, "Service details should have 'assets' field"
|
||||
assert isinstance(data["assets"], dict), "Assets should be a dictionary"
|
||||
|
||||
|
||||
|
||||
def test_get_service_details_with_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test getting service details for a specific connected account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account and do check-in
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Get list of services first
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_data = list_response.json().get("data", list_response.json())
|
||||
assert len(list_data["services"]) > 0, "Should have at least one service"
|
||||
service_id = list_data["services"][0]["id"]
|
||||
|
||||
# Get service details with account
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}?cloud_account_id={cloud_account_id}"
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
|
||||
# Verify service details structure
|
||||
assert "id" in data, "Service details should have 'id' field"
|
||||
assert data["id"] == service_id, "Service ID should match requested ID"
|
||||
assert "title" in data, "Service details should have 'title' field"
|
||||
assert "overview" in data, "Service details should have 'overview' field"
|
||||
assert "assets" in data, "Service details should have 'assets' field"
|
||||
assert "config" in data, "Service details should have 'config' field"
|
||||
assert "status" in data, "Config should have 'status' field"
|
||||
|
||||
|
||||
|
||||
def test_get_service_details_invalid_service(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test getting details for a non-existent service."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "aws"
|
||||
fake_service_id = "non-existent-service"
|
||||
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{fake_service_id}"
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.NOT_FOUND
|
||||
), f"Expected 404, got {response.status_code}"
|
||||
|
||||
|
||||
|
||||
def test_list_services_unsupported_provider(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test listing services for an unsupported cloud provider."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "gcp" # Unsupported provider
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.BAD_REQUEST
|
||||
), f"Expected 400, got {response.status_code}"
|
||||
|
||||
|
||||
|
||||
def test_update_service_config(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test updating service configuration for a connected account."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account and do check-in
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Get list of services to pick a valid service ID
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_data = list_response.json().get("data", list_response.json())
|
||||
assert len(list_data["services"]) > 0, "Should have at least one service"
|
||||
service_id = list_data["services"][0]["id"]
|
||||
|
||||
# Update service configuration
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
|
||||
|
||||
config_payload = {
|
||||
"cloud_account_id": cloud_account_id,
|
||||
"config": {
|
||||
"metrics": {"enabled": True},
|
||||
"logs": {"enabled": True},
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=config_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {response.status_code}"
|
||||
|
||||
response_data = response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
|
||||
# Verify response structure
|
||||
assert "id" in data, "Response should contain 'id' field"
|
||||
assert data["id"] == service_id, "Service ID should match"
|
||||
assert "config" in data, "Response should contain 'config' field"
|
||||
assert "metrics" in data["config"], "Config should contain 'metrics' field"
|
||||
assert "logs" in data["config"], "Config should contain 'logs' field"
|
||||
|
||||
|
||||
|
||||
def test_update_service_config_without_account(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
"""Test updating service config without a connected account should fail."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
cloud_provider = "aws"
|
||||
|
||||
# Get a valid service ID
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_data = list_response.json().get("data", list_response.json())
|
||||
service_id = list_data["services"][0]["id"]
|
||||
|
||||
# Try to update config with non-existent account
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
|
||||
|
||||
fake_cloud_account_id = str(uuid.uuid4())
|
||||
config_payload = {
|
||||
"cloud_account_id": fake_cloud_account_id,
|
||||
"config": {
|
||||
"metrics": {"enabled": True},
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=config_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
), f"Expected 500 for non-existent account, got {response.status_code}"
|
||||
|
||||
|
||||
|
||||
def test_update_service_config_invalid_service(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test updating config for a non-existent service should fail."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account and do check-in
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Try to update config for invalid service
|
||||
fake_service_id = "non-existent-service"
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{fake_service_id}/config"
|
||||
|
||||
config_payload = {
|
||||
"cloud_account_id": cloud_account_id,
|
||||
"config": {
|
||||
"metrics": {"enabled": True},
|
||||
},
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=config_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == HTTPStatus.NOT_FOUND
|
||||
), f"Expected 404 for invalid service, got {response.status_code}"
|
||||
|
||||
|
||||
|
||||
def test_update_service_config_disable_service(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_cloud_integration_account: Callable,
|
||||
) -> None:
|
||||
"""Test disabling a service by updating config with enabled=false."""
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# Create a test account and do check-in
|
||||
cloud_provider = "aws"
|
||||
account_data = create_cloud_integration_account(admin_token, cloud_provider)
|
||||
account_id = account_data["account_id"]
|
||||
|
||||
cloud_account_id = str(uuid.uuid4())
|
||||
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
|
||||
|
||||
# Get a valid service
|
||||
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
|
||||
list_response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(list_endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
list_data = list_response.json().get("data", list_response.json())
|
||||
service_id = list_data["services"][0]["id"]
|
||||
|
||||
# First enable the service
|
||||
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
|
||||
|
||||
enable_payload = {
|
||||
"cloud_account_id": cloud_account_id,
|
||||
"config": {
|
||||
"metrics": {"enabled": True},
|
||||
},
|
||||
}
|
||||
|
||||
enable_response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=enable_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert enable_response.status_code == HTTPStatus.OK, "Failed to enable service"
|
||||
|
||||
# Now disable the service
|
||||
disable_payload = {
|
||||
"cloud_account_id": cloud_account_id,
|
||||
"config": {
|
||||
"metrics": {"enabled": False},
|
||||
"logs": {"enabled": False},
|
||||
},
|
||||
}
|
||||
|
||||
disable_response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(endpoint),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json=disable_payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
assert (
|
||||
disable_response.status_code == HTTPStatus.OK
|
||||
), f"Expected 200, got {disable_response.status_code}"
|
||||
|
||||
response_data = disable_response.json()
|
||||
data = response_data.get("data", response_data)
|
||||
|
||||
# Verify service is disabled
|
||||
assert data["config"]["metrics"]["enabled"] is False, "Metrics should be disabled"
|
||||
assert data["config"]["logs"]["enabled"] is False, "Logs should be disabled"
|
||||
@@ -1167,9 +1167,6 @@ def test_logs_time_series_count(
|
||||
{
|
||||
"key": {
|
||||
"name": "host.name",
|
||||
"signal": "",
|
||||
"fieldContext": "",
|
||||
"fieldDataType": "",
|
||||
},
|
||||
"value": "linux-001",
|
||||
}
|
||||
@@ -1200,9 +1197,6 @@ def test_logs_time_series_count(
|
||||
{
|
||||
"key": {
|
||||
"name": "host.name",
|
||||
"signal": "",
|
||||
"fieldContext": "",
|
||||
"fieldDataType": "",
|
||||
},
|
||||
"value": "linux-000",
|
||||
}
|
||||
|
||||
399
tests/integration/src/querier/07_metrics_multi_temporality.py
Normal file
399
tests/integration/src/querier/07_metrics_multi_temporality.py
Normal file
@@ -0,0 +1,399 @@
|
||||
"""
|
||||
Look at the multi_temporality_counters_1h.jsonl file for the relevant data
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from http import HTTPStatus
|
||||
import random
|
||||
from typing import Any, Callable, List
|
||||
import pytest
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.metrics import Metrics
|
||||
from fixtures.querier import (
|
||||
build_builder_query,
|
||||
get_all_series,
|
||||
get_series_values,
|
||||
make_query_request,
|
||||
)
|
||||
from fixtures.utils import get_testdata_file_path
|
||||
|
||||
MULTI_TEMPORALITY_FILE = get_testdata_file_path("multi_temporality_counters_1h.jsonl")
|
||||
MULTI_TEMPORALITY_FILE_10h = get_testdata_file_path("multi_temporality_counters_10h.jsonl")
|
||||
MULTI_TEMPORALITY_FILE_24h = get_testdata_file_path("multi_temporality_counters_24h.jsonl")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time_aggregation, expected_value_at_31st_minute, expected_value_at_32nd_minute, steady_value",
|
||||
[
|
||||
("rate", 0.0167, 0.133, 0.0833),
|
||||
("increase", 2, 8, 5),
|
||||
],
|
||||
)
|
||||
def test_with_steady_values_and_reset(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
time_aggregation: str,
|
||||
expected_value_at_31st_minute: float,
|
||||
expected_value_at_32nd_minute: float,
|
||||
steady_value: float
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{time_aggregation}_stale"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
MULTI_TEMPORALITY_FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
time_aggregation,
|
||||
"sum",
|
||||
filter_expression='endpoint = "/orders"',
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
assert len(result_values) >= 59
|
||||
# the counter reset happened at 31st minute
|
||||
assert (
|
||||
result_values[30]["value"] == expected_value_at_31st_minute
|
||||
)
|
||||
assert (
|
||||
result_values[31]["value"] == expected_value_at_32nd_minute
|
||||
)
|
||||
assert (
|
||||
result_values[39]["value"] == steady_value
|
||||
) # 39th minute is when cumulative shifts to delta
|
||||
count_of_steady_rate = sum(1 for v in result_values if v["value"] == steady_value)
|
||||
assert (
|
||||
count_of_steady_rate >= 56
|
||||
) # 59 - (1 reset + 1 high rate + 1 at the beginning)
|
||||
# All rates should be non-negative (stale periods = 0 rate)
|
||||
for v in result_values:
|
||||
assert v["value"] >= 0, f"{time_aggregation} should not be negative: {v['value']}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time_aggregation, stable_health_value, stable_products_value, stable_checkout_value, spike_checkout_value, stable_orders_value, spike_users_value",
|
||||
[
|
||||
("rate", 0.167, 0.333, 0.0167, 0.833, 0.0833, 0.0167),
|
||||
("increase", 10, 20, 1, 50, 5, 1),
|
||||
],
|
||||
)
|
||||
def test_group_by_endpoint(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
time_aggregation: str,
|
||||
stable_health_value: float,
|
||||
stable_products_value: float,
|
||||
stable_checkout_value: float,
|
||||
spike_checkout_value: float,
|
||||
stable_orders_value: float,
|
||||
spike_users_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{time_aggregation}_groupby"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
MULTI_TEMPORALITY_FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
time_aggregation,
|
||||
"sum",
|
||||
group_by=["endpoint"],
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
all_series = get_all_series(data, "A")
|
||||
# Should have 5 different endpoints
|
||||
assert (
|
||||
len(all_series) == 5
|
||||
), f"Expected 5 series for 5 endpoints, got {len(all_series)}"
|
||||
|
||||
# endpoint -> values
|
||||
endpoint_values = {}
|
||||
for series in all_series:
|
||||
endpoint = series.get("labels", [{}])[0].get("value", "unknown")
|
||||
values = sorted(series.get("values", []), key=lambda x: x["timestamp"])
|
||||
endpoint_values[endpoint] = values
|
||||
|
||||
expected_endpoints = {"/products", "/health", "/checkout", "/orders", "/users"}
|
||||
assert (
|
||||
set(endpoint_values.keys()) == expected_endpoints
|
||||
), f"Expected endpoints {expected_endpoints}, got {set(endpoint_values.keys())}"
|
||||
|
||||
# at no point rate should be negative
|
||||
for endpoint, values in endpoint_values.items():
|
||||
for v in values:
|
||||
assert (
|
||||
v["value"] >= 0
|
||||
), f"Rate for {endpoint} should not be negative: {v['value']}"
|
||||
|
||||
# /health: 60 data points (t01-t60), steady +10/min
|
||||
health_values = endpoint_values["/health"]
|
||||
assert (
|
||||
len(health_values) >= 58
|
||||
), f"Expected >= 58 values for /health, got {len(health_values)}"
|
||||
count_steady_health = sum(1 for v in health_values if v["value"] == stable_health_value)
|
||||
assert (
|
||||
count_steady_health >= 57
|
||||
), f"Expected >= 57 steady rate values ({stable_health_value}) for /health, got {count_steady_health}"
|
||||
# all /health rates should be state except possibly first/last due to boundaries
|
||||
for v in health_values[1:-1]:
|
||||
assert v["value"] == stable_health_value, f"Expected /health rate {stable_health_value}, got {v['value']}"
|
||||
|
||||
# /products: 51 data points with 10-minute gap (t20-t29 missing), steady +20/min
|
||||
products_values = endpoint_values["/products"]
|
||||
assert (
|
||||
len(products_values) >= 49
|
||||
), f"Expected >= 49 values for /products, got {len(products_values)}"
|
||||
count_steady_products = sum(1 for v in products_values if v["value"] == stable_products_value)
|
||||
|
||||
# most values should be stable, some boundary values differ due to 10-min gap
|
||||
assert (
|
||||
count_steady_products >= 46
|
||||
), f"Expected >= 46 steady rate values ({stable_products_value}) for /products, got {count_steady_products}"
|
||||
|
||||
# check that non-stable values are due to gap averaging (should be lower)
|
||||
gap_boundary_values = [v["value"] for v in products_values if v["value"] != stable_products_value]
|
||||
for val in gap_boundary_values:
|
||||
assert (
|
||||
0 < val < stable_products_value
|
||||
), f"Gap boundary values should be between 0 and {stable_products_value}, got {val}"
|
||||
|
||||
# /checkout: 61 data points (t00-t60), +1/min normal, +50/min spike at t40-t44
|
||||
checkout_values = endpoint_values["/checkout"]
|
||||
assert (
|
||||
len(checkout_values) >= 59
|
||||
), f"Expected >= 59 values for /checkout, got {len(checkout_values)}"
|
||||
count_steady_checkout = sum(1 for v in checkout_values if v["value"] == stable_checkout_value)
|
||||
assert (
|
||||
count_steady_checkout >= 53
|
||||
), f"Expected >= 53 steady {time_aggregation} values ({stable_checkout_value}) for /checkout, got {count_steady_checkout}"
|
||||
# check that spike values exist (traffic spike +50/min at t40-t44)
|
||||
count_spike_checkout = sum(1 for v in checkout_values if v["value"] == spike_checkout_value)
|
||||
assert (
|
||||
count_spike_checkout >= 4
|
||||
), f"Expected >= 4 spike {time_aggregation} values ({spike_checkout_value}) for /checkout, got {count_spike_checkout}"
|
||||
|
||||
# spike values should be consecutive
|
||||
spike_indices = [
|
||||
i for i, v in enumerate(checkout_values) if v["value"] == spike_checkout_value
|
||||
]
|
||||
assert len(spike_indices) >= 4, f"Expected >= 4 spike indices, got {spike_indices}"
|
||||
# consecutiveness
|
||||
for i in range(1, len(spike_indices)):
|
||||
assert (
|
||||
spike_indices[i] == spike_indices[i - 1] + 1
|
||||
), f"Spike indices should be consecutive, got {spike_indices}"
|
||||
|
||||
# /orders: 60 data points (t00-t60) with gap at t30, counter reset at t31 (150->2)
|
||||
# reset at t31 causes: rate/increase at t30 includes gap (lower), t31 has high rate after reset
|
||||
orders_values = endpoint_values["/orders"]
|
||||
assert (
|
||||
len(orders_values) >= 58
|
||||
), f"Expected >= 58 values for /orders, got {len(orders_values)}"
|
||||
count_steady_orders = sum(1 for v in orders_values if v["value"] == stable_orders_value)
|
||||
assert (
|
||||
count_steady_orders >= 55
|
||||
), f"Expected >= 55 steady {time_aggregation} values ({stable_orders_value}) for /orders, got {count_steady_orders}"
|
||||
# check for counter reset effects - there should be some non-standard values
|
||||
non_standard_orders = [v["value"] for v in orders_values if v["value"] != stable_orders_value]
|
||||
assert (
|
||||
len(non_standard_orders) >= 2
|
||||
), f"Expected >= 2 non-standard values due to counter reset, got {non_standard_orders}"
|
||||
# post-reset value should be higher (new counter value / interval)
|
||||
high_rate_orders = [v for v in non_standard_orders if v > stable_orders_value]
|
||||
assert (
|
||||
len(high_rate_orders) >= 1
|
||||
), f"Expected at least one high {time_aggregation} value after counter reset, got {non_standard_orders}"
|
||||
|
||||
# /users: 56 data points (t05-t60), sparse +1 every 5 minutes
|
||||
users_values = endpoint_values["/users"]
|
||||
assert (
|
||||
len(users_values) >= 54
|
||||
), f"Expected >= 54 values for /users, got {len(users_values)}"
|
||||
count_zero_users = sum(1 for v in users_values if v["value"] == 0)
|
||||
# most values should be 0 (flat periods between increments)
|
||||
assert (
|
||||
count_zero_users >= 40
|
||||
), f"Expected >= 40 zero {time_aggregation} values for /users (sparse data), got {count_zero_users}"
|
||||
# non-zero values should be 0.0167 (1/60 increment rate)
|
||||
non_zero_users = [v["value"] for v in users_values if v["value"] != 0]
|
||||
count_increment_rate = sum(1 for v in non_zero_users if v == spike_users_value)
|
||||
assert (
|
||||
count_increment_rate >= 8
|
||||
), f"Expected >= 8 increment {time_aggregation} values ({spike_users_value}) for /users, got {count_increment_rate}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time_aggregation, expected_value_at_30th_minute, expected_value_at_31st_minute, value_at_switch",
|
||||
[
|
||||
("rate", 0.183, 0.183, 0.25),
|
||||
("increase", 11, 12, 15),
|
||||
],
|
||||
)
|
||||
def test_for_service_with_switch(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
time_aggregation: str,
|
||||
expected_value_at_30th_minute: float,
|
||||
expected_value_at_31st_minute: float,
|
||||
value_at_switch: float
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{time_aggregation}_count"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
MULTI_TEMPORALITY_FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
time_aggregation,
|
||||
"sum",
|
||||
filter_expression='service = "api"',
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
assert len(result_values) >= 60
|
||||
assert (
|
||||
result_values[30]["value"] == expected_value_at_30th_minute #0.183
|
||||
)
|
||||
assert (
|
||||
result_values[31]["value"] == expected_value_at_31st_minute # 0.183
|
||||
)
|
||||
assert (
|
||||
result_values[38]["value"] == value_at_switch # 0.25
|
||||
)
|
||||
assert (
|
||||
result_values[39]["value"] == value_at_switch # 0.25
|
||||
) # 39th minute is when cumulative shifts to delta
|
||||
# All rates should be non-negative (stale periods = 0 rate)
|
||||
for v in result_values:
|
||||
assert v["value"] >= 0, f"{time_aggregation} should not be negative: {v['value']}"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time_aggregation, expected_value",
|
||||
[
|
||||
("rate", 0.0122),
|
||||
("increase", 22),
|
||||
],
|
||||
)
|
||||
def test_for_week_long_time_range(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
time_aggregation: str,
|
||||
expected_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(days=7)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{time_aggregation}_" + hex(random.getrandbits(32))[2:]
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
MULTI_TEMPORALITY_FILE_10h,
|
||||
base_time=now - timedelta(minutes=600),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
time_aggregation,
|
||||
"sum",
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
# the zeroth value may not cover the whole time range
|
||||
for value in result_values[1:]:
|
||||
assert value["value"] == expected_value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"time_aggregation, expected_value",
|
||||
[
|
||||
("rate", 0.0122),
|
||||
("increase", 110),
|
||||
],
|
||||
)
|
||||
def test_for_month_long_time_range(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
time_aggregation: str,
|
||||
expected_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(days=31)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{time_aggregation}_" + hex(random.getrandbits(32))[2:]
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
MULTI_TEMPORALITY_FILE_24h,
|
||||
base_time=now - timedelta(minutes=1441),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
time_aggregation,
|
||||
"sum",
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
## the zeroth and last values may not cover the whole 9000 seconds
|
||||
for value in result_values[1:-1]:
|
||||
assert value["value"] == expected_value
|
||||
84
tests/integration/testdata/multi_temporality_counters_10h.jsonl
vendored
Normal file
84
tests/integration/testdata/multi_temporality_counters_10h.jsonl
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
288
tests/integration/testdata/multi_temporality_counters_1h.jsonl
vendored
Normal file
288
tests/integration/testdata/multi_temporality_counters_1h.jsonl
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:20:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:21:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:22:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:23:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:24:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:25:00+00:00","value":250,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:26:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:27:00+00:00","value":270,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:28:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:29:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":310,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":330,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":350,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":370,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:05:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:06:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:07:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:08:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:09:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:10:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:11:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:12:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:13:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:14:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:15:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:16:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:17:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:18:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:19:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:20:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:21:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:22:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:23:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:24:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:25:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:26:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:27:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:28:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:29:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:31:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:32:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:33:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:34:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:35:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:36:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:37:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:38:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:39:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:40:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:41:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:42:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:43:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:44:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:45:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:46:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:47:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:48:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:49:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:50:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:51:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:52:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:53:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:54:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:55:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:56:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:57:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:58:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:59:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":45,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":55,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":65,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":75,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":85,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":95,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:20:00+00:00","value":105,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:21:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:22:00+00:00","value":115,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:23:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:24:00+00:00","value":125,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:25:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:26:00+00:00","value":135,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:27:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:28:00+00:00","value":145,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:29:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":400,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":420,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":440,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":460,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":480,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":500,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":520,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":540,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":560,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":580,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":600,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":620,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":640,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":660,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":680,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":700,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":720,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":740,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":760,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":780,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":800,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":820,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":840,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":860,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":880,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":900,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":920,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":940,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":960,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":980,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":1000,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":1020,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:01:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:02:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:03:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:04:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:05:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:06:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:07:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:08:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:09:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:10:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:11:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:12:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:13:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:14:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:15:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:16:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:17:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:18:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:19:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:20:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:21:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:22:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:23:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:24:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:25:00+00:00","value":26,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:26:00+00:00","value":27,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:27:00+00:00","value":28,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:28:00+00:00","value":29,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:29:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:30:00+00:00","value":31,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:31:00+00:00","value":32,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:32:00+00:00","value":33,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:33:00+00:00","value":34,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:34:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:35:00+00:00","value":36,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:36:00+00:00","value":37,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:37:00+00:00","value":38,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:38:00+00:00","value":39,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:39:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:40:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:41:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:42:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:43:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:44:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:45:00+00:00","value":291,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:46:00+00:00","value":292,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:47:00+00:00","value":293,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:48:00+00:00","value":294,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:49:00+00:00","value":295,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:50:00+00:00","value":296,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:51:00+00:00","value":297,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:52:00+00:00","value":298,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:53:00+00:00","value":299,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:54:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:55:00+00:00","value":301,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:56:00+00:00","value":302,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:57:00+00:00","value":303,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:58:00+00:00","value":304,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:59:00+00:00","value":305,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T11:00:00+00:00","value":306,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
196
tests/integration/testdata/multi_temporality_counters_24h.jsonl
vendored
Normal file
196
tests/integration/testdata/multi_temporality_counters_24h.jsonl
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T00:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T00:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T01:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T01:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T02:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T02:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T03:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T03:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T04:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T04:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T05:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T05:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T06:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T06:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T07:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T07:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T08:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T08:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T09:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T09:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:30:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T21:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T21:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T22:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T22:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T23:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T23:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-11T00:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":250,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":270,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":310,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":330,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":350,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":370,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":390,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":400,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":410,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":420,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":430,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":440,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":450,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":460,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":470,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":480,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":490,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":26,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":27,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":28,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":29,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":31,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":32,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":33,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":34,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":36,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":37,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":38,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":39,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":41,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":42,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":43,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":44,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":45,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":46,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":47,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":48,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":49,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
|
||||
Reference in New Issue
Block a user