mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-25 01:20:32 +01:00
Compare commits
6 Commits
feat/trace
...
issue_5325
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
858e3699c9 | ||
|
|
ab71159312 | ||
|
|
a207dd6bcf | ||
|
|
3369ed7172 | ||
|
|
a98b84c1cd | ||
|
|
1eb3ef40e1 |
@@ -659,6 +659,29 @@ components:
|
||||
refreshToken:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesPostableUser:
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
frontendBaseUrl:
|
||||
type: string
|
||||
userRoles:
|
||||
items:
|
||||
$ref: '#/components/schemas/AuthtypesPostableUserRole'
|
||||
type: array
|
||||
required:
|
||||
- email
|
||||
- userRoles
|
||||
type: object
|
||||
AuthtypesPostableUserRole:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
AuthtypesRelation:
|
||||
enum:
|
||||
- create
|
||||
@@ -4945,6 +4968,16 @@ components:
|
||||
- offset
|
||||
- limit
|
||||
type: object
|
||||
LlmpricingruletypesGettableUnmappedModels:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesUnmappedModel'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
LlmpricingruletypesLLMPricingCacheCosts:
|
||||
properties:
|
||||
mode:
|
||||
@@ -5034,6 +5067,19 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
LlmpricingruletypesUnmappedModel:
|
||||
properties:
|
||||
modelName:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
spanCount:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- modelName
|
||||
- spanCount
|
||||
type: object
|
||||
LlmpricingruletypesUpdatableLLMPricingRule:
|
||||
properties:
|
||||
enabled:
|
||||
@@ -10183,7 +10229,7 @@ paths:
|
||||
- global
|
||||
/api/v1/invite:
|
||||
post:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint creates an invite for a user
|
||||
operationId: CreateInvite
|
||||
requestBody:
|
||||
@@ -10246,7 +10292,7 @@ paths:
|
||||
- users
|
||||
/api/v1/invite/bulk:
|
||||
post:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint creates a bulk invite for a user
|
||||
operationId: CreateBulkInvite
|
||||
requestBody:
|
||||
@@ -10516,6 +10562,60 @@ paths:
|
||||
summary: Get a pricing rule
|
||||
tags:
|
||||
- llmpricingrules
|
||||
/api/v1/llm_pricing_rules/unmapped_models:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns models seen in the last hour of trace data (gen_ai.request.model)
|
||||
that no pricing rule pattern matches, so the user can add them to an existing
|
||||
rule or create a new one.
|
||||
operationId: ListUnmappedLLMModels
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/LlmpricingruletypesGettableUnmappedModels'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: List unmapped models
|
||||
tags:
|
||||
- llmpricingrules
|
||||
/api/v1/logs/promote_paths:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -13087,7 +13187,7 @@ paths:
|
||||
- tracedetail
|
||||
/api/v1/user:
|
||||
get:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint lists all users
|
||||
operationId: ListUsersDeprecated
|
||||
responses:
|
||||
@@ -13180,7 +13280,7 @@ paths:
|
||||
tags:
|
||||
- users
|
||||
get:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint returns the user by id
|
||||
operationId: GetUserDeprecated
|
||||
parameters:
|
||||
@@ -13237,7 +13337,7 @@ paths:
|
||||
tags:
|
||||
- users
|
||||
put:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint updates the user by id
|
||||
operationId: UpdateUserDeprecated
|
||||
parameters:
|
||||
@@ -13306,7 +13406,7 @@ paths:
|
||||
- users
|
||||
/api/v1/user/me:
|
||||
get:
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
description: This endpoint returns the user I belong to
|
||||
operationId: GetMyUserDeprecated
|
||||
responses:
|
||||
@@ -20722,6 +20822,68 @@ paths:
|
||||
summary: List users v2
|
||||
tags:
|
||||
- users
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint creates a user for the organization
|
||||
operationId: CreateUser
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesPostableUser'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"409":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Conflict
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Create user
|
||||
tags:
|
||||
- users
|
||||
/api/v2/users/{id}:
|
||||
get:
|
||||
deprecated: false
|
||||
|
||||
@@ -98,6 +98,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
aiObservability := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureEnableAIObservability, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableAIObservability.String()),
|
||||
Active: aiObservability,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
GetLLMPricingRulePathParameters,
|
||||
ListLLMPricingRules200,
|
||||
ListLLMPricingRulesParams,
|
||||
ListUnmappedLLMModels200,
|
||||
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -393,3 +394,87 @@ export const invalidateGetLLMPricingRule = async (
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns models seen in the last hour of trace data (gen_ai.request.model) that no pricing rule pattern matches, so the user can add them to an existing rule or create a new one.
|
||||
* @summary List unmapped models
|
||||
*/
|
||||
export const listUnmappedLLMModels = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListUnmappedLLMModels200>({
|
||||
url: `/api/v1/llm_pricing_rules/unmapped_models`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListUnmappedLLMModelsQueryKey = () => {
|
||||
return [`/api/v1/llm_pricing_rules/unmapped_models`] as const;
|
||||
};
|
||||
|
||||
export const getListUnmappedLLMModelsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listUnmappedLLMModels>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listUnmappedLLMModels>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListUnmappedLLMModelsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listUnmappedLLMModels>>
|
||||
> = ({ signal }) => listUnmappedLLMModels(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listUnmappedLLMModels>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListUnmappedLLMModelsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listUnmappedLLMModels>>
|
||||
>;
|
||||
export type ListUnmappedLLMModelsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List unmapped models
|
||||
*/
|
||||
|
||||
export function useListUnmappedLLMModels<
|
||||
TData = Awaited<ReturnType<typeof listUnmappedLLMModels>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listUnmappedLLMModels>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListUnmappedLLMModelsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List unmapped models
|
||||
*/
|
||||
export const invalidateListUnmappedLLMModels = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListUnmappedLLMModelsQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
@@ -2258,6 +2258,32 @@ export interface AuthtypesPostableRotateTokenDTO {
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableUserRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AuthtypesPostableUserDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
displayName?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
frontendBaseUrl?: string;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
userRoles: AuthtypesPostableUserRoleDTO[];
|
||||
}
|
||||
|
||||
export interface AuthtypesRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -6587,6 +6613,29 @@ export interface LlmpricingruletypesGettablePricingRulesDTO {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesUnmappedModelDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
modelName: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
provider?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
spanCount: number;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesGettableUnmappedModelsDTO {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
items: LlmpricingruletypesUnmappedModelDTO[] | null;
|
||||
}
|
||||
|
||||
export interface LlmpricingruletypesUpdatableLLMPricingRuleDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
@@ -9534,6 +9583,14 @@ export type GetLLMPricingRule200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListUnmappedLLMModels200 = {
|
||||
data: LlmpricingruletypesGettableUnmappedModelsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPromotedAndIndexedPaths200 = {
|
||||
/**
|
||||
* @type array,null
|
||||
@@ -10807,6 +10864,14 @@ export type ListUsers200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateUser201 = {
|
||||
data: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetUserPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
@@ -18,9 +18,11 @@ import type {
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
AuthtypesPostableUserDTO,
|
||||
CreateInvite201,
|
||||
CreateResetPasswordToken201,
|
||||
CreateResetPasswordTokenPathParameters,
|
||||
CreateUser201,
|
||||
DeleteUserPathParameters,
|
||||
GetMyUser200,
|
||||
GetMyUserDeprecated200,
|
||||
@@ -169,6 +171,7 @@ export const invalidateGetResetPasswordTokenDeprecated = async (
|
||||
|
||||
/**
|
||||
* This endpoint creates an invite for a user
|
||||
* @deprecated
|
||||
* @summary Create invite
|
||||
*/
|
||||
export const createInvite = (
|
||||
@@ -230,6 +233,7 @@ export type CreateInviteMutationBody =
|
||||
export type CreateInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Create invite
|
||||
*/
|
||||
export const useCreateInvite = <
|
||||
@@ -252,6 +256,7 @@ export const useCreateInvite = <
|
||||
};
|
||||
/**
|
||||
* This endpoint creates a bulk invite for a user
|
||||
* @deprecated
|
||||
* @summary Create bulk invite
|
||||
*/
|
||||
export const createBulkInvite = (
|
||||
@@ -313,6 +318,7 @@ export type CreateBulkInviteMutationBody =
|
||||
export type CreateBulkInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Create bulk invite
|
||||
*/
|
||||
export const useCreateBulkInvite = <
|
||||
@@ -418,6 +424,7 @@ export const useResetPassword = <
|
||||
};
|
||||
/**
|
||||
* This endpoint lists all users
|
||||
* @deprecated
|
||||
* @summary List users
|
||||
*/
|
||||
export const listUsersDeprecated = (signal?: AbortSignal) => {
|
||||
@@ -463,6 +470,7 @@ export type ListUsersDeprecatedQueryResult = NonNullable<
|
||||
export type ListUsersDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary List users
|
||||
*/
|
||||
|
||||
@@ -486,6 +494,7 @@ export function useListUsersDeprecated<
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary List users
|
||||
*/
|
||||
export const invalidateListUsersDeprecated = async (
|
||||
@@ -581,6 +590,7 @@ export const useDeleteUser = <
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user by id
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
export const getUserDeprecated = (
|
||||
@@ -640,6 +650,7 @@ export type GetUserDeprecatedQueryResult = NonNullable<
|
||||
export type GetUserDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
|
||||
@@ -666,6 +677,7 @@ export function useGetUserDeprecated<
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
export const invalidateGetUserDeprecated = async (
|
||||
@@ -683,6 +695,7 @@ export const invalidateGetUserDeprecated = async (
|
||||
|
||||
/**
|
||||
* This endpoint updates the user by id
|
||||
* @deprecated
|
||||
* @summary Update user
|
||||
*/
|
||||
export const updateUserDeprecated = (
|
||||
@@ -755,6 +768,7 @@ export type UpdateUserDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Update user
|
||||
*/
|
||||
export const useUpdateUserDeprecated = <
|
||||
@@ -783,6 +797,7 @@ export const useUpdateUserDeprecated = <
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user I belong to
|
||||
* @deprecated
|
||||
* @summary Get my user
|
||||
*/
|
||||
export const getMyUserDeprecated = (signal?: AbortSignal) => {
|
||||
@@ -828,6 +843,7 @@ export type GetMyUserDeprecatedQueryResult = NonNullable<
|
||||
export type GetMyUserDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get my user
|
||||
*/
|
||||
|
||||
@@ -851,6 +867,7 @@ export function useGetMyUserDeprecated<
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get my user
|
||||
*/
|
||||
export const invalidateGetMyUserDeprecated = async (
|
||||
@@ -1209,6 +1226,89 @@ export const invalidateListUsers = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a user for the organization
|
||||
* @summary Create user
|
||||
*/
|
||||
export const createUser = (
|
||||
authtypesPostableUserDTO?: BodyType<AuthtypesPostableUserDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateUser201>({
|
||||
url: `/api/v2/users`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesPostableUserDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateUserMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createUser'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
{ data?: BodyType<AuthtypesPostableUserDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createUser(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateUserMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createUser>>
|
||||
>;
|
||||
export type CreateUserMutationBody =
|
||||
| BodyType<AuthtypesPostableUserDTO>
|
||||
| undefined;
|
||||
export type CreateUserMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create user
|
||||
*/
|
||||
export const useCreateUser = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createUser>>,
|
||||
TError,
|
||||
{ data?: BodyType<AuthtypesPostableUserDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateUserMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user by id
|
||||
* @summary Get user by user id
|
||||
|
||||
@@ -12,4 +12,5 @@ export enum FeatureKeys {
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
USE_FINE_GRAINED_AUTHZ = 'use_fine_grained_authz',
|
||||
USE_DASHBOARD_V2 = 'use_dashboard_v2',
|
||||
EMABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
}
|
||||
|
||||
@@ -49,6 +49,26 @@ func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/unmapped_models", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.llmPricingRuleHandler.ListUnmappedModels),
|
||||
handler.OpenAPIDef{
|
||||
ID: "ListUnmappedLLMModels",
|
||||
Tags: []string{"llmpricingrules"},
|
||||
Summary: "List unmapped models",
|
||||
Description: "Returns models seen in the last hour of trace data (gen_ai.request.model) that no pricing rule pattern matches, so the user can add them to an existing rule or create a new one.",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(llmpricingruletypes.GettableUnmappedModels),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
},
|
||||
)).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
|
||||
provider.authzMiddleware.ViewAccess(provider.llmPricingRuleHandler.Get),
|
||||
handler.OpenAPIDef{
|
||||
|
||||
@@ -21,7 +21,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
@@ -37,7 +37,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Response: nil,
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
@@ -54,7 +54,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
@@ -88,7 +88,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{{Name: authtypes.IdentNProviderTokenizer.StringValue()}},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
@@ -111,6 +111,23 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users", handler.New(provider.authzMiddleware.AdminAccess(provider.userHandler.CreateUser), handler.OpenAPIDef{
|
||||
ID: "CreateUser",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Create user",
|
||||
Description: "This endpoint creates a user for the organization",
|
||||
Request: new(authtypes.PostableUser),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/users/me", handler.New(provider.authzMiddleware.OpenAccess(provider.userHandler.UpdateMyUser), handler.OpenAPIDef{
|
||||
ID: "UpdateMyUserV2",
|
||||
Tags: []string{"users"},
|
||||
@@ -139,7 +156,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
@@ -173,7 +190,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
Deprecated: true,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,15 +3,16 @@ package flagger
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
var (
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
FeatureKafkaSpanEval = featuretypes.MustNewName("kafka_span_eval")
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureGetMetersFromZeus = featuretypes.MustNewName("get_meters_from_zeus")
|
||||
FeaturePutMetersInZeus = featuretypes.MustNewName("put_meters_in_zeus")
|
||||
FeatureUseMeterReporter = featuretypes.MustNewName("use_meter_reporter")
|
||||
FeatureUseJSONBody = featuretypes.MustNewName("use_json_body")
|
||||
FeatureUseFineGrainedAuthz = featuretypes.MustNewName("use_fine_grained_authz")
|
||||
FeatureUseDashboardV2 = featuretypes.MustNewName("use_dashboard_v2")
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
FeatureKafkaSpanEval = featuretypes.MustNewName("kafka_span_eval")
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureGetMetersFromZeus = featuretypes.MustNewName("get_meters_from_zeus")
|
||||
FeaturePutMetersInZeus = featuretypes.MustNewName("put_meters_in_zeus")
|
||||
FeatureUseMeterReporter = featuretypes.MustNewName("use_meter_reporter")
|
||||
FeatureUseJSONBody = featuretypes.MustNewName("use_json_body")
|
||||
FeatureUseFineGrainedAuthz = featuretypes.MustNewName("use_fine_grained_authz")
|
||||
FeatureUseDashboardV2 = featuretypes.MustNewName("use_dashboard_v2")
|
||||
FeatureEnableAIObservability = featuretypes.MustNewName("enable_ai_observability")
|
||||
)
|
||||
|
||||
func MustNewRegistry() featuretypes.Registry {
|
||||
@@ -88,6 +89,14 @@ func MustNewRegistry() featuretypes.Registry {
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureEnableAIObservability,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Stage: featuretypes.StageExperimental,
|
||||
Description: "Controls whether ai observability is enabled",
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -92,7 +92,7 @@ func (h *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *handler) CreateOrUpdate(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@@ -118,6 +118,28 @@ func (h *handler) CreateOrUpdate(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
// ListUnmappedModels handles GET /api/v1/llm_pricing_rules/unmapped_models.
|
||||
func (h *handler) ListUnmappedModels(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
models, err := h.module.ListUnmappedModels(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, llmpricingruletypes.NewGettableUnmappedModels(models))
|
||||
}
|
||||
|
||||
// Delete handles DELETE /api/v1/llm_pricing_rules/{id}.
|
||||
func (h *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
|
||||
@@ -3,22 +3,30 @@ package impllmpricingrule
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// unmappedModelsLookback is the trace data window scanned to discover models in use.
|
||||
const unmappedModelsLookback = time.Hour
|
||||
|
||||
type module struct {
|
||||
store llmpricingruletypes.Store
|
||||
store llmpricingruletypes.Store
|
||||
querier querier.Querier
|
||||
}
|
||||
|
||||
func NewModule(store llmpricingruletypes.Store) llmpricingrule.Module {
|
||||
return &module{store: store}
|
||||
func NewModule(store llmpricingruletypes.Store, querier querier.Querier) llmpricingrule.Module {
|
||||
return &module{store: store, querier: querier}
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID, offset, limit int, search string, isOverride *bool) ([]*llmpricingruletypes.LLMPricingRule, int, error) {
|
||||
@@ -29,6 +37,28 @@ func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID
|
||||
return module.store.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
// ListUnmappedModels discovers the models present in the last hour of trace data
|
||||
// (gen_ai.request.model) and returns the ones that no pricing rule pattern matches.
|
||||
func (module *module) ListUnmappedModels(ctx context.Context, orgID valuer.UUID) ([]*llmpricingruletypes.UnmappedModel, error) {
|
||||
models, err := module.discoverModels(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules, err := module.listAllRules(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmapped := make([]*llmpricingruletypes.UnmappedModel, 0, len(models))
|
||||
for _, m := range models {
|
||||
if !llmpricingruletypes.ModelMatchesAnyRule(m.ModelName, rules) {
|
||||
unmapped = append(unmapped, m)
|
||||
}
|
||||
}
|
||||
return unmapped, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdate applies a batch of pricing rule changes:
|
||||
// - ID set → match by id, overwrite fields.
|
||||
// - SourceID set → match by source_id; if found overwrite, else insert.
|
||||
@@ -108,7 +138,7 @@ func (module *module) RecommendAgentConfig(orgID valuer.UUID, currentConfYaml []
|
||||
}
|
||||
|
||||
func (module *module) getEnabledRules(ctx context.Context, orgID valuer.UUID) ([]*llmpricingruletypes.LLMPricingRule, error) {
|
||||
rules, _, err := module.List(ctx, orgID, 0, 10000, "", nil)
|
||||
rules, err := module.listAllRules(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,6 +152,25 @@ func (module *module) getEnabledRules(ctx context.Context, orgID valuer.UUID) ([
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
// listAllRules pages through every pricing rule for the org, since rule matching
|
||||
// needs the full set and the count is otherwise unbounded.
|
||||
func (module *module) listAllRules(ctx context.Context, orgID valuer.UUID) ([]*llmpricingruletypes.LLMPricingRule, error) {
|
||||
const pageSize = 1000
|
||||
|
||||
all := make([]*llmpricingruletypes.LLMPricingRule, 0)
|
||||
for offset := 0; ; offset += pageSize {
|
||||
page, total, err := module.store.List(ctx, orgID, offset, pageSize, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, page...)
|
||||
if len(page) == 0 || len(all) >= total {
|
||||
break
|
||||
}
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// findExisting returns the row matching the updatable's ID or SourceID.
|
||||
// Returns a TypeNotFound error when neither matches; the caller treats that
|
||||
// as "insert new".
|
||||
@@ -135,3 +184,91 @@ func (module *module) findExisting(ctx context.Context, orgID valuer.UUID, u *ll
|
||||
return nil, errors.Newf(errors.TypeNotFound, llmpricingruletypes.ErrCodePricingRuleNotFound, "rule has neither id nor sourceId")
|
||||
}
|
||||
}
|
||||
|
||||
// discoverModels runs a QBv5 traces aggregation grouped by gen_ai.request.model
|
||||
// over the lookback window and returns each distinct model with its span count.
|
||||
func (module *module) discoverModels(ctx context.Context, orgID valuer.UUID) ([]*llmpricingruletypes.UnmappedModel, error) {
|
||||
now := time.Now()
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
Start: uint64(now.Add(-unmappedModelsLookback).UnixMilli()),
|
||||
End: uint64(now.UnixMilli()),
|
||||
RequestType: qbtypes.RequestTypeScalar,
|
||||
CompositeQuery: qbtypes.CompositeQuery{
|
||||
Queries: []qbtypes.QueryEnvelope{
|
||||
{
|
||||
Type: qbtypes.QueryTypeBuilder,
|
||||
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
||||
Name: "A",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
Filter: &qbtypes.Filter{Expression: fmt.Sprintf("%s EXISTS", llmpricingruletypes.GenAIRequestModel)},
|
||||
Aggregations: []qbtypes.TraceAggregation{
|
||||
{Expression: "count()", Alias: "spanCount"},
|
||||
},
|
||||
GroupBy: []qbtypes.GroupByKey{
|
||||
{TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: llmpricingruletypes.GenAIRequestModel,
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
}},
|
||||
{TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: llmpricingruletypes.GenAIProviderName,
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
}},
|
||||
},
|
||||
Limit: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := module.querier.QueryRange(ctx, orgID, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp == nil || len(resp.Data.Results) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
sd, ok := resp.Data.Results[0].(*qbtypes.ScalarData)
|
||||
if !ok || sd == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
modelIdx, providerIdx, countIdx := -1, -1, -1
|
||||
for i, c := range sd.Columns {
|
||||
switch c.Type {
|
||||
case qbtypes.ColumnTypeGroup:
|
||||
switch c.Name {
|
||||
case llmpricingruletypes.GenAIRequestModel:
|
||||
modelIdx = i
|
||||
case llmpricingruletypes.GenAIProviderName:
|
||||
providerIdx = i
|
||||
}
|
||||
case qbtypes.ColumnTypeAggregation:
|
||||
countIdx = i
|
||||
}
|
||||
}
|
||||
if modelIdx == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
models := make([]*llmpricingruletypes.UnmappedModel, 0, len(sd.Data))
|
||||
for _, row := range sd.Data {
|
||||
name, _ := row[modelIdx].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
provider := ""
|
||||
if providerIdx != -1 {
|
||||
provider, _ = row[providerIdx].(string)
|
||||
}
|
||||
var spanCount uint64
|
||||
if countIdx >= 0 && countIdx < len(row) {
|
||||
spanCount, _ = row[countIdx].(uint64)
|
||||
}
|
||||
models = append(models, &llmpricingruletypes.UnmappedModel{ModelName: name, Provider: provider, SpanCount: spanCount})
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Module interface {
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*llmpricingruletypes.LLMPricingRule, error)
|
||||
CreateOrUpdate(ctx context.Context, orgID valuer.UUID, userEmail string, rules []*llmpricingruletypes.UpdatableLLMPricingRule) (err error)
|
||||
Delete(ctx context.Context, orgID, id valuer.UUID) error
|
||||
ListUnmappedModels(ctx context.Context, orgID valuer.UUID) ([]*llmpricingruletypes.UnmappedModel, error)
|
||||
}
|
||||
|
||||
// Handler defines the HTTP handler interface for pricing rule endpoints.
|
||||
@@ -25,4 +26,5 @@ type Handler interface {
|
||||
Get(rw http.ResponseWriter, r *http.Request)
|
||||
CreateOrUpdate(rw http.ResponseWriter, r *http.Request)
|
||||
Delete(rw http.ResponseWriter, r *http.Request)
|
||||
ListUnmappedModels(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,42 @@ func NewHandler(setter root.Setter, getter root.Getter) root.Handler {
|
||||
return &handler{setter: setter, getter: getter}
|
||||
}
|
||||
|
||||
func (handler *handler) CreateUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(authtypes.PostableUser)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := types.NewUser(req.DisplayName, req.Email, valuer.MustNewUUID(claims.OrgID), types.UserStatusPendingInvite)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
roleIDs := make([]valuer.UUID, 0, len(req.UserRoles))
|
||||
for _, role := range req.UserRoles {
|
||||
roleIDs = append(roleIDs, role.ID)
|
||||
}
|
||||
|
||||
user, err = handler.setter.CreatePendingInviteUser(ctx, valuer.MustNewUUID(claims.IdentityID()), valuer.MustNewEmail(claims.Email), req.FrontendBaseUrl, user, root.WithRoleIDs(roleIDs))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, types.Identifiable{ID: user.ID})
|
||||
}
|
||||
|
||||
func (handler *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -215,6 +215,67 @@ func (module *setter) CreateUser(ctx context.Context, user *types.User, opts ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *setter) CreatePendingInviteUser(ctx context.Context, identityID valuer.UUID, identityEmail valuer.Email, frontendBaseURL string, user *types.User, opts ...root.CreateUserOption) (*types.User, error) {
|
||||
if err := user.ErrIfNotPending(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
|
||||
roleNames := createUserOpts.RoleNames
|
||||
if len(createUserOpts.RoleIDs) > 0 {
|
||||
roles, err := module.authz.ListByOrgIDAndIDs(ctx, user.OrgID, createUserOpts.RoleIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, role := range roles {
|
||||
roleNames = append(roleNames, role.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var resetPasswordToken *types.ResetPasswordToken
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.createUserWithoutGrant(ctx, user, root.WithRoleNames(roleNames), root.WithFactorPassword(createUserOpts.FactorPassword)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := module.GetOrCreateResetPasswordToken(ctx, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resetPasswordToken = token
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module.analytics.TrackUser(ctx, user.OrgID.String(), identityID.String(), "Invite Sent", map[string]any{
|
||||
"invitee_email": user.Email,
|
||||
"invitee_role": roleNames,
|
||||
})
|
||||
|
||||
if frontendBaseURL == "" {
|
||||
module.settings.Logger().InfoContext(ctx, "frontend base url is not provided, skipping email", slog.Any("invitee_email", user.Email))
|
||||
return user, nil
|
||||
}
|
||||
|
||||
resetLink := resetPasswordToken.FactorPasswordResetLink(frontendBaseURL)
|
||||
|
||||
tokenLifetime := module.config.Password.Invite.MaxTokenLifetime
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
|
||||
if err := module.emailing.SendHTML(ctx, user.Email.String(), "You're Invited to Join SigNoz", emailtypes.TemplateNameInvitationEmail, map[string]any{
|
||||
"inviter_email": identityEmail.StringValue(),
|
||||
"link": resetLink,
|
||||
"Expiry": humanizedTokenLifetime,
|
||||
}); err != nil {
|
||||
module.settings.Logger().ErrorContext(ctx, "failed to send invite email", errors.Attr(err))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (module *setter) UpdateUserDeprecated(ctx context.Context, orgID valuer.UUID, id string, user *types.DeprecatedUser) (*types.DeprecatedUser, error) {
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
type createUserOptions struct {
|
||||
FactorPassword *types.FactorPassword
|
||||
RoleNames []string
|
||||
RoleIDs []valuer.UUID
|
||||
}
|
||||
|
||||
type CreateUserOption func(*createUserOptions)
|
||||
@@ -24,6 +25,12 @@ func WithRoleNames(roleNames []string) CreateUserOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithRoleIDs(roleIDs []valuer.UUID) CreateUserOption {
|
||||
return func(o *createUserOptions) {
|
||||
o.RoleIDs = roleIDs
|
||||
}
|
||||
}
|
||||
|
||||
func NewCreateUserOptions(opts ...CreateUserOption) *createUserOptions {
|
||||
o := &createUserOptions{
|
||||
FactorPassword: nil,
|
||||
|
||||
@@ -45,6 +45,9 @@ type Setter interface {
|
||||
// invite
|
||||
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, identityID valuer.UUID, identityEmail valuer.Email, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error)
|
||||
|
||||
// Creates a pending invite user with the roles given via opts and emails them the invite link.
|
||||
CreatePendingInviteUser(ctx context.Context, identityID valuer.UUID, identityEmail valuer.Email, frontendBaseURL string, user *types.User, opts ...CreateUserOption) (*types.User, error)
|
||||
|
||||
// Roles
|
||||
UpdateUserRoles(ctx context.Context, orgID, userID valuer.UUID, finalRoleNames []string) error
|
||||
AddUserRole(ctx context.Context, orgID, userID valuer.UUID, roleName string) error
|
||||
@@ -107,6 +110,7 @@ type Handler interface {
|
||||
// users
|
||||
ListUsersDeprecated(http.ResponseWriter, *http.Request)
|
||||
ListUsers(http.ResponseWriter, *http.Request)
|
||||
CreateUser(http.ResponseWriter, *http.Request)
|
||||
UpdateUserDeprecated(http.ResponseWriter, *http.Request)
|
||||
UpdateUser(http.ResponseWriter, *http.Request)
|
||||
DeleteUser(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -1678,6 +1678,15 @@ func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
aiObservability := aH.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureEnableAIObservability, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableAIObservability.String()),
|
||||
Active: aiObservability,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -155,7 +155,7 @@ func NewModules(
|
||||
CloudIntegration: cloudIntegrationModule,
|
||||
TraceDetail: impltracedetail.NewModule(impltracedetail.NewTraceStore(telemetryStore), providerSettings, config.TraceDetail),
|
||||
SpanMapper: implspanmapper.NewModule(implspanmapper.NewStore(sqlstore)),
|
||||
LLMPricingRule: impllmpricingrule.NewModule(impllmpricingrule.NewStore(sqlstore)),
|
||||
LLMPricingRule: impllmpricingrule.NewModule(impllmpricingrule.NewStore(sqlstore), querier),
|
||||
Tag: tagModule,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package authtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
var (
|
||||
ErrCodeUserRoleAlreadyExists = errors.MustNewCode("user_role_already_exists")
|
||||
ErrCodeUserRolesNotFound = errors.MustNewCode("user_roles_not_found")
|
||||
ErrCodeUserRoleInvalidInput = errors.MustNewCode("user_role_invalid_input")
|
||||
)
|
||||
|
||||
type UserRole struct {
|
||||
@@ -28,6 +30,44 @@ type UserRole struct {
|
||||
Role *Role `bun:"rel:belongs-to,join:role_id=id" json:"role" required:"true"`
|
||||
}
|
||||
|
||||
type UserWithRoles struct {
|
||||
*types.User
|
||||
UserRoles []*UserRole `json:"userRoles"`
|
||||
}
|
||||
|
||||
type PostableUser struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Email valuer.Email `json:"email" required:"true"`
|
||||
FrontendBaseUrl string `json:"frontendBaseUrl"`
|
||||
UserRoles []*PostableUserRole `json:"userRoles" required:"true" nullable:"false"`
|
||||
}
|
||||
|
||||
type PostableUserRole struct {
|
||||
ID valuer.UUID `json:"id" required:"true"`
|
||||
}
|
||||
|
||||
func (p *PostableUser) UnmarshalJSON(data []byte) error {
|
||||
type Alias PostableUser
|
||||
|
||||
var temp Alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if temp.UserRoles == nil {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeUserRoleInvalidInput, "userRoles is required").WithSuggestions("send an empty array to create user without role")
|
||||
}
|
||||
|
||||
for _, role := range temp.UserRoles {
|
||||
if role == nil {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeUserRoleInvalidInput, "userRoles cannot contain null entries")
|
||||
}
|
||||
}
|
||||
|
||||
*p = PostableUser(temp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newUserRole(userID valuer.UUID, roleID valuer.UUID) *UserRole {
|
||||
return &UserRole{
|
||||
ID: valuer.GenerateUUID(),
|
||||
@@ -48,11 +88,6 @@ func NewUserRoles(userID valuer.UUID, roles []*Role) []*UserRole {
|
||||
return userRoles
|
||||
}
|
||||
|
||||
type UserWithRoles struct {
|
||||
*types.User
|
||||
UserRoles []*UserRole `json:"userRoles"`
|
||||
}
|
||||
|
||||
type UserRoleStore interface {
|
||||
// create user roles in bulk
|
||||
CreateUserRoles(ctx context.Context, userRoles []*UserRole) error
|
||||
|
||||
@@ -3,6 +3,7 @@ package llmpricingruletypes
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -16,6 +17,7 @@ const (
|
||||
LLMCostFeatureType agentConf.AgentFeatureType = "llm_pricing"
|
||||
|
||||
GenAIRequestModel = "gen_ai.request.model"
|
||||
GenAIProviderName = "gen_ai.provider.name"
|
||||
GenAIUsageInputTokens = "gen_ai.usage.input_tokens"
|
||||
GenAIUsageOutputTokens = "gen_ai.usage.output_tokens"
|
||||
GenAIUsageCacheReadInputTokens = "gen_ai.usage.cache_read.input_tokens"
|
||||
@@ -138,6 +140,17 @@ type GettablePricingRules struct {
|
||||
Limit int `json:"limit" required:"true"`
|
||||
}
|
||||
|
||||
// Models deleted from spans which doesn't have a corresponding pricing entry.
|
||||
type UnmappedModel struct {
|
||||
ModelName string `json:"modelName" required:"true"`
|
||||
Provider string `json:"provider"`
|
||||
SpanCount uint64 `json:"spanCount" required:"true"`
|
||||
}
|
||||
|
||||
type GettableUnmappedModels struct {
|
||||
Items []*UnmappedModel `json:"items" required:"true"`
|
||||
}
|
||||
|
||||
func (LLMPricingRuleUnit) Enum() []any {
|
||||
return []any{UnitPerMillionTokens}
|
||||
}
|
||||
@@ -206,6 +219,12 @@ func NewGettableLLMPricingRulesFromLLMPricingRules(items []*LLMPricingRule, tota
|
||||
}
|
||||
}
|
||||
|
||||
func NewGettableUnmappedModels(items []*UnmappedModel) *GettableUnmappedModels {
|
||||
return &GettableUnmappedModels{
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
||||
func NewLLMPricingRuleFromUpdatable(u *UpdatableLLMPricingRule, orgID valuer.UUID, userEmail string, now time.Time) *LLMPricingRule {
|
||||
isOverride := true
|
||||
if u.IsOverride != nil {
|
||||
@@ -250,3 +269,14 @@ func (r *LLMPricingRule) Update(u *UpdatableLLMPricingRule, userEmail string, no
|
||||
r.UpdatedAt = now
|
||||
r.UpdatedBy = userEmail
|
||||
}
|
||||
|
||||
func ModelMatchesAnyRule(model string, rules []*LLMPricingRule) bool {
|
||||
for _, r := range rules {
|
||||
for _, pattern := range r.ModelPattern {
|
||||
if ok, err := path.Match(pattern, model); err == nil && ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ var (
|
||||
ErrCodeRootUserOperationUnsupported = errors.MustNewCode("root_user_operation_unsupported")
|
||||
ErrCodeUserStatusDeleted = errors.MustNewCode("user_status_deleted")
|
||||
ErrCodeUserStatusPendingInvite = errors.MustNewCode("user_status_pending_invite")
|
||||
ErrCodeUserStatusNotPendingInvite = errors.MustNewCode("user_status_not_pending_invite")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -214,6 +215,15 @@ func (u *User) ErrIfPending() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrIfNotPending returns an error if the user is not in pending invite state.
|
||||
// This error can be enriched with specific operation by the called using errors.WithAdditionalf.
|
||||
func (u *User) ErrIfNotPending() error {
|
||||
if u.Status != UserStatusPendingInvite {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeUserStatusNotPendingInvite, "operation is only supported for pending invite user")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTraitsFromUser(user *User) map[string]any {
|
||||
return map[string]any{
|
||||
"name": user.DisplayName,
|
||||
|
||||
Reference in New Issue
Block a user