mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-05 18:10:31 +01:00
Compare commits
1 Commits
fix/rule-d
...
issue/4817
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38d516cbbe |
@@ -301,20 +301,34 @@ components:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesGettableAuthDomain:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
properties:
|
||||
authNProviderInfo:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProviderInfo'
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
googleAuthConfig:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
oidcConfig:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
orgId:
|
||||
type: string
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
samlConfig:
|
||||
$ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
ssoType:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -575,7 +589,7 @@ components:
|
||||
- relation
|
||||
- object
|
||||
type: object
|
||||
AuthtypesUpdatableAuthDomain:
|
||||
AuthtypesUpdateableAuthDomain:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
@@ -4348,19 +4362,18 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesEvaluationEnvelope:
|
||||
discriminator:
|
||||
mapping:
|
||||
cumulative: '#/components/schemas/RuletypesEvaluationCumulative'
|
||||
rolling: '#/components/schemas/RuletypesEvaluationRolling'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationRolling'
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationCumulative'
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec: {}
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesEvaluationKind:
|
||||
enum:
|
||||
@@ -4373,9 +4386,6 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesGettableTestRule:
|
||||
properties:
|
||||
@@ -4683,12 +4693,15 @@ components:
|
||||
- compositeQuery
|
||||
type: object
|
||||
RuletypesRuleThresholdData:
|
||||
discriminator:
|
||||
mapping:
|
||||
basic: '#/components/schemas/RuletypesThresholdBasic'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/RuletypesThresholdBasic'
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
spec: {}
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesRuleType:
|
||||
enum:
|
||||
@@ -4730,9 +4743,6 @@ components:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
RuletypesThresholdKind:
|
||||
enum:
|
||||
@@ -7069,20 +7079,20 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesPostableAuthDomain'
|
||||
responses:
|
||||
"201":
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
$ref: '#/components/schemas/AuthtypesGettableAuthDomain'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
@@ -7238,7 +7248,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesUpdatableAuthDomain'
|
||||
$ref: '#/components/schemas/AuthtypesUpdateableAuthDomain'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
|
||||
@@ -19,8 +19,8 @@ import type {
|
||||
|
||||
import type {
|
||||
AuthtypesPostableAuthDomainDTO,
|
||||
AuthtypesUpdatableAuthDomainDTO,
|
||||
CreateAuthDomain201,
|
||||
AuthtypesUpdateableAuthDomainDTO,
|
||||
CreateAuthDomain200,
|
||||
DeleteAuthDomainPathParameters,
|
||||
GetAuthDomain200,
|
||||
GetAuthDomainPathParameters,
|
||||
@@ -126,7 +126,7 @@ export const createAuthDomain = (
|
||||
authtypesPostableAuthDomainDTO: BodyType<AuthtypesPostableAuthDomainDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateAuthDomain201>({
|
||||
return GeneratedAPIInstance<CreateAuthDomain200>({
|
||||
url: `/api/v1/domains`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -388,13 +388,13 @@ export const invalidateGetAuthDomain = async (
|
||||
*/
|
||||
export const updateAuthDomain = (
|
||||
{ id }: UpdateAuthDomainPathParameters,
|
||||
authtypesUpdatableAuthDomainDTO: BodyType<AuthtypesUpdatableAuthDomainDTO>,
|
||||
authtypesUpdateableAuthDomainDTO: BodyType<AuthtypesUpdateableAuthDomainDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/domains/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesUpdatableAuthDomainDTO,
|
||||
data: authtypesUpdateableAuthDomainDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -407,7 +407,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -416,7 +416,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -433,7 +433,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
Awaited<ReturnType<typeof updateAuthDomain>>,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -448,7 +448,7 @@ export type UpdateAuthDomainMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateAuthDomain>>
|
||||
>;
|
||||
export type UpdateAuthDomainMutationBody =
|
||||
BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
export type UpdateAuthDomainMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -463,7 +463,7 @@ export const useUpdateAuthDomain = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -472,7 +472,7 @@ export const useUpdateAuthDomain = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
|
||||
@@ -1641,32 +1641,109 @@ export interface AuthtypesCallbackAuthNSupportDTO {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesGettableAuthDomainDTO {
|
||||
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
|
||||
config?: AuthtypesAuthDomainConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
export type AuthtypesGettableAuthDomainDTO =
|
||||
| (AuthtypesSamlConfigDTO & {
|
||||
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
googleAuthConfig?: AuthtypesGoogleConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
oidcConfig?: AuthtypesOIDCConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId?: string;
|
||||
roleMapping?: AuthtypesRoleMappingDTO;
|
||||
samlConfig?: AuthtypesSamlConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
ssoEnabled?: boolean;
|
||||
ssoType?: AuthtypesAuthNProviderDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
})
|
||||
| (AuthtypesGoogleConfigDTO & {
|
||||
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
googleAuthConfig?: AuthtypesGoogleConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
oidcConfig?: AuthtypesOIDCConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId?: string;
|
||||
roleMapping?: AuthtypesRoleMappingDTO;
|
||||
samlConfig?: AuthtypesSamlConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
ssoEnabled?: boolean;
|
||||
ssoType?: AuthtypesAuthNProviderDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
})
|
||||
| (AuthtypesOIDCConfigDTO & {
|
||||
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
googleAuthConfig?: AuthtypesGoogleConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
oidcConfig?: AuthtypesOIDCConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId?: string;
|
||||
roleMapping?: AuthtypesRoleMappingDTO;
|
||||
samlConfig?: AuthtypesSamlConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
ssoEnabled?: boolean;
|
||||
ssoType?: AuthtypesAuthNProviderDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
});
|
||||
|
||||
export interface AuthtypesGettableObjectsDTO {
|
||||
resource: AuthtypesResourceDTO;
|
||||
@@ -1990,7 +2067,7 @@ export interface AuthtypesTransactionDTO {
|
||||
relation: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesUpdatableAuthDomainDTO {
|
||||
export interface AuthtypesUpdateableAuthDomainDTO {
|
||||
config?: AuthtypesAuthDomainConfigDTO;
|
||||
}
|
||||
|
||||
@@ -6578,36 +6655,28 @@ export interface RuletypesCumulativeWindowDTO {
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
export enum RuletypesEvaluationCumulativeDTOKind {
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
export interface RuletypesEvaluationCumulativeDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum cumulative
|
||||
*/
|
||||
kind: RuletypesEvaluationCumulativeDTOKind;
|
||||
spec: RuletypesCumulativeWindowDTO;
|
||||
kind?: RuletypesEvaluationKindDTO;
|
||||
spec?: RuletypesCumulativeWindowDTO;
|
||||
}
|
||||
|
||||
export type RuletypesEvaluationEnvelopeDTO =
|
||||
| RuletypesEvaluationRollingDTO
|
||||
| RuletypesEvaluationCumulativeDTO;
|
||||
| (RuletypesEvaluationRollingDTO & {
|
||||
kind: RuletypesEvaluationKindDTO;
|
||||
spec: unknown;
|
||||
})
|
||||
| (RuletypesEvaluationCumulativeDTO & {
|
||||
kind: RuletypesEvaluationKindDTO;
|
||||
spec: unknown;
|
||||
});
|
||||
|
||||
export enum RuletypesEvaluationKindDTO {
|
||||
rolling = 'rolling',
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
export enum RuletypesEvaluationRollingDTOKind {
|
||||
rolling = 'rolling',
|
||||
}
|
||||
export interface RuletypesEvaluationRollingDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum rolling
|
||||
*/
|
||||
kind: RuletypesEvaluationRollingDTOKind;
|
||||
spec: RuletypesRollingWindowDTO;
|
||||
kind?: RuletypesEvaluationKindDTO;
|
||||
spec?: RuletypesRollingWindowDTO;
|
||||
}
|
||||
|
||||
export interface RuletypesGettableTestRuleDTO {
|
||||
@@ -6962,7 +7031,10 @@ export interface RuletypesRuleConditionDTO {
|
||||
thresholds?: RuletypesRuleThresholdDataDTO;
|
||||
}
|
||||
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO;
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO & {
|
||||
kind: RuletypesThresholdKindDTO;
|
||||
spec: unknown;
|
||||
};
|
||||
|
||||
export enum RuletypesRuleTypeDTO {
|
||||
threshold_rule = 'threshold_rule',
|
||||
@@ -6998,16 +7070,9 @@ export enum RuletypesSeasonalityDTO {
|
||||
daily = 'daily',
|
||||
weekly = 'weekly',
|
||||
}
|
||||
export enum RuletypesThresholdBasicDTOKind {
|
||||
basic = 'basic',
|
||||
}
|
||||
export interface RuletypesThresholdBasicDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum basic
|
||||
*/
|
||||
kind: RuletypesThresholdBasicDTOKind;
|
||||
spec: RuletypesBasicRuleThresholdsDTO;
|
||||
kind?: RuletypesThresholdKindDTO;
|
||||
spec?: RuletypesBasicRuleThresholdsDTO;
|
||||
}
|
||||
|
||||
export enum RuletypesThresholdKindDTO {
|
||||
@@ -8367,8 +8432,8 @@ export type ListAuthDomains200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateAuthDomain201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
export type CreateAuthDomain200 = {
|
||||
data: AuthtypesGettableAuthDomainDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
|
||||
@@ -437,11 +437,16 @@ export function convertTraceOperatorToV5(
|
||||
panelType,
|
||||
);
|
||||
|
||||
// Skip aggregation for raw request type
|
||||
// Skip aggregation for raw request type. Force dataSource to traces so
|
||||
// createAggregation never takes the metrics branch (which would emit a
|
||||
// metricName field the backend rejects for trace operators).
|
||||
const aggregations =
|
||||
requestType === 'raw'
|
||||
? undefined
|
||||
: createAggregation(traceOperatorData, panelType);
|
||||
: createAggregation(
|
||||
{ ...traceOperatorData, dataSource: DataSource.TRACES },
|
||||
panelType,
|
||||
);
|
||||
|
||||
const spec: QueryEnvelope['spec'] = {
|
||||
name: queryName,
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
getUserExpressionFromCombined,
|
||||
} from '../utils';
|
||||
import { QuerySearchV2Context } from './context';
|
||||
import type { QuerySearchV2ContextValue } from './QuerySearchV2.store';
|
||||
import { createExpressionStore } from './QuerySearchV2.store';
|
||||
|
||||
export interface QuerySearchV2ProviderProps {
|
||||
queryParamKey: string;
|
||||
initialExpression?: string;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
persistOnUnmount?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that creates a scoped zustand store and exposes
|
||||
* expression state to children via context.
|
||||
*/
|
||||
export function QuerySearchV2Provider({
|
||||
initialExpression = '',
|
||||
persistOnUnmount = false,
|
||||
queryParamKey,
|
||||
children,
|
||||
}: QuerySearchV2ProviderProps): JSX.Element {
|
||||
const storeRef = useRef(createExpressionStore());
|
||||
const store = storeRef.current;
|
||||
|
||||
const [urlExpression, setUrlExpression] = useQueryState(
|
||||
queryParamKey,
|
||||
parseAsString,
|
||||
);
|
||||
|
||||
const committedExpression = useStore(store, (s) => s.committedExpression);
|
||||
const setInputExpression = useStore(store, (s) => s.setInputExpression);
|
||||
const commitExpression = useStore(store, (s) => s.commitExpression);
|
||||
const initializeFromUrl = useStore(store, (s) => s.initializeFromUrl);
|
||||
const resetExpression = useStore(store, (s) => s.resetExpression);
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!isInitialized.current && urlExpression) {
|
||||
const cleanedExpression = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
urlExpression,
|
||||
);
|
||||
initializeFromUrl(cleanedExpression);
|
||||
isInitialized.current = true;
|
||||
}
|
||||
}, [urlExpression, initialExpression, initializeFromUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current || !urlExpression) {
|
||||
setUrlExpression(committedExpression || null);
|
||||
}
|
||||
}, [committedExpression, setUrlExpression, urlExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
if (!persistOnUnmount) {
|
||||
setUrlExpression(null);
|
||||
resetExpression();
|
||||
}
|
||||
};
|
||||
}, [persistOnUnmount, setUrlExpression, resetExpression]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(expression: string): void => {
|
||||
const userOnly = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
expression,
|
||||
);
|
||||
setInputExpression(userOnly);
|
||||
},
|
||||
[initialExpression, setInputExpression],
|
||||
);
|
||||
|
||||
const handleRun = useCallback(
|
||||
(expression: string): void => {
|
||||
const userOnly = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
expression,
|
||||
);
|
||||
commitExpression(userOnly);
|
||||
},
|
||||
[initialExpression, commitExpression],
|
||||
);
|
||||
|
||||
const combinedExpression = useMemo(
|
||||
() => combineInitialAndUserExpression(initialExpression, committedExpression),
|
||||
[initialExpression, committedExpression],
|
||||
);
|
||||
|
||||
const contextValue = useMemo<QuerySearchV2ContextValue>(
|
||||
() => ({
|
||||
expression: combinedExpression,
|
||||
userExpression: committedExpression,
|
||||
initialExpression,
|
||||
querySearchProps: {
|
||||
initialExpression: initialExpression.trim() ? initialExpression : undefined,
|
||||
onChange: handleChange,
|
||||
onRun: handleRun,
|
||||
},
|
||||
}),
|
||||
[
|
||||
combinedExpression,
|
||||
committedExpression,
|
||||
initialExpression,
|
||||
handleChange,
|
||||
handleRun,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<QuerySearchV2Context.Provider value={contextValue}>
|
||||
{children}
|
||||
</QuerySearchV2Context.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { createStore, StoreApi } from 'zustand';
|
||||
|
||||
export type QuerySearchV2Store = {
|
||||
/**
|
||||
* User-typed expression (local state, updates on typing)
|
||||
*/
|
||||
inputExpression: string;
|
||||
/**
|
||||
* Committed expression (synced to URL, updates on submit)
|
||||
*/
|
||||
committedExpression: string;
|
||||
setInputExpression: (expression: string) => void;
|
||||
commitExpression: (expression: string) => void;
|
||||
resetExpression: () => void;
|
||||
initializeFromUrl: (urlExpression: string) => void;
|
||||
};
|
||||
|
||||
export interface QuerySearchProps {
|
||||
initialExpression: string | undefined;
|
||||
onChange: (expression: string) => void;
|
||||
onRun: (expression: string) => void;
|
||||
}
|
||||
|
||||
export interface QuerySearchV2ContextValue {
|
||||
/**
|
||||
* Combined expression: "initialExpression AND (userExpression)"
|
||||
*/
|
||||
expression: string;
|
||||
userExpression: string;
|
||||
initialExpression: string;
|
||||
querySearchProps: QuerySearchProps;
|
||||
}
|
||||
|
||||
export function createExpressionStore(): StoreApi<QuerySearchV2Store> {
|
||||
return createStore<QuerySearchV2Store>((set) => ({
|
||||
inputExpression: '',
|
||||
committedExpression: '',
|
||||
setInputExpression: (expression: string): void => {
|
||||
set({ inputExpression: expression });
|
||||
},
|
||||
commitExpression: (expression: string): void => {
|
||||
set({
|
||||
inputExpression: expression,
|
||||
committedExpression: expression,
|
||||
});
|
||||
},
|
||||
resetExpression: (): void => {
|
||||
set({
|
||||
inputExpression: '',
|
||||
committedExpression: '',
|
||||
});
|
||||
},
|
||||
initializeFromUrl: (urlExpression: string): void => {
|
||||
set({
|
||||
inputExpression: urlExpression,
|
||||
committedExpression: urlExpression,
|
||||
});
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { useQuerySearchV2Context } from '../context';
|
||||
import {
|
||||
QuerySearchV2Provider,
|
||||
QuerySearchV2ProviderProps,
|
||||
} from '../QuerySearchV2.provider';
|
||||
|
||||
const mockSetQueryState = jest.fn();
|
||||
let mockUrlValue: string | null = null;
|
||||
|
||||
jest.mock('nuqs', () => ({
|
||||
parseAsString: {},
|
||||
useQueryState: jest.fn(() => [mockUrlValue, mockSetQueryState]),
|
||||
}));
|
||||
|
||||
function createWrapper(
|
||||
props: Partial<QuerySearchV2ProviderProps> = {},
|
||||
): ({ children }: { children: ReactNode }) => JSX.Element {
|
||||
return function Wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
return (
|
||||
<QuerySearchV2Provider queryParamKey="testExpression" {...props}>
|
||||
{children}
|
||||
</QuerySearchV2Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
describe('QuerySearchExpressionProvider', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUrlValue = null;
|
||||
});
|
||||
|
||||
it('should provide initial context values', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe('');
|
||||
expect(result.current.userExpression).toBe('');
|
||||
expect(result.current.initialExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should combine initialExpression with userExpression', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper({ initialExpression: 'k8s.pod.name = "my-pod"' }),
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe('k8s.pod.name = "my-pod"');
|
||||
expect(result.current.initialExpression).toBe('k8s.pod.name = "my-pod"');
|
||||
|
||||
act(() => {
|
||||
result.current.querySearchProps.onChange('service = "api"');
|
||||
});
|
||||
act(() => {
|
||||
result.current.querySearchProps.onRun('service = "api"');
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe(
|
||||
'k8s.pod.name = "my-pod" AND (service = "api")',
|
||||
);
|
||||
expect(result.current.userExpression).toBe('service = "api"');
|
||||
});
|
||||
|
||||
it('should provide querySearchProps with correct callbacks', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper({ initialExpression: 'initial' }),
|
||||
});
|
||||
|
||||
expect(result.current.querySearchProps.initialExpression).toBe('initial');
|
||||
expect(typeof result.current.querySearchProps.onChange).toBe('function');
|
||||
expect(typeof result.current.querySearchProps.onRun).toBe('function');
|
||||
});
|
||||
|
||||
it('should initialize from URL value on mount', () => {
|
||||
mockUrlValue = 'status = 500';
|
||||
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.userExpression).toBe('status = 500');
|
||||
expect(result.current.expression).toBe('status = 500');
|
||||
});
|
||||
|
||||
it('should throw error when used outside provider', () => {
|
||||
expect(() => {
|
||||
renderHook(() => useQuerySearchV2Context());
|
||||
}).toThrow(
|
||||
'useQuerySearchV2Context must be used within a QuerySearchV2Provider',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { createExpressionStore } from '../QuerySearchV2.store';
|
||||
|
||||
describe('createExpressionStore', () => {
|
||||
it('should create a store with initial state', () => {
|
||||
const store = createExpressionStore();
|
||||
const state = store.getState();
|
||||
|
||||
expect(state.inputExpression).toBe('');
|
||||
expect(state.committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should update inputExpression via setInputExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('service.name = "api"');
|
||||
expect(store.getState().committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should update both expressions via commitExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
store.getState().commitExpression('service.name = "api"');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('service.name = "api"');
|
||||
expect(store.getState().committedExpression).toBe('service.name = "api"');
|
||||
});
|
||||
|
||||
it('should reset all state via resetExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
store.getState().commitExpression('service.name = "api"');
|
||||
store.getState().resetExpression();
|
||||
|
||||
expect(store.getState().inputExpression).toBe('');
|
||||
expect(store.getState().committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should initialize from URL value', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().initializeFromUrl('status = 500');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('status = 500');
|
||||
expect(store.getState().committedExpression).toBe('status = 500');
|
||||
});
|
||||
|
||||
it('should create isolated store instances', () => {
|
||||
const store1 = createExpressionStore();
|
||||
const store2 = createExpressionStore();
|
||||
|
||||
store1.getState().setInputExpression('expr1');
|
||||
store2.getState().setInputExpression('expr2');
|
||||
|
||||
expect(store1.getState().inputExpression).toBe('expr1');
|
||||
expect(store2.getState().inputExpression).toBe('expr2');
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports -- React Context required for scoped store pattern
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import type { QuerySearchV2ContextValue } from './QuerySearchV2.store';
|
||||
|
||||
export const QuerySearchV2Context =
|
||||
createContext<QuerySearchV2ContextValue | null>(null);
|
||||
|
||||
export function useQuerySearchV2Context(): QuerySearchV2ContextValue {
|
||||
const context = useContext(QuerySearchV2Context);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useQuerySearchV2Context must be used within a QuerySearchV2Provider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export { useQuerySearchV2Context } from './context';
|
||||
export type { QuerySearchV2ProviderProps } from './QuerySearchV2.provider';
|
||||
export { QuerySearchV2Provider } from './QuerySearchV2.provider';
|
||||
export type {
|
||||
QuerySearchProps,
|
||||
QuerySearchV2ContextValue,
|
||||
QuerySearchV2Store,
|
||||
} from './QuerySearchV2.store';
|
||||
@@ -19,13 +19,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.query-search-initial-scope-label {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.query-where-clause-editor {
|
||||
flex: 1;
|
||||
min-width: 400px;
|
||||
@@ -60,10 +53,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hasInitialExpression .cm-editor .cm-content {
|
||||
padding-left: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
@@ -79,6 +68,7 @@
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
padding: 0px !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border);
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useDashboardVariablesByType } from 'hooks/dashboard/useDashboardVariabl
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { debounce, isNull } from 'lodash-es';
|
||||
import { Filter, Info, TriangleAlert } from 'lucide-react';
|
||||
import { Info, TriangleAlert } from 'lucide-react';
|
||||
import {
|
||||
IDetailedError,
|
||||
IQueryContext,
|
||||
@@ -47,7 +47,6 @@ import { validateQuery } from 'utils/queryValidationUtils';
|
||||
import { unquote } from 'utils/stringUtils';
|
||||
|
||||
import { queryExamples } from './constants';
|
||||
import { combineInitialAndUserExpression } from './utils';
|
||||
|
||||
import './QuerySearch.styles.scss';
|
||||
|
||||
@@ -86,8 +85,6 @@ interface QuerySearchProps {
|
||||
hardcodedAttributeKeys?: QueryKeyDataSuggestionsProps[];
|
||||
onRun?: (query: string) => void;
|
||||
showFilterSuggestionsWithoutMetric?: boolean;
|
||||
/** When set, the editor shows only the user expression; API/filter uses `initial AND (user)`. */
|
||||
initialExpression?: string;
|
||||
}
|
||||
|
||||
function QuerySearch({
|
||||
@@ -99,7 +96,6 @@ function QuerySearch({
|
||||
signalSource,
|
||||
hardcodedAttributeKeys,
|
||||
showFilterSuggestionsWithoutMetric,
|
||||
initialExpression,
|
||||
}: QuerySearchProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
|
||||
@@ -116,26 +112,18 @@ function QuerySearch({
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const editorRef = useRef<EditorView | null>(null);
|
||||
|
||||
const isScopedFilter = initialExpression !== undefined;
|
||||
|
||||
const validateExpressionForEditor = useCallback(
|
||||
(editorDoc: string): void => {
|
||||
const toValidate = isScopedFilter
|
||||
? combineInitialAndUserExpression(initialExpression ?? '', editorDoc)
|
||||
: editorDoc;
|
||||
try {
|
||||
const validationResponse = validateQuery(toValidate);
|
||||
setValidation(validationResponse);
|
||||
} catch (error) {
|
||||
setValidation({
|
||||
isValid: false,
|
||||
message: 'Failed to process query',
|
||||
errors: [error as IDetailedError],
|
||||
});
|
||||
}
|
||||
},
|
||||
[initialExpression, isScopedFilter],
|
||||
);
|
||||
const handleQueryValidation = useCallback((newExpression: string): void => {
|
||||
try {
|
||||
const validationResponse = validateQuery(newExpression);
|
||||
setValidation(validationResponse);
|
||||
} catch (error) {
|
||||
setValidation({
|
||||
isValid: false,
|
||||
message: 'Failed to process query',
|
||||
errors: [error as IDetailedError],
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getCurrentExpression = useCallback(
|
||||
(): string => editorRef.current?.state.doc.toString() || '',
|
||||
@@ -177,8 +165,6 @@ function QuerySearch({
|
||||
setIsEditorReady(true);
|
||||
}, []);
|
||||
|
||||
const prevQueryDataExpressionRef = useRef<string | undefined>();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!isEditorReady) {
|
||||
@@ -187,22 +173,13 @@ function QuerySearch({
|
||||
|
||||
const newExpression = queryData.filter?.expression || '';
|
||||
const currentExpression = getCurrentExpression();
|
||||
const prevExpression = prevQueryDataExpressionRef.current;
|
||||
|
||||
// Only sync editor when queryData.filter?.expression actually changed from external source
|
||||
// Not when focus changed (which would reset uncommitted user input)
|
||||
const queryDataExpressionChanged = prevExpression !== newExpression;
|
||||
prevQueryDataExpressionRef.current = newExpression;
|
||||
|
||||
if (
|
||||
queryDataExpressionChanged &&
|
||||
newExpression !== currentExpression &&
|
||||
!isFocused
|
||||
) {
|
||||
// Do not update codemirror editor if the expression is the same
|
||||
if (newExpression !== currentExpression && !isFocused) {
|
||||
updateEditorValue(newExpression, { skipOnChange: true });
|
||||
}
|
||||
if (!isFocused) {
|
||||
validateExpressionForEditor(currentExpression);
|
||||
if (newExpression) {
|
||||
handleQueryValidation(newExpression);
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -307,7 +284,7 @@ function QuerySearch({
|
||||
}
|
||||
});
|
||||
}
|
||||
setKeySuggestions([...merged.values()]);
|
||||
setKeySuggestions(Array.from(merged.values()));
|
||||
|
||||
// Force reopen the completion if editor is available and focused
|
||||
if (editorRef.current) {
|
||||
@@ -360,7 +337,7 @@ function QuerySearch({
|
||||
// If value contains single quotes, escape them and wrap in single quotes
|
||||
if (value.includes("'")) {
|
||||
// Replace single quotes with escaped single quotes
|
||||
const escapedValue = value.replaceAll(/'/g, "\\'");
|
||||
const escapedValue = value.replace(/'/g, "\\'");
|
||||
return `'${escapedValue}'`;
|
||||
}
|
||||
|
||||
@@ -637,7 +614,7 @@ function QuerySearch({
|
||||
|
||||
const handleBlur = (): void => {
|
||||
const currentExpression = getCurrentExpression();
|
||||
validateExpressionForEditor(currentExpression);
|
||||
handleQueryValidation(currentExpression);
|
||||
setIsFocused(false);
|
||||
};
|
||||
|
||||
@@ -655,6 +632,7 @@ function QuerySearch({
|
||||
);
|
||||
|
||||
const handleExampleClick = (exampleQuery: string): void => {
|
||||
// If there's an existing query, append the example with AND
|
||||
const currentExpression = getCurrentExpression();
|
||||
const newExpression = currentExpression
|
||||
? `${currentExpression} AND ${exampleQuery}`
|
||||
@@ -919,12 +897,12 @@ function QuerySearch({
|
||||
|
||||
// If we have previous pairs, we can prioritize keys that haven't been used yet
|
||||
if (queryContext.queryPairs && queryContext.queryPairs.length > 0) {
|
||||
const usedKeys = new Set(queryContext.queryPairs.map((pair) => pair.key));
|
||||
const usedKeys = queryContext.queryPairs.map((pair) => pair.key);
|
||||
|
||||
// Add boost to unused keys to prioritize them
|
||||
options = options.map((option) => ({
|
||||
...option,
|
||||
boost: usedKeys.has(option.label) ? -10 : 10,
|
||||
boost: usedKeys.includes(option.label) ? -10 : 10,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1339,19 +1317,6 @@ function QuerySearch({
|
||||
)}
|
||||
|
||||
<div className="query-where-clause-editor-container">
|
||||
{isScopedFilter ? (
|
||||
<Tooltip title={initialExpression || ''} placement="left">
|
||||
<div className="query-search-initial-scope-label">
|
||||
<Filter
|
||||
size={14}
|
||||
style={{
|
||||
opacity: 0.9,
|
||||
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip
|
||||
title={<div data-log-detail-ignore="true">{getTooltipContent()}</div>}
|
||||
placement="left"
|
||||
@@ -1391,7 +1356,6 @@ function QuerySearch({
|
||||
className={cx('query-where-clause-editor', {
|
||||
isValid: validation.isValid === true,
|
||||
hasErrors: validation.errors.length > 0,
|
||||
hasInitialExpression: isScopedFilter,
|
||||
})}
|
||||
extensions={[
|
||||
autocompletion({
|
||||
@@ -1426,12 +1390,7 @@ function QuerySearch({
|
||||
// Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS
|
||||
run: (): boolean => {
|
||||
if (onRun && typeof onRun === 'function') {
|
||||
const user = getCurrentExpression();
|
||||
onRun(
|
||||
isScopedFilter
|
||||
? combineInitialAndUserExpression(initialExpression ?? '', user)
|
||||
: user,
|
||||
);
|
||||
onRun(getCurrentExpression());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -1596,7 +1555,6 @@ QuerySearch.defaultProps = {
|
||||
placeholder:
|
||||
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')",
|
||||
showFilterSuggestionsWithoutMetric: false,
|
||||
initialExpression: undefined,
|
||||
};
|
||||
|
||||
export default QuerySearch;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
getUserExpressionFromCombined,
|
||||
} from '../utils';
|
||||
|
||||
describe('entityLogsExpression', () => {
|
||||
describe('combineInitialAndUserExpression', () => {
|
||||
it('returns user when initial is empty', () => {
|
||||
expect(combineInitialAndUserExpression('', 'body contains error')).toBe(
|
||||
'body contains error',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns initial when user is empty', () => {
|
||||
expect(combineInitialAndUserExpression('k8s.pod.name = "x"', '')).toBe(
|
||||
'k8s.pod.name = "x"',
|
||||
);
|
||||
});
|
||||
|
||||
it('wraps user in parentheses with AND', () => {
|
||||
expect(
|
||||
combineInitialAndUserExpression('k8s.pod.name = "x"', 'body = "a"'),
|
||||
).toBe('k8s.pod.name = "x" AND (body = "a")');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserExpressionFromCombined', () => {
|
||||
it('returns empty when combined equals initial', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined('k8s.pod.name = "x"', 'k8s.pod.name = "x"'),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('extracts user from wrapped form', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined(
|
||||
'k8s.pod.name = "x"',
|
||||
'k8s.pod.name = "x" AND (body = "a")',
|
||||
),
|
||||
).toBe('body = "a"');
|
||||
});
|
||||
|
||||
it('extracts user from legacy AND without parens', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined(
|
||||
'k8s.pod.name = "x"',
|
||||
'k8s.pod.name = "x" AND body = "a"',
|
||||
),
|
||||
).toBe('body = "a"');
|
||||
});
|
||||
|
||||
it('returns full combined when initial is empty', () => {
|
||||
expect(getUserExpressionFromCombined('', 'service.name = "a"')).toBe(
|
||||
'service.name = "a"',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
export function combineInitialAndUserExpression(
|
||||
initial: string,
|
||||
user: string,
|
||||
): string {
|
||||
const i = initial.trim();
|
||||
const u = user.trim();
|
||||
if (!i) {
|
||||
return u;
|
||||
}
|
||||
if (!u) {
|
||||
return i;
|
||||
}
|
||||
return `${i} AND (${u})`;
|
||||
}
|
||||
|
||||
export function getUserExpressionFromCombined(
|
||||
initial: string,
|
||||
combined: string | null | undefined,
|
||||
): string {
|
||||
const i = initial.trim();
|
||||
const c = (combined ?? '').trim();
|
||||
if (!c) {
|
||||
return '';
|
||||
}
|
||||
if (!i) {
|
||||
return c;
|
||||
}
|
||||
if (c === i) {
|
||||
return '';
|
||||
}
|
||||
const wrappedPrefix = `${i} AND (`;
|
||||
if (c.startsWith(wrappedPrefix) && c.endsWith(')')) {
|
||||
return c.slice(wrappedPrefix.length, -1);
|
||||
}
|
||||
const plainPrefix = `${i} AND `;
|
||||
if (c.startsWith(plainPrefix)) {
|
||||
return c.slice(plainPrefix.length);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export type {
|
||||
QuerySearchProps,
|
||||
QuerySearchV2ContextValue,
|
||||
QuerySearchV2ProviderProps,
|
||||
} from './QueryV2/QuerySearch/Provider';
|
||||
export {
|
||||
QuerySearchV2Provider,
|
||||
useQuerySearchV2Context,
|
||||
} from './QueryV2/QuerySearch/Provider';
|
||||
export { QueryBuilderV2 } from './QueryBuilderV2';
|
||||
export {
|
||||
QueryBuilderV2Provider,
|
||||
useQueryBuilderV2Context,
|
||||
} from './QueryBuilderV2Context';
|
||||
@@ -220,6 +220,7 @@ export function buildCreateThresholdAlertRulePayload(
|
||||
builderQueries: {
|
||||
...mapQueryDataToApi(query.builder.queryData, 'queryName').data,
|
||||
...mapQueryDataToApi(query.builder.queryFormulas, 'queryName').data,
|
||||
...mapQueryDataToApi(query.builder.queryTraceOperator, 'queryName').data,
|
||||
},
|
||||
promQueries: mapQueryDataToApi(query.promql, 'name').data,
|
||||
chQueries: mapQueryDataToApi(query.clickhouse_sql, 'name').data,
|
||||
|
||||
@@ -60,7 +60,7 @@ function CreateOrEdit(props: CreateOrEditProps): JSX.Element {
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const [authnProvider, setAuthnProvider] = useState<
|
||||
AuthtypesAuthNProviderDTO | ''
|
||||
>(record?.config?.ssoType || '');
|
||||
>(record?.ssoType || '');
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
@@ -112,26 +112,21 @@ export function prepareInitialValues(
|
||||
};
|
||||
}
|
||||
|
||||
const config = record.config ?? {};
|
||||
return {
|
||||
name: record.name,
|
||||
ssoEnabled: config.ssoEnabled,
|
||||
ssoType: config.ssoType,
|
||||
samlConfig: config.samlConfig ?? undefined,
|
||||
oidcConfig: config.oidcConfig ?? undefined,
|
||||
googleAuthConfig: config.googleAuthConfig
|
||||
...record,
|
||||
googleAuthConfig: record.googleAuthConfig
|
||||
? {
|
||||
...config.googleAuthConfig,
|
||||
...record.googleAuthConfig,
|
||||
domainToAdminEmailList: convertDomainMappingsToList(
|
||||
config.googleAuthConfig.domainToAdminEmail,
|
||||
record.googleAuthConfig.domainToAdminEmail,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
roleMapping: config.roleMapping
|
||||
roleMapping: record.roleMapping
|
||||
? {
|
||||
...config.roleMapping,
|
||||
...record.roleMapping,
|
||||
groupMappingsList: convertGroupMappingsToList(
|
||||
config.roleMapping.groupMappings,
|
||||
record.roleMapping.groupMappings,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
|
||||
@@ -43,11 +43,11 @@ function SSOEnforcementToggle({
|
||||
data: {
|
||||
config: {
|
||||
ssoEnabled: checked,
|
||||
ssoType: record.config?.ssoType,
|
||||
googleAuthConfig: record.config?.googleAuthConfig,
|
||||
oidcConfig: record.config?.oidcConfig,
|
||||
samlConfig: record.config?.samlConfig,
|
||||
roleMapping: record.config?.roleMapping,
|
||||
ssoType: record.ssoType,
|
||||
googleAuthConfig: record.googleAuthConfig,
|
||||
oidcConfig: record.oidcConfig,
|
||||
samlConfig: record.samlConfig,
|
||||
roleMapping: record.roleMapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -55,10 +55,7 @@ describe('SSOEnforcementToggle', () => {
|
||||
render(
|
||||
<SSOEnforcementToggle
|
||||
isDefaultChecked={false}
|
||||
record={{
|
||||
...mockGoogleAuthDomain,
|
||||
config: { ...mockGoogleAuthDomain.config, ssoEnabled: false },
|
||||
}}
|
||||
record={{ ...mockGoogleAuthDomain, ssoEnabled: false }}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -13,13 +13,11 @@ export const AUTH_DOMAINS_DELETE_ENDPOINT = '*/api/v1/domains/:id';
|
||||
export const mockGoogleAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-1',
|
||||
name: 'signoz.io',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.google_auth,
|
||||
googleAuthConfig: {
|
||||
clientId: 'test-client-id',
|
||||
clientSecret: 'test-client-secret',
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.google_auth,
|
||||
googleAuthConfig: {
|
||||
clientId: 'test-client-id',
|
||||
clientSecret: 'test-client-secret',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-1',
|
||||
@@ -30,14 +28,12 @@ export const mockGoogleAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
export const mockSamlAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-2',
|
||||
name: 'example.com',
|
||||
config: {
|
||||
ssoEnabled: false,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.example.com/sso',
|
||||
samlEntity: 'urn:example:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
ssoEnabled: false,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.example.com/sso',
|
||||
samlEntity: 'urn:example:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-2',
|
||||
@@ -48,14 +44,12 @@ export const mockSamlAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
export const mockOidcAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-3',
|
||||
name: 'corp.io',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.corp.io',
|
||||
clientId: 'oidc-client-id',
|
||||
clientSecret: 'oidc-client-secret',
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.corp.io',
|
||||
clientId: 'oidc-client-id',
|
||||
clientSecret: 'oidc-client-secret',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-3',
|
||||
@@ -66,22 +60,20 @@ export const mockOidcAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
export const mockDomainWithRoleMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-4',
|
||||
name: 'enterprise.com',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.enterprise.com/sso',
|
||||
samlEntity: 'urn:enterprise:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'EDITOR',
|
||||
useRoleAttribute: false,
|
||||
groupMappings: {
|
||||
'admin-group': 'ADMIN',
|
||||
'dev-team': 'EDITOR',
|
||||
viewers: 'VIEWER',
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.enterprise.com/sso',
|
||||
samlEntity: 'urn:enterprise:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'EDITOR',
|
||||
useRoleAttribute: false,
|
||||
groupMappings: {
|
||||
'admin-group': 'ADMIN',
|
||||
'dev-team': 'EDITOR',
|
||||
viewers: 'VIEWER',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
@@ -94,18 +86,16 @@ export const mockDomainWithDirectRoleAttribute: AuthtypesGettableAuthDomainDTO =
|
||||
{
|
||||
id: 'domain-5',
|
||||
name: 'direct-role.com',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.direct-role.com',
|
||||
clientId: 'direct-role-client-id',
|
||||
clientSecret: 'direct-role-client-secret',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'VIEWER',
|
||||
useRoleAttribute: true,
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.direct-role.com',
|
||||
clientId: 'direct-role-client-id',
|
||||
clientSecret: 'direct-role-client-secret',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'VIEWER',
|
||||
useRoleAttribute: true,
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-5',
|
||||
@@ -116,22 +106,20 @@ export const mockDomainWithDirectRoleAttribute: AuthtypesGettableAuthDomainDTO =
|
||||
export const mockOidcWithClaimMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-6',
|
||||
name: 'oidc-claims.com',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.claims.com',
|
||||
issuerAlias: 'https://alias.claims.com',
|
||||
clientId: 'claims-client-id',
|
||||
clientSecret: 'claims-client-secret',
|
||||
insecureSkipEmailVerified: true,
|
||||
getUserInfo: true,
|
||||
claimMapping: {
|
||||
email: 'user_email',
|
||||
name: 'display_name',
|
||||
groups: 'user_groups',
|
||||
role: 'user_role',
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.oidc,
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.claims.com',
|
||||
issuerAlias: 'https://alias.claims.com',
|
||||
clientId: 'claims-client-id',
|
||||
clientSecret: 'claims-client-secret',
|
||||
insecureSkipEmailVerified: true,
|
||||
getUserInfo: true,
|
||||
claimMapping: {
|
||||
email: 'user_email',
|
||||
name: 'display_name',
|
||||
groups: 'user_groups',
|
||||
role: 'user_role',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
@@ -143,19 +131,17 @@ export const mockOidcWithClaimMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
export const mockSamlWithAttributeMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-7',
|
||||
name: 'saml-attrs.com',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.saml-attrs.com/sso',
|
||||
samlEntity: 'urn:saml-attrs:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE_ATTRS',
|
||||
insecureSkipAuthNRequestsSigned: true,
|
||||
attributeMapping: {
|
||||
name: 'user_display_name',
|
||||
groups: 'member_of',
|
||||
role: 'signoz_role',
|
||||
},
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.saml,
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.saml-attrs.com/sso',
|
||||
samlEntity: 'urn:saml-attrs:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE_ATTRS',
|
||||
insecureSkipAuthNRequestsSigned: true,
|
||||
attributeMapping: {
|
||||
name: 'user_display_name',
|
||||
groups: 'member_of',
|
||||
role: 'signoz_role',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
@@ -168,21 +154,19 @@ export const mockGoogleAuthWithWorkspaceGroups: AuthtypesGettableAuthDomainDTO =
|
||||
{
|
||||
id: 'domain-8',
|
||||
name: 'google-groups.com',
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.google_auth,
|
||||
googleAuthConfig: {
|
||||
clientId: 'google-groups-client-id',
|
||||
clientSecret: 'google-groups-client-secret',
|
||||
insecureSkipEmailVerified: false,
|
||||
fetchGroups: true,
|
||||
serviceAccountJson: '{"type": "service_account"}',
|
||||
domainToAdminEmail: {
|
||||
'google-groups.com': 'admin@google-groups.com',
|
||||
},
|
||||
fetchTransitiveGroupMembership: true,
|
||||
allowedGroups: ['allowed-group-1', 'allowed-group-2'],
|
||||
ssoEnabled: true,
|
||||
ssoType: AuthtypesAuthNProviderDTO.google_auth,
|
||||
googleAuthConfig: {
|
||||
clientId: 'google-groups-client-id',
|
||||
clientSecret: 'google-groups-client-secret',
|
||||
insecureSkipEmailVerified: false,
|
||||
fetchGroups: true,
|
||||
serviceAccountJson: '{"type": "service_account"}',
|
||||
domainToAdminEmail: {
|
||||
'google-groups.com': 'admin@google-groups.com',
|
||||
},
|
||||
fetchTransitiveGroupMembership: true,
|
||||
allowedGroups: ['allowed-group-1', 'allowed-group-2'],
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-8',
|
||||
@@ -207,19 +191,15 @@ export const mockSingleDomainResponse = {
|
||||
data: [mockGoogleAuthDomain],
|
||||
};
|
||||
|
||||
// Mock success responses. CreateAuthDomain returns just an Identifiable
|
||||
// (the new domain ID); clients re-Read to get the full domain.
|
||||
// Mock success responses
|
||||
export const mockCreateSuccessResponse = {
|
||||
status: 'success',
|
||||
data: { id: mockGoogleAuthDomain.id },
|
||||
data: mockGoogleAuthDomain,
|
||||
};
|
||||
|
||||
export const mockUpdateSuccessResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
...mockGoogleAuthDomain,
|
||||
config: { ...mockGoogleAuthDomain.config, ssoEnabled: false },
|
||||
},
|
||||
data: { ...mockGoogleAuthDomain, ssoEnabled: false },
|
||||
};
|
||||
|
||||
export const mockDeleteSuccessResponse = {
|
||||
|
||||
@@ -158,7 +158,7 @@ function AuthDomain(): JSX.Element {
|
||||
onClick={(): void => setRecord(record)}
|
||||
variant="link"
|
||||
>
|
||||
Configure {SSOType.get(record.config?.ssoType || '')}
|
||||
Configure {SSOType.get(record.ssoType || '')}
|
||||
</Button>
|
||||
<Button
|
||||
className="auth-domain-list-action-link delete"
|
||||
|
||||
@@ -53,7 +53,9 @@ const mapQueryFromV5 = (compositeQuery: ICompositeMetricQuery): Query => {
|
||||
}
|
||||
} else if (q.type === 'builder_trace_operator') {
|
||||
if (spec.name) {
|
||||
builderQueries[spec.name] = spec as unknown as IBuilderTraceOperator;
|
||||
builderQueries[spec.name] = convertBuilderQueryToIBuilderQuery(
|
||||
spec as BuilderQuery,
|
||||
) as IBuilderTraceOperator;
|
||||
builderQueryTypes[spec.name] = 'builder_trace_operator';
|
||||
}
|
||||
} else if (q.type === 'promql') {
|
||||
|
||||
@@ -34,9 +34,9 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint creates an auth domain",
|
||||
Request: new(authtypes.PostableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
Response: new(authtypes.GettableAuthDomain),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
@@ -66,7 +66,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
|
||||
Tags: []string{"authdomains"},
|
||||
Summary: "Update auth domain",
|
||||
Description: "This endpoint updates an auth domain",
|
||||
Request: new(authtypes.UpdatableAuthDomain),
|
||||
Request: new(authtypes.UpdateableAuthDomain),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
|
||||
@@ -142,7 +142,7 @@ func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
body := new(authtypes.UpdatableAuthDomain)
|
||||
body := new(authtypes.UpdateableAuthDomain)
|
||||
if err := binding.JSON.BindBody(r.Body, body); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -44,8 +44,6 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const signozDiscriminatorKey string = "x-signoz-discriminator"
|
||||
|
||||
type OpenAPI struct {
|
||||
apiserver apiserver.APIServer
|
||||
reflector *openapi3.Reflector
|
||||
@@ -144,8 +142,6 @@ func (openapi *OpenAPI) CreateAndWrite(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
attachDiscriminators(openapi.reflector.Spec)
|
||||
|
||||
// The library's MarshalYAML does a JSON round-trip that converts all numbers
|
||||
// to float64, causing large integers (e.g. epoch millisecond timestamps) to
|
||||
// render in scientific notation (1.6409952e+12).
|
||||
@@ -203,59 +199,3 @@ func convertJSONNumbers(v interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attachDiscriminators promotes x-signoz-discriminator extensions
|
||||
// into openapi3 Discriminator fields. Malformed markers are dropped.
|
||||
func attachDiscriminators(spec *openapi3.Spec) {
|
||||
if spec.Components == nil || spec.Components.Schemas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for name, entry := range spec.Components.Schemas.MapOfSchemaOrRefValues {
|
||||
if entry.Schema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
raw, ok := entry.Schema.MapOfAnything[signozDiscriminatorKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
marker, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
propertyName, ok := marker["propertyName"].(string)
|
||||
if !ok || propertyName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
disc := openapi3.Discriminator{PropertyName: propertyName}
|
||||
if rawMapping, ok := marker["mapping"]; ok {
|
||||
if mapping, ok := rawMapping.(map[string]string); ok {
|
||||
disc.Mapping = mapping
|
||||
} else if mapping, ok := rawMapping.(map[string]any); ok {
|
||||
converted := make(map[string]string, len(mapping))
|
||||
for k, v := range mapping {
|
||||
if s, ok := v.(string); ok {
|
||||
converted[k] = s
|
||||
}
|
||||
}
|
||||
disc.Mapping = converted
|
||||
}
|
||||
}
|
||||
|
||||
entry.Schema.Discriminator = &disc
|
||||
delete(entry.Schema.MapOfAnything, signozDiscriminatorKey)
|
||||
|
||||
// The parent's reflected `properties` / `required` duplicate
|
||||
// what the oneOf variants already declare, and orval intersects
|
||||
// the two — turning a clean discriminated union DTO into a
|
||||
// noisy union of intersections. Drop them here.
|
||||
entry.Schema.Properties = nil
|
||||
entry.Schema.Required = nil
|
||||
|
||||
spec.Components.Schemas.MapOfSchemaOrRefValues[name] = entry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
|
||||
type GettableAuthDomain struct {
|
||||
StorableAuthDomain
|
||||
Config AuthDomainConfig `json:"config"`
|
||||
AuthDomainConfig
|
||||
AuthNProviderInfo *AuthNProviderInfo `json:"authNProviderInfo"`
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ type PostableAuthDomain struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type UpdatableAuthDomain struct {
|
||||
type UpdateableAuthDomain struct {
|
||||
Config AuthDomainConfig `json:"config"`
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func NewAuthDomainFromStorableAuthDomain(storableAuthDomain *StorableAuthDomain)
|
||||
func NewGettableAuthDomainFromAuthDomain(authDomain *AuthDomain, authNProviderInfo *AuthNProviderInfo) *GettableAuthDomain {
|
||||
return &GettableAuthDomain{
|
||||
StorableAuthDomain: *authDomain.StorableAuthDomain(),
|
||||
Config: *authDomain.AuthDomainConfig(),
|
||||
AuthDomainConfig: *authDomain.AuthDomainConfig(),
|
||||
AuthNProviderInfo: authNProviderInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,20 +250,17 @@ type EvaluationEnvelope struct {
|
||||
|
||||
// evaluationRolling is the OpenAPI schema for an EvaluationEnvelope with kind=rolling.
|
||||
type evaluationRolling struct {
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation." required:"true"`
|
||||
Spec RollingWindow `json:"spec" description:"The rolling window evaluation specification." required:"true"`
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
|
||||
Spec RollingWindow `json:"spec" description:"The rolling window evaluation specification."`
|
||||
}
|
||||
|
||||
// evaluationCumulative is the OpenAPI schema for an EvaluationEnvelope with kind=cumulative.
|
||||
type evaluationCumulative struct {
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation." required:"true"`
|
||||
Spec CumulativeWindow `json:"spec" description:"The cumulative window evaluation specification." required:"true"`
|
||||
Kind EvaluationKind `json:"kind" description:"The kind of evaluation."`
|
||||
Spec CumulativeWindow `json:"spec" description:"The cumulative window evaluation specification."`
|
||||
}
|
||||
|
||||
var (
|
||||
_ jsonschema.OneOfExposer = EvaluationEnvelope{}
|
||||
_ jsonschema.Preparer = EvaluationEnvelope{}
|
||||
)
|
||||
var _ jsonschema.OneOfExposer = EvaluationEnvelope{}
|
||||
|
||||
// JSONSchemaOneOf returns the oneOf variants for the EvaluationEnvelope discriminated union.
|
||||
// Each variant represents a different evaluation kind with its corresponding spec schema.
|
||||
@@ -274,22 +271,6 @@ func (EvaluationEnvelope) JSONSchemaOneOf() []any {
|
||||
}
|
||||
}
|
||||
|
||||
func (EvaluationEnvelope) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
if schema.ExtraProperties == nil {
|
||||
schema.ExtraProperties = map[string]any{}
|
||||
}
|
||||
|
||||
schema.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "kind",
|
||||
"mapping": map[string]string{
|
||||
"rolling": "#/components/schemas/RuletypesEvaluationRolling",
|
||||
"cumulative": "#/components/schemas/RuletypesEvaluationCumulative",
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EvaluationEnvelope) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
|
||||
@@ -36,14 +36,11 @@ type RuleThresholdData struct {
|
||||
|
||||
// thresholdBasic is the OpenAPI schema for a RuleThresholdData with kind=basic.
|
||||
type thresholdBasic struct {
|
||||
Kind ThresholdKind `json:"kind" description:"The kind of threshold." required:"true"`
|
||||
Spec BasicRuleThresholds `json:"spec" description:"The basic threshold specification (array of thresholds)." required:"true"`
|
||||
Kind ThresholdKind `json:"kind" description:"The kind of threshold."`
|
||||
Spec BasicRuleThresholds `json:"spec" description:"The basic threshold specification (array of thresholds)."`
|
||||
}
|
||||
|
||||
var (
|
||||
_ jsonschema.OneOfExposer = RuleThresholdData{}
|
||||
_ jsonschema.Preparer = RuleThresholdData{}
|
||||
)
|
||||
var _ jsonschema.OneOfExposer = RuleThresholdData{}
|
||||
|
||||
// JSONSchemaOneOf returns the oneOf variants for the RuleThresholdData discriminated union.
|
||||
// Each variant represents a different threshold kind with its corresponding spec schema.
|
||||
@@ -53,24 +50,6 @@ func (RuleThresholdData) JSONSchemaOneOf() []any {
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareJSONSchema marks the schema with x-signoz-discriminator;
|
||||
// signoz.attachDiscriminators promotes it to a real OpenAPI 3
|
||||
// discriminator after reflection.
|
||||
func (RuleThresholdData) PrepareJSONSchema(schema *jsonschema.Schema) error {
|
||||
if schema.ExtraProperties == nil {
|
||||
schema.ExtraProperties = map[string]any{}
|
||||
}
|
||||
|
||||
schema.ExtraProperties["x-signoz-discriminator"] = map[string]any{
|
||||
"propertyName": "kind",
|
||||
"mapping": map[string]string{
|
||||
"basic": "#/components/schemas/RuletypesThresholdBasic",
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuleThresholdData) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
|
||||
@@ -86,7 +86,7 @@ def test_create_and_get_domain(
|
||||
"domain-google.integration.test",
|
||||
"domain-saml.integration.test",
|
||||
]
|
||||
assert domain["config"]["ssoType"] in ["google_auth", "saml"]
|
||||
assert domain["ssoType"] in ["google_auth", "saml"]
|
||||
|
||||
|
||||
def test_create_invalid(
|
||||
|
||||
Reference in New Issue
Block a user