mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-06 18:20:50 +01:00
Compare commits
45 Commits
main
...
ns/trace-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a09ace42fb | ||
|
|
62f1e0cebc | ||
|
|
120c953caa | ||
|
|
c689834ba5 | ||
|
|
fd6e2e6061 | ||
|
|
a10586906f | ||
|
|
d1ed7426bc | ||
|
|
7ee745d4ce | ||
|
|
82a4e395cb | ||
|
|
9eaea370e6 | ||
|
|
75316dbd52 | ||
|
|
b14d5663a3 | ||
|
|
77155f79c1 | ||
|
|
dbcfbf8012 | ||
|
|
9e6b8f832d | ||
|
|
4a30d464d2 | ||
|
|
974fe05ba3 | ||
|
|
55b541a268 | ||
|
|
9d3f1db01f | ||
|
|
a69bc741fb | ||
|
|
26ad334fdb | ||
|
|
204d7384b3 | ||
|
|
e7c49bfce7 | ||
|
|
94de5cf72b | ||
|
|
6f24594cf7 | ||
|
|
8e607aa6d7 | ||
|
|
e35ad7b1cd | ||
|
|
f59cbd85bb | ||
|
|
3498eb00b1 | ||
|
|
33b6d629b2 | ||
|
|
fc1d0cc504 | ||
|
|
a6ab2f3583 | ||
|
|
1525a334c2 | ||
|
|
378717a3c0 | ||
|
|
41427714fe | ||
|
|
a329c0fef9 | ||
|
|
cadad6d61d | ||
|
|
d0eb63073e | ||
|
|
d9f2ebbf0e | ||
|
|
cb683a82b6 | ||
|
|
66fc58054b | ||
|
|
e482ad78b4 | ||
|
|
d26cc928bf | ||
|
|
17b8b6504b | ||
|
|
bcd3937c4c |
1
.github/workflows/integrationci.yaml
vendored
1
.github/workflows/integrationci.yaml
vendored
@@ -60,6 +60,7 @@ jobs:
|
||||
- rawexportdata
|
||||
- promqlconformance
|
||||
- promapiconformance
|
||||
- promote
|
||||
- querierauthz
|
||||
- role
|
||||
- rootuser
|
||||
|
||||
@@ -7661,8 +7661,19 @@ components:
|
||||
- metric
|
||||
- value
|
||||
type: object
|
||||
PromotetypesIndexMaterializedPathsParams:
|
||||
properties:
|
||||
context:
|
||||
type: string
|
||||
dryRun:
|
||||
type: boolean
|
||||
signal:
|
||||
type: string
|
||||
type: object
|
||||
PromotetypesPromotePath:
|
||||
properties:
|
||||
context:
|
||||
type: string
|
||||
indexes:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesWrappedIndex'
|
||||
@@ -7671,6 +7682,12 @@ components:
|
||||
type: string
|
||||
promote:
|
||||
type: boolean
|
||||
signal:
|
||||
type: string
|
||||
required:
|
||||
- signal
|
||||
- context
|
||||
- path
|
||||
type: object
|
||||
PromotetypesWrappedIndex:
|
||||
properties:
|
||||
@@ -9041,15 +9058,6 @@ components:
|
||||
- alertType
|
||||
- ruleType
|
||||
type: object
|
||||
RuletypesListableRuleViews:
|
||||
properties:
|
||||
views:
|
||||
items:
|
||||
$ref: '#/components/schemas/RuletypesRuleView'
|
||||
type: array
|
||||
required:
|
||||
- views
|
||||
type: object
|
||||
RuletypesListableRules:
|
||||
properties:
|
||||
labels:
|
||||
@@ -9147,16 +9155,6 @@ components:
|
||||
- ruleType
|
||||
- condition
|
||||
type: object
|
||||
RuletypesPostableRuleView:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RuletypesRuleViewData'
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- data
|
||||
type: object
|
||||
RuletypesQueryType:
|
||||
enum:
|
||||
- builder
|
||||
@@ -9295,45 +9293,6 @@ components:
|
||||
- promql_rule
|
||||
- anomaly_rule
|
||||
type: string
|
||||
RuletypesRuleView:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/RuletypesRuleViewData'
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- data
|
||||
- orgId
|
||||
type: object
|
||||
RuletypesRuleViewData:
|
||||
properties:
|
||||
order:
|
||||
$ref: '#/components/schemas/RuletypesListOrder'
|
||||
query:
|
||||
type: string
|
||||
sort:
|
||||
$ref: '#/components/schemas/RuletypesListSort'
|
||||
states:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
version:
|
||||
type: string
|
||||
required:
|
||||
- version
|
||||
type: object
|
||||
RuletypesScheduleType:
|
||||
enum:
|
||||
- hourly
|
||||
@@ -13204,110 +13163,6 @@ paths:
|
||||
tags:
|
||||
- llmpricingrules
|
||||
x-signoz-stability: alpha
|
||||
/api/v1/logs/promote_paths:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoints promotes and indexes paths
|
||||
operationId: ListPromotedAndIndexedPaths
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesPromotePath'
|
||||
nullable: true
|
||||
type: array
|
||||
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: Promote and index paths
|
||||
tags:
|
||||
- logs
|
||||
x-signoz-stability: alpha
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoints promotes and indexes paths
|
||||
operationId: HandlePromoteAndIndexPaths
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesPromotePath'
|
||||
nullable: true
|
||||
type: array
|
||||
responses:
|
||||
"201":
|
||||
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
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
summary: Promote and index paths
|
||||
tags:
|
||||
- logs
|
||||
x-signoz-stability: alpha
|
||||
/api/v1/org/preferences:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -13479,6 +13334,215 @@ paths:
|
||||
tags:
|
||||
- preferences
|
||||
x-signoz-stability: alpha
|
||||
/api/v1/promoted_path:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint lists the promoted paths of every JSON column, each
|
||||
annotated with its signal and context. The signal, context, promoted and indexes
|
||||
query parameters filter the listing. Requires the list scope of each signal
|
||||
the filters select, or of every signal when none match.
|
||||
operationId: ListPromotedPaths
|
||||
parameters:
|
||||
- in: query
|
||||
name: signal
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: context
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: promoted
|
||||
schema:
|
||||
nullable: true
|
||||
type: boolean
|
||||
- in: query
|
||||
name: indexes
|
||||
schema:
|
||||
nullable: true
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesPromotePath'
|
||||
nullable: true
|
||||
type: array
|
||||
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:
|
||||
- logs-field:list
|
||||
- traces-field:list
|
||||
- tokenizer:
|
||||
- logs-field:list
|
||||
- traces-field:list
|
||||
summary: List promoted paths
|
||||
tags:
|
||||
- promote
|
||||
x-signoz-stability: alpha
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint promotes paths of JSON columns to their promoted
|
||||
columns. Each path names its promotion domain with its signal and context,
|
||||
e.g. traces/attribute. Requires the update scope of each signal in the request.
|
||||
operationId: PromotePaths
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesPromotePath'
|
||||
nullable: true
|
||||
type: array
|
||||
responses:
|
||||
"201":
|
||||
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
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- logs-field:update
|
||||
- traces-field:update
|
||||
- tokenizer:
|
||||
- logs-field:update
|
||||
- traces-field:update
|
||||
summary: Promote paths
|
||||
tags:
|
||||
- promote
|
||||
x-signoz-stability: alpha
|
||||
/api/v1/promoted_path/materialized:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint indexes the JSON sub-columns of materialized paths
|
||||
and returns them. Requires the update scope of each signal the filters select.
|
||||
operationId: IndexMaterializedPaths
|
||||
parameters:
|
||||
- in: query
|
||||
name: signal
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: context
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: dryRun
|
||||
schema:
|
||||
type: boolean
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromotetypesIndexMaterializedPathsParams'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/PromotetypesPromotePath'
|
||||
nullable: true
|
||||
type: array
|
||||
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:
|
||||
- logs-field:update
|
||||
- traces-field:update
|
||||
- tokenizer:
|
||||
- logs-field:update
|
||||
- traces-field:update
|
||||
summary: Index materialized paths
|
||||
tags:
|
||||
- promote
|
||||
x-signoz-stability: alpha
|
||||
/api/v1/roles:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -21251,235 +21315,6 @@ paths:
|
||||
tags:
|
||||
- users
|
||||
x-signoz-stability: alpha
|
||||
/api/v2/rule_views:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns every saved view in the calling user's org. Saved views
|
||||
are shared org-wide.
|
||||
operationId: ListRuleViews
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RuletypesListableRuleViews'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"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 rule saved views
|
||||
tags:
|
||||
- rules
|
||||
x-signoz-stability: alpha
|
||||
post:
|
||||
deprecated: false
|
||||
description: Persists the calling user's rule listing state (query, states,
|
||||
sort, order) as a named, reusable view shared across the org.
|
||||
operationId: CreateRuleView
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuletypesPostableRuleView'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RuletypesRuleView'
|
||||
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
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Create rule saved view
|
||||
tags:
|
||||
- rules
|
||||
x-signoz-stability: alpha
|
||||
/api/v2/rule_views/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: Removes a saved view. Saved views are shared org-wide. Deleting
|
||||
a non-existent view returns 404.
|
||||
operationId: DeleteRuleView
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"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
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Delete rule saved view
|
||||
tags:
|
||||
- rules
|
||||
x-signoz-stability: alpha
|
||||
put:
|
||||
deprecated: false
|
||||
description: Replaces a saved view's name and data. Saved views are shared org-wide.
|
||||
operationId: UpdateRuleView
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RuletypesPostableRuleView'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RuletypesRuleView'
|
||||
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
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Update rule saved view
|
||||
tags:
|
||||
- rules
|
||||
x-signoz-stability: alpha
|
||||
/api/v2/rules:
|
||||
get:
|
||||
deprecated: true
|
||||
|
||||
@@ -4,23 +4,15 @@
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useMutation } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
HandleExportRawDataPOSTParams,
|
||||
ListPromotedAndIndexedPaths200,
|
||||
PromotetypesPromotePathDTO,
|
||||
Querybuildertypesv5QueryRangeRequestDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -28,26 +20,6 @@ import type {
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
const withQueryKey = <T extends object, K>(
|
||||
query: T,
|
||||
queryKey: K,
|
||||
): T & { queryKey: K } => {
|
||||
const result = { queryKey } as T & { queryKey: K };
|
||||
for (const key of Object.keys(query)) {
|
||||
// The explicit queryKey always wins, matching the previous
|
||||
// `{ ...query, queryKey }` spread where it was set last.
|
||||
if (key === 'queryKey') {
|
||||
continue;
|
||||
}
|
||||
Object.defineProperty(result, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: () => (query as Record<string, unknown>)[key],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoints allows complex query exporting raw data for traces and logs
|
||||
* @summary Export raw data
|
||||
@@ -149,175 +121,3 @@ export const useHandleExportRawDataPOST = <
|
||||
> => {
|
||||
return useMutation(getHandleExportRawDataPOSTMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoints promotes and indexes paths
|
||||
* @summary Promote and index paths
|
||||
*/
|
||||
export const listPromotedAndIndexedPaths = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListPromotedAndIndexedPaths200>({
|
||||
url: `/api/v1/logs/promote_paths`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListPromotedAndIndexedPathsQueryKey = () => {
|
||||
return [`/api/v1/logs/promote_paths`] as const;
|
||||
};
|
||||
|
||||
export const getListPromotedAndIndexedPathsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListPromotedAndIndexedPathsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>
|
||||
> = ({ signal }) => listPromotedAndIndexedPaths(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListPromotedAndIndexedPathsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>
|
||||
>;
|
||||
export type ListPromotedAndIndexedPathsQueryError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Promote and index paths
|
||||
*/
|
||||
|
||||
export function useListPromotedAndIndexedPaths<
|
||||
TData = Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedAndIndexedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListPromotedAndIndexedPathsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Promote and index paths
|
||||
*/
|
||||
export const invalidateListPromotedAndIndexedPaths = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListPromotedAndIndexedPathsQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoints promotes and indexes paths
|
||||
* @summary Promote and index paths
|
||||
*/
|
||||
export const handlePromoteAndIndexPaths = (
|
||||
promotetypesPromotePathDTONull?: BodyType<
|
||||
PromotetypesPromotePathDTO[] | null
|
||||
> | null,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/logs/promote_paths`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: promotetypesPromotePathDTONull,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getHandlePromoteAndIndexPathsMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof handlePromoteAndIndexPaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof handlePromoteAndIndexPaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['handlePromoteAndIndexPaths'];
|
||||
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 handlePromoteAndIndexPaths>>,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return handlePromoteAndIndexPaths(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type HandlePromoteAndIndexPathsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof handlePromoteAndIndexPaths>>
|
||||
>;
|
||||
export type HandlePromoteAndIndexPathsMutationBody =
|
||||
| BodyType<PromotetypesPromotePathDTO[] | null>
|
||||
| undefined;
|
||||
export type HandlePromoteAndIndexPathsMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Promote and index paths
|
||||
*/
|
||||
export const useHandlePromoteAndIndexPaths = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof handlePromoteAndIndexPaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof handlePromoteAndIndexPaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getHandlePromoteAndIndexPathsMutationOptions(options));
|
||||
};
|
||||
|
||||
336
frontend/src/api/generated/services/promote/index.ts
Normal file
336
frontend/src/api/generated/services/promote/index.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'pnpm generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
IndexMaterializedPaths200,
|
||||
IndexMaterializedPathsParams,
|
||||
ListPromotedPaths200,
|
||||
ListPromotedPathsParams,
|
||||
PromotetypesIndexMaterializedPathsParamsDTO,
|
||||
PromotetypesPromotePathDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
const withQueryKey = <T extends object, K>(
|
||||
query: T,
|
||||
queryKey: K,
|
||||
): T & { queryKey: K } => {
|
||||
const result = { queryKey } as T & { queryKey: K };
|
||||
for (const key of Object.keys(query)) {
|
||||
// The explicit queryKey always wins, matching the previous
|
||||
// `{ ...query, queryKey }` spread where it was set last.
|
||||
if (key === 'queryKey') {
|
||||
continue;
|
||||
}
|
||||
Object.defineProperty(result, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: () => (query as Record<string, unknown>)[key],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint lists the promoted paths of every JSON column, each annotated with its signal and context. The signal, context, promoted and indexes query parameters filter the listing. Requires the list scope of each signal the filters select, or of every signal when none match.
|
||||
* @summary List promoted paths
|
||||
*/
|
||||
export const listPromotedPaths = (
|
||||
params?: ListPromotedPathsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListPromotedPaths200>({
|
||||
url: `/api/v1/promoted_path`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListPromotedPathsQueryKey = (
|
||||
params?: ListPromotedPathsParams,
|
||||
) => {
|
||||
return [`/api/v1/promoted_path`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListPromotedPathsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listPromotedPaths>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListPromotedPathsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListPromotedPathsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listPromotedPaths>>
|
||||
> = ({ signal }) => listPromotedPaths(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListPromotedPathsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listPromotedPaths>>
|
||||
>;
|
||||
export type ListPromotedPathsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List promoted paths
|
||||
*/
|
||||
|
||||
export function useListPromotedPaths<
|
||||
TData = Awaited<ReturnType<typeof listPromotedPaths>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListPromotedPathsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listPromotedPaths>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListPromotedPathsQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List promoted paths
|
||||
*/
|
||||
export const invalidateListPromotedPaths = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListPromotedPathsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListPromotedPathsQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint promotes paths of JSON columns to their promoted columns. Each path names its promotion domain with its signal and context, e.g. traces/attribute. Requires the update scope of each signal in the request.
|
||||
* @summary Promote paths
|
||||
*/
|
||||
export const promotePaths = (
|
||||
promotetypesPromotePathDTONull?: BodyType<
|
||||
PromotetypesPromotePathDTO[] | null
|
||||
> | null,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/promoted_path`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: promotetypesPromotePathDTONull,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getPromotePathsMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof promotePaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof promotePaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['promotePaths'];
|
||||
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 promotePaths>>,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return promotePaths(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type PromotePathsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof promotePaths>>
|
||||
>;
|
||||
export type PromotePathsMutationBody =
|
||||
| BodyType<PromotetypesPromotePathDTO[] | null>
|
||||
| undefined;
|
||||
export type PromotePathsMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Promote paths
|
||||
*/
|
||||
export const usePromotePaths = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof promotePaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof promotePaths>>,
|
||||
TError,
|
||||
{ data?: BodyType<PromotetypesPromotePathDTO[] | null> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getPromotePathsMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint indexes the JSON sub-columns of materialized paths and returns them. Requires the update scope of each signal the filters select.
|
||||
* @summary Index materialized paths
|
||||
*/
|
||||
export const indexMaterializedPaths = (
|
||||
promotetypesIndexMaterializedPathsParamsDTO?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>,
|
||||
params?: IndexMaterializedPathsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<IndexMaterializedPaths200>({
|
||||
url: `/api/v1/promoted_path/materialized`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: promotetypesIndexMaterializedPathsParamsDTO,
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getIndexMaterializedPathsMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof indexMaterializedPaths>>,
|
||||
TError,
|
||||
{
|
||||
data?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>;
|
||||
params?: IndexMaterializedPathsParams;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof indexMaterializedPaths>>,
|
||||
TError,
|
||||
{
|
||||
data?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>;
|
||||
params?: IndexMaterializedPathsParams;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['indexMaterializedPaths'];
|
||||
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 indexMaterializedPaths>>,
|
||||
{
|
||||
data?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>;
|
||||
params?: IndexMaterializedPathsParams;
|
||||
}
|
||||
> = (props) => {
|
||||
const { data, params } = props ?? {};
|
||||
|
||||
return indexMaterializedPaths(data, params);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type IndexMaterializedPathsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof indexMaterializedPaths>>
|
||||
>;
|
||||
export type IndexMaterializedPathsMutationBody =
|
||||
| BodyType<PromotetypesIndexMaterializedPathsParamsDTO>
|
||||
| undefined;
|
||||
export type IndexMaterializedPathsMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Index materialized paths
|
||||
*/
|
||||
export const useIndexMaterializedPaths = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof indexMaterializedPaths>>,
|
||||
TError,
|
||||
{
|
||||
data?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>;
|
||||
params?: IndexMaterializedPathsParams;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof indexMaterializedPaths>>,
|
||||
TError,
|
||||
{
|
||||
data?: BodyType<PromotetypesIndexMaterializedPathsParamsDTO>;
|
||||
params?: IndexMaterializedPathsParams;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getIndexMaterializedPathsMutationOptions(options));
|
||||
};
|
||||
@@ -19,9 +19,7 @@ import type {
|
||||
|
||||
import type {
|
||||
CreateRule201,
|
||||
CreateRuleView201,
|
||||
DeleteRuleByIDPathParameters,
|
||||
DeleteRuleViewPathParameters,
|
||||
GetRuleByID200,
|
||||
GetRuleByIDPathParameters,
|
||||
GetRuleHistoryFilterKeys200,
|
||||
@@ -42,7 +40,6 @@ import type {
|
||||
GetRuleHistoryTopContributors200,
|
||||
GetRuleHistoryTopContributorsParams,
|
||||
GetRuleHistoryTopContributorsPathParameters,
|
||||
ListRuleViews200,
|
||||
ListRules200,
|
||||
ListRulesV3200,
|
||||
ListRulesV3Params,
|
||||
@@ -50,11 +47,8 @@ import type {
|
||||
PatchRuleByIDPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesPostableRuleDTO,
|
||||
RuletypesPostableRuleViewDTO,
|
||||
TestRule200,
|
||||
UpdateRuleByIDPathParameters,
|
||||
UpdateRuleView200,
|
||||
UpdateRuleViewPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
@@ -80,351 +74,6 @@ const withQueryKey = <T extends object, K>(
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns every saved view in the calling user's org. Saved views are shared org-wide.
|
||||
* @summary List rule saved views
|
||||
*/
|
||||
export const listRuleViews = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListRuleViews200>({
|
||||
url: `/api/v2/rule_views`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListRuleViewsQueryKey = () => {
|
||||
return [`/api/v2/rule_views`] as const;
|
||||
};
|
||||
|
||||
export const getListRuleViewsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listRuleViews>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listRuleViews>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListRuleViewsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listRuleViews>>> = ({
|
||||
signal,
|
||||
}) => listRuleViews(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listRuleViews>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListRuleViewsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listRuleViews>>
|
||||
>;
|
||||
export type ListRuleViewsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List rule saved views
|
||||
*/
|
||||
|
||||
export function useListRuleViews<
|
||||
TData = Awaited<ReturnType<typeof listRuleViews>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listRuleViews>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListRuleViewsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return withQueryKey(query, queryOptions.queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List rule saved views
|
||||
*/
|
||||
export const invalidateListRuleViews = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListRuleViewsQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Persists the calling user's rule listing state (query, states, sort, order) as a named, reusable view shared across the org.
|
||||
* @summary Create rule saved view
|
||||
*/
|
||||
export const createRuleView = (
|
||||
ruletypesPostableRuleViewDTO?: BodyType<RuletypesPostableRuleViewDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateRuleView201>({
|
||||
url: `/api/v2/rule_views`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: ruletypesPostableRuleViewDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateRuleViewMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRuleView>>,
|
||||
TError,
|
||||
{ data?: BodyType<RuletypesPostableRuleViewDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRuleView>>,
|
||||
TError,
|
||||
{ data?: BodyType<RuletypesPostableRuleViewDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createRuleView'];
|
||||
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 createRuleView>>,
|
||||
{ data?: BodyType<RuletypesPostableRuleViewDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createRuleView(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateRuleViewMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createRuleView>>
|
||||
>;
|
||||
export type CreateRuleViewMutationBody =
|
||||
| BodyType<RuletypesPostableRuleViewDTO>
|
||||
| undefined;
|
||||
export type CreateRuleViewMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create rule saved view
|
||||
*/
|
||||
export const useCreateRuleView = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRuleView>>,
|
||||
TError,
|
||||
{ data?: BodyType<RuletypesPostableRuleViewDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createRuleView>>,
|
||||
TError,
|
||||
{ data?: BodyType<RuletypesPostableRuleViewDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateRuleViewMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Removes a saved view. Saved views are shared org-wide. Deleting a non-existent view returns 404.
|
||||
* @summary Delete rule saved view
|
||||
*/
|
||||
export const deleteRuleView = (
|
||||
{ id }: DeleteRuleViewPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/rule_views/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteRuleViewMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRuleView>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRuleViewPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRuleView>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRuleViewPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteRuleView'];
|
||||
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 deleteRuleView>>,
|
||||
{ pathParams: DeleteRuleViewPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteRuleView(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteRuleViewMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteRuleView>>
|
||||
>;
|
||||
|
||||
export type DeleteRuleViewMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete rule saved view
|
||||
*/
|
||||
export const useDeleteRuleView = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRuleView>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRuleViewPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteRuleView>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRuleViewPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteRuleViewMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Replaces a saved view's name and data. Saved views are shared org-wide.
|
||||
* @summary Update rule saved view
|
||||
*/
|
||||
export const updateRuleView = (
|
||||
{ id }: UpdateRuleViewPathParameters,
|
||||
ruletypesPostableRuleViewDTO?: BodyType<RuletypesPostableRuleViewDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateRuleView200>({
|
||||
url: `/api/v2/rule_views/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: ruletypesPostableRuleViewDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateRuleViewMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRuleView>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRuleViewPathParameters;
|
||||
data?: BodyType<RuletypesPostableRuleViewDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRuleView>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRuleViewPathParameters;
|
||||
data?: BodyType<RuletypesPostableRuleViewDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateRuleView'];
|
||||
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 updateRuleView>>,
|
||||
{
|
||||
pathParams: UpdateRuleViewPathParameters;
|
||||
data?: BodyType<RuletypesPostableRuleViewDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateRuleView(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateRuleViewMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateRuleView>>
|
||||
>;
|
||||
export type UpdateRuleViewMutationBody =
|
||||
| BodyType<RuletypesPostableRuleViewDTO>
|
||||
| undefined;
|
||||
export type UpdateRuleViewMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update rule saved view
|
||||
*/
|
||||
export const useUpdateRuleView = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRuleView>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRuleViewPathParameters;
|
||||
data?: BodyType<RuletypesPostableRuleViewDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateRuleView>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRuleViewPathParameters;
|
||||
data?: BodyType<RuletypesPostableRuleViewDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUpdateRuleViewMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint lists all alert rules with their current evaluation state. Deprecated: use ListRulesV3, which supports filtering, sorting and pagination.
|
||||
* @deprecated
|
||||
|
||||
@@ -9423,6 +9423,21 @@ export interface PrometheusSuccessResponseSchemaDTO {
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
export interface PromotetypesIndexMaterializedPathsParamsDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
context?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
dryRun?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal?: string;
|
||||
}
|
||||
|
||||
export interface PromotetypesWrappedIndexDTO {
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
/**
|
||||
@@ -9436,6 +9451,10 @@ export interface PromotetypesWrappedIndexDTO {
|
||||
}
|
||||
|
||||
export interface PromotetypesPromotePathDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
context: string;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -9443,11 +9462,15 @@ export interface PromotetypesPromotePathDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
path?: string;
|
||||
path: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
promote?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal: string;
|
||||
}
|
||||
|
||||
export interface Querybuildertypesv5AggregationMetaDTO {
|
||||
@@ -10328,56 +10351,6 @@ export interface RuletypesListableRuleDTO {
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface RuletypesRuleViewDataDTO {
|
||||
order?: RuletypesListOrderDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
query?: string;
|
||||
sort?: RuletypesListSortDTO;
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
states?: string[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface RuletypesRuleViewDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: string;
|
||||
data: RuletypesRuleViewDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface RuletypesListableRuleViewsDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
views: RuletypesRuleViewDTO[];
|
||||
}
|
||||
|
||||
export interface RuletypesListableRulesDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -10546,14 +10519,6 @@ export interface RuletypesPostableRuleDTO {
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface RuletypesPostableRuleViewDTO {
|
||||
data: RuletypesRuleViewDataDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type RuletypesRuleDTOAnnotations = { [key: string]: string };
|
||||
|
||||
export type RuletypesRuleDTOLabels = { [key: string]: string };
|
||||
@@ -12595,17 +12560,6 @@ export type ListUnmappedLLMModels200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPromotedAndIndexedPaths200 = {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
data: PromotetypesPromotePathDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListOrgPreferences200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -12631,6 +12585,69 @@ export type GetOrgPreference200 = {
|
||||
export type UpdateOrgPreferencePathParameters = {
|
||||
name: string;
|
||||
};
|
||||
export type ListPromotedPathsParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
context?: string;
|
||||
/**
|
||||
* @type boolean,null
|
||||
* @description undefined
|
||||
*/
|
||||
promoted?: boolean | null;
|
||||
/**
|
||||
* @type boolean,null
|
||||
* @description undefined
|
||||
*/
|
||||
indexes?: boolean | null;
|
||||
};
|
||||
|
||||
export type ListPromotedPaths200 = {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
data: PromotetypesPromotePathDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type IndexMaterializedPathsParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
context?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
* @description undefined
|
||||
*/
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type IndexMaterializedPaths200 = {
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
data: PromotetypesPromotePathDTO[] | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListRoles200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -13841,36 +13858,6 @@ export type GetUsersByRoleID200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListRuleViews200 = {
|
||||
data: RuletypesListableRuleViewsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateRuleView201 = {
|
||||
data: RuletypesRuleViewDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteRuleViewPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateRuleViewPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateRuleView200 = {
|
||||
data: RuletypesRuleViewDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListRules200 = {
|
||||
/**
|
||||
* @type array
|
||||
|
||||
@@ -5,9 +5,22 @@
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-5);
|
||||
padding: var(--spacing-6) var(--spacing-8);
|
||||
border-radius: var(--radius-2);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-amber-400) 30%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-amber-400) 8%, transparent);
|
||||
color: var(--bg-amber-300, var(--bg-amber-400));
|
||||
margin-top: var(--spacing-4);
|
||||
}
|
||||
|
||||
.bannerIcon {
|
||||
flex-shrink: 0;
|
||||
margin-top: var(--spacing-1);
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: var(--spacing-6) var(--spacing-8);
|
||||
border-radius: var(--radius-2);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Callout } from '@signozhq/ui/callout';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TriangleAlert } from '@signozhq/icons';
|
||||
import { useListUnmappedLLMModels } from 'api/generated/services/llmpricingrules';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -80,10 +80,13 @@ function UnpricedModelsTab(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className={styles.unpricedModelsTab}>
|
||||
<Callout type="warning" size="small" showIcon className={styles.banner}>
|
||||
Models detected in traces without pricing. Map each to a billing model or
|
||||
create pricing so estimated cost can be computed.
|
||||
</Callout>
|
||||
<div className={styles.banner}>
|
||||
<TriangleAlert size="sm" className={styles.bannerIcon} />
|
||||
<Typography.Text as="span" size="small" color="warning">
|
||||
Models detected in traces without pricing. Map each to a billing model or
|
||||
create pricing so estimated cost can be computed.
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{isError && (
|
||||
<div className={styles.error}>
|
||||
|
||||
46
go.mod
46
go.mod
@@ -73,18 +73,18 @@ require (
|
||||
go.opentelemetry.io/contrib/config v0.10.0
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.63.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.45.0
|
||||
go.opentelemetry.io/otel/metric v1.45.0
|
||||
go.opentelemetry.io/otel/sdk v1.45.0
|
||||
go.opentelemetry.io/otel/trace v1.45.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/text v0.41.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/text v0.37.0
|
||||
gonum.org/v1/gonum v0.17.0
|
||||
google.golang.org/api v0.272.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
@@ -151,7 +151,7 @@ require (
|
||||
go.opentelemetry.io/collector/pdata/xpdata v0.148.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
@@ -164,7 +164,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.2 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
@@ -197,7 +197,7 @@ require (
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.24.2 // indirect
|
||||
@@ -222,7 +222,7 @@ require (
|
||||
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.5 // indirect
|
||||
@@ -360,27 +360,27 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0
|
||||
go.opentelemetry.io/proto/otlp v1.11.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/proto/otlp v1.10.0
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
gopkg.in/telebot.v3 v3.3.8 // indirect
|
||||
k8s.io/client-go v0.35.3 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
|
||||
94
go.sum
94
go.sum
@@ -1,5 +1,5 @@
|
||||
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@@ -336,8 +336,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
@@ -549,8 +549,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajR
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
@@ -1255,8 +1255,8 @@ go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLb
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
|
||||
go.opentelemetry.io/contrib/zpages v0.63.0 h1:TppOKuZGbqXMgsfjqq3i09N5Vbo1JLtLImUqiTPGnX4=
|
||||
go.opentelemetry.io/contrib/zpages v0.63.0/go.mod h1:5F8uugz75ay/MMhRRhxAXY33FuaI8dl7jTxefrIy5qk=
|
||||
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
|
||||
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
|
||||
@@ -1265,12 +1265,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
|
||||
@@ -1283,23 +1283,21 @@ go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIE
|
||||
go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
|
||||
go.opentelemetry.io/otel/log/logtest v0.14.0 h1:BGTqNeluJDK2uIHAY8lRqxjVAYfqgcaTbVk1n3MWe5A=
|
||||
go.opentelemetry.io/otel/log/logtest v0.14.0/go.mod h1:IuguGt8XVP4XA4d2oEEDMVDBBCesMg8/tSGWDjuKfoA=
|
||||
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
|
||||
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
|
||||
go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns=
|
||||
go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
|
||||
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
|
||||
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=
|
||||
go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=
|
||||
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=
|
||||
@@ -1339,8 +1337,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1378,8 +1376,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1427,8 +1425,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1463,8 +1461,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1552,12 +1550,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1568,8 +1566,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1632,8 +1630,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1774,10 +1772,10 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc=
|
||||
google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
@@ -1810,8 +1808,8 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -3,42 +3,77 @@ package signozapiserver
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
)
|
||||
|
||||
func (provider *provider) addPromoteRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authzMiddleware.EditAccess(provider.promoteHandler.HandlePromoteAndIndexPaths), handler.OpenAPIDef{
|
||||
ID: "HandlePromoteAndIndexPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
Description: "This endpoints promotes and indexes paths",
|
||||
if err := router.Handle("/api/v1/promoted_path", handler.New(provider.authzMiddleware.CheckResources(provider.promoteHandler.PromotePaths, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName), handler.OpenAPIDef{
|
||||
ID: "PromotePaths",
|
||||
Tags: []string{"promote"},
|
||||
Summary: "Promote paths",
|
||||
Description: "This endpoint promotes paths of JSON columns to their promoted columns. Each path names its promotion domain with its signal and context, e.g. traces/attribute. Requires the update scope of each signal in the request.",
|
||||
Request: new([]*promotetypes.PromotePath),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLogsField.Scope(coretypes.VerbUpdate), coretypes.ResourceMetaResourceTracesField.Scope(coretypes.VerbUpdate)}),
|
||||
}, handler.WithResourceDefs(handler.TelemetryResourceDef{
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
Resources: promotetypes.PromotePathsResources,
|
||||
}))).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/logs/promote_paths", handler.New(provider.authzMiddleware.ViewAccess(provider.promoteHandler.ListPromotedAndIndexedPaths), handler.OpenAPIDef{
|
||||
ID: "ListPromotedAndIndexedPaths",
|
||||
Tags: []string{"logs"},
|
||||
Summary: "Promote and index paths",
|
||||
Description: "This endpoints promotes and indexes paths",
|
||||
if err := router.Handle("/api/v1/promoted_path", handler.New(provider.authzMiddleware.CheckResources(provider.promoteHandler.ListPromotedPaths, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName), handler.OpenAPIDef{
|
||||
ID: "ListPromotedPaths",
|
||||
Tags: []string{"promote"},
|
||||
Summary: "List promoted paths",
|
||||
Description: "This endpoint lists the promoted paths of every JSON column, each annotated with its signal and context. The signal, context, promoted and indexes query parameters filter the listing. Requires the list scope of each signal the filters select, or of every signal when none match.",
|
||||
Request: nil,
|
||||
RequestQuery: new(promotetypes.ListPromotedPathsFilters),
|
||||
RequestContentType: "",
|
||||
Response: new([]*promotetypes.PromotePath),
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLogsField.Scope(coretypes.VerbList), coretypes.ResourceMetaResourceTracesField.Scope(coretypes.VerbList)}),
|
||||
}, handler.WithResourceDefs(handler.TelemetryResourceDef{
|
||||
Verb: coretypes.VerbList,
|
||||
Category: coretypes.ActionCategoryDataAccess,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
Resources: promotetypes.FilteredSignalsResources,
|
||||
}))).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/promoted_path/materialized", handler.New(provider.authzMiddleware.CheckResources(provider.promoteHandler.IndexMaterializedPaths, authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName), handler.OpenAPIDef{
|
||||
ID: "IndexMaterializedPaths",
|
||||
Tags: []string{"promote"},
|
||||
Summary: "Index materialized paths",
|
||||
Description: "This endpoint indexes the JSON sub-columns of materialized paths and returns them. Requires the update scope of each signal the filters select.",
|
||||
Request: nil,
|
||||
RequestQuery: new(promotetypes.IndexMaterializedPathsParams),
|
||||
RequestContentType: "",
|
||||
Response: new([]*promotetypes.PromotePath),
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceLogsField.Scope(coretypes.VerbUpdate), coretypes.ResourceMetaResourceTracesField.Scope(coretypes.VerbUpdate)}),
|
||||
}, handler.WithResourceDefs(handler.TelemetryResourceDef{
|
||||
Verb: coretypes.VerbUpdate,
|
||||
Category: coretypes.ActionCategoryConfigurationChange,
|
||||
Selector: coretypes.WildcardSelector,
|
||||
Resources: promotetypes.FilteredSignalsResources,
|
||||
}))).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -132,64 +132,6 @@ func (provider *provider) addRulerRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListRuleViews), handler.OpenAPIDef{
|
||||
ID: "ListRuleViews",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "List rule saved views",
|
||||
Description: "Returns every saved view in the calling user's org. Saved views are shared org-wide.",
|
||||
Response: new(ruletypes.ListableRuleViews),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.CreateRuleView), handler.OpenAPIDef{
|
||||
ID: "CreateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Create rule saved view",
|
||||
Description: "Persists the calling user's rule listing state (query, states, sort, order) as a named, reusable view shared across the org.",
|
||||
Request: new(ruletypes.PostableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.UpdateRuleView), handler.OpenAPIDef{
|
||||
ID: "UpdateRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Update rule saved view",
|
||||
Description: "Replaces a saved view's name and data. Saved views are shared org-wide.",
|
||||
Request: new(ruletypes.UpdatableRuleView),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(ruletypes.RuleView),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodPut).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/rule_views/{id}", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.DeleteRuleView), handler.OpenAPIDef{
|
||||
ID: "DeleteRuleView",
|
||||
Tags: []string{"rules"},
|
||||
Summary: "Delete rule saved view",
|
||||
Description: "Removes a saved view. Saved views are shared org-wide. Deleting a non-existent view returns 404.",
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/downtime_schedules", handler.New(provider.authzMiddleware.ViewAccess(provider.rulerHandler.ListDowntimeSchedules), handler.OpenAPIDef{
|
||||
ID: "ListDowntimeSchedules",
|
||||
Tags: []string{"downtimeschedules"},
|
||||
|
||||
@@ -58,9 +58,7 @@ func New(ctx context.Context, cfg Config, build version.Build, serviceName strin
|
||||
|
||||
// Prepare the resource configuration by merging
|
||||
// resource and attributes.
|
||||
// contribsdkconfig merges this resource with sdkresource.Default().
|
||||
// The merge fails if the schema URLs are different.
|
||||
sch := sdkresource.Default().SchemaURL()
|
||||
sch := semconv.SchemaURL
|
||||
configResource := contribsdkconfig.Resource{
|
||||
Attributes: mergeAttributes(cfg.Resource.Attributes, resource),
|
||||
Detectors: nil,
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewHandler(module promote.Module) promote.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (h *handler) HandlePromoteAndIndexPaths(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *handler) PromotePaths(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO(Nitya): Use in multi tenant setup
|
||||
_, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
@@ -32,8 +32,23 @@ func (h *handler) HandlePromoteAndIndexPaths(w http.ResponseWriter, r *http.Requ
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
if len(req) == 0 {
|
||||
render.Error(w, errors.NewInvalidInputf(errors.CodeInvalidInput, "paths cannot be empty"))
|
||||
return
|
||||
}
|
||||
for _, path := range req {
|
||||
target, err := path.Target()
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
if err := path.ValidateAndSetDefaults(target); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = h.module.PromoteAndIndexPaths(r.Context(), req...)
|
||||
err = h.module.PromotePaths(r.Context(), req...)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
@@ -42,7 +57,7 @@ func (h *handler) HandlePromoteAndIndexPaths(w http.ResponseWriter, r *http.Requ
|
||||
render.Success(w, http.StatusCreated, nil)
|
||||
}
|
||||
|
||||
func (h *handler) ListPromotedAndIndexedPaths(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *handler) ListPromotedPaths(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO(Nitya): Use in multi tenant setup
|
||||
_, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
@@ -50,7 +65,38 @@ func (h *handler) ListPromotedAndIndexedPaths(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := h.module.ListPromotedAndIndexedPaths(r.Context())
|
||||
var filters promotetypes.ListPromotedPathsFilters
|
||||
if err := binding.Query.BindQuery(r.URL.Query(), &filters); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
if err := filters.Validate(); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := h.module.ListPromotedPaths(r.Context(), filters)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, paths)
|
||||
}
|
||||
|
||||
func (h *handler) IndexMaterializedPaths(w http.ResponseWriter, r *http.Request) {
|
||||
var params promotetypes.IndexMaterializedPathsParams
|
||||
if err := binding.Query.BindQuery(r.URL.Query(), ¶ms); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
filters := params.Filters()
|
||||
if err := filters.Validate(); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := h.module.IndexMaterializedPaths(r.Context(), params)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
|
||||
@@ -2,14 +2,11 @@ package implpromote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryschema/logstelemetryschema"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
@@ -31,46 +28,72 @@ func NewModule(metadataStore telemetrytypes.MetadataStore, telemetrystore teleme
|
||||
return &module{metadataStore: metadataStore, telemetryStore: telemetrystore}
|
||||
}
|
||||
|
||||
func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetypes.PromotePath, error) {
|
||||
indexes, err := m.metadataStore.ListLogsJSONIndexes(ctx)
|
||||
func (m *module) ListPromotedPaths(ctx context.Context, filters promotetypes.ListPromotedPathsFilters) ([]promotetypes.PromotePath, error) {
|
||||
response := make([]promotetypes.PromotePath, 0)
|
||||
for _, target := range promotetypes.Targets() {
|
||||
if !filters.MatchesTarget(target) {
|
||||
continue
|
||||
}
|
||||
paths, err := m.listPromotedPaths(ctx, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, path := range paths {
|
||||
if filters.MatchesPath(path) {
|
||||
response = append(response, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (m *module) listPromotedPaths(ctx context.Context, target promotetypes.Target) ([]promotetypes.PromotePath, error) {
|
||||
promotedPaths, err := m.metadataStore.GetPromotedPaths(ctx, target.Entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := make([]promotetypes.PromotePath, 0, len(promotedPaths))
|
||||
for path := range promotedPaths {
|
||||
response = append(response, promotetypes.PromotePath{
|
||||
Signal: target.Entry.Signal.StringValue(),
|
||||
Context: target.Entry.FieldContext.StringValue(),
|
||||
Path: path,
|
||||
Promote: true,
|
||||
})
|
||||
}
|
||||
|
||||
indexes, err := m.metadataStore.ListJSONIndexes(ctx, target.JSONIndexLookup())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// aggr keys are full sub-column paths: index.BaseColumn carries the
|
||||
// column prefix and index.Name the bare path.
|
||||
aggr := map[string][]promotetypes.WrappedIndex{}
|
||||
for _, index := range indexes {
|
||||
aggr[index.Name] = append(aggr[index.Name], promotetypes.WrappedIndex{
|
||||
fullPath := index.BaseColumn + index.Name
|
||||
aggr[fullPath] = append(aggr[fullPath], promotetypes.WrappedIndex{
|
||||
FieldDataType: index.FieldDataType,
|
||||
Type: index.IndexType,
|
||||
Granularity: index.Granularity,
|
||||
})
|
||||
}
|
||||
promotedPaths, err := m.listPromotedPaths(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := []promotetypes.PromotePath{}
|
||||
for _, path := range promotedPaths {
|
||||
fullPath := logstelemetryschema.BodyPromotedColumnPrefix + path
|
||||
path = telemetrytypes.BodyJSONStringSearchPrefix + path
|
||||
item := promotetypes.PromotePath{
|
||||
Path: path,
|
||||
Promote: true,
|
||||
}
|
||||
indexes, ok := aggr[fullPath]
|
||||
if ok {
|
||||
item.Indexes = indexes
|
||||
for i := range response {
|
||||
fullPath := target.PromotedColumnPrefix() + response[i].Path
|
||||
if indexes, ok := aggr[fullPath]; ok {
|
||||
response[i].Indexes = indexes
|
||||
delete(aggr, fullPath)
|
||||
}
|
||||
response = append(response, item)
|
||||
}
|
||||
|
||||
// add the paths that are not promoted but have indexes
|
||||
for path, indexes := range aggr {
|
||||
path := strings.TrimPrefix(path, logstelemetryschema.BodyV2ColumnPrefix)
|
||||
path = telemetrytypes.BodyJSONStringSearchPrefix + path
|
||||
for fullPath, indexes := range aggr {
|
||||
path := strings.TrimPrefix(fullPath, target.BaseColumnPrefix())
|
||||
path = strings.TrimPrefix(path, target.PromotedColumnPrefix())
|
||||
response = append(response, promotetypes.PromotePath{
|
||||
Signal: target.Entry.Signal.StringValue(),
|
||||
Context: target.Entry.FieldContext.StringValue(),
|
||||
Path: path,
|
||||
Indexes: indexes,
|
||||
})
|
||||
@@ -78,68 +101,94 @@ func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetype
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (m *module) listPromotedPaths(ctx context.Context) ([]string, error) {
|
||||
paths, err := m.metadataStore.GetPromotedPaths(ctx)
|
||||
if err != nil {
|
||||
func (m *module) IndexMaterializedPaths(ctx context.Context, params promotetypes.IndexMaterializedPathsParams) ([]*promotetypes.PromotePath, error) {
|
||||
filters := params.Filters()
|
||||
keysBySignal := map[string][]*telemetrytypes.TelemetryFieldKey{}
|
||||
response := []*promotetypes.PromotePath{}
|
||||
for _, target := range promotetypes.Targets() {
|
||||
if !filters.MatchesTarget(target) {
|
||||
continue
|
||||
}
|
||||
|
||||
keys, ok := keysBySignal[target.Entry.Signal.StringValue()]
|
||||
if !ok {
|
||||
var err error
|
||||
keys, err = m.metadataStore.GetMaterializedKeys(ctx, target.Entry.Signal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keysBySignal[target.Entry.Signal.StringValue()] = keys
|
||||
}
|
||||
|
||||
paths := []*promotetypes.PromotePath{}
|
||||
names := []string{}
|
||||
for _, key := range keys {
|
||||
if key.FieldContext != target.Entry.FieldContext || key.FieldDataType != telemetrytypes.FieldDataTypeString || target.IsDefaultMaterialized(key.Name) {
|
||||
continue
|
||||
}
|
||||
if path, ok := promotetypes.NewMaterializedPromotePath(target, key.Name); ok {
|
||||
paths = append(paths, path)
|
||||
names = append(names, path.Path)
|
||||
}
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
existing, err := m.metadataStore.ListJSONIndexes(ctx, target.JSONIndexLookup(), names...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingByPath := map[string][]telemetrytypes.TelemetryFieldKeySkipIndex{}
|
||||
for _, index := range existing {
|
||||
existingByPath[index.Name] = append(existingByPath[index.Name], index)
|
||||
}
|
||||
for _, path := range paths {
|
||||
path.SkipExistingIndexes(existingByPath[path.Path])
|
||||
if len(path.Indexes) > 0 {
|
||||
response = append(response, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params.DryRun || len(response) == 0 {
|
||||
return response, nil
|
||||
}
|
||||
if err := m.PromotePaths(ctx, response...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slices.Collect(maps.Keys(paths)), nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// PromotePaths inserts provided JSON paths into the promoted paths table for logs queries.
|
||||
func (m *module) PromotePaths(ctx context.Context, paths []string) error {
|
||||
if len(paths) == 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "paths cannot be empty")
|
||||
}
|
||||
|
||||
return m.metadataStore.PromotePaths(ctx, paths...)
|
||||
}
|
||||
|
||||
// createIndexes creates string ngram + token filter indexes on JSON path subcolumns for LIKE queries.
|
||||
func (m *module) createIndexes(ctx context.Context, indexes []schemamigrator.Index) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "promote",
|
||||
instrumentationtypes.CodeFunctionName: "createIndexes",
|
||||
})
|
||||
if len(indexes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, index := range indexes {
|
||||
alterStmt := schemamigrator.AlterTableAddIndex{
|
||||
Database: logstelemetryschema.DBName,
|
||||
Table: logstelemetryschema.LogsV2LocalTableName,
|
||||
Index: index,
|
||||
func (m *module) PromotePaths(ctx context.Context, paths ...*promotetypes.PromotePath) error {
|
||||
byTarget := map[promotetypes.Target][]*promotetypes.PromotePath{}
|
||||
targets := []promotetypes.Target{}
|
||||
for _, path := range paths {
|
||||
target, err := path.Target()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
op := alterStmt.OnCluster(m.telemetryStore.Cluster())
|
||||
if err := m.telemetryStore.ClickhouseDB().Exec(ctx, op.ToSQL()); err != nil {
|
||||
return errors.WrapInternalf(err, CodeFailedToCreateIndex, "failed to create index")
|
||||
if _, ok := byTarget[target]; !ok {
|
||||
targets = append(targets, target)
|
||||
}
|
||||
byTarget[target] = append(byTarget[target], path)
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if err := m.promotePaths(ctx, target, byTarget[target]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PromoteAndIndexPaths handles promoting paths and creating indexes in one call.
|
||||
func (m *module) PromoteAndIndexPaths(
|
||||
ctx context.Context,
|
||||
paths ...*promotetypes.PromotePath,
|
||||
) error {
|
||||
if len(paths) == 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "paths cannot be empty")
|
||||
}
|
||||
|
||||
func (m *module) promotePaths(ctx context.Context, target promotetypes.Target, paths ...*promotetypes.PromotePath) error {
|
||||
pathsStr := []string{}
|
||||
// validate the paths
|
||||
for _, path := range paths {
|
||||
if err := path.ValidateAndSetDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
pathsStr = append(pathsStr, path.Path)
|
||||
}
|
||||
|
||||
existingPromotedPaths, err := m.metadataStore.GetPromotedPaths(ctx, pathsStr...)
|
||||
existingPromotedPaths, err := m.metadataStore.GetPromotedPaths(ctx, target.Entry, pathsStr...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -153,27 +202,20 @@ func (m *module) PromoteAndIndexPaths(
|
||||
}
|
||||
}
|
||||
if len(it.Indexes) > 0 {
|
||||
parentColumn := logstelemetryschema.LogsV2BodyV2Column
|
||||
parentColumn := target.BaseColumn
|
||||
// if the path is already promoted or is being promoted, add it to the promoted column
|
||||
if _, promoted := existingPromotedPaths[it.Path]; promoted || it.Promote {
|
||||
parentColumn = logstelemetryschema.LogsV2BodyPromotedColumn
|
||||
parentColumn = target.PromotedColumn()
|
||||
}
|
||||
|
||||
for _, index := range it.Indexes {
|
||||
var typeIndex schemamigrator.IndexType
|
||||
switch {
|
||||
case strings.HasPrefix(index.Type, string(schemamigrator.IndexTypeNGramBF)):
|
||||
typeIndex = schemamigrator.IndexTypeNGramBF
|
||||
case strings.HasPrefix(index.Type, string(schemamigrator.IndexTypeTokenBF)):
|
||||
typeIndex = schemamigrator.IndexTypeTokenBF
|
||||
case strings.HasPrefix(index.Type, string(schemamigrator.IndexTypeMinMax)):
|
||||
typeIndex = schemamigrator.IndexTypeMinMax
|
||||
default:
|
||||
typeIndex, ok := index.SkipIndexType()
|
||||
if !ok {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid index type: %s", index.Type)
|
||||
}
|
||||
indexes = append(indexes, schemamigrator.Index{
|
||||
Name: schemamigrator.JSONSubColumnIndexName(parentColumn, it.Path, index.JSONDataType.StringValue(), typeIndex),
|
||||
Expression: schemamigrator.JSONSubColumnIndexExpr(parentColumn, it.Path, index.JSONDataType.StringValue()),
|
||||
Expression: target.IndexExpression(parentColumn, it.Path, index.JSONDataType.StringValue()),
|
||||
Type: index.Type,
|
||||
Granularity: index.Granularity,
|
||||
})
|
||||
@@ -182,17 +224,42 @@ func (m *module) PromoteAndIndexPaths(
|
||||
}
|
||||
|
||||
if len(toInsert) > 0 {
|
||||
err := m.PromotePaths(ctx, toInsert)
|
||||
err := m.metadataStore.PromotePaths(ctx, target.Entry, toInsert...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(indexes) > 0 {
|
||||
if err := m.createIndexes(ctx, indexes); err != nil {
|
||||
if err := m.createIndexes(ctx, target, indexes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *module) createIndexes(ctx context.Context, target promotetypes.Target, indexes []schemamigrator.Index) error {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: target.Entry.Signal.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "promote",
|
||||
instrumentationtypes.CodeFunctionName: "createIndexes",
|
||||
})
|
||||
if len(indexes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, index := range indexes {
|
||||
alterStmt := schemamigrator.AlterTableAddIndex{
|
||||
Database: target.DBName,
|
||||
Table: target.LocalTableName,
|
||||
Index: index,
|
||||
}
|
||||
op := alterStmt.OnCluster(m.telemetryStore.Cluster())
|
||||
if err := m.telemetryStore.ClickhouseDB().Exec(ctx, op.ToSQL()); err != nil {
|
||||
return errors.WrapInternalf(err, CodeFailedToCreateIndex, "failed to create index")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
448
pkg/modules/promote/implpromote/module_test.go
Normal file
448
pkg/modules/promote/implpromote/module_test.go
Normal file
@@ -0,0 +1,448 @@
|
||||
package implpromote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
sqlmock "github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes/telemetrytypestest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPromotePaths(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
paths []*promotetypes.PromotePath
|
||||
promoteTwice bool
|
||||
wantErr bool
|
||||
wantPromoted []string
|
||||
}{
|
||||
{
|
||||
name: "PromotesNewAttributes_Idempotent",
|
||||
paths: []*promotetypes.PromotePath{
|
||||
{Signal: "traces", Context: "attribute", Path: "http.method", Promote: true},
|
||||
{Signal: "traces", Context: "attribute", Path: "span.operation", Promote: true},
|
||||
},
|
||||
promoteTwice: true,
|
||||
wantPromoted: []string{"http.method", "span.operation"},
|
||||
},
|
||||
{
|
||||
name: "MixedDomains_RecordedPerTarget",
|
||||
paths: []*promotetypes.PromotePath{
|
||||
{Signal: "traces", Context: "attribute", Path: "http.method", Promote: true},
|
||||
{Signal: "logs", Context: "body", Path: "user.name", Promote: true},
|
||||
},
|
||||
wantPromoted: []string{"http.method", "user.name"},
|
||||
},
|
||||
{
|
||||
name: "NonPromoteEntries_NotRecorded",
|
||||
paths: []*promotetypes.PromotePath{{Signal: "traces", Context: "attribute", Path: "http.method"}},
|
||||
},
|
||||
{
|
||||
name: "InvalidSignal_Rejected",
|
||||
paths: []*promotetypes.PromotePath{{Signal: "events", Context: "attribute", Path: "http.method", Promote: true}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UnsupportedDomain_Rejected",
|
||||
paths: []*promotetypes.PromotePath{{Signal: "metrics", Context: "attribute", Path: "http.method", Promote: true}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "PromotesBareBodyPath_AsIs",
|
||||
paths: []*promotetypes.PromotePath{{Signal: "logs", Context: "body", Path: "user.name", Promote: true}},
|
||||
wantPromoted: []string{"user.name"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
store := telemetrytypestest.NewMockMetadataStore()
|
||||
m := NewModule(store, nil)
|
||||
|
||||
err := m.PromotePaths(ctx, testCase.paths...)
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, store.PromotedPathsMap)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, store.PromotedPathsMap, len(testCase.wantPromoted))
|
||||
for _, path := range testCase.wantPromoted {
|
||||
assert.True(t, store.PromotedPathsMap[path], path)
|
||||
}
|
||||
|
||||
if testCase.promoteTwice {
|
||||
require.NoError(t, m.PromotePaths(ctx, testCase.paths...))
|
||||
assert.Len(t, store.PromotedPathsMap, len(testCase.wantPromoted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromotePathsCreatesIndexes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
promoted map[string]bool
|
||||
path *promotetypes.PromotePath
|
||||
wantDDLColumn string
|
||||
}{
|
||||
{
|
||||
name: "LogsNewPromotion_IndexesPromotedColumn",
|
||||
path: &promotetypes.PromotePath{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "user.name",
|
||||
Promote: true,
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1, JSONDataType: telemetrytypes.String},
|
||||
},
|
||||
},
|
||||
wantDDLColumn: "dynamicElement(body_promoted.user.name",
|
||||
},
|
||||
{
|
||||
name: "LogsAlreadyPromoted_IndexesPromotedColumn",
|
||||
promoted: map[string]bool{"user.name": true},
|
||||
path: &promotetypes.PromotePath{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "user.name",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1, JSONDataType: telemetrytypes.String},
|
||||
},
|
||||
},
|
||||
wantDDLColumn: "dynamicElement(body_promoted.user.name",
|
||||
},
|
||||
{
|
||||
name: "LogsUnpromotedPath_IndexesBaseColumn",
|
||||
path: &promotetypes.PromotePath{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "user.name",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1, JSONDataType: telemetrytypes.String},
|
||||
},
|
||||
},
|
||||
wantDDLColumn: "dynamicElement(body_v2.user.name",
|
||||
},
|
||||
{
|
||||
name: "TracesNewPromotion_IndexesPromotedColumn",
|
||||
path: &promotetypes.PromotePath{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: "http.method",
|
||||
Promote: true,
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1, JSONDataType: telemetrytypes.String},
|
||||
},
|
||||
},
|
||||
wantDDLColumn: "`attributes_promoted.http.method_String_ngrambf_v1` attributes_promoted.http.method::String",
|
||||
},
|
||||
{
|
||||
name: "TracesUnpromotedPath_IndexesBaseColumn",
|
||||
path: &promotetypes.PromotePath{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: "http.method",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1, JSONDataType: telemetrytypes.String},
|
||||
},
|
||||
},
|
||||
wantDDLColumn: "`attributes.http.method_String_ngrambf_v1` attributes.http.method::String",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
ts := telemetrystoretest.New(telemetrystore.Config{}, sqlmock.QueryMatcherRegexp)
|
||||
store := telemetrytypestest.NewMockMetadataStore()
|
||||
if testCase.promoted != nil {
|
||||
store.PromotedPathsMap = testCase.promoted
|
||||
}
|
||||
m := NewModule(store, ts)
|
||||
|
||||
ts.Mock().ExpectExec("ADD INDEX (.+)" + regexp.QuoteMeta(testCase.wantDDLColumn)).WillReturnError(nil)
|
||||
require.NoError(t, m.PromotePaths(ctx, testCase.path))
|
||||
assert.NoError(t, ts.Mock().ExpectationsWereMet())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPromotedPaths(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
filters promotetypes.ListPromotedPathsFilters
|
||||
promoted map[string]bool
|
||||
indexes []telemetrytypes.TelemetryFieldKeySkipIndex
|
||||
wantPaths []promotetypes.PromotePath
|
||||
}{
|
||||
{
|
||||
name: "PromotedPaths_EveryDomainAnnotated",
|
||||
promoted: map[string]bool{"http.method": true},
|
||||
wantPaths: []promotetypes.PromotePath{
|
||||
{Signal: "logs", Context: "body", Path: "http.method", Promote: true},
|
||||
{Signal: "traces", Context: "attribute", Path: "http.method", Promote: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SignalFilter_SkipsOtherDomains",
|
||||
filters: promotetypes.ListPromotedPathsFilters{Signal: "traces"},
|
||||
promoted: map[string]bool{"http.method": true},
|
||||
wantPaths: []promotetypes.PromotePath{{Signal: "traces", Context: "attribute", Path: "http.method", Promote: true}},
|
||||
},
|
||||
{
|
||||
name: "ContextFilter_SkipsOtherDomains",
|
||||
filters: promotetypes.ListPromotedPathsFilters{Context: "body"},
|
||||
promoted: map[string]bool{"http.method": true},
|
||||
wantPaths: []promotetypes.PromotePath{{Signal: "logs", Context: "body", Path: "http.method", Promote: true}},
|
||||
},
|
||||
{
|
||||
name: "LogsIndexes_MergedWithLogsPaths",
|
||||
promoted: map[string]bool{"user.name": true},
|
||||
indexes: []telemetrytypes.TelemetryFieldKeySkipIndex{
|
||||
{
|
||||
Name: "user.name",
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
BaseColumn: "body_promoted.",
|
||||
IndexType: "ngrambf_v1(4, 1024, 2, 0)",
|
||||
Granularity: 1,
|
||||
},
|
||||
{
|
||||
Name: "request.duration",
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
BaseColumn: "body_v2.",
|
||||
IndexType: "minmax",
|
||||
Granularity: 1,
|
||||
},
|
||||
},
|
||||
wantPaths: []promotetypes.PromotePath{
|
||||
{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "user.name",
|
||||
Promote: true,
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "request.duration",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeFloat64, Type: "minmax", Granularity: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: "user.name",
|
||||
Promote: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PromotedFalseFilter_IndexOnlyPaths",
|
||||
filters: promotetypes.ListPromotedPathsFilters{Promoted: &falseValue},
|
||||
promoted: map[string]bool{"user.name": true},
|
||||
indexes: []telemetrytypes.TelemetryFieldKeySkipIndex{
|
||||
{
|
||||
Name: "request.duration",
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
BaseColumn: "body_v2.",
|
||||
IndexType: "minmax",
|
||||
Granularity: 1,
|
||||
},
|
||||
},
|
||||
wantPaths: []promotetypes.PromotePath{
|
||||
{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "request.duration",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeFloat64, Type: "minmax", Granularity: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IndexesTrueFilter_PathsWithIndexes",
|
||||
filters: promotetypes.ListPromotedPathsFilters{Indexes: &trueValue},
|
||||
promoted: map[string]bool{"user.name": true},
|
||||
indexes: []telemetrytypes.TelemetryFieldKeySkipIndex{
|
||||
{
|
||||
Name: "user.name",
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
BaseColumn: "body_promoted.",
|
||||
IndexType: "ngrambf_v1(4, 1024, 2, 0)",
|
||||
Granularity: 1,
|
||||
},
|
||||
},
|
||||
wantPaths: []promotetypes.PromotePath{
|
||||
{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "user.name",
|
||||
Promote: true,
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TracesIndexes_MergedWithTracesPaths",
|
||||
promoted: map[string]bool{"http.method": true},
|
||||
indexes: []telemetrytypes.TelemetryFieldKeySkipIndex{
|
||||
{
|
||||
Name: "http.method",
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
BaseColumn: "attributes_promoted.",
|
||||
IndexType: "ngrambf_v1(4, 1024, 2, 0)",
|
||||
Granularity: 1,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
BaseColumn: "attributes.",
|
||||
IndexType: "minmax",
|
||||
Granularity: 1,
|
||||
},
|
||||
},
|
||||
wantPaths: []promotetypes.PromotePath{
|
||||
{
|
||||
Signal: "logs",
|
||||
Context: "body",
|
||||
Path: "http.method",
|
||||
Promote: true,
|
||||
},
|
||||
{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: "http.method",
|
||||
Promote: true,
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: "http.status_code",
|
||||
Indexes: []promotetypes.WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeFloat64, Type: "minmax", Granularity: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
store := telemetrytypestest.NewMockMetadataStore()
|
||||
store.PromotedPathsMap = testCase.promoted
|
||||
store.LogsJSONIndexes = testCase.indexes
|
||||
m := NewModule(store, nil)
|
||||
|
||||
paths, err := m.ListPromotedPaths(ctx, testCase.filters)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, paths, len(testCase.wantPaths))
|
||||
|
||||
byDomainPath := map[string]promotetypes.PromotePath{}
|
||||
for _, path := range paths {
|
||||
byDomainPath[path.Signal+"/"+path.Context+"/"+path.Path] = path
|
||||
}
|
||||
for _, want := range testCase.wantPaths {
|
||||
key := want.Signal + "/" + want.Context + "/" + want.Path
|
||||
require.Contains(t, byDomainPath, key)
|
||||
assert.Equal(t, want, byDomainPath[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexMaterializedPaths(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
materializedKeys := []*telemetrytypes.TelemetryFieldKey{
|
||||
{Name: "user.id", Signal: telemetrytypes.SignalTraces, FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "http.route", Signal: telemetrytypes.SignalTraces, FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "retry.count", Signal: telemetrytypes.SignalTraces, FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeNumber},
|
||||
{Name: "k8s.pod.name", Signal: telemetrytypes.SignalLogs, FieldContext: telemetrytypes.FieldContextResource, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "tenant", Signal: telemetrytypes.SignalLogs, FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
params promotetypes.IndexMaterializedPathsParams
|
||||
existing []telemetrytypes.TelemetryFieldKeySkipIndex
|
||||
wantPaths []string
|
||||
wantDDL []string
|
||||
}{
|
||||
{
|
||||
name: "DryRun_NoDDL",
|
||||
params: promotetypes.IndexMaterializedPathsParams{DryRun: true},
|
||||
wantPaths: []string{"traces/attribute/user.id"},
|
||||
},
|
||||
{
|
||||
name: "Run_IndexesBaseColumns",
|
||||
wantPaths: []string{"traces/attribute/user.id"},
|
||||
wantDDL: []string{"`attributes.user.id_String_bloom_filter` attributes.user.id::String TYPE bloom_filter(0.01) GRANULARITY 64"},
|
||||
},
|
||||
{
|
||||
name: "SignalFilter_SkipsOtherSignals",
|
||||
params: promotetypes.IndexMaterializedPathsParams{Signal: "logs", DryRun: true},
|
||||
wantPaths: []string{},
|
||||
},
|
||||
{
|
||||
name: "IndexAlreadyBuilt_Skipped",
|
||||
params: promotetypes.IndexMaterializedPathsParams{Signal: "traces"},
|
||||
existing: []telemetrytypes.TelemetryFieldKeySkipIndex{{Name: "user.id", FieldContext: telemetrytypes.FieldContextAttribute, IndexType: "bloom_filter(0.05)"}},
|
||||
wantPaths: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
ts := telemetrystoretest.New(telemetrystore.Config{}, sqlmock.QueryMatcherRegexp)
|
||||
store := telemetrytypestest.NewMockMetadataStore()
|
||||
store.MaterializedKeys = materializedKeys
|
||||
store.LogsJSONIndexes = testCase.existing
|
||||
m := NewModule(store, ts)
|
||||
for _, ddl := range testCase.wantDDL {
|
||||
ts.Mock().ExpectExec("ADD INDEX (.+)" + regexp.QuoteMeta(ddl)).WillReturnError(nil)
|
||||
}
|
||||
|
||||
paths, err := m.IndexMaterializedPaths(ctx, testCase.params)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := []string{}
|
||||
for _, path := range paths {
|
||||
assert.False(t, path.Promote)
|
||||
got = append(got, path.Signal+"/"+path.Context+"/"+path.Path)
|
||||
}
|
||||
assert.Equal(t, testCase.wantPaths, got)
|
||||
assert.NoError(t, ts.Mock().ExpectationsWereMet())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,14 @@ import (
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetypes.PromotePath, error)
|
||||
PromoteAndIndexPaths(ctx context.Context, paths ...*promotetypes.PromotePath) error
|
||||
ListPromotedPaths(ctx context.Context, filters promotetypes.ListPromotedPathsFilters) ([]promotetypes.PromotePath, error)
|
||||
PromotePaths(ctx context.Context, paths ...*promotetypes.PromotePath) error
|
||||
|
||||
IndexMaterializedPaths(ctx context.Context, params promotetypes.IndexMaterializedPathsParams) ([]*promotetypes.PromotePath, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
HandlePromoteAndIndexPaths(w http.ResponseWriter, r *http.Request)
|
||||
ListPromotedAndIndexedPaths(w http.ResponseWriter, r *http.Request)
|
||||
PromotePaths(w http.ResponseWriter, r *http.Request)
|
||||
ListPromotedPaths(w http.ResponseWriter, r *http.Request)
|
||||
IndexMaterializedPaths(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestManager_ListRules_ValidatesParams(t *testing.T) {
|
||||
_, err = m.ListRules(context.Background(), &ruletypes.ListRulesParams{Limit: -1})
|
||||
require.ErrorContains(t, err, "invalid limit")
|
||||
|
||||
_, err = m.ListRules(context.Background(), &ruletypes.ListRulesParams{ListFilter: ruletypes.ListFilter{States: []string{"bogus"}}})
|
||||
_, err = m.ListRules(context.Background(), &ruletypes.ListRulesParams{States: []string{"bogus"}})
|
||||
require.ErrorContains(t, err, `invalid state "bogus"`)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,6 @@ type Handler interface {
|
||||
PatchRuleByID(http.ResponseWriter, *http.Request)
|
||||
TestRule(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListRuleViews(http.ResponseWriter, *http.Request)
|
||||
CreateRuleView(http.ResponseWriter, *http.Request)
|
||||
UpdateRuleView(http.ResponseWriter, *http.Request)
|
||||
DeleteRuleView(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListDowntimeSchedules(http.ResponseWriter, *http.Request)
|
||||
GetDowntimeScheduleByID(http.ResponseWriter, *http.Request)
|
||||
CreateDowntimeSchedule(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -49,9 +49,4 @@ type Ruler interface {
|
||||
// TODO: expose downtime CRUD as methods on Ruler directly instead of leaking the
|
||||
// store interface. The handler should not call store methods directly.
|
||||
MaintenanceStore() alertmanagertypes.MaintenanceStore
|
||||
|
||||
CreateRuleView(ctx context.Context, orgID valuer.UUID, postable ruletypes.PostableRuleView) (*ruletypes.RuleView, error)
|
||||
ListRuleViews(ctx context.Context, orgID valuer.UUID) (*ruletypes.ListableRuleViews, error)
|
||||
UpdateRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatable ruletypes.UpdatableRuleView) (*ruletypes.RuleView, error)
|
||||
DeleteRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package sqlrulestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func (r *rule) CreateRuleView(ctx context.Context, view *ruletypes.RuleView) error {
|
||||
_, err := r.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(view).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return r.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "rule view with id %s already exists", view.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rule) GetRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*ruletypes.RuleView, error) {
|
||||
view := new(ruletypes.RuleView)
|
||||
err := r.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(view).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, r.sqlstore.WrapNotFoundErrf(err, ruletypes.ErrCodeRuleViewNotFound, "rule view with id %s doesn't exist", id)
|
||||
}
|
||||
return view, nil
|
||||
}
|
||||
|
||||
func (r *rule) ListRuleViews(ctx context.Context, orgID valuer.UUID) ([]*ruletypes.RuleView, error) {
|
||||
views := make([]*ruletypes.RuleView, 0)
|
||||
err := r.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&views).
|
||||
Where("org_id = ?", orgID).
|
||||
OrderExpr("updated_at DESC").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't list rule views")
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
func (r *rule) UpdateRuleView(ctx context.Context, view *ruletypes.RuleView) error {
|
||||
res, err := r.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(view).
|
||||
WherePK().
|
||||
Where("org_id = ?", view.OrgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't update rule view")
|
||||
}
|
||||
rows, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't read rule view update result")
|
||||
}
|
||||
if rows == 0 {
|
||||
return errors.Newf(errors.TypeNotFound, ruletypes.ErrCodeRuleViewNotFound, "rule view with id %s doesn't exist", view.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rule) DeleteRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
res, err := r.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(ruletypes.RuleView)).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't delete rule view")
|
||||
}
|
||||
rows, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't read rule view delete result")
|
||||
}
|
||||
if rows == 0 {
|
||||
return errors.Newf(errors.TypeNotFound, ruletypes.ErrCodeRuleViewNotFound, "rule view with id %s doesn't exist", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -345,122 +345,3 @@ func (handler *handler) DeleteDowntimeScheduleByID(rw http.ResponseWriter, req *
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) ListRuleViews(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
views, err := handler.ruler.ListRuleViews(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, views)
|
||||
}
|
||||
|
||||
func (handler *handler) CreateRuleView(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
var postable ruletypes.PostableRuleView
|
||||
if err := binding.JSON.BindBody(req.Body, &postable); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
view, err := handler.ruler.CreateRuleView(ctx, orgID, postable)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, view)
|
||||
}
|
||||
|
||||
func (handler *handler) UpdateRuleView(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
var updatable ruletypes.UpdatableRuleView
|
||||
if err := binding.JSON.BindBody(req.Body, &updatable); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
view, err := handler.ruler.UpdateRuleView(ctx, orgID, id, updatable)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, view)
|
||||
}
|
||||
|
||||
func (handler *handler) DeleteRuleView(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := handler.ruler.DeleteRuleView(ctx, orgID, id); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
@@ -147,41 +147,3 @@ func (provider *provider) TestNotification(ctx context.Context, orgID valuer.UUI
|
||||
func (provider *provider) MaintenanceStore() alertmanagertypes.MaintenanceStore {
|
||||
return provider.manager.MaintenanceStore()
|
||||
}
|
||||
|
||||
func (provider *provider) CreateRuleView(ctx context.Context, orgID valuer.UUID, postable ruletypes.PostableRuleView) (*ruletypes.RuleView, error) {
|
||||
if err := postable.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view := postable.NewRuleView(orgID)
|
||||
if err := provider.ruleStore.CreateRuleView(ctx, view); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return view, nil
|
||||
}
|
||||
|
||||
func (provider *provider) ListRuleViews(ctx context.Context, orgID valuer.UUID) (*ruletypes.ListableRuleViews, error) {
|
||||
views, err := provider.ruleStore.ListRuleViews(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ruletypes.ListableRuleViews{Views: views}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) UpdateRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatable ruletypes.UpdatableRuleView) (*ruletypes.RuleView, error) {
|
||||
if err := updatable.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view, err := provider.ruleStore.GetRuleView(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view.Update(updatable)
|
||||
if err := provider.ruleStore.UpdateRuleView(ctx, view); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return view, nil
|
||||
}
|
||||
|
||||
func (provider *provider) DeleteRuleView(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return provider.ruleStore.DeleteRuleView(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewAddAIObservabilityQuickFiltersFactory(sqlstore),
|
||||
sqlmigration.NewAddChannelSpecFactory(sqlschema),
|
||||
sqlmigration.NewAddUserTuplesFactory(sqlstore),
|
||||
sqlmigration.NewAddRuleViewFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddFieldTuplesFactory(sqlstore),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
144
pkg/sqlmigration/133_add_field_tuples.go
Normal file
144
pkg/sqlmigration/133_add_field_tuples.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
type addFieldTuples struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewAddFieldTuplesFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_field_tuples"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addFieldTuples{sqlstore: sqlstore}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addFieldTuples) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addFieldTuples) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
var storeID string
|
||||
err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var orgIDs []string
|
||||
err = tx.NewSelect().
|
||||
Table("organizations").
|
||||
Column("id").
|
||||
Scan(ctx, &orgIDs)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
isPG := migration.sqlstore.BunDB().Dialect().Name() == dialect.PG
|
||||
|
||||
tuples := []migrationTuple{
|
||||
{"signoz-admin", "metaresource", "logs-field", "read"},
|
||||
{"signoz-admin", "metaresource", "logs-field", "update"},
|
||||
{"signoz-admin", "metaresource", "logs-field", "list"},
|
||||
{"signoz-editor", "metaresource", "logs-field", "read"},
|
||||
{"signoz-editor", "metaresource", "logs-field", "update"},
|
||||
{"signoz-editor", "metaresource", "logs-field", "list"},
|
||||
{"signoz-viewer", "metaresource", "logs-field", "read"},
|
||||
{"signoz-viewer", "metaresource", "logs-field", "list"},
|
||||
{"signoz-admin", "metaresource", "traces-field", "read"},
|
||||
{"signoz-admin", "metaresource", "traces-field", "update"},
|
||||
{"signoz-admin", "metaresource", "traces-field", "list"},
|
||||
{"signoz-editor", "metaresource", "traces-field", "read"},
|
||||
{"signoz-editor", "metaresource", "traces-field", "update"},
|
||||
{"signoz-editor", "metaresource", "traces-field", "list"},
|
||||
{"signoz-viewer", "metaresource", "traces-field", "read"},
|
||||
{"signoz-viewer", "metaresource", "traces-field", "list"},
|
||||
}
|
||||
|
||||
for _, orgID := range orgIDs {
|
||||
for _, tuple := range tuples {
|
||||
entropy := ulid.DefaultEntropy()
|
||||
now := time.Now().UTC()
|
||||
tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String()
|
||||
|
||||
objectID := "organization/" + orgID + "/" + tuple.objectName + "/*"
|
||||
roleSubject := "organization/" + orgID + "/role/" + tuple.roleName
|
||||
|
||||
if isPG {
|
||||
user := "role:" + roleSubject + "#assignee"
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, user, 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", "userset", tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
continue
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
|
||||
storeID, tuple.objectType, objectID, tuple.relation, "role", roleSubject, "assignee", 0, tupleID, now,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addFieldTuples) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addRuleView struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddRuleViewFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_rule_view"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addRuleView{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addRuleView) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addRuleView) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
sqls := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "rule_view",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "data", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"id"}},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("org_id"),
|
||||
ReferencedTableName: sqlschema.TableName("organizations"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tx.NewCreateIndex().
|
||||
Table("rule_view").
|
||||
Column("org_id").
|
||||
Index("idx_rule_view_org_id").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addRuleView) Down(_ context.Context, _ *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -5,17 +5,18 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/chcol"
|
||||
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryschema/logstelemetryschema"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/promotetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
@@ -33,6 +34,26 @@ var (
|
||||
CodeFailedToAppendPath = errors.MustNewCode("failed_to_append_path_promoted_paths")
|
||||
)
|
||||
|
||||
var logsBodyPromotedEntry = promotetypes.NewLogsBodyTarget().Entry
|
||||
|
||||
var logsBodyIndexLookup = promotetypes.NewLogsBodyTarget().JSONIndexLookup()
|
||||
|
||||
// ClickHouse stores a `col.path::Type` skip index expression as CAST(col.path, 'Type').
|
||||
var simpleJSONSubColumnIndexExprRe = regexp.MustCompile(`^CAST\((?P<expr>[^()]+), '(?P<type>.+)'\)$`)
|
||||
|
||||
// unfoldJSONSubColumnIndexExpr accepts both the folded (lower/assumeNotNull)
|
||||
// and the bare type-cast expression forms.
|
||||
func unfoldJSONSubColumnIndexExpr(expr string) (string, string, error) {
|
||||
if columnExpr, columnType, err := schemamigrator.UnfoldJSONSubColumnIndexExpr(expr); err == nil {
|
||||
return columnExpr, columnType, nil
|
||||
}
|
||||
matches := simpleJSONSubColumnIndexExprRe.FindStringSubmatch(expr)
|
||||
if matches == nil {
|
||||
return "", "", errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid expression: %s", expr)
|
||||
}
|
||||
return matches[1], matches[2], nil
|
||||
}
|
||||
|
||||
// enrichJSONKeys enriches body-context keys with promoted path info, indexes,
|
||||
// and JSON access plans. parentTypeCache contains parent array types (ArrayJSON/ArrayDynamic)
|
||||
// pre-fetched in the main UNION query.
|
||||
@@ -67,7 +88,7 @@ func (t *telemetryMetaStore) enrichJSONKeys(ctx context.Context, selectors []*te
|
||||
}
|
||||
|
||||
// fetch promoted paths
|
||||
promoted, err := t.GetPromotedPaths(ctx, paths...)
|
||||
promoted, err := t.GetPromotedPaths(ctx, logsBodyPromotedEntry, paths...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,7 +141,7 @@ func (t *telemetryMetaStore) getJSONPathIndexes(ctx context.Context, paths ...st
|
||||
}
|
||||
|
||||
// list indexes for the paths
|
||||
indexes, err := t.ListLogsJSONIndexes(ctx, filteredPaths...)
|
||||
indexes, err := t.ListJSONIndexes(ctx, logsBodyIndexLookup, filteredPaths...)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to list JSON path indexes")
|
||||
}
|
||||
@@ -134,16 +155,16 @@ func (t *telemetryMetaStore) getJSONPathIndexes(ctx context.Context, paths ...st
|
||||
return fieldPathToIndexes, nil
|
||||
}
|
||||
|
||||
func buildListLogsJSONIndexesQuery(cluster string, filters ...string) (string, []any) {
|
||||
func buildListJSONIndexesQuery(cluster string, lookup telemetrytypes.JSONIndexLookup, filters ...string) (string, []any) {
|
||||
sb := sqlbuilder.Select(
|
||||
"name", "type_full", "expr", "granularity",
|
||||
).From(fmt.Sprintf("clusterAllReplicas('%s', %s)", cluster, SkipIndexTableName))
|
||||
|
||||
sb.Where(sb.Equal("database", logstelemetryschema.DBName))
|
||||
sb.Where(sb.Equal("table", logstelemetryschema.LogsV2LocalTableName))
|
||||
sb.Where(sb.Equal("database", lookup.DBName))
|
||||
sb.Where(sb.Equal("table", lookup.LocalTableName))
|
||||
sb.Where(sb.Or(
|
||||
sb.ILike("expr", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(constants.BodyV2ColumnPrefix))),
|
||||
sb.ILike("expr", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(constants.BodyPromotedColumnPrefix))),
|
||||
sb.ILike("expr", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(lookup.BaseColumnPrefix))),
|
||||
sb.ILike("expr", fmt.Sprintf("%%%s%%", querybuilder.FormatValueForContains(lookup.PromotedColumnPrefix))),
|
||||
))
|
||||
|
||||
filterExprs := []string{}
|
||||
@@ -156,9 +177,9 @@ func buildListLogsJSONIndexesQuery(cluster string, filters ...string) (string, [
|
||||
return sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
|
||||
ctx = withTelemetryContext(ctx, "ListLogsJSONIndexes")
|
||||
query, args := buildListLogsJSONIndexesQuery(t.telemetrystore.Cluster(), filters...)
|
||||
func (t *telemetryMetaStore) ListJSONIndexes(ctx context.Context, lookup telemetrytypes.JSONIndexLookup, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
|
||||
ctx = withTelemetryContext(ctx, lookup.Signal, "ListJSONIndexes")
|
||||
query, args := buildListJSONIndexesQuery(t.telemetrystore.Cluster(), lookup, filters...)
|
||||
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to load string indexed columns")
|
||||
@@ -175,7 +196,7 @@ func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ..
|
||||
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to scan string indexed column")
|
||||
}
|
||||
|
||||
columnExpr, columnType, err := schemamigrator.UnfoldJSONSubColumnIndexExpr(expr)
|
||||
columnExpr, columnType, err := unfoldJSONSubColumnIndexExpr(expr)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, CodeFailLoadLogsJSONIndexes, "failed to unfold JSON sub column index expression: %s", expr)
|
||||
}
|
||||
@@ -189,18 +210,18 @@ func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ..
|
||||
baseColumn := ""
|
||||
fieldName := ""
|
||||
switch {
|
||||
case strings.HasPrefix(columnExpr, logstelemetryschema.BodyV2ColumnPrefix):
|
||||
baseColumn = logstelemetryschema.BodyV2ColumnPrefix
|
||||
fieldName = strings.TrimPrefix(columnExpr, logstelemetryschema.BodyV2ColumnPrefix)
|
||||
case strings.HasPrefix(columnExpr, logstelemetryschema.BodyPromotedColumnPrefix):
|
||||
baseColumn = logstelemetryschema.BodyPromotedColumnPrefix
|
||||
fieldName = strings.TrimPrefix(columnExpr, logstelemetryschema.BodyPromotedColumnPrefix)
|
||||
case strings.HasPrefix(columnExpr, lookup.BaseColumnPrefix):
|
||||
baseColumn = lookup.BaseColumnPrefix
|
||||
fieldName = strings.TrimPrefix(columnExpr, lookup.BaseColumnPrefix)
|
||||
case strings.HasPrefix(columnExpr, lookup.PromotedColumnPrefix):
|
||||
baseColumn = lookup.PromotedColumnPrefix
|
||||
fieldName = strings.TrimPrefix(columnExpr, lookup.PromotedColumnPrefix)
|
||||
}
|
||||
fieldName = strings.ReplaceAll(fieldName, "`", "")
|
||||
|
||||
indexes = append(indexes, telemetrytypes.TelemetryFieldKeySkipIndex{
|
||||
Name: fieldName,
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldContext: lookup.FieldContext,
|
||||
FieldDataType: fdt,
|
||||
BaseColumn: baseColumn,
|
||||
IndexName: name,
|
||||
@@ -215,14 +236,14 @@ func (t *telemetryMetaStore) ListLogsJSONIndexes(ctx context.Context, filters ..
|
||||
|
||||
// TODO(Piyush): Remove this if not used in future.
|
||||
func (t *telemetryMetaStore) ListJSONValues(ctx context.Context, path string, limit int) (*telemetrytypes.TelemetryFieldValues, bool, error) {
|
||||
ctx = withTelemetryContext(ctx, "ListJSONValues")
|
||||
ctx = withTelemetryContext(ctx, telemetrytypes.SignalLogs, "ListJSONValues")
|
||||
path = CleanPathPrefixes(path)
|
||||
|
||||
if strings.Contains(path, telemetrytypes.ArraySep) || strings.Contains(path, telemetrytypes.ArrayAnyIndex) {
|
||||
return nil, false, errors.NewInvalidInputf(errors.CodeInvalidInput, "array paths are not supported")
|
||||
}
|
||||
|
||||
promoted, err := t.IsPathPromoted(ctx, path)
|
||||
promoted, err := t.isPathPromoted(ctx, logsBodyPromotedEntry, path)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -376,13 +397,12 @@ func derefValue(v any) any {
|
||||
return val.Interface()
|
||||
}
|
||||
|
||||
// IsPathPromoted checks if a specific path is promoted (Column Evolution table: field_name for logs body).
|
||||
func (t *telemetryMetaStore) IsPathPromoted(ctx context.Context, path string) (bool, error) {
|
||||
ctx = withTelemetryContext(ctx, "IsPathPromoted")
|
||||
func (t *telemetryMetaStore) isPathPromoted(ctx context.Context, entry telemetrytypes.EvolutionEntry, path string) (bool, error) {
|
||||
ctx = withTelemetryContext(ctx, entry.Signal, "isPathPromoted")
|
||||
split := strings.Split(path, telemetrytypes.ArraySep)
|
||||
pathSegment := split[0]
|
||||
query := fmt.Sprintf("SELECT 1 FROM %s.%s WHERE signal = ? AND column_name = ? AND field_context = ? AND field_name = ? LIMIT 1", DBName, PromotedPathsTableName)
|
||||
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, telemetrytypes.SignalLogs, logstelemetryschema.LogsV2BodyPromotedColumn, telemetrytypes.FieldContextBody, pathSegment)
|
||||
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, entry.Signal, entry.ColumnName, entry.FieldContext, pathSegment)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, CodeFailCheckPathPromoted, "failed to check if path %s is promoted", path)
|
||||
}
|
||||
@@ -391,14 +411,13 @@ func (t *telemetryMetaStore) IsPathPromoted(ctx context.Context, path string) (b
|
||||
return rows.Next(), nil
|
||||
}
|
||||
|
||||
// GetPromotedPaths returns promoted paths from the Column Evolution table (field_name for logs body).
|
||||
func (t *telemetryMetaStore) GetPromotedPaths(ctx context.Context, paths ...string) (map[string]bool, error) {
|
||||
ctx = withTelemetryContext(ctx, "GetPromotedPaths")
|
||||
func (t *telemetryMetaStore) GetPromotedPaths(ctx context.Context, entry telemetrytypes.EvolutionEntry, paths ...string) (map[string]bool, error) {
|
||||
ctx = withTelemetryContext(ctx, entry.Signal, "GetPromotedPaths")
|
||||
sb := sqlbuilder.Select("field_name").From(fmt.Sprintf("%s.%s", DBName, PromotedPathsTableName))
|
||||
conditions := []string{
|
||||
sb.Equal("signal", telemetrytypes.SignalLogs),
|
||||
sb.Equal("column_name", logstelemetryschema.LogsV2BodyPromotedColumn),
|
||||
sb.Equal("field_context", telemetrytypes.FieldContextBody),
|
||||
sb.Equal("signal", entry.Signal),
|
||||
sb.Equal("column_name", entry.ColumnName),
|
||||
sb.Equal("field_context", entry.FieldContext),
|
||||
sb.NotEqual("field_name", "__all__"),
|
||||
}
|
||||
if len(paths) > 0 {
|
||||
@@ -438,9 +457,8 @@ func CleanPathPrefixes(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// PromotePaths inserts promoted paths into the Column Evolution table (same schema as signoz-otel-collector metadata_migrations).
|
||||
func (t *telemetryMetaStore) PromotePaths(ctx context.Context, paths ...string) error {
|
||||
ctx = withTelemetryContext(ctx, "PromotePaths")
|
||||
func (t *telemetryMetaStore) PromotePaths(ctx context.Context, entry telemetrytypes.EvolutionEntry, paths ...string) error {
|
||||
ctx = withTelemetryContext(ctx, entry.Signal, "PromotePaths")
|
||||
batch, err := t.telemetrystore.ClickhouseDB().PrepareBatch(ctx,
|
||||
fmt.Sprintf("INSERT INTO %s.%s (signal, column_name, column_type, field_context, field_name, version, release_time) VALUES", DBName,
|
||||
PromotedPathsTableName))
|
||||
@@ -454,7 +472,7 @@ func (t *telemetryMetaStore) PromotePaths(ctx context.Context, paths ...string)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
if err := batch.Append(telemetrytypes.SignalLogs, logstelemetryschema.LogsV2BodyPromotedColumn, "JSON()", telemetrytypes.FieldContextBody, trimmed, 0, releaseTime); err != nil {
|
||||
if err := batch.Append(entry.Signal, entry.ColumnName, entry.ColumnType, entry.FieldContext, trimmed, entry.Version, releaseTime); err != nil {
|
||||
_ = batch.Abort()
|
||||
return errors.WrapInternalf(err, CodeFailedToAppendPath, "failed to append path")
|
||||
}
|
||||
@@ -466,9 +484,9 @@ func (t *telemetryMetaStore) PromotePaths(ctx context.Context, paths ...string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func withTelemetryContext(ctx context.Context, functionName string) context.Context {
|
||||
func withTelemetryContext(ctx context.Context, signal telemetrytypes.Signal, functionName string) context.Context {
|
||||
return ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.TelemetrySignal: signal.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "metadata",
|
||||
instrumentationtypes.CodeFunctionName: functionName,
|
||||
})
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestBuildListLogsJSONIndexesQuery(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
query, args := buildListLogsJSONIndexesQuery(tc.cluster, tc.filters...)
|
||||
query, args := buildListJSONIndexesQuery(tc.cluster, logsBodyIndexLookup, tc.filters...)
|
||||
|
||||
require.Equal(t, tc.expectedSQL, query)
|
||||
require.Equal(t, tc.expectedArgs, args)
|
||||
|
||||
@@ -118,6 +118,17 @@ func NewTelemetryMetaStore(
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *telemetryMetaStore) GetMaterializedKeys(ctx context.Context, signal telemetrytypes.Signal) ([]*telemetrytypes.TelemetryFieldKey, error) {
|
||||
switch signal {
|
||||
case telemetrytypes.SignalTraces:
|
||||
return t.tracesTblStatementToFieldKeys(ctx)
|
||||
case telemetrytypes.SignalLogs:
|
||||
return t.logsTblStatementToFieldKeys(ctx)
|
||||
default:
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "materialized keys are not supported for signal %s", signal.StringValue())
|
||||
}
|
||||
}
|
||||
|
||||
// tracesTblStatementToFieldKeys returns materialised attribute/resource/scope keys from the traces table.
|
||||
func (t *telemetryMetaStore) tracesTblStatementToFieldKeys(ctx context.Context) ([]*telemetrytypes.TelemetryFieldKey, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
|
||||
86
pkg/types/promotetypes/materialized.go
Normal file
86
pkg/types/promotetypes/materialized.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaterializedIndexType = "bloom_filter(0.01)"
|
||||
DefaultMaterializedIndexGranularity = 64
|
||||
)
|
||||
|
||||
// defaultMaterializedPaths are keyed by signal/context.
|
||||
var defaultMaterializedPaths = map[string]map[string]struct{}{
|
||||
"traces/attribute": {
|
||||
"db.system": {},
|
||||
"http.route": {},
|
||||
"messaging.operation": {},
|
||||
"messaging.system": {},
|
||||
"peer.service": {},
|
||||
"rpc.method": {},
|
||||
"rpc.service": {},
|
||||
"rpc.system": {},
|
||||
|
||||
"gen_ai.agent.name": {},
|
||||
"gen_ai.provider.name": {},
|
||||
"gen_ai.request.model": {},
|
||||
"gen_ai.tool.name": {},
|
||||
"gen_ai.usage.cache_creation.input_tokens": {},
|
||||
"gen_ai.usage.cache_read.input_tokens": {},
|
||||
"gen_ai.usage.input_tokens": {},
|
||||
"gen_ai.usage.output_tokens": {},
|
||||
"signoz.gen_ai.usage.cache_read.input_tokens.cost": {},
|
||||
"signoz.gen_ai.usage.cache_write.input_tokens.cost": {},
|
||||
"signoz.gen_ai.usage.input_tokens.cost": {},
|
||||
"signoz.gen_ai.usage.output_tokens.cost": {},
|
||||
"signoz.gen_ai.usage.tokens.cost": {},
|
||||
},
|
||||
}
|
||||
|
||||
type IndexMaterializedPathsParams struct {
|
||||
Signal string `query:"signal" json:"signal"`
|
||||
Context string `query:"context" json:"context"`
|
||||
DryRun bool `query:"dryRun" json:"dryRun"`
|
||||
}
|
||||
|
||||
func (p *IndexMaterializedPathsParams) Filters() ListPromotedPathsFilters {
|
||||
return ListPromotedPathsFilters{Signal: p.Signal, Context: p.Context}
|
||||
}
|
||||
|
||||
func NewMaterializedPromotePath(target Target, name string) (*PromotePath, bool) {
|
||||
path := &PromotePath{
|
||||
Signal: target.Entry.Signal.StringValue(),
|
||||
Context: target.Entry.FieldContext.StringValue(),
|
||||
Path: name,
|
||||
Promote: false,
|
||||
Indexes: []WrappedIndex{{FieldDataType: telemetrytypes.FieldDataTypeString, Type: DefaultMaterializedIndexType, Granularity: DefaultMaterializedIndexGranularity}},
|
||||
}
|
||||
if err := path.ValidateAndSetDefaults(target); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return path, true
|
||||
}
|
||||
|
||||
// SkipExistingIndexes matches on index type, ignoring its parameters.
|
||||
func (i *PromotePath) SkipExistingIndexes(existing []telemetrytypes.TelemetryFieldKeySkipIndex) {
|
||||
built := map[string]struct{}{}
|
||||
for _, index := range existing {
|
||||
if indexType, ok := (WrappedIndex{Type: index.IndexType}).SkipIndexType(); ok {
|
||||
built[string(indexType)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
indexes := make([]WrappedIndex, 0, len(i.Indexes))
|
||||
for _, index := range i.Indexes {
|
||||
indexType, _ := index.SkipIndexType()
|
||||
if _, ok := built[string(indexType)]; !ok {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
}
|
||||
i.Indexes = indexes
|
||||
}
|
||||
|
||||
func (t Target) IsDefaultMaterialized(path string) bool {
|
||||
_, ok := defaultMaterializedPaths[t.Entry.Signal.StringValue()+"/"+t.Entry.FieldContext.StringValue()][path]
|
||||
return ok
|
||||
}
|
||||
92
pkg/types/promotetypes/materialized_test.go
Normal file
92
pkg/types/promotetypes/materialized_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
func TestNewMaterializedPromotePath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "BarePath_DefaultIndex",
|
||||
path: "user.id",
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "CardinalPath_Rejected",
|
||||
path: "session.550e8400-e29b-41d4-a716-446655440000",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
path, ok := NewMaterializedPromotePath(NewTracesAttributesTarget(), testCase.path)
|
||||
require.Equal(t, testCase.wantOK, ok)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, &PromotePath{
|
||||
Signal: "traces",
|
||||
Context: "attribute",
|
||||
Path: testCase.path,
|
||||
Promote: false,
|
||||
Indexes: []WrappedIndex{{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "bloom_filter(0.01)", Granularity: 64, JSONDataType: telemetrytypes.String}},
|
||||
}, path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipExistingIndexes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
existing []telemetrytypes.TelemetryFieldKeySkipIndex
|
||||
want []WrappedIndex
|
||||
}{
|
||||
{
|
||||
name: "SameTypeOtherParams_Skipped",
|
||||
existing: []telemetrytypes.TelemetryFieldKeySkipIndex{{IndexType: "bloom_filter(0.05)"}},
|
||||
want: []WrappedIndex{{Type: "tokenbf_v1(1024, 2, 0)", Granularity: 1}},
|
||||
},
|
||||
{
|
||||
name: "OtherType_Kept",
|
||||
existing: []telemetrytypes.TelemetryFieldKeySkipIndex{{IndexType: "ngrambf_v1(4, 1024, 2, 0)"}},
|
||||
want: []WrappedIndex{{Type: "bloom_filter(0.01)", Granularity: 64}, {Type: "tokenbf_v1(1024, 2, 0)", Granularity: 1}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
path := &PromotePath{Indexes: []WrappedIndex{{Type: "bloom_filter(0.01)", Granularity: 64}, {Type: "tokenbf_v1(1024, 2, 0)", Granularity: 1}}}
|
||||
path.SkipExistingIndexes(testCase.existing)
|
||||
assert.Equal(t, testCase.want, path.Indexes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDefaultMaterialized(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
target Target
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{name: "TracesDefaultAttribute_Default", target: NewTracesAttributesTarget(), path: "http.route", want: true},
|
||||
{name: "TracesGenAIAttribute_Default", target: NewTracesAttributesTarget(), path: "gen_ai.request.model", want: true},
|
||||
{name: "TracesCustomAttribute_NotDefault", target: NewTracesAttributesTarget(), path: "user.id"},
|
||||
{name: "LogsBodySamePath_NotDefault", target: NewLogsBodyTarget(), path: "http.route"},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.want, testCase.target.IsDefaultMaterialized(testCase.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
72
pkg/types/promotetypes/resource.go
Normal file
72
pkg/types/promotetypes/resource.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
// PromotePathsResources resolves the field resources of the signals in a promote request body.
|
||||
func PromotePathsResources(ec coretypes.ExtractorContext) ([]coretypes.ResourceWithID, error) {
|
||||
var paths []PromotePath
|
||||
if err := json.Unmarshal(ec.RequestBody, &paths); err != nil {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid promote paths request body")
|
||||
}
|
||||
|
||||
signals := make([]telemetrytypes.Signal, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
signal, ok := telemetrytypes.SignalFromText(path.Signal)
|
||||
if !ok {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid signal: %s", path.Signal)
|
||||
}
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
return fieldResources(signals)
|
||||
}
|
||||
|
||||
// FilteredSignalsResources falls back to every signal when the filters match none.
|
||||
func FilteredSignalsResources(ec coretypes.ExtractorContext) ([]coretypes.ResourceWithID, error) {
|
||||
filters := ListPromotedPathsFilters{}
|
||||
if ec.Request != nil {
|
||||
filters.Signal = ec.Request.URL.Query().Get("signal")
|
||||
filters.Context = ec.Request.URL.Query().Get("context")
|
||||
}
|
||||
if err := filters.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signals := make([]telemetrytypes.Signal, 0)
|
||||
for _, target := range Targets() {
|
||||
if filters.MatchesTarget(target) {
|
||||
signals = append(signals, target.Entry.Signal)
|
||||
}
|
||||
}
|
||||
if len(signals) == 0 {
|
||||
for _, target := range Targets() {
|
||||
signals = append(signals, target.Entry.Signal)
|
||||
}
|
||||
}
|
||||
|
||||
return fieldResources(signals)
|
||||
}
|
||||
|
||||
func fieldResources(signals []telemetrytypes.Signal) ([]coretypes.ResourceWithID, error) {
|
||||
resources := make([]coretypes.ResourceWithID, 0, len(signals))
|
||||
seen := make(map[string]struct{})
|
||||
for _, signal := range signals {
|
||||
resource, ok := signal.FieldResource()
|
||||
if !ok {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "promotion is not supported for signal %s", signal.StringValue())
|
||||
}
|
||||
if _, ok := seen[resource.Kind().String()]; ok {
|
||||
continue
|
||||
}
|
||||
seen[resource.Kind().String()] = struct{}{}
|
||||
resources = append(resources, coretypes.ResourceWithID{Resource: resource})
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
119
pkg/types/promotetypes/resource_test.go
Normal file
119
pkg/types/promotetypes/resource_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
)
|
||||
|
||||
func resourceKinds(resources []coretypes.ResourceWithID) []string {
|
||||
kinds := make([]string, 0, len(resources))
|
||||
for _, resource := range resources {
|
||||
kinds = append(kinds, resource.Resource.Kind().String())
|
||||
}
|
||||
return kinds
|
||||
}
|
||||
|
||||
func TestPromotePathsResources(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
body string
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Traces_TracesField",
|
||||
body: `[{"signal":"traces","context":"attribute","path":"http.method"}]`,
|
||||
want: []string{"traces-field"},
|
||||
},
|
||||
{
|
||||
name: "LogsAndTraces_BothFieldsOnce",
|
||||
body: `[{"signal":"logs","context":"body","path":"a"},{"signal":"traces","context":"attribute","path":"b"},{"signal":"logs","context":"body","path":"c"}]`,
|
||||
want: []string{"logs-field", "traces-field"},
|
||||
},
|
||||
{
|
||||
name: "Empty_NoResources",
|
||||
body: `[]`,
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "InvalidSignal_Rejected",
|
||||
body: `[{"signal":"events","context":"attribute","path":"a"}]`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UnsupportedSignal_Rejected",
|
||||
body: `[{"signal":"metrics","context":"attribute","path":"a"}]`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "MalformedBody_Rejected",
|
||||
body: `{"signal":"traces"}`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
resources, err := PromotePathsResources(coretypes.ExtractorContext{RequestBody: []byte(testCase.body)})
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.want, resourceKinds(resources))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilteredSignalsResources(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
query string
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "NoFilters_EveryField",
|
||||
query: "",
|
||||
want: []string{"logs-field", "traces-field"},
|
||||
},
|
||||
{
|
||||
name: "TracesSignal_TracesField",
|
||||
query: "?signal=traces",
|
||||
want: []string{"traces-field"},
|
||||
},
|
||||
{
|
||||
name: "BodyContext_LogsField",
|
||||
query: "?context=body",
|
||||
want: []string{"logs-field"},
|
||||
},
|
||||
{
|
||||
name: "NoMatchingDomain_EveryField",
|
||||
query: "?signal=metrics",
|
||||
want: []string{"logs-field", "traces-field"},
|
||||
},
|
||||
{
|
||||
name: "InvalidSignal_Rejected",
|
||||
query: "?signal=events",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest("GET", "/api/v1/promoted_path"+testCase.query, nil)
|
||||
resources, err := FilteredSignalsResources(coretypes.ExtractorContext{Request: request})
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.want, resourceKinds(resources))
|
||||
})
|
||||
}
|
||||
}
|
||||
152
pkg/types/promotetypes/target.go
Normal file
152
pkg/types/promotetypes/target.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz-otel-collector/pkg/keycheck"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryschema/logstelemetryschema"
|
||||
"github.com/SigNoz/signoz/pkg/telemetryschema/tracestelemetryschema"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
// Target identifies a promotion domain.
|
||||
type Target struct {
|
||||
Entry telemetrytypes.EvolutionEntry // evolution row template; FieldName and ReleaseTime are set per write
|
||||
DBName string // index DDL database
|
||||
LocalTableName string // index DDL local table
|
||||
BaseColumn string // column holding every path; indexes for unpromoted paths are created on it
|
||||
}
|
||||
|
||||
func (t Target) PromotedColumn() string { return t.Entry.ColumnName }
|
||||
|
||||
func (t Target) BaseColumnPrefix() string { return t.BaseColumn + "." }
|
||||
|
||||
func (t Target) PromotedColumnPrefix() string { return t.PromotedColumn() + "." }
|
||||
|
||||
// IndexExpression folds logs strings to lower case over assumeNotNull for
|
||||
// case-insensitive LIKE searches; traces indexes are a bare type cast.
|
||||
func (t Target) IndexExpression(column, path, jsonDataType string) string {
|
||||
switch t.Entry.Signal {
|
||||
case telemetrytypes.SignalLogs:
|
||||
return schemamigrator.JSONSubColumnIndexExpr(column, path, jsonDataType)
|
||||
default:
|
||||
return simpleJSONSubColumnIndexExpr(column, path, jsonDataType)
|
||||
}
|
||||
}
|
||||
|
||||
func (t Target) JSONIndexLookup() telemetrytypes.JSONIndexLookup {
|
||||
return telemetrytypes.JSONIndexLookup{
|
||||
Signal: t.Entry.Signal,
|
||||
FieldContext: t.Entry.FieldContext,
|
||||
DBName: t.DBName,
|
||||
LocalTableName: t.LocalTableName,
|
||||
BaseColumnPrefix: t.BaseColumnPrefix(),
|
||||
PromotedColumnPrefix: t.PromotedColumnPrefix(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTarget(entry telemetrytypes.EvolutionEntry, dbName, localTableName, baseColumn string) Target {
|
||||
return Target{
|
||||
Entry: entry,
|
||||
DBName: dbName,
|
||||
LocalTableName: localTableName,
|
||||
BaseColumn: baseColumn,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLogsBodyTarget returns the logs body domain (body_v2 -> body_promoted).
|
||||
func NewLogsBodyTarget() Target {
|
||||
return NewTarget(
|
||||
telemetrytypes.EvolutionEntry{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
ColumnName: logstelemetryschema.LogsV2BodyPromotedColumn,
|
||||
ColumnType: "JSON()",
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
},
|
||||
logstelemetryschema.DBName,
|
||||
logstelemetryschema.LogsV2LocalTableName,
|
||||
logstelemetryschema.LogsV2BodyV2Column,
|
||||
)
|
||||
}
|
||||
|
||||
// NewTracesAttributesTarget returns the spans attributes domain (attributes
|
||||
// -> attributes_promoted).
|
||||
func NewTracesAttributesTarget() Target {
|
||||
return NewTarget(
|
||||
telemetrytypes.EvolutionEntry{
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
ColumnName: tracestelemetryschema.SpanAttributesPromotedColumn,
|
||||
ColumnType: "JSON()",
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
},
|
||||
tracestelemetryschema.DBName,
|
||||
tracestelemetryschema.SpanIndexV3LocalTableName,
|
||||
tracestelemetryschema.SpanAttributesColumn,
|
||||
)
|
||||
}
|
||||
|
||||
func NewTargetFromText(signal, context string) (Target, error) {
|
||||
parsedSignal, ok := telemetrytypes.SignalFromText(signal)
|
||||
if !ok {
|
||||
return Target{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid signal: %s", signal)
|
||||
}
|
||||
parsedContext, ok := telemetrytypes.FieldContextFromText(context)
|
||||
if !ok {
|
||||
return Target{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid context: %s", context)
|
||||
}
|
||||
target, ok := TargetFor(parsedSignal, parsedContext)
|
||||
if !ok {
|
||||
return Target{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "promotion is not supported for %s %s", parsedSignal.StringValue(), parsedContext.StringValue())
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func Targets() []Target {
|
||||
return []Target{
|
||||
NewLogsBodyTarget(),
|
||||
NewTracesAttributesTarget(),
|
||||
}
|
||||
}
|
||||
|
||||
func TargetFor(signal telemetrytypes.Signal, context telemetrytypes.FieldContext) (Target, bool) {
|
||||
for _, target := range Targets() {
|
||||
if target.Entry.Signal.StringValue() == signal.StringValue() &&
|
||||
target.Entry.FieldContext.StringValue() == context.StringValue() {
|
||||
return target, true
|
||||
}
|
||||
}
|
||||
return Target{}, false
|
||||
}
|
||||
|
||||
// simpleJSONSubColumnIndexExpr renders `column.path::Type`; the cast unwraps
|
||||
// the Nullable the sub-column access returns, which bloom filter indexes
|
||||
// reject.
|
||||
func simpleJSONSubColumnIndexExpr(column, path, jsonDataType string) string {
|
||||
parts := strings.Split(column+"."+path, ".")
|
||||
for idx, part := range parts {
|
||||
if keycheck.IsBacktickRequired(part) {
|
||||
part := strings.Trim(part, "`")
|
||||
parts[idx] = "`" + part + "`"
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s::%s", strings.Join(parts, "."), jsonDataType)
|
||||
}
|
||||
|
||||
// reservedPathPrefix returns the domain prefix path carries, if any: the base
|
||||
// or promoted column prefix, or the logs body search alias, which is stripped
|
||||
// from queries before metadata lookup and can never match.
|
||||
func (t Target) reservedPathPrefix(path string) (string, bool) {
|
||||
prefixes := []string{t.BaseColumnPrefix(), t.PromotedColumnPrefix()}
|
||||
if t.Entry.Signal == telemetrytypes.SignalLogs {
|
||||
prefixes = append(prefixes, telemetrytypes.BodyJSONStringSearchPrefix)
|
||||
}
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
return prefix, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
57
pkg/types/promotetypes/target_test.go
Normal file
57
pkg/types/promotetypes/target_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexExpression(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
target Target
|
||||
column string
|
||||
path string
|
||||
jsonDataType string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "LogsString_LoweredOverAssumeNotNull",
|
||||
target: NewLogsBodyTarget(),
|
||||
column: "body_promoted",
|
||||
path: "user.name",
|
||||
jsonDataType: "String",
|
||||
want: "lower(assumeNotNull(dynamicElement(body_promoted.user.name, 'String')))",
|
||||
},
|
||||
{
|
||||
name: "LogsNumber_AssumeNotNullOnly",
|
||||
target: NewLogsBodyTarget(),
|
||||
column: "body_v2",
|
||||
path: "request.duration",
|
||||
jsonDataType: "Float64",
|
||||
want: "assumeNotNull(dynamicElement(body_v2.request.duration, 'Float64'))",
|
||||
},
|
||||
{
|
||||
name: "TracesString_TypeCastOnly",
|
||||
target: NewTracesAttributesTarget(),
|
||||
column: "attributes_promoted",
|
||||
path: "http.method",
|
||||
jsonDataType: "String",
|
||||
want: "attributes_promoted.http.method::String",
|
||||
},
|
||||
{
|
||||
name: "TracesPathNeedingBackticks_Backticked",
|
||||
target: NewTracesAttributesTarget(),
|
||||
column: "attributes",
|
||||
path: "user-name",
|
||||
jsonDataType: "String",
|
||||
want: "attributes.`user-name`::String",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.want, testCase.target.IndexExpression(testCase.column, testCase.path, testCase.jsonDataType))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,15 @@ package promotetypes
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz-otel-collector/constants"
|
||||
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz-otel-collector/pkg/keycheck"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
// TODO: use schemamigrator.IndexTypeBloomFilter once SigNoz/signoz-otel-collector#942 is released.
|
||||
const IndexTypeBloomFilter schemamigrator.IndexType = "bloom_filter"
|
||||
|
||||
type WrappedIndex struct {
|
||||
JSONDataType telemetrytypes.JSONDataType `json:"-"`
|
||||
FieldDataType telemetrytypes.FieldDataType `json:"fieldDataType"`
|
||||
@@ -17,13 +20,71 @@ type WrappedIndex struct {
|
||||
}
|
||||
|
||||
type PromotePath struct {
|
||||
Path string `json:"path"`
|
||||
Signal string `json:"signal" required:"true"`
|
||||
Context string `json:"context" required:"true"`
|
||||
Path string `json:"path" required:"true"`
|
||||
Promote bool `json:"promote,omitempty"`
|
||||
|
||||
Indexes []WrappedIndex `json:"indexes,omitempty"`
|
||||
}
|
||||
|
||||
func (i *PromotePath) ValidateAndSetDefaults() error {
|
||||
func (w WrappedIndex) SkipIndexType() (schemamigrator.IndexType, bool) {
|
||||
for _, indexType := range []schemamigrator.IndexType{schemamigrator.IndexTypeNGramBF, schemamigrator.IndexTypeTokenBF, schemamigrator.IndexTypeMinMax, IndexTypeBloomFilter} {
|
||||
if strings.HasPrefix(w.Type, string(indexType)) {
|
||||
return indexType, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (i *PromotePath) Target() (Target, error) {
|
||||
return NewTargetFromText(i.Signal, i.Context)
|
||||
}
|
||||
|
||||
type ListPromotedPathsFilters struct {
|
||||
Signal string `query:"signal" json:"signal"`
|
||||
Context string `query:"context" json:"context"`
|
||||
Promoted *bool `query:"promoted" json:"promoted"`
|
||||
Indexes *bool `query:"indexes" json:"indexes"`
|
||||
}
|
||||
|
||||
// Validate checks the signal and context words are known; the pair need not
|
||||
// name a supported domain.
|
||||
func (f *ListPromotedPathsFilters) Validate() error {
|
||||
if f.Signal != "" {
|
||||
if _, ok := telemetrytypes.SignalFromText(f.Signal); !ok {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid signal: %s", f.Signal)
|
||||
}
|
||||
}
|
||||
if f.Context != "" {
|
||||
if _, ok := telemetrytypes.FieldContextFromText(f.Context); !ok {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid context: %s", f.Context)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ListPromotedPathsFilters) MatchesTarget(target Target) bool {
|
||||
if f.Signal != "" && f.Signal != target.Entry.Signal.StringValue() {
|
||||
return false
|
||||
}
|
||||
if f.Context != "" && f.Context != target.Entry.FieldContext.StringValue() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *ListPromotedPathsFilters) MatchesPath(path PromotePath) bool {
|
||||
if f.Promoted != nil && *f.Promoted != path.Promote {
|
||||
return false
|
||||
}
|
||||
if f.Indexes != nil && *f.Indexes != (len(path.Indexes) > 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *PromotePath) ValidateAndSetDefaults(target Target) error {
|
||||
if i.Path == "" {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "path is required")
|
||||
}
|
||||
@@ -36,17 +97,10 @@ func (i *PromotePath) ValidateAndSetDefaults() error {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "array paths can not be promoted or indexed")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(i.Path, constants.BodyV2ColumnPrefix) || strings.HasPrefix(i.Path, constants.BodyPromotedColumnPrefix) {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "`%s`, `%s` don't add these prefixes to the path", constants.BodyV2ColumnPrefix, constants.BodyPromotedColumnPrefix)
|
||||
if prefix, ok := target.reservedPathPrefix(i.Path); ok {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "path must be a bare attribute name, without the `%s` prefix", prefix)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(i.Path, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "path must start with `body.`")
|
||||
}
|
||||
|
||||
// remove the "body." prefix from the path
|
||||
i.Path = strings.TrimPrefix(i.Path, telemetrytypes.BodyJSONStringSearchPrefix)
|
||||
|
||||
isCardinal := keycheck.IsCardinal(i.Path)
|
||||
if isCardinal {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cardinal paths can not be promoted or indexed")
|
||||
|
||||
349
pkg/types/promotetypes/types_test.go
Normal file
349
pkg/types/promotetypes/types_test.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package promotetypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPromotePathTarget(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path *PromotePath
|
||||
want Target
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "LogsBody_Resolved",
|
||||
path: &PromotePath{Signal: "logs", Context: "body", Path: "body.user.name"},
|
||||
want: NewLogsBodyTarget(),
|
||||
},
|
||||
{
|
||||
name: "TracesAttribute_Resolved",
|
||||
path: &PromotePath{Signal: "traces", Context: "attribute", Path: "http.method"},
|
||||
want: NewTracesAttributesTarget(),
|
||||
},
|
||||
{
|
||||
name: "InvalidSignal_Rejected",
|
||||
path: &PromotePath{Signal: "events", Context: "attribute", Path: "http.method"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "InvalidContext_Rejected",
|
||||
path: &PromotePath{Signal: "logs", Context: "span", Path: "user.name"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UnsupportedDomain_Rejected",
|
||||
path: &PromotePath{Signal: "metrics", Context: "attribute", Path: "http.method"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
target, err := testCase.path.Target()
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.want, target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAndSetDefaultsLogsBody(t *testing.T) {
|
||||
target := NewLogsBodyTarget()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
path *PromotePath
|
||||
wantErr bool
|
||||
wantPath string
|
||||
wantJSONDataType telemetrytypes.JSONDataType
|
||||
}{
|
||||
{
|
||||
name: "BarePath_KeptAsIs",
|
||||
path: &PromotePath{Path: "user.name", Promote: true},
|
||||
wantPath: "user.name",
|
||||
},
|
||||
{
|
||||
name: "BodyPrefixedPath_Rejected",
|
||||
path: &PromotePath{Path: "body.user.name", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "BodyV2PrefixedPath_Rejected",
|
||||
path: &PromotePath{Path: "body_v2.user.name", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "BodyPromotedPrefixedPath_Rejected",
|
||||
path: &PromotePath{Path: "body_promoted.user.name", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "EmptyPath_Rejected",
|
||||
path: &PromotePath{Path: "", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "SpacedPath_Rejected",
|
||||
path: &PromotePath{Path: "body.my path", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ArrayIndexPath_Rejected",
|
||||
path: &PromotePath{Path: "body.users[].id", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ArrayWildcardPath_Rejected",
|
||||
path: &PromotePath{Path: "body.users[*].id", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "CardinalPath_Rejected",
|
||||
path: &PromotePath{Path: "body.request.550e8400-e29b-41d4-a716-446655440000", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ValidIndex_JSONDataTypeDefaulted",
|
||||
path: &PromotePath{
|
||||
Path: "user.name",
|
||||
Indexes: []WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1},
|
||||
},
|
||||
},
|
||||
wantPath: "user.name",
|
||||
wantJSONDataType: telemetrytypes.String,
|
||||
},
|
||||
{
|
||||
name: "UnsupportedColumnTypeIndex_Rejected",
|
||||
path: &PromotePath{
|
||||
Path: "user.active",
|
||||
Indexes: []WrappedIndex{{FieldDataType: telemetrytypes.FieldDataTypeBool, Type: "minmax", Granularity: 1}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IndexWithoutType_Rejected",
|
||||
path: &PromotePath{
|
||||
Path: "user.name",
|
||||
Indexes: []WrappedIndex{{FieldDataType: telemetrytypes.FieldDataTypeString, Granularity: 1}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "IndexWithoutGranularity_Rejected",
|
||||
path: &PromotePath{
|
||||
Path: "user.name",
|
||||
Indexes: []WrappedIndex{{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "minmax"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := testCase.path.ValidateAndSetDefaults(target)
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.wantPath, testCase.path.Path)
|
||||
if testCase.wantJSONDataType != (telemetrytypes.JSONDataType{}) {
|
||||
require.Len(t, testCase.path.Indexes, 1)
|
||||
assert.Equal(t, testCase.wantJSONDataType, testCase.path.Indexes[0].JSONDataType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAndSetDefaultsTracesAttributes(t *testing.T) {
|
||||
target := NewTracesAttributesTarget()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
path *PromotePath
|
||||
wantErr bool
|
||||
wantPath string
|
||||
wantJSONDataType telemetrytypes.JSONDataType
|
||||
}{
|
||||
{
|
||||
name: "BareAttributeName_KeptAsIs",
|
||||
path: &PromotePath{Path: "http.method", Promote: true},
|
||||
wantPath: "http.method",
|
||||
},
|
||||
{
|
||||
name: "ValidIndex_JSONDataTypeDefaulted",
|
||||
path: &PromotePath{
|
||||
Path: "http.method",
|
||||
Indexes: []WrappedIndex{
|
||||
{FieldDataType: telemetrytypes.FieldDataTypeString, Type: "ngrambf_v1(4, 1024, 2, 0)", Granularity: 1},
|
||||
},
|
||||
},
|
||||
wantPath: "http.method",
|
||||
wantJSONDataType: telemetrytypes.String,
|
||||
},
|
||||
{
|
||||
name: "AttributesPrefixedPath_Rejected",
|
||||
path: &PromotePath{Path: "attributes.http.method", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "AttributesPromotedPrefixedPath_Rejected",
|
||||
path: &PromotePath{Path: "attributes_promoted.http.method", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "EmptyPath_Rejected",
|
||||
path: &PromotePath{Path: "", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "SpacedPath_Rejected",
|
||||
path: &PromotePath{Path: "my attr", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ArrayIndexPath_Rejected",
|
||||
path: &PromotePath{Path: "tags[].id", Promote: true},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := testCase.path.ValidateAndSetDefaults(target)
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.wantPath, testCase.path.Path)
|
||||
if testCase.wantJSONDataType != (telemetrytypes.JSONDataType{}) {
|
||||
require.Len(t, testCase.path.Indexes, 1)
|
||||
assert.Equal(t, testCase.wantJSONDataType, testCase.path.Indexes[0].JSONDataType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPromotedPathsFiltersValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
filters ListPromotedPathsFilters
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "EmptyFilters_Valid",
|
||||
filters: ListPromotedPathsFilters{},
|
||||
},
|
||||
{
|
||||
name: "KnownSignalAndContext_Valid",
|
||||
filters: ListPromotedPathsFilters{Signal: "traces", Context: "attribute"},
|
||||
},
|
||||
{
|
||||
name: "InvalidSignal_Rejected",
|
||||
filters: ListPromotedPathsFilters{Signal: "events"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "InvalidContext_Rejected",
|
||||
filters: ListPromotedPathsFilters{Context: "json"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := testCase.filters.Validate()
|
||||
if testCase.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPromotedPathsFiltersMatch(t *testing.T) {
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
filters ListPromotedPathsFilters
|
||||
target Target
|
||||
path PromotePath
|
||||
wantTarget bool
|
||||
wantPath bool
|
||||
}{
|
||||
{
|
||||
name: "EmptyFilters_MatchEverything",
|
||||
filters: ListPromotedPathsFilters{},
|
||||
target: NewTracesAttributesTarget(),
|
||||
path: PromotePath{Path: "http.method", Promote: true},
|
||||
wantTarget: true,
|
||||
wantPath: true,
|
||||
},
|
||||
{
|
||||
name: "SignalFilter_MatchesSameSignal",
|
||||
filters: ListPromotedPathsFilters{Signal: "traces"},
|
||||
target: NewTracesAttributesTarget(),
|
||||
wantTarget: true,
|
||||
},
|
||||
{
|
||||
name: "SignalFilter_SkipsOtherSignals",
|
||||
filters: ListPromotedPathsFilters{Signal: "traces"},
|
||||
target: NewLogsBodyTarget(),
|
||||
wantTarget: false,
|
||||
},
|
||||
{
|
||||
name: "ContextFilter_SkipsOtherContexts",
|
||||
filters: ListPromotedPathsFilters{Context: "body"},
|
||||
target: NewTracesAttributesTarget(),
|
||||
wantTarget: false,
|
||||
},
|
||||
{
|
||||
name: "PromotedFalseFilter_MatchesUnpromotedPath",
|
||||
filters: ListPromotedPathsFilters{Promoted: &falseValue},
|
||||
path: PromotePath{Path: "request.duration"},
|
||||
wantPath: true,
|
||||
},
|
||||
{
|
||||
name: "PromotedFalseFilter_SkipsPromotedPath",
|
||||
filters: ListPromotedPathsFilters{Promoted: &falseValue},
|
||||
path: PromotePath{Path: "http.method", Promote: true},
|
||||
wantPath: false,
|
||||
},
|
||||
{
|
||||
name: "IndexesTrueFilter_MatchesIndexedPath",
|
||||
filters: ListPromotedPathsFilters{Indexes: &trueValue},
|
||||
path: PromotePath{Path: "user.name", Indexes: []WrappedIndex{{Type: "minmax"}}},
|
||||
wantPath: true,
|
||||
},
|
||||
{
|
||||
name: "IndexesTrueFilter_SkipsUnindexedPath",
|
||||
filters: ListPromotedPathsFilters{Indexes: &trueValue},
|
||||
path: PromotePath{Path: "http.method", Promote: true},
|
||||
wantPath: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
if testCase.target.Entry.Signal.StringValue() != "" {
|
||||
assert.Equal(t, testCase.wantTarget, testCase.filters.MatchesTarget(testCase.target))
|
||||
}
|
||||
if testCase.path.Path != "" {
|
||||
assert.Equal(t, testCase.wantPath, testCase.filters.MatchesPath(testCase.path))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -49,55 +49,66 @@ func (o ListOrder) IsValid() bool {
|
||||
return slices.ContainsFunc(o.Enum(), func(v any) bool { return v == o })
|
||||
}
|
||||
|
||||
// ListFilter is the rule listing state shared by the v3 list params and saved views.
|
||||
type ListFilter struct {
|
||||
Query string `query:"query" json:"query"`
|
||||
// gin cannot bind a slice of valuer enums; GetAlertStates converts these.
|
||||
States []string `query:"states" json:"states" nullable:"false"`
|
||||
Sort ListSort `query:"sort" json:"sort"`
|
||||
Order ListOrder `query:"order" json:"order"`
|
||||
type ListRulesParams struct {
|
||||
Query string `query:"query"`
|
||||
// gin cannot bind a slice of valuer enums; AlertStates converts these.
|
||||
States []string `query:"states"`
|
||||
Sort ListSort `query:"sort"`
|
||||
Order ListOrder `query:"order"`
|
||||
Limit int `query:"limit"`
|
||||
Offset int `query:"offset"`
|
||||
}
|
||||
|
||||
// Validate normalizes in place; zero sort/order get the defaults, nil states an empty slice.
|
||||
func (f *ListFilter) Validate() error {
|
||||
if f.States == nil {
|
||||
f.States = []string{}
|
||||
}
|
||||
|
||||
if n := utf8.RuneCountInString(f.Query); n > MaxListQueryLen {
|
||||
// Validate normalizes in place; an over-max limit is clamped, not rejected.
|
||||
func (p *ListRulesParams) Validate() error {
|
||||
if n := utf8.RuneCountInString(p.Query); n > MaxListQueryLen {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"query cannot be longer than %d characters, got %d", MaxListQueryLen, n)
|
||||
}
|
||||
|
||||
if _, err := f.GetAlertStates(); err != nil {
|
||||
if p.Sort.IsZero() {
|
||||
p.Sort = ListSortUpdatedAt
|
||||
} else if !p.Sort.IsValid() {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid sort %q, expected one of: `updated_at`, `created_at`, `name`, `state`, `severity`", p.Sort)
|
||||
}
|
||||
|
||||
if p.Order.IsZero() {
|
||||
p.Order = ListOrderDesc
|
||||
} else if !p.Order.IsValid() {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid order %q, expected `asc` or `desc`", p.Order)
|
||||
}
|
||||
|
||||
if p.Limit == 0 {
|
||||
p.Limit = DefaultListLimit
|
||||
} else if p.Limit < 0 {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid limit %d, must be a positive integer", p.Limit)
|
||||
} else if p.Limit > MaxListLimit {
|
||||
p.Limit = MaxListLimit
|
||||
}
|
||||
|
||||
if p.Offset < 0 {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid offset %d, must be a non-negative integer", p.Offset)
|
||||
}
|
||||
|
||||
if _, err := p.GetAlertStates(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.Sort.IsZero() {
|
||||
f.Sort = ListSortUpdatedAt
|
||||
} else if !f.Sort.IsValid() {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid sort %q, expected one of: `updated_at`, `created_at`, `name`, `state`, `severity`", f.Sort)
|
||||
}
|
||||
|
||||
if f.Order.IsZero() {
|
||||
f.Order = ListOrderDesc
|
||||
} else if !f.Order.IsValid() {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid order %q, expected `asc` or `desc`", f.Order)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlertStates parses States; empty means no state filtering.
|
||||
func (f *ListFilter) GetAlertStates() ([]AlertState, error) {
|
||||
if len(f.States) == 0 {
|
||||
func (p *ListRulesParams) GetAlertStates() ([]AlertState, error) {
|
||||
if len(p.States) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
states := make([]AlertState, 0, len(f.States))
|
||||
for _, raw := range f.States {
|
||||
states := make([]AlertState, 0, len(p.States))
|
||||
for _, raw := range p.States {
|
||||
state, err := parseAlertState(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -116,32 +127,3 @@ func parseAlertState(raw string) (AlertState, error) {
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
type ListRulesParams struct {
|
||||
ListFilter
|
||||
Limit int `query:"limit"`
|
||||
Offset int `query:"offset"`
|
||||
}
|
||||
|
||||
// Validate normalizes in place; an over-max limit is clamped, not rejected.
|
||||
func (p *ListRulesParams) Validate() error {
|
||||
if err := p.ListFilter.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Limit == 0 {
|
||||
p.Limit = DefaultListLimit
|
||||
} else if p.Limit < 0 {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid limit %d, must be a positive integer", p.Limit)
|
||||
} else if p.Limit > MaxListLimit {
|
||||
p.Limit = MaxListLimit
|
||||
}
|
||||
|
||||
if p.Offset < 0 {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleListInvalid,
|
||||
"invalid offset %d, must be a non-negative integer", p.Offset)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestListRulesParamsValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ExplicitValues_Kept",
|
||||
params: ListRulesParams{ListFilter: ListFilter{Sort: ListSortSeverity, Order: ListOrderAsc}, Limit: 50, Offset: 100},
|
||||
params: ListRulesParams{Sort: ListSortSeverity, Order: ListOrderAsc, Limit: 50, Offset: 100},
|
||||
wantSort: ListSortSeverity,
|
||||
wantOrder: ListOrderAsc,
|
||||
wantLimit: 50,
|
||||
@@ -41,17 +41,17 @@ func TestListRulesParamsValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "InvalidState_Rejected",
|
||||
params: ListRulesParams{ListFilter: ListFilter{States: []string{"bogus"}}},
|
||||
params: ListRulesParams{States: []string{"bogus"}},
|
||||
wantErr: `invalid state "bogus"`,
|
||||
},
|
||||
{
|
||||
name: "InvalidSort_Rejected",
|
||||
params: ListRulesParams{ListFilter: ListFilter{Sort: ListSort{valuer.NewString("bogus")}}},
|
||||
params: ListRulesParams{Sort: ListSort{valuer.NewString("bogus")}},
|
||||
wantErr: "invalid sort",
|
||||
},
|
||||
{
|
||||
name: "InvalidOrder_Rejected",
|
||||
params: ListRulesParams{ListFilter: ListFilter{Order: ListOrder{valuer.NewString("bogus")}}},
|
||||
params: ListRulesParams{Order: ListOrder{valuer.NewString("bogus")}},
|
||||
wantErr: "invalid order",
|
||||
},
|
||||
{
|
||||
@@ -66,7 +66,7 @@ func TestListRulesParamsValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "OverLongQuery_Rejected",
|
||||
params: ListRulesParams{ListFilter: ListFilter{Query: strings.Repeat("a", MaxListQueryLen+1)}},
|
||||
params: ListRulesParams{Query: strings.Repeat("a", MaxListQueryLen+1)},
|
||||
wantErr: "query cannot be longer",
|
||||
},
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func TestListRulesParamsAlertStates(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
params := ListRulesParams{ListFilter: ListFilter{States: tc.states}}
|
||||
params := ListRulesParams{States: tc.states}
|
||||
states, err := params.GetAlertStates()
|
||||
if tc.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -65,10 +65,4 @@ type RuleStore interface {
|
||||
GetStoredRuleLabels(context.Context, string) ([]string, error)
|
||||
GetStoredRule(context.Context, valuer.UUID, valuer.UUID) (*StorableRule, error)
|
||||
GetStoredRulesByMetricName(context.Context, string, string) ([]RuleAlert, error)
|
||||
|
||||
CreateRuleView(context.Context, *RuleView) error
|
||||
GetRuleView(context.Context, valuer.UUID, valuer.UUID) (*RuleView, error)
|
||||
ListRuleViews(context.Context, valuer.UUID) ([]*RuleView, error)
|
||||
UpdateRuleView(context.Context, *RuleView) error
|
||||
DeleteRuleView(context.Context, valuer.UUID, valuer.UUID) error
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
const (
|
||||
RuleViewSchemaVersion = "v1"
|
||||
MaxRuleViewNameLen = 64
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeRuleViewInvalidInput = errors.MustNewCode("rule_view_invalid_input")
|
||||
ErrCodeRuleViewNotFound = errors.MustNewCode("rule_view_not_found")
|
||||
)
|
||||
|
||||
type RuleView struct {
|
||||
bun.BaseModel `bun:"table:rule_view,alias:rule_view"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
|
||||
Name string `bun:"name,type:text,notnull" json:"name" required:"true"`
|
||||
Data RuleViewData `bun:"data,type:text,notnull" json:"data" required:"true"`
|
||||
OrgID valuer.UUID `bun:"org_id,type:text,notnull" json:"orgId" required:"true"`
|
||||
}
|
||||
|
||||
func (v *RuleView) Update(updatable UpdatableRuleView) {
|
||||
v.Name = updatable.Name
|
||||
v.Data = updatable.Data
|
||||
v.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// RuleViewData holds the rule listing state (ListRulesParams minus pagination) a view replays.
|
||||
type RuleViewData struct {
|
||||
Version string `json:"version" required:"true"`
|
||||
ListFilter
|
||||
}
|
||||
|
||||
func (d *RuleViewData) Validate() error {
|
||||
if d.Version != RuleViewSchemaVersion {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleViewInvalidInput,
|
||||
"version must be %q, got %q", RuleViewSchemaVersion, d.Version)
|
||||
}
|
||||
return d.ListFilter.Validate()
|
||||
}
|
||||
|
||||
type PostableRuleView struct {
|
||||
Name string `json:"name" required:"true"`
|
||||
Data RuleViewData `json:"data" required:"true"`
|
||||
}
|
||||
|
||||
func (p *PostableRuleView) UnmarshalJSON(data []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.DisallowUnknownFields()
|
||||
type alias PostableRuleView
|
||||
var tmp alias
|
||||
if err := dec.Decode(&tmp); err != nil {
|
||||
return errors.WrapInvalidInputf(err, ErrCodeRuleViewInvalidInput, "invalid saved view request body").WithAdditional(err.Error())
|
||||
}
|
||||
*p = PostableRuleView(tmp)
|
||||
return p.Validate()
|
||||
}
|
||||
|
||||
func (p *PostableRuleView) Validate() error {
|
||||
if err := validateRuleViewName(p.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Data.Validate()
|
||||
}
|
||||
|
||||
func (p PostableRuleView) NewRuleView(orgID valuer.UUID) *RuleView {
|
||||
now := time.Now()
|
||||
return &RuleView{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
Name: p.Name,
|
||||
Data: p.Data,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
type UpdatableRuleView = PostableRuleView
|
||||
|
||||
type ListableRuleViews struct {
|
||||
Views []*RuleView `json:"views" required:"true" nullable:"false"`
|
||||
}
|
||||
|
||||
func validateRuleViewName(name string) error {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleViewInvalidInput, "name is required")
|
||||
}
|
||||
if name != strings.TrimSpace(name) {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleViewInvalidInput, "name must not have leading or trailing whitespace")
|
||||
}
|
||||
if n := utf8.RuneCountInString(name); n > MaxRuleViewNameLen {
|
||||
return errors.NewInvalidInputf(ErrCodeRuleViewInvalidInput,
|
||||
"name must be at most %d characters, got %d", MaxRuleViewNameLen, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package ruletypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRuleViewDataValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data RuleViewData
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "AllFieldsSet_Valid",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{Query: "name CONTAINS 'prod'", States: []string{"firing", "pending"}, Sort: ListSortName, Order: ListOrderAsc}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "ZeroStatesSortOrder_Valid",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "QueryOverCap_Rejected",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{Query: strings.Repeat("x", MaxListQueryLen+1)}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "WrongVersion_Rejected",
|
||||
data: RuleViewData{Version: "v2"},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "EmptyVersion_Rejected",
|
||||
data: RuleViewData{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "UnknownState_Rejected",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{States: []string{"exploding"}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "UnknownSort_Rejected",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{Sort: ListSort{valuer.NewString("bogus")}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "UnknownOrder_Rejected",
|
||||
data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{Order: ListOrder{valuer.NewString("sideways")}}},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := testCase.data.Validate()
|
||||
if testCase.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleViewDataValidateDefaults(t *testing.T) {
|
||||
data := RuleViewData{Version: RuleViewSchemaVersion}
|
||||
require.NoError(t, data.Validate())
|
||||
assert.Equal(t, ListSortUpdatedAt, data.Sort)
|
||||
assert.Equal(t, ListOrderDesc, data.Order)
|
||||
assert.Equal(t, []string{}, data.States)
|
||||
}
|
||||
|
||||
func TestPostableRuleViewUnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
body string
|
||||
expectError bool
|
||||
expectedErrMsg string
|
||||
expectedName string
|
||||
}{
|
||||
{
|
||||
name: "ValidBody_NameKeptAsIs",
|
||||
body: `{"name":"my view","data":{"version":"v1","query":"severity = 'critical'","states":["firing"],"sort":"name","order":"asc"}}`,
|
||||
expectError: false,
|
||||
expectedName: "my view",
|
||||
},
|
||||
{
|
||||
name: "NameSurroundingWhitespace_Rejected",
|
||||
body: `{"name":" my view ","data":{"version":"v1"}}`,
|
||||
expectError: true,
|
||||
expectedErrMsg: "name must not have leading or trailing whitespace",
|
||||
},
|
||||
{
|
||||
name: "UnknownField_Rejected",
|
||||
body: `{"name":"my view","data":{"version":"v1"},"extra":true}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "BlankName_Rejected",
|
||||
body: `{"name":" ","data":{"version":"v1"}}`,
|
||||
expectError: true,
|
||||
expectedErrMsg: "name is required",
|
||||
},
|
||||
{
|
||||
name: "NameOverMaxLength_Rejected",
|
||||
body: `{"name":"` + strings.Repeat("x", MaxRuleViewNameLen+1) + `","data":{"version":"v1"}}`,
|
||||
expectError: true,
|
||||
expectedErrMsg: "name must be at most",
|
||||
},
|
||||
{
|
||||
name: "InvalidDataVersion_Rejected",
|
||||
body: `{"name":"my view","data":{"version":"v9"}}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "InvalidState_Rejected",
|
||||
body: `{"name":"my view","data":{"version":"v1","states":["exploding"]}}`,
|
||||
expectError: true,
|
||||
expectedErrMsg: "invalid state",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var p PostableRuleView
|
||||
err := json.Unmarshal([]byte(testCase.body), &p)
|
||||
if testCase.expectError {
|
||||
assert.Error(t, err)
|
||||
if testCase.expectedErrMsg != "" {
|
||||
assert.ErrorContains(t, err, testCase.expectedErrMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.expectedName, p.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostableRuleViewNewRuleView(t *testing.T) {
|
||||
orgID := valuer.GenerateUUID()
|
||||
postable := PostableRuleView{
|
||||
Name: "my view",
|
||||
Data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{States: []string{"firing"}, Sort: ListSortName, Order: ListOrderAsc}},
|
||||
}
|
||||
|
||||
view := postable.NewRuleView(orgID)
|
||||
|
||||
assert.Equal(t, orgID, view.OrgID)
|
||||
assert.Equal(t, "my view", view.Name)
|
||||
assert.Equal(t, postable.Data, view.Data)
|
||||
assert.False(t, view.ID.IsZero())
|
||||
assert.False(t, view.CreatedAt.IsZero())
|
||||
assert.Equal(t, view.CreatedAt, view.UpdatedAt)
|
||||
}
|
||||
|
||||
func TestRuleViewUpdate(t *testing.T) {
|
||||
orgID := valuer.GenerateUUID()
|
||||
view := PostableRuleView{
|
||||
Name: "original",
|
||||
Data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{Sort: ListSortName, Order: ListOrderAsc}},
|
||||
}.NewRuleView(orgID)
|
||||
createdAt := view.CreatedAt
|
||||
|
||||
view.Update(UpdatableRuleView{
|
||||
Name: "renamed",
|
||||
Data: RuleViewData{Version: RuleViewSchemaVersion, ListFilter: ListFilter{States: []string{"disabled"}, Sort: ListSortCreatedAt, Order: ListOrderDesc}},
|
||||
})
|
||||
|
||||
assert.Equal(t, "renamed", view.Name)
|
||||
assert.Equal(t, []string{"disabled"}, view.Data.States)
|
||||
assert.Equal(t, ListSortCreatedAt, view.Data.Sort)
|
||||
assert.Equal(t, ListOrderDesc, view.Data.Order)
|
||||
assert.Equal(t, createdAt, view.CreatedAt)
|
||||
assert.True(t, view.UpdatedAt.After(createdAt))
|
||||
}
|
||||
@@ -398,6 +398,19 @@ func NewTelemetryFieldKey(name string, fieldContext FieldContext, fieldDataType
|
||||
}
|
||||
}
|
||||
|
||||
// JSONIndexLookup locates one domain's JSON sub-column indexes in
|
||||
// system.data_skipping_indices: the table to read, the base and promoted
|
||||
// column prefixes to match index expressions against, and the signal and
|
||||
// context to stamp on the results.
|
||||
type JSONIndexLookup struct {
|
||||
Signal Signal
|
||||
FieldContext FieldContext
|
||||
DBName string
|
||||
LocalTableName string
|
||||
BaseColumnPrefix string
|
||||
PromotedColumnPrefix string
|
||||
}
|
||||
|
||||
type TelemetryFieldKeySkipIndex struct {
|
||||
Name string `json:"name"` // Name is TelemetryFieldKey.Name not IndexName from ClickHouse
|
||||
FieldContext FieldContext `json:"fieldContext,omitzero"`
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package telemetrytypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Signal struct {
|
||||
valuer.String
|
||||
@@ -22,3 +25,30 @@ func (Signal) Enum() []any {
|
||||
SignalUnspecified,
|
||||
}
|
||||
}
|
||||
|
||||
// FieldResource is the authz resource guarding the signal's field configuration.
|
||||
func (signal Signal) FieldResource() (coretypes.Resource, bool) {
|
||||
switch signal {
|
||||
case SignalLogs:
|
||||
return coretypes.ResourceMetaResourceLogsField, true
|
||||
case SignalTraces:
|
||||
return coretypes.ResourceMetaResourceTracesField, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// SignalFromText resolves a signal word to its Signal; ok is false for an
|
||||
// unknown word.
|
||||
func SignalFromText(text string) (Signal, bool) {
|
||||
s := Signal{valuer.NewString(text)}
|
||||
switch s {
|
||||
case SignalTraces:
|
||||
return SignalTraces, true
|
||||
case SignalLogs:
|
||||
return SignalLogs, true
|
||||
case SignalMetrics:
|
||||
return SignalMetrics, true
|
||||
}
|
||||
return Signal{}, false
|
||||
}
|
||||
|
||||
@@ -34,14 +34,19 @@ type MetadataStore interface {
|
||||
|
||||
FetchTemporalityAndTypeMulti(ctx context.Context, orgID valuer.UUID, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, map[string]metrictypes.Type, map[string]bool, error)
|
||||
|
||||
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
|
||||
ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]TelemetryFieldKeySkipIndex, error)
|
||||
// ListJSONIndexes lists the per-path JSON skip indexes of the given source.
|
||||
ListJSONIndexes(ctx context.Context, lookup JSONIndexLookup, filters ...string) ([]TelemetryFieldKeySkipIndex, error)
|
||||
|
||||
// ListPromotedPaths lists the promoted paths.
|
||||
GetPromotedPaths(ctx context.Context, paths ...string) (map[string]bool, error)
|
||||
// GetPromotedPaths lists the promoted paths recorded in the column
|
||||
// evolution table for the entry's signal, column and field context.
|
||||
GetPromotedPaths(ctx context.Context, entry EvolutionEntry, paths ...string) (map[string]bool, error)
|
||||
|
||||
// PromotePaths promotes the paths.
|
||||
PromotePaths(ctx context.Context, paths ...string) error
|
||||
// PromotePaths records promoted paths in the column evolution table as
|
||||
// rows templated by entry; FieldName and ReleaseTime are set per path.
|
||||
PromotePaths(ctx context.Context, entry EvolutionEntry, paths ...string) error
|
||||
|
||||
// GetMaterializedKeys lists the keys materialized as dedicated columns.
|
||||
GetMaterializedKeys(ctx context.Context, signal Signal) ([]*TelemetryFieldKey, error)
|
||||
|
||||
// GetFirstSeenFromMetricMetadata gets the first seen timestamp for a metric metadata lookup key.
|
||||
GetFirstSeenFromMetricMetadata(ctx context.Context, lookupKeys []MetricMetadataLookupKey) (map[MetricMetadataLookupKey]int64, error)
|
||||
|
||||
@@ -20,6 +20,7 @@ type MockMetadataStore struct {
|
||||
ReducedMap map[string]bool
|
||||
PromotedPathsMap map[string]bool
|
||||
LogsJSONIndexes []telemetrytypes.TelemetryFieldKeySkipIndex
|
||||
MaterializedKeys []*telemetrytypes.TelemetryFieldKey
|
||||
ColumnEvolutionMetadataMap map[string][]*telemetrytypes.EvolutionEntry
|
||||
LookupKeysMap map[telemetrytypes.MetricMetadataLookupKey]int64
|
||||
// StaticFields holds signal-specific intrinsic field definitions (e.g. logstelemetryschema.IntrinsicFields).
|
||||
@@ -361,7 +362,7 @@ func (m *MockMetadataStore) SetTemporality(metricName string, temporality metric
|
||||
}
|
||||
|
||||
// PromotePaths promotes the paths.
|
||||
func (m *MockMetadataStore) PromotePaths(ctx context.Context, paths ...string) error {
|
||||
func (m *MockMetadataStore) PromotePaths(_ context.Context, _ telemetrytypes.EvolutionEntry, paths ...string) error {
|
||||
for _, path := range paths {
|
||||
m.PromotedPathsMap[path] = true
|
||||
}
|
||||
@@ -369,13 +370,29 @@ func (m *MockMetadataStore) PromotePaths(ctx context.Context, paths ...string) e
|
||||
}
|
||||
|
||||
// GetPromotedPaths returns the promoted paths.
|
||||
func (m *MockMetadataStore) GetPromotedPaths(ctx context.Context, paths ...string) (map[string]bool, error) {
|
||||
func (m *MockMetadataStore) GetPromotedPaths(_ context.Context, _ telemetrytypes.EvolutionEntry, _ ...string) (map[string]bool, error) {
|
||||
return m.PromotedPathsMap, nil
|
||||
}
|
||||
|
||||
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
|
||||
func (m *MockMetadataStore) ListLogsJSONIndexes(ctx context.Context, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
|
||||
return m.LogsJSONIndexes, nil
|
||||
func (m *MockMetadataStore) GetMaterializedKeys(_ context.Context, signal telemetrytypes.Signal) ([]*telemetrytypes.TelemetryFieldKey, error) {
|
||||
keys := []*telemetrytypes.TelemetryFieldKey{}
|
||||
for _, key := range m.MaterializedKeys {
|
||||
if key.Signal == signal {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// ListJSONIndexes narrows the stored indexes to the lookup's field context like the real query.
|
||||
func (m *MockMetadataStore) ListJSONIndexes(ctx context.Context, lookup telemetrytypes.JSONIndexLookup, filters ...string) ([]telemetrytypes.TelemetryFieldKeySkipIndex, error) {
|
||||
indexes := []telemetrytypes.TelemetryFieldKeySkipIndex{}
|
||||
for _, index := range m.LogsJSONIndexes {
|
||||
if index.FieldContext.StringValue() == lookup.FieldContext.StringValue() {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
}
|
||||
return indexes, nil
|
||||
}
|
||||
|
||||
func (m *MockMetadataStore) updateColumnEvolutionMetadataForKeys(_ context.Context, keysToUpdate []*telemetrytypes.TelemetryFieldKey) map[string][]*telemetrytypes.EvolutionEntry {
|
||||
|
||||
@@ -32,6 +32,7 @@ pytest_plugins = [
|
||||
"fixtures.alerts",
|
||||
"fixtures.cloudintegrations",
|
||||
"fixtures.jsontypes",
|
||||
"fixtures.materialized",
|
||||
"fixtures.seeder",
|
||||
"fixtures.serviceaccount",
|
||||
"fixtures.role",
|
||||
|
||||
35
tests/fixtures/materialized.py
vendored
Normal file
35
tests/fixtures/materialized.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
from collections.abc import Callable, Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from fixtures import types
|
||||
|
||||
# signal -> (database, local table, distributed table)
|
||||
MATERIALIZED_TABLES = {
|
||||
"traces": ("signoz_traces", "signoz_index_v3", "distributed_signoz_index_v3"),
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="materialize_attribute", scope="function")
|
||||
def materialize_attribute(signoz: types.SigNoz) -> Generator[Callable[[str, str], str]]:
|
||||
"""Teardown drops the columns and every index whose expression mentions a materialized key."""
|
||||
cluster = signoz.telemetrystore.env["SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_CLUSTER"]
|
||||
created: list[tuple[str, str, str]] = []
|
||||
|
||||
def materialize(signal: str, key: str) -> str:
|
||||
database, local_table, distributed_table = MATERIALIZED_TABLES[signal]
|
||||
column = f"attribute_string_{key.replace('.', '$$')}"
|
||||
for table in (local_table, distributed_table):
|
||||
signoz.telemetrystore.conn.query(f"ALTER TABLE {database}.{table} ON CLUSTER '{cluster}' ADD COLUMN IF NOT EXISTS `{column}` LowCardinality(String) DEFAULT attributes_string['{key}']")
|
||||
created.append((signal, key, column))
|
||||
return column
|
||||
|
||||
yield materialize
|
||||
|
||||
for signal, key, column in created:
|
||||
database, local_table, distributed_table = MATERIALIZED_TABLES[signal]
|
||||
indexes = signoz.telemetrystore.conn.query(f"SELECT name FROM system.data_skipping_indices WHERE database = '{database}' AND table = '{local_table}' AND expr LIKE '%{key}%'").result_rows
|
||||
for (index_name,) in indexes:
|
||||
signoz.telemetrystore.conn.query(f"ALTER TABLE {database}.{local_table} ON CLUSTER '{cluster}' DROP INDEX IF EXISTS `{index_name}`")
|
||||
for table in (local_table, distributed_table):
|
||||
signoz.telemetrystore.conn.query(f"ALTER TABLE {database}.{table} ON CLUSTER '{cluster}' DROP COLUMN IF EXISTS `{column}`")
|
||||
164
tests/integration/tests/promote/01_authz.py
Normal file
164
tests/integration/tests/promote/01_authz.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
from wiremock.resources.mappings import Mapping
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import (
|
||||
USER_ADMIN_EMAIL,
|
||||
USER_ADMIN_PASSWORD,
|
||||
add_license,
|
||||
change_user_role,
|
||||
create_active_user,
|
||||
)
|
||||
from fixtures.role import find_role_by_name, transaction_group
|
||||
|
||||
PROMOTED_PATH_BASE = "/api/v1/promoted_path"
|
||||
|
||||
_EDITOR_EMAIL = "editor+promote@integration.test"
|
||||
_VIEWER_EMAIL = "viewer+promote@integration.test"
|
||||
_TRACES_ONLY_ROLE_NAME = "promote-traces-only"
|
||||
_TRACES_ONLY_EMAIL = "customrole+promote@integration.test"
|
||||
_PASSWORD = "password123Z$"
|
||||
|
||||
_TRACES_PATH = {"signal": "traces", "context": "attribute", "path": "authz.probe", "promote": True}
|
||||
_LOGS_PATH = {"signal": "logs", "context": "body", "path": "authz.probe", "promote": True}
|
||||
|
||||
|
||||
def test_apply_license(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
make_http_mocks: Callable[[types.TestContainerDocker, list[Mapping]], None],
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
add_license(signoz, make_http_mocks, get_token)
|
||||
|
||||
|
||||
def test_setup_users(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
create_role: Callable[..., str],
|
||||
) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
create_active_user(signoz, admin_token, email=_EDITOR_EMAIL, role="signoz-editor", password=_PASSWORD, name="promote editor")
|
||||
create_active_user(signoz, admin_token, email=_VIEWER_EMAIL, role="signoz-viewer", password=_PASSWORD, name="promote viewer")
|
||||
|
||||
create_role(
|
||||
admin_token,
|
||||
_TRACES_ONLY_ROLE_NAME,
|
||||
[
|
||||
transaction_group("update", "metaresource", "traces-field", ["*"]),
|
||||
transaction_group("list", "metaresource", "traces-field", ["*"]),
|
||||
],
|
||||
)
|
||||
user_id = create_active_user(signoz, admin_token, email=_TRACES_ONLY_EMAIL, role="signoz-viewer", password=_PASSWORD, name="promote traces only")
|
||||
change_user_role(signoz, admin_token, user_id, "signoz-viewer", _TRACES_ONLY_ROLE_NAME)
|
||||
|
||||
|
||||
def test_managed_roles(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
cases = [
|
||||
(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, HTTPStatus.CREATED),
|
||||
(_EDITOR_EMAIL, _PASSWORD, HTTPStatus.CREATED),
|
||||
(_VIEWER_EMAIL, _PASSWORD, HTTPStatus.FORBIDDEN),
|
||||
]
|
||||
for email, password, promote_status in cases:
|
||||
token = get_token(email, password)
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
json=[_TRACES_PATH, _LOGS_PATH],
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == promote_status, f"{email} promote: expected {promote_status}, got {resp.status_code}: {resp.text}"
|
||||
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"{email} list: {resp.text}"
|
||||
|
||||
|
||||
def test_promote_scoped_to_granted_signal(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
token = get_token(_TRACES_ONLY_EMAIL, _PASSWORD)
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
json=[_TRACES_PATH],
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.CREATED, f"promote traces: {resp.text}"
|
||||
|
||||
for body in ([_LOGS_PATH], [_TRACES_PATH, _LOGS_PATH]):
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
json=body,
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"promote {body}: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
def test_list_scoped_to_granted_signal(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
token = get_token(_TRACES_ONLY_EMAIL, _PASSWORD)
|
||||
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
params={"signal": "traces"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, f"list traces: {resp.text}"
|
||||
assert {(path["signal"], path["path"]) for path in resp.json()["data"]} >= {("traces", "authz.probe")}
|
||||
|
||||
for params in ({"signal": "logs"}, {}):
|
||||
resp = requests.get(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
params=params,
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"list {params}: expected 403, got {resp.status_code}: {resp.text}"
|
||||
|
||||
|
||||
def test_revoke_update(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
) -> None:
|
||||
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
role_id = find_role_by_name(signoz, admin_token, _TRACES_ONLY_ROLE_NAME)
|
||||
|
||||
resp = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"/api/v1/roles/{role_id}"),
|
||||
json={"description": "", "transactionGroups": [transaction_group("list", "metaresource", "traces-field", ["*"])]},
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT, resp.text
|
||||
|
||||
token = get_token(_TRACES_ONLY_EMAIL, _PASSWORD)
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(PROMOTED_PATH_BASE),
|
||||
json=[_TRACES_PATH],
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.FORBIDDEN, f"promote after revoke: expected 403, got {resp.status_code}: {resp.text}"
|
||||
59
tests/integration/tests/promote/02_materialized.py
Normal file
59
tests/integration/tests/promote/02_materialized.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import requests
|
||||
|
||||
from fixtures import types
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
|
||||
MATERIALIZED_PATH = "/api/v1/promoted_path/materialized"
|
||||
PROBE_INDEX_EXPRS_QUERY = "SELECT expr FROM system.data_skipping_indices WHERE database = 'signoz_traces' AND table = 'signoz_index_v3' AND expr LIKE 'CAST(%materialized.probe%'"
|
||||
|
||||
|
||||
def test_index_materialized_paths(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: types.Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
materialize_attribute: Callable[[str, str], str],
|
||||
) -> None:
|
||||
materialize_attribute("traces", "materialized.probe")
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(MATERIALIZED_PATH),
|
||||
params={"dryRun": "true"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=30,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
paths = {(path["signal"], path["context"], path["path"]): path["indexes"] for path in resp.json()["data"]}
|
||||
assert paths[("traces", "attribute", "materialized.probe")] == [{"fieldDataType": "string", "type": "bloom_filter(0.01)", "granularity": 64}]
|
||||
assert ("traces", "attribute", "http.route") not in paths
|
||||
assert not signoz.telemetrystore.conn.query(PROBE_INDEX_EXPRS_QUERY).result_rows
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(MATERIALIZED_PATH),
|
||||
params={"signal": "logs"},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=30,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
assert not [path for path in resp.json()["data"] if path["path"] == "materialized.probe"]
|
||||
assert not signoz.telemetrystore.conn.query(PROBE_INDEX_EXPRS_QUERY).result_rows
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(MATERIALIZED_PATH),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=30,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
assert ("traces", "attribute", "materialized.probe") in {(path["signal"], path["context"], path["path"]) for path in resp.json()["data"]}
|
||||
assert [row[0] for row in signoz.telemetrystore.conn.query(PROBE_INDEX_EXPRS_QUERY).result_rows] == ["CAST(attributes.materialized.probe, 'String')"]
|
||||
|
||||
resp = requests.post(
|
||||
signoz.self.host_configs["8080"].get(MATERIALIZED_PATH),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=30,
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.OK, resp.text
|
||||
assert not [path for path in resp.json()["data"] if path["path"] == "materialized.probe"]
|
||||
@@ -1,289 +0,0 @@
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
|
||||
from fixtures.types import Operation, SigNoz
|
||||
|
||||
BASE_URL = "/api/v2/rule_views"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("body", "expected_code", "expected_message"),
|
||||
[
|
||||
({"data": {"version": "v1"}}, "rule_view_invalid_input", "name is required"),
|
||||
({"name": " ", "data": {"version": "v1"}}, "rule_view_invalid_input", "name is required"),
|
||||
(
|
||||
{"name": " Storage ", "data": {"version": "v1"}},
|
||||
"rule_view_invalid_input",
|
||||
"name must not have leading or trailing whitespace",
|
||||
),
|
||||
(
|
||||
{"name": "x" * 65, "data": {"version": "v1"}},
|
||||
"rule_view_invalid_input",
|
||||
"name must be at most 64 characters, got 65",
|
||||
),
|
||||
(
|
||||
{"name": "wrong-version", "data": {"version": "v2"}},
|
||||
"rule_view_invalid_input",
|
||||
'version must be "v1", got "v2"',
|
||||
),
|
||||
(
|
||||
{"name": "missing-version", "data": {}},
|
||||
"rule_view_invalid_input",
|
||||
'version must be "v1", got ""',
|
||||
),
|
||||
(
|
||||
{"name": "bad-state", "data": {"version": "v1", "states": ["exploding"]}},
|
||||
"rule_list_invalid",
|
||||
'invalid state "exploding"',
|
||||
),
|
||||
(
|
||||
{"name": "bad-sort", "data": {"version": "v1", "sort": "bogus"}},
|
||||
"rule_list_invalid",
|
||||
"invalid sort",
|
||||
),
|
||||
(
|
||||
{"name": "bad-order", "data": {"version": "v1", "order": "bogus"}},
|
||||
"rule_list_invalid",
|
||||
"invalid order",
|
||||
),
|
||||
(
|
||||
{"name": "long-query", "data": {"version": "v1", "query": "x" * 1025}},
|
||||
"rule_list_invalid",
|
||||
"query cannot be longer than 1024 characters",
|
||||
),
|
||||
(
|
||||
{"name": "rejects-unknown", "data": {"version": "v1"}, "unknownfield": "boom"},
|
||||
"rule_view_invalid_input",
|
||||
"invalid saved view request body",
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"missing_name",
|
||||
"blank_name",
|
||||
"whitespace_name",
|
||||
"name_too_long",
|
||||
"wrong_schema_version",
|
||||
"missing_version",
|
||||
"invalid_state",
|
||||
"invalid_sort",
|
||||
"invalid_order",
|
||||
"query_too_long",
|
||||
"unknown_field",
|
||||
],
|
||||
)
|
||||
def test_create_rejects_invalid_body(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
body: dict,
|
||||
expected_code: str,
|
||||
expected_message: str,
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
json=body,
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
assert response.json()["error"]["code"] == expected_code
|
||||
assert expected_message in response.json()["error"]["message"]
|
||||
|
||||
|
||||
def test_update_rejects_malformed_id(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/not-a-uuid"),
|
||||
json={"name": "x", "data": {"version": "v1"}},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
def test_update_missing_view_returns_not_found(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{uuid.uuid4()}"),
|
||||
json={"name": "x", "data": {"version": "v1"}},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.NOT_FOUND
|
||||
assert response.json()["error"]["code"] == "rule_view_not_found"
|
||||
|
||||
|
||||
def test_delete_rejects_malformed_id(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/not-a-uuid"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
def test_delete_missing_view_returns_not_found(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
response = requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{uuid.uuid4()}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.NOT_FOUND
|
||||
assert response.json()["error"]["code"] == "rule_view_not_found"
|
||||
|
||||
|
||||
def test_rule_view_lifecycle(
|
||||
signoz: SigNoz,
|
||||
create_user_admin: Operation, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
):
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# List assertions filter on this test's names so foreign views never interfere.
|
||||
owned_names = {"Critical Prod", "Critical Staging", "Disabled"}
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
json={
|
||||
"name": "Critical Prod",
|
||||
"data": {
|
||||
"version": "v1",
|
||||
"query": "name CONTAINS 'prod' AND severity = 'critical'",
|
||||
"states": ["firing", "pending"],
|
||||
"sort": "name",
|
||||
"order": "asc",
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
created = response.json()["data"]
|
||||
view_id = created["id"]
|
||||
assert created["name"] == "Critical Prod"
|
||||
assert created["data"]["version"] == "v1"
|
||||
assert created["data"]["query"] == "name CONTAINS 'prod' AND severity = 'critical'"
|
||||
assert created["data"]["states"] == ["firing", "pending"]
|
||||
|
||||
# Omitted states, sort and order are normalized on save: [] and the list defaults, never null.
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
json={"name": "Disabled", "data": {"version": "v1"}},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.CREATED, response.text
|
||||
disabled = response.json()["data"]
|
||||
assert disabled["name"] == "Disabled"
|
||||
assert disabled["data"]["states"] == []
|
||||
assert disabled["data"]["sort"] == "updated_at"
|
||||
assert disabled["data"]["order"] == "desc"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
views = [v for v in response.json()["data"]["views"] if v["name"] in owned_names]
|
||||
assert {v["name"] for v in views} == {"Critical Prod", "Disabled"}
|
||||
|
||||
response = requests.put(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{view_id}"),
|
||||
json={
|
||||
"name": "Critical Staging",
|
||||
"data": {
|
||||
"version": "v1",
|
||||
"query": "name CONTAINS 'staging'",
|
||||
"states": ["firing"],
|
||||
"sort": "created_at",
|
||||
"order": "desc",
|
||||
},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.OK, response.text
|
||||
updated = response.json()["data"]
|
||||
assert updated["id"] == view_id
|
||||
assert updated["name"] == "Critical Staging"
|
||||
assert updated["data"]["query"] == "name CONTAINS 'staging'"
|
||||
assert updated["data"]["states"] == ["firing"]
|
||||
assert updated["data"]["sort"] == "created_at"
|
||||
assert updated["data"]["order"] == "desc"
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
listed = {v["name"]: v for v in response.json()["data"]["views"] if v["name"] in owned_names}
|
||||
assert set(listed) == {"Critical Staging", "Disabled"}
|
||||
assert listed["Critical Staging"]["data"]["query"] == "name CONTAINS 'staging'"
|
||||
|
||||
assert (
|
||||
requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{view_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
).status_code
|
||||
== HTTPStatus.NO_CONTENT
|
||||
)
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get(BASE_URL),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
)
|
||||
assert {v["name"] for v in response.json()["data"]["views"] if v["name"] in owned_names} == {"Disabled"}
|
||||
|
||||
assert (
|
||||
requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{view_id}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
).status_code
|
||||
== HTTPStatus.NOT_FOUND
|
||||
)
|
||||
|
||||
assert (
|
||||
requests.delete(
|
||||
signoz.self.host_configs["8080"].get(f"{BASE_URL}/{disabled['id']}"),
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
timeout=5,
|
||||
).status_code
|
||||
== HTTPStatus.NO_CONTENT
|
||||
)
|
||||
6
tests/uv.lock
generated
6
tests/uv.lock
generated
@@ -1190,11 +1190,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.8.0"
|
||||
version = "2.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
||||
Reference in New Issue
Block a user