mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-28 14:40:32 +01:00
Compare commits
6 Commits
nv/dashboa
...
nv/v2-dash
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb6fdd54ec | ||
|
|
64b8ba62da | ||
|
|
a82f4237c8 | ||
|
|
db5ce958eb | ||
|
|
c8d3a9a54b | ||
|
|
637870b1fc |
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
@@ -100,8 +101,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), store, settings, analytics, orgGetter, queryParser, tagModule)
|
||||
},
|
||||
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return noopgateway.NewProviderFactory()
|
||||
|
||||
@@ -42,6 +42,7 @@ import (
|
||||
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
@@ -144,8 +145,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
}
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, dashboardModule), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), store, settings, analytics, orgGetter, queryParser, querier, licensing, tagModule)
|
||||
},
|
||||
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return httpgateway.NewProviderFactory(licensing)
|
||||
|
||||
@@ -1137,6 +1137,18 @@ components:
|
||||
required:
|
||||
- config
|
||||
type: object
|
||||
CommonDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
CommonJSONRef:
|
||||
properties:
|
||||
$ref:
|
||||
type: string
|
||||
type: object
|
||||
ConfigAuthorization:
|
||||
properties:
|
||||
credentials:
|
||||
@@ -2088,6 +2100,43 @@ components:
|
||||
to_user:
|
||||
type: string
|
||||
type: object
|
||||
DashboardGridItem:
|
||||
properties:
|
||||
content:
|
||||
$ref: '#/components/schemas/CommonJSONRef'
|
||||
height:
|
||||
type: integer
|
||||
width:
|
||||
type: integer
|
||||
x:
|
||||
type: integer
|
||||
"y":
|
||||
type: integer
|
||||
type: object
|
||||
DashboardGridLayoutCollapse:
|
||||
properties:
|
||||
open:
|
||||
type: boolean
|
||||
type: object
|
||||
DashboardGridLayoutDisplay:
|
||||
properties:
|
||||
collapse:
|
||||
$ref: '#/components/schemas/DashboardGridLayoutCollapse'
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
DashboardGridLayoutSpec:
|
||||
properties:
|
||||
display:
|
||||
$ref: '#/components/schemas/DashboardGridLayoutDisplay'
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardGridItem'
|
||||
nullable: true
|
||||
type: array
|
||||
repeatVariable:
|
||||
type: string
|
||||
type: object
|
||||
DashboardtypesDashboard:
|
||||
properties:
|
||||
createdAt:
|
||||
@@ -2142,6 +2191,789 @@ components:
|
||||
timeRangeEnabled:
|
||||
type: boolean
|
||||
type: object
|
||||
Dashboardtypesv2Axes:
|
||||
properties:
|
||||
isLogScale:
|
||||
type: boolean
|
||||
softMax:
|
||||
nullable: true
|
||||
type: number
|
||||
softMin:
|
||||
nullable: true
|
||||
type: number
|
||||
type: object
|
||||
Dashboardtypesv2BarChartPanelSpec:
|
||||
properties:
|
||||
axes:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Axes'
|
||||
formatting:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelFormatting'
|
||||
legend:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Legend'
|
||||
thresholds:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ThresholdWithLabel'
|
||||
nullable: true
|
||||
type: array
|
||||
visualization:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BarChartVisualization'
|
||||
type: object
|
||||
Dashboardtypesv2BarChartVisualization:
|
||||
properties:
|
||||
fillSpans:
|
||||
type: boolean
|
||||
stackedBarChart:
|
||||
type: boolean
|
||||
timePreference:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimePreference'
|
||||
type: object
|
||||
Dashboardtypesv2BasicVisualization:
|
||||
properties:
|
||||
timePreference:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimePreference'
|
||||
type: object
|
||||
Dashboardtypesv2BuilderQuerySpec:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregation'
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregation'
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
Dashboardtypesv2ComparisonOperator:
|
||||
enum:
|
||||
- '>'
|
||||
- <
|
||||
- '>='
|
||||
- <=
|
||||
- =
|
||||
- above
|
||||
- below
|
||||
- above_or_equal
|
||||
- below_or_equal
|
||||
- equal
|
||||
- not_equal
|
||||
type: string
|
||||
Dashboardtypesv2ComparisonThreshold:
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
format:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ThresholdFormat'
|
||||
operator:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ComparisonOperator'
|
||||
unit:
|
||||
type: string
|
||||
value:
|
||||
format: double
|
||||
type: number
|
||||
required:
|
||||
- value
|
||||
- color
|
||||
type: object
|
||||
Dashboardtypesv2CustomVariableSpec:
|
||||
properties:
|
||||
customValue:
|
||||
type: string
|
||||
required:
|
||||
- customValue
|
||||
type: object
|
||||
Dashboardtypesv2DashboardData:
|
||||
properties:
|
||||
datasources:
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DatasourceSpec'
|
||||
type: object
|
||||
display:
|
||||
$ref: '#/components/schemas/CommonDisplay'
|
||||
duration:
|
||||
type: string
|
||||
layouts:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Layout'
|
||||
nullable: true
|
||||
type: array
|
||||
links:
|
||||
items:
|
||||
$ref: '#/components/schemas/V1Link'
|
||||
type: array
|
||||
panels:
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Panel'
|
||||
nullable: true
|
||||
type: object
|
||||
refreshInterval:
|
||||
type: string
|
||||
variables:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Variable'
|
||||
type: array
|
||||
type: object
|
||||
Dashboardtypesv2DashboardInfo:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DashboardData'
|
||||
metadata:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DashboardMetadata'
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesTag'
|
||||
type: array
|
||||
type: object
|
||||
Dashboardtypesv2DashboardMetadata:
|
||||
properties:
|
||||
image:
|
||||
type: string
|
||||
schemaVersion:
|
||||
type: string
|
||||
uploadedGrafana:
|
||||
type: boolean
|
||||
type: object
|
||||
Dashboardtypesv2DatasourcePlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2DatasourcePluginVariantStruct'
|
||||
Dashboardtypesv2DatasourcePluginKind:
|
||||
enum:
|
||||
- signoz/Datasource
|
||||
type: string
|
||||
Dashboardtypesv2DatasourcePluginVariantStruct:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/Datasource
|
||||
type: string
|
||||
spec:
|
||||
nullable: true
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2DatasourceSpec:
|
||||
properties:
|
||||
default:
|
||||
type: boolean
|
||||
display:
|
||||
$ref: '#/components/schemas/CommonDisplay'
|
||||
plugin:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DatasourcePlugin'
|
||||
type: object
|
||||
Dashboardtypesv2DynamicVariableSpec:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
signal:
|
||||
$ref: '#/components/schemas/TelemetrytypesSignal'
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
Dashboardtypesv2FillMode:
|
||||
enum:
|
||||
- solid
|
||||
- gradient
|
||||
- none
|
||||
type: string
|
||||
Dashboardtypesv2GettableDashboard:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
info:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DashboardInfo'
|
||||
locked:
|
||||
type: boolean
|
||||
orgId:
|
||||
type: string
|
||||
publicConfig:
|
||||
$ref: '#/components/schemas/DashboardtypesGettablePublicDasbhboard'
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
Dashboardtypesv2HistogramBuckets:
|
||||
properties:
|
||||
bucketCount:
|
||||
nullable: true
|
||||
type: number
|
||||
bucketWidth:
|
||||
nullable: true
|
||||
type: number
|
||||
mergeAllActiveQueries:
|
||||
type: boolean
|
||||
type: object
|
||||
Dashboardtypesv2HistogramPanelSpec:
|
||||
properties:
|
||||
histogramBuckets:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2HistogramBuckets'
|
||||
legend:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Legend'
|
||||
type: object
|
||||
Dashboardtypesv2Layout:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2LayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpec'
|
||||
Dashboardtypesv2LayoutEnvelopeGithubComPersesPersesPkgModelApiV1DashboardGridLayoutSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- Grid
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/DashboardGridLayoutSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2Legend:
|
||||
properties:
|
||||
customColors:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
position:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2LegendPosition'
|
||||
type: object
|
||||
Dashboardtypesv2LegendPosition:
|
||||
enum:
|
||||
- bottom
|
||||
- right
|
||||
type: string
|
||||
Dashboardtypesv2LineInterpolation:
|
||||
enum:
|
||||
- linear
|
||||
- spline
|
||||
- step_after
|
||||
- step_before
|
||||
type: string
|
||||
Dashboardtypesv2LineStyle:
|
||||
enum:
|
||||
- solid
|
||||
- dashed
|
||||
type: string
|
||||
Dashboardtypesv2ListPanelSpec:
|
||||
properties:
|
||||
selectFields:
|
||||
items:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
type: object
|
||||
Dashboardtypesv2ListVariableSpec:
|
||||
properties:
|
||||
allowAllValue:
|
||||
type: boolean
|
||||
allowMultiple:
|
||||
type: boolean
|
||||
capturingRegexp:
|
||||
type: string
|
||||
customAllValue:
|
||||
type: string
|
||||
defaultValue:
|
||||
$ref: '#/components/schemas/VariableDefaultValue'
|
||||
display:
|
||||
$ref: '#/components/schemas/VariableDisplay'
|
||||
name:
|
||||
type: string
|
||||
plugin:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2VariablePlugin'
|
||||
sort:
|
||||
nullable: true
|
||||
type: string
|
||||
type: object
|
||||
Dashboardtypesv2NumberPanelSpec:
|
||||
properties:
|
||||
formatting:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelFormatting'
|
||||
thresholds:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ComparisonThreshold'
|
||||
nullable: true
|
||||
type: array
|
||||
visualization:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BasicVisualization'
|
||||
type: object
|
||||
Dashboardtypesv2Panel:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelSpec'
|
||||
type: object
|
||||
Dashboardtypesv2PanelFormatting:
|
||||
properties:
|
||||
decimalPrecision:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PrecisionOption'
|
||||
unit:
|
||||
type: string
|
||||
type: object
|
||||
Dashboardtypesv2PanelPlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TimeSeriesPanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2BarChartPanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2NumberPanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2PieChartPanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TablePanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2HistogramPanelSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2ListPanelSpec'
|
||||
Dashboardtypesv2PanelPluginKind:
|
||||
enum:
|
||||
- signoz/TimeSeriesPanel
|
||||
- signoz/BarChartPanel
|
||||
- signoz/NumberPanel
|
||||
- signoz/PieChartPanel
|
||||
- signoz/TablePanel
|
||||
- signoz/HistogramPanel
|
||||
- signoz/ListPanel
|
||||
type: string
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2BarChartPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/BarChartPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BarChartPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2HistogramPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/HistogramPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2HistogramPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2ListPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/ListPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ListPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2NumberPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/NumberPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2NumberPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2PieChartPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/PieChartPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PieChartPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TablePanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/TablePanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TablePanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TimeSeriesPanelSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/TimeSeriesPanel
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimeSeriesPanelSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2PanelSpec:
|
||||
properties:
|
||||
display:
|
||||
$ref: '#/components/schemas/V1PanelDisplay'
|
||||
links:
|
||||
items:
|
||||
$ref: '#/components/schemas/V1Link'
|
||||
type: array
|
||||
plugin:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelPlugin'
|
||||
queries:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Query'
|
||||
type: array
|
||||
type: object
|
||||
Dashboardtypesv2PieChartPanelSpec:
|
||||
properties:
|
||||
formatting:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelFormatting'
|
||||
legend:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Legend'
|
||||
visualization:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BasicVisualization'
|
||||
type: object
|
||||
Dashboardtypesv2PostableDashboard:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DashboardData'
|
||||
metadata:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DashboardMetadata'
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesPostableTag'
|
||||
type: array
|
||||
type: object
|
||||
Dashboardtypesv2PrecisionOption:
|
||||
enum:
|
||||
- "0"
|
||||
- "1"
|
||||
- "2"
|
||||
- "3"
|
||||
- "4"
|
||||
- full
|
||||
type: string
|
||||
Dashboardtypesv2Query:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2QuerySpec'
|
||||
type: object
|
||||
Dashboardtypesv2QueryPlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2BuilderQuerySpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormula'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperator'
|
||||
Dashboardtypesv2QueryPluginKind:
|
||||
enum:
|
||||
- signoz/BuilderQuery
|
||||
- signoz/CompositeQuery
|
||||
- signoz/Formula
|
||||
- signoz/PromQLQuery
|
||||
- signoz/ClickHouseSQL
|
||||
- signoz/TraceOperator
|
||||
type: string
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2BuilderQuerySpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/BuilderQuery
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BuilderQuerySpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5ClickHouseQuery:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/ClickHouseSQL
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5CompositeQuery:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/CompositeQuery
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5CompositeQuery'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5PromQuery:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/PromQLQuery
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5PromQuery'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderFormula:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/Formula
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QueryPluginVariantGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5QueryBuilderTraceOperator:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/TraceOperator
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2QuerySpec:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
plugin:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2QueryPlugin'
|
||||
type: object
|
||||
Dashboardtypesv2QueryVariableSpec:
|
||||
properties:
|
||||
queryValue:
|
||||
type: string
|
||||
required:
|
||||
- queryValue
|
||||
type: object
|
||||
Dashboardtypesv2SpanGaps:
|
||||
properties:
|
||||
fillLessThan:
|
||||
type: string
|
||||
fillOnlyBelow:
|
||||
type: boolean
|
||||
type: object
|
||||
Dashboardtypesv2TableFormatting:
|
||||
properties:
|
||||
columnUnits:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
decimalPrecision:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PrecisionOption'
|
||||
type: object
|
||||
Dashboardtypesv2TablePanelSpec:
|
||||
properties:
|
||||
formatting:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TableFormatting'
|
||||
thresholds:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TableThreshold'
|
||||
nullable: true
|
||||
type: array
|
||||
visualization:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2BasicVisualization'
|
||||
type: object
|
||||
Dashboardtypesv2TableThreshold:
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
columnName:
|
||||
type: string
|
||||
format:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ThresholdFormat'
|
||||
operator:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ComparisonOperator'
|
||||
unit:
|
||||
type: string
|
||||
value:
|
||||
format: double
|
||||
type: number
|
||||
required:
|
||||
- value
|
||||
- color
|
||||
- columnName
|
||||
type: object
|
||||
Dashboardtypesv2TextVariableSpec:
|
||||
properties:
|
||||
constant:
|
||||
type: boolean
|
||||
display:
|
||||
$ref: '#/components/schemas/VariableDisplay'
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
Dashboardtypesv2ThresholdFormat:
|
||||
enum:
|
||||
- text
|
||||
- background
|
||||
type: string
|
||||
Dashboardtypesv2ThresholdWithLabel:
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
label:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
value:
|
||||
format: double
|
||||
type: number
|
||||
required:
|
||||
- value
|
||||
- color
|
||||
- label
|
||||
type: object
|
||||
Dashboardtypesv2TimePreference:
|
||||
enum:
|
||||
- global_time
|
||||
- last_5_min
|
||||
- last_15_min
|
||||
- last_30_min
|
||||
- last_1_hr
|
||||
- last_6_hr
|
||||
- last_1_day
|
||||
- last_3_days
|
||||
- last_1_week
|
||||
- last_1_month
|
||||
type: string
|
||||
Dashboardtypesv2TimeSeriesChartAppearance:
|
||||
properties:
|
||||
fillMode:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2FillMode'
|
||||
lineInterpolation:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2LineInterpolation'
|
||||
lineStyle:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2LineStyle'
|
||||
showPoints:
|
||||
type: boolean
|
||||
spanGaps:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2SpanGaps'
|
||||
type: object
|
||||
Dashboardtypesv2TimeSeriesPanelSpec:
|
||||
properties:
|
||||
axes:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Axes'
|
||||
chartAppearance:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimeSeriesChartAppearance'
|
||||
formatting:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PanelFormatting'
|
||||
legend:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2Legend'
|
||||
thresholds:
|
||||
items:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ThresholdWithLabel'
|
||||
nullable: true
|
||||
type: array
|
||||
visualization:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimeSeriesVisualization'
|
||||
type: object
|
||||
Dashboardtypesv2TimeSeriesVisualization:
|
||||
properties:
|
||||
fillSpans:
|
||||
type: boolean
|
||||
timePreference:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TimePreference'
|
||||
type: object
|
||||
Dashboardtypesv2Variable:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2VariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2ListVariableSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2VariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TextVariableSpec'
|
||||
Dashboardtypesv2VariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2ListVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- ListVariable
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2ListVariableSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2VariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2TextVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- TextVariable
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2TextVariableSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2VariablePlugin:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2DynamicVariableSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2QueryVariableSpec'
|
||||
- $ref: '#/components/schemas/Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2CustomVariableSpec'
|
||||
Dashboardtypesv2VariablePluginKind:
|
||||
enum:
|
||||
- signoz/DynamicVariable
|
||||
- signoz/QueryVariable
|
||||
- signoz/CustomVariable
|
||||
type: string
|
||||
Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2CustomVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/CustomVariable
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2CustomVariableSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2DynamicVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/DynamicVariable
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2DynamicVariableSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
Dashboardtypesv2VariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDashboardtypesv2QueryVariableSpec:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- signoz/QueryVariable
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2QueryVariableSpec'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
ErrorsJSON:
|
||||
properties:
|
||||
code:
|
||||
@@ -4674,6 +5506,39 @@ components:
|
||||
nullable: true
|
||||
type: string
|
||||
type: object
|
||||
TagtypesPostableTag:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
TagtypesTag:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
internalName:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- internalName
|
||||
- orgId
|
||||
type: object
|
||||
TelemetrytypesFieldContext:
|
||||
enum:
|
||||
- metric
|
||||
@@ -5091,6 +5956,37 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
V1Link:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
renderVariables:
|
||||
type: boolean
|
||||
targetBlank:
|
||||
type: boolean
|
||||
tooltip:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
V1PanelDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
VariableDefaultValue:
|
||||
type: object
|
||||
VariableDisplay:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
hidden:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
ZeustypesGettableHost:
|
||||
properties:
|
||||
hosts:
|
||||
@@ -10366,6 +11262,58 @@ paths:
|
||||
summary: Update user preference
|
||||
tags:
|
||||
- preferences
|
||||
/api/v2/dashboards:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint creates a v2-shape dashboard with structured metadata,
|
||||
a typed data tree, and resolved tags.
|
||||
operationId: CreateDashboardV2
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2PostableDashboard'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Dashboardtypesv2GettableDashboard'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"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: Create dashboard (v2)
|
||||
tags:
|
||||
- dashboard
|
||||
/api/v2/factor_password/forgot:
|
||||
post:
|
||||
deprecated: false
|
||||
|
||||
@@ -11,12 +11,15 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -30,9 +33,9 @@ type module struct {
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
func NewModule(store dashboardtypes.Store, sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, sqlstore, settings, analytics, orgGetter, queryParser, tagModule)
|
||||
|
||||
return &module{
|
||||
pkgDashboardModule: pkgDashboardModule,
|
||||
@@ -197,6 +200,10 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy s
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, data)
|
||||
}
|
||||
|
||||
func (module *module) CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postable dashboardtypesv2.PostableDashboard) (*dashboardtypesv2.Dashboard, error) {
|
||||
return module.pkgDashboardModule.CreateV2(ctx, orgID, createdBy, creator, postable)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ import type {
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
CreateDashboardV2201,
|
||||
CreatePublicDashboard201,
|
||||
CreatePublicDashboardPathParameters,
|
||||
DashboardtypesPostablePublicDashboardDTO,
|
||||
DashboardtypesUpdatablePublicDashboardDTO,
|
||||
Dashboardtypesv2PostableDashboardDTO,
|
||||
DeletePublicDashboardPathParameters,
|
||||
GetPublicDashboard200,
|
||||
GetPublicDashboardData200,
|
||||
@@ -634,3 +636,88 @@ export const invalidateGetPublicDashboardWidgetQueryRange = async (
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a v2-shape dashboard with structured metadata, a typed data tree, and resolved tags.
|
||||
* @summary Create dashboard (v2)
|
||||
*/
|
||||
export const createDashboardV2 = (
|
||||
dashboardtypesv2PostableDashboardDTO: BodyType<Dashboardtypesv2PostableDashboardDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateDashboardV2201>({
|
||||
url: `/api/v2/dashboards`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: dashboardtypesv2PostableDashboardDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateDashboardV2MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createDashboardV2'];
|
||||
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 createDashboardV2>>,
|
||||
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createDashboardV2(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateDashboardV2MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>
|
||||
>;
|
||||
export type CreateDashboardV2MutationBody =
|
||||
BodyType<Dashboardtypesv2PostableDashboardDTO>;
|
||||
export type CreateDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create dashboard (v2)
|
||||
*/
|
||||
export const useCreateDashboardV2 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data: BodyType<Dashboardtypesv2PostableDashboardDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateDashboardV2MutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,30 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v2/dashboards", handler.New(provider.authZ.EditAccess(provider.dashboardHandler.CreateV2), handler.OpenAPIDef{
|
||||
ID: "CreateDashboardV2",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Create dashboard (v2)",
|
||||
Description: "This endpoint creates a v2-shape dashboard with structured metadata, a typed data tree, and resolved tags.",
|
||||
Request: new(dashboardtypesv2.PostableDashboard),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(dashboardtypesv2.GettableDashboard),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/dashboards/{id}/public", handler.New(provider.authZ.AdminAccess(provider.dashboardHandler.CreatePublic), handler.OpenAPIDef{
|
||||
ID: "CreatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -52,6 +53,11 @@ type Module interface {
|
||||
statsreporter.StatsCollector
|
||||
|
||||
authz.RegisterTypeable
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// v2 dashboard methods
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postable dashboardtypesv2.PostableDashboard) (*dashboardtypesv2.Dashboard, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
@@ -74,4 +80,9 @@ type Handler interface {
|
||||
LockUnlock(http.ResponseWriter, *http.Request)
|
||||
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// v2 dashboard methods
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
CreateV2(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
@@ -20,20 +22,24 @@ import (
|
||||
|
||||
type module struct {
|
||||
store dashboardtypes.Store
|
||||
sqlstore sqlstore.SQLStore
|
||||
settings factory.ScopedProviderSettings
|
||||
analytics analytics.Analytics
|
||||
orgGetter organization.Getter
|
||||
queryParser queryparser.QueryParser
|
||||
tagModule tag.Module
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser) dashboard.Module {
|
||||
func NewModule(store dashboardtypes.Store, sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, tagModule tag.Module) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard")
|
||||
return &module{
|
||||
store: store,
|
||||
sqlstore: sqlstore,
|
||||
settings: scopedProviderSettings,
|
||||
analytics: analytics,
|
||||
orgGetter: orgGetter,
|
||||
queryParser: queryParser,
|
||||
tagModule: tagModule,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
pkg/modules/dashboard/impldashboard/v2_handler.go
Normal file
44
pkg/modules/dashboard/impldashboard/v2_handler.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func (handler *handler) CreateV2(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := dashboardtypesv2.PostableDashboard{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.CreateV2(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, dashboardtypesv2.NewGettableDashboardFromDashboard(dashboard))
|
||||
}
|
||||
44
pkg/modules/dashboard/impldashboard/v2_module.go
Normal file
44
pkg/modules/dashboard/impldashboard/v2_module.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/dashboardtypesv2"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func (module *module) CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postable dashboardtypesv2.PostableDashboard) (*dashboardtypesv2.Dashboard, error) {
|
||||
// Tag upserts run outside the dashboard transaction by design: a successful
|
||||
// upsert that loses an outer dashboard insert just leaves resolved tag rows
|
||||
// around for the next attempt — preferable to coupling the two.
|
||||
resolvedTags, err := module.tagModule.CreateMany(ctx, orgID, postable.Tags, createdBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboard := dashboardtypesv2.NewDashboard(orgID, createdBy, postable, resolvedTags)
|
||||
|
||||
storableDashboard, err := dashboard.ToStorableDashboard()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tagIDs := make([]valuer.UUID, len(resolvedTags))
|
||||
for i, t := range resolvedTags {
|
||||
tagIDs[i] = t.ID
|
||||
}
|
||||
|
||||
err = module.sqlstore.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
|
||||
if err := module.store.Create(ctx, storableDashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
return module.tagModule.LinkToEntity(ctx, orgID, dashboardtypes.EntityTypeDashboard, dashboard.ID, tagIDs)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module.analytics.TrackUser(ctx, orgID.String(), creator.String(), "Dashboard Created", dashboardtypes.NewStatsFromStorableDashboards([]*dashboardtypes.StorableDashboard{storableDashboard}))
|
||||
return dashboard, nil
|
||||
}
|
||||
42
pkg/modules/tag/impltag/module.go
Normal file
42
pkg/modules/tag/impltag/module.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package impltag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store tagtypes.Store
|
||||
}
|
||||
|
||||
func NewModule(store tagtypes.Store) tag.Module {
|
||||
return &module{store: store}
|
||||
}
|
||||
|
||||
func (m *module) CreateMany(ctx context.Context, orgID valuer.UUID, postable []tagtypes.PostableTag, createdBy string) ([]*tagtypes.Tag, error) {
|
||||
if len(postable) == 0 {
|
||||
return []*tagtypes.Tag{}, nil
|
||||
}
|
||||
|
||||
toCreate, matched, err := tagtypes.Resolve(ctx, m.store, orgID, postable, createdBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
created, err := m.store.Create(ctx, toCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(matched, created...), nil
|
||||
}
|
||||
|
||||
func (m *module) LinkToEntity(ctx context.Context, orgID valuer.UUID, entityType tagtypes.EntityType, entityID valuer.UUID, tagIDs []valuer.UUID) error {
|
||||
if len(tagIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return m.store.CreateRelations(ctx, tagtypes.NewTagRelations(orgID, entityType, entityID, tagIDs))
|
||||
}
|
||||
66
pkg/modules/tag/impltag/store.go
Normal file
66
pkg/modules/tag/impltag/store.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package impltag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) tagtypes.Store {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (s *store) List(ctx context.Context, orgID valuer.UUID) ([]*tagtypes.Tag, error) {
|
||||
tags := make([]*tagtypes.Tag, 0)
|
||||
err := s.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&tags).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (s *store) Create(ctx context.Context, tags []*tagtypes.Tag) ([]*tagtypes.Tag, error) {
|
||||
if len(tags) == 0 {
|
||||
return tags, nil
|
||||
}
|
||||
// DO UPDATE on a self-set is a deliberate no-op write whose only purpose
|
||||
// is to make RETURNING fire on conflicting rows. Without it, RETURNING is
|
||||
// silent on the conflict path and we'd have to refetch by internal name to
|
||||
// learn the existing rows' IDs after a concurrent-insert race.
|
||||
err := s.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(&tags).
|
||||
On("CONFLICT (org_id, internal_name) DO UPDATE").
|
||||
Set("internal_name = EXCLUDED.internal_name").
|
||||
Returning("*").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (s *store) CreateRelations(ctx context.Context, relations []*tagtypes.TagRelation) error {
|
||||
if len(relations) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := s.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(&relations).
|
||||
On("CONFLICT (entity_id, tag_id) DO NOTHING").
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
140
pkg/modules/tag/impltag/store_test.go
Normal file
140
pkg/modules/tag/impltag/store_test.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package impltag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlitesqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func newTestStore(t *testing.T) sqlstore.SQLStore {
|
||||
t.Helper()
|
||||
dbPath := filepath.Join(t.TempDir(), "test.db")
|
||||
store, err := sqlitesqlstore.New(context.Background(), factorytest.NewSettings(), sqlstore.Config{
|
||||
Provider: "sqlite",
|
||||
Connection: sqlstore.ConnectionConfig{
|
||||
MaxOpenConns: 1,
|
||||
MaxConnLifetime: 0,
|
||||
},
|
||||
Sqlite: sqlstore.SqliteConfig{
|
||||
Path: dbPath,
|
||||
Mode: "wal",
|
||||
BusyTimeout: 5 * time.Second,
|
||||
TransactionMode: "deferred",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.BunDB().NewCreateTable().
|
||||
Model((*tagtypes.Tag)(nil)).
|
||||
IfNotExists().
|
||||
Exec(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.BunDB().Exec(`CREATE UNIQUE INDEX IF NOT EXISTS uq_tag_org_id_internal_name ON tag (org_id, internal_name)`)
|
||||
require.NoError(t, err)
|
||||
return store
|
||||
}
|
||||
|
||||
func tagsByInternalName(t *testing.T, db *bun.DB) map[string]*tagtypes.Tag {
|
||||
t.Helper()
|
||||
all := make([]*tagtypes.Tag, 0)
|
||||
require.NoError(t, db.NewSelect().Model(&all).Scan(context.Background()))
|
||||
out := map[string]*tagtypes.Tag{}
|
||||
for _, tag := range all {
|
||||
out[tag.InternalName] = tag
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestStore_Create_PopulatesIDsOnFreshInsert(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sqlstore := newTestStore(t)
|
||||
s := NewStore(sqlstore)
|
||||
|
||||
orgID := valuer.GenerateUUID()
|
||||
tagA := tagtypes.NewTag(orgID, "Database", "database", "u@signoz.io")
|
||||
tagB := tagtypes.NewTag(orgID, "team/BLR", "team::blr", "u@signoz.io")
|
||||
preIDA := tagA.ID
|
||||
preIDB := tagB.ID
|
||||
|
||||
got, err := s.Create(ctx, []*tagtypes.Tag{tagA, tagB})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 2)
|
||||
|
||||
// No race → pre-generated IDs stand. The slice is what we passed in,
|
||||
// confirming Scan didn't reallocate.
|
||||
assert.Equal(t, preIDA, got[0].ID)
|
||||
assert.Equal(t, preIDB, got[1].ID)
|
||||
|
||||
// And the rows are in the DB.
|
||||
stored := tagsByInternalName(t, sqlstore.BunDB())
|
||||
require.Contains(t, stored, "database")
|
||||
require.Contains(t, stored, "team::blr")
|
||||
assert.Equal(t, preIDA, stored["database"].ID)
|
||||
assert.Equal(t, preIDB, stored["team::blr"].ID)
|
||||
}
|
||||
|
||||
func TestStore_Create_ConflictReturnsExistingRowID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sqlstore := newTestStore(t)
|
||||
s := NewStore(sqlstore)
|
||||
|
||||
orgID := valuer.GenerateUUID()
|
||||
|
||||
// Simulate a concurrent insert: someone else has already inserted "database".
|
||||
winner := tagtypes.NewTag(orgID, "Database", "database", "concurrent")
|
||||
_, err := s.Create(ctx, []*tagtypes.Tag{winner})
|
||||
require.NoError(t, err)
|
||||
winnerID := winner.ID
|
||||
|
||||
// Now our request runs with a different pre-generated ID for the same
|
||||
// internal name. RETURNING should overwrite our stale ID with winner's ID.
|
||||
loser := tagtypes.NewTag(orgID, "Database", "database", "u@signoz.io")
|
||||
loserPreID := loser.ID
|
||||
require.NotEqual(t, winnerID, loserPreID, "pre-generated IDs must differ for this test to be meaningful")
|
||||
|
||||
got, err := s.Create(ctx, []*tagtypes.Tag{loser})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
|
||||
assert.Equal(t, winnerID, got[0].ID, "returned slice should carry the existing row's ID, not our stale one")
|
||||
assert.Equal(t, winnerID, loser.ID, "input slice element is mutated in place")
|
||||
|
||||
// And the DB still has exactly one row for that internal name — winner's.
|
||||
stored := tagsByInternalName(t, sqlstore.BunDB())
|
||||
require.Len(t, stored, 1)
|
||||
assert.Equal(t, winnerID, stored["database"].ID)
|
||||
}
|
||||
|
||||
func TestStore_Create_MixedFreshAndConflict(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sqlstore := newTestStore(t)
|
||||
s := NewStore(sqlstore)
|
||||
|
||||
orgID := valuer.GenerateUUID()
|
||||
pre := tagtypes.NewTag(orgID, "Database", "database", "concurrent")
|
||||
_, err := s.Create(ctx, []*tagtypes.Tag{pre})
|
||||
require.NoError(t, err)
|
||||
preExistingID := pre.ID
|
||||
|
||||
conflict := tagtypes.NewTag(orgID, "Database", "database", "u@signoz.io")
|
||||
fresh := tagtypes.NewTag(orgID, "team/BLR", "team::blr", "u@signoz.io")
|
||||
freshPreID := fresh.ID
|
||||
|
||||
got, err := s.Create(ctx, []*tagtypes.Tag{conflict, fresh})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 2)
|
||||
|
||||
assert.Equal(t, preExistingID, got[0].ID, "conflicting row's ID overwritten with the existing row's")
|
||||
assert.Equal(t, freshPreID, got[1].ID, "fresh row's pre-generated ID is preserved")
|
||||
}
|
||||
23
pkg/modules/tag/tag.go
Normal file
23
pkg/modules/tag/tag.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// CreateMany resolves user-supplied tag names against the existing tags for the
|
||||
// org — reusing the casing of any existing parent tag so that
|
||||
// "teams/blr/platform" inherits the "BLR" casing from a pre-existing
|
||||
// "teams/BLR" tag — and inserts any tags that don't yet exist.
|
||||
//
|
||||
// Does not link the resolved tags to any entity — call LinkToEntity for that.
|
||||
CreateMany(ctx context.Context, orgID valuer.UUID, postable []tagtypes.PostableTag, createdBy string) ([]*tagtypes.Tag, error)
|
||||
|
||||
// LinkToEntity inserts (entity, tag) rows in tag_relations. Existing rows
|
||||
// are left untouched. Uses the caller's transaction context if any so that
|
||||
// it can be made atomic with the entity row insert.
|
||||
LinkToEntity(ctx context.Context, orgID valuer.UUID, entityType tagtypes.EntityType, entityID valuer.UUID, tagIDs []valuer.UUID) error
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag/impltag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -44,7 +45,8 @@ func TestNewHandlers(t *testing.T) {
|
||||
emailing := emailingtest.New()
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
require.NoError(t, err)
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
|
||||
tagModule := impltag.NewModule(impltag.NewStore(sqlstore))
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), sqlstore, providerSettings, nil, orgGetter, queryParser, tagModule)
|
||||
|
||||
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
|
||||
require.NoError(t, err)
|
||||
@@ -52,7 +54,7 @@ func TestNewHandlers(t *testing.T) {
|
||||
userRoleStore := impluser.NewUserRoleStore(sqlstore, providerSettings)
|
||||
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), userRoleStore, flagger)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, nil, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, nil, nil, tagModule)
|
||||
|
||||
querierHandler := querier.NewHandler(providerSettings, nil, nil)
|
||||
registryHandler := factory.NewHandler(nil)
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/session/implsession"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracedetail/impltracedetail"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
@@ -79,6 +80,7 @@ type Modules struct {
|
||||
CloudIntegration cloudintegration.Module
|
||||
RuleStateHistory rulestatehistory.Module
|
||||
TraceDetail tracedetail.Module
|
||||
Tag tag.Module
|
||||
}
|
||||
|
||||
func NewModules(
|
||||
@@ -102,6 +104,7 @@ func NewModules(
|
||||
userRoleStore authtypes.UserRoleStore,
|
||||
serviceAccount serviceaccount.Module,
|
||||
cloudIntegrationModule cloudintegration.Module,
|
||||
tagModule tag.Module,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
@@ -131,5 +134,6 @@ func NewModules(
|
||||
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
|
||||
CloudIntegration: cloudIntegrationModule,
|
||||
TraceDetail: impltracedetail.NewModule(impltracedetail.NewTraceStore(telemetryStore), providerSettings, config.TraceDetail),
|
||||
Tag: tagModule,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag/impltag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
@@ -45,7 +46,8 @@ func TestNewModules(t *testing.T) {
|
||||
emailing := emailingtest.New()
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
require.NoError(t, err)
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
|
||||
tagModule := impltag.NewModule(impltag.NewStore(sqlstore))
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), sqlstore, providerSettings, nil, orgGetter, queryParser, tagModule)
|
||||
|
||||
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
|
||||
require.NoError(t, err)
|
||||
@@ -56,7 +58,7 @@ func TestNewModules(t *testing.T) {
|
||||
|
||||
serviceAccount := implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), nil, nil, nil, providerSettings, serviceaccount.Config{})
|
||||
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, serviceAccount, implcloudintegration.NewModule())
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, userRoleStore, serviceAccount, implcloudintegration.NewModule(), tagModule)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -195,6 +195,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
|
||||
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
|
||||
sqlmigration.NewAddTagsFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag/impltag"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
@@ -101,7 +103,7 @@ func New(
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, []authz.OnBeforeRoleDelete, dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error),
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing, tag.Module) dashboard.Module,
|
||||
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
|
||||
auditorProviderFactories func(licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]],
|
||||
querierHandlerCallback func(factory.ProviderSettings, querier.Querier, analytics.Analytics) querier.Handler,
|
||||
@@ -325,8 +327,13 @@ func New(
|
||||
// Initialize query parser (needed for dashboard module)
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
|
||||
// Initialize tag module — shared across modules that link entities to tags
|
||||
// (currently dashboard; future: alerts, RBAC). Built once here and injected
|
||||
// where needed.
|
||||
tagModule := impltag.NewModule(impltag.NewStore(sqlstore))
|
||||
|
||||
// Initialize dashboard module (needed for authz registry)
|
||||
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, queryParser, querier, licensing, tagModule)
|
||||
|
||||
// Initialize user getter
|
||||
userGetter := impluser.NewGetter(userStore, userRoleStore, flagger)
|
||||
@@ -440,7 +447,7 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, userRoleStore, serviceAccount, cloudIntegrationModule, tagModule)
|
||||
|
||||
// Initialize ruler from the variant-specific provider factories
|
||||
rulerInstance, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.Ruler, rulerProviderFactories(cache, alertmanager, sqlstore, telemetrystore, telemetryMetadataStore, prometheus, orgGetter, modules.RuleStateHistory, querier, queryParser), "signoz")
|
||||
|
||||
103
pkg/sqlmigration/078_add_tags.go
Normal file
103
pkg/sqlmigration/078_add_tags.go
Normal file
@@ -0,0 +1,103 @@
|
||||
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 addTags struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddTagsFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_tags"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addTags{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addTags) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addTags) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sqls := [][]byte{}
|
||||
|
||||
tagTableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "tag",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "internal_name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "created_by", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_by", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"id"}},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("org_id"),
|
||||
ReferencedTableName: sqlschema.TableName("organizations"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tagTableSQLs...)
|
||||
|
||||
tagUniqueIndexSQL := migration.sqlschema.Operator().CreateIndex(
|
||||
&sqlschema.UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []sqlschema.ColumnName{"org_id", "internal_name"},
|
||||
})
|
||||
sqls = append(sqls, tagUniqueIndexSQL...)
|
||||
|
||||
tagRelationsTableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "tag_relations",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "entity_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "entity_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "tag_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"entity_id", "tag_id"}},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("org_id"),
|
||||
ReferencedTableName: sqlschema.TableName("organizations"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tagRelationsTableSQLs...)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addTags) Down(_ context.Context, _ *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -19,6 +20,8 @@ var (
|
||||
TypeableMetaResourceDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("dashboard"))
|
||||
TypeableMetaResourcePublicDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("public-dashboard"))
|
||||
TypeableMetaResourcesDashboards = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("dashboards"))
|
||||
|
||||
EntityTypeDashboard = tagtypes.MustNewEntityType("dashboard")
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
144
pkg/types/dashboardtypes/dashboardtypesv2/dashboard.go
Normal file
144
pkg/types/dashboardtypes/dashboardtypesv2/dashboard.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package dashboardtypesv2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/tagtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
const SchemaVersion = "v6"
|
||||
|
||||
type Dashboard struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
Locked bool `json:"locked"`
|
||||
Info DashboardInfo `json:"info"`
|
||||
PublicConfig *dashboardtypes.PublicDashboard `json:"publicConfig,omitempty"`
|
||||
}
|
||||
|
||||
// DashboardInfo is the serializable view of a dashboard's contents — what the UI renders as "the dashboard JSON".
|
||||
type DashboardInfo struct {
|
||||
StoredDashboardInfo
|
||||
Tags []*tagtypes.Tag `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// StoredDashboardInfo is exactly what serializes into the dashboard.data column.
|
||||
type StoredDashboardInfo struct {
|
||||
Metadata DashboardMetadata `json:"metadata"`
|
||||
Data DashboardData `json:"data"`
|
||||
}
|
||||
|
||||
type DashboardMetadata struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
Image string `json:"image,omitempty"`
|
||||
UploadedGrafana bool `json:"uploadedGrafana"`
|
||||
}
|
||||
|
||||
type PostableDashboard struct {
|
||||
StoredDashboardInfo
|
||||
Tags []tagtypes.PostableTag `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PostableDashboard) UnmarshalJSON(data []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.DisallowUnknownFields()
|
||||
type alias PostableDashboard
|
||||
var tmp alias
|
||||
if err := dec.Decode(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
*p = PostableDashboard(tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GettableDashboard mirrors Dashboard except PublicConfig is the trimmed
|
||||
// GettablePublicDashboard (no internal IDs, with the formatted public
|
||||
// path) since clients never need the raw row's id or audit fields.
|
||||
type GettableDashboard struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
Locked bool `json:"locked"`
|
||||
Info DashboardInfo `json:"info"`
|
||||
PublicConfig *dashboardtypes.GettablePublicDasbhboard `json:"publicConfig,omitempty"`
|
||||
}
|
||||
|
||||
func NewGettableDashboardFromDashboard(dashboard *Dashboard) *GettableDashboard {
|
||||
gettable := &GettableDashboard{
|
||||
Identifiable: dashboard.Identifiable,
|
||||
TimeAuditable: dashboard.TimeAuditable,
|
||||
UserAuditable: dashboard.UserAuditable,
|
||||
OrgID: dashboard.OrgID,
|
||||
Locked: dashboard.Locked,
|
||||
Info: dashboard.Info,
|
||||
}
|
||||
if dashboard.PublicConfig != nil {
|
||||
gettable.PublicConfig = &dashboardtypes.GettablePublicDasbhboard{
|
||||
TimeRangeEnabled: dashboard.PublicConfig.TimeRangeEnabled,
|
||||
DefaultTimeRange: dashboard.PublicConfig.DefaultTimeRange,
|
||||
PublicPath: dashboard.PublicConfig.PublicPath(),
|
||||
}
|
||||
}
|
||||
return gettable
|
||||
}
|
||||
|
||||
func NewDashboard(orgID valuer.UUID, createdBy string, postable PostableDashboard, resolvedTags []*tagtypes.Tag) *Dashboard {
|
||||
now := time.Now()
|
||||
postable.Metadata.SchemaVersion = SchemaVersion
|
||||
|
||||
return &Dashboard{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
UserAuditable: types.UserAuditable{CreatedBy: createdBy, UpdatedBy: createdBy},
|
||||
OrgID: orgID,
|
||||
Locked: false,
|
||||
Info: DashboardInfo{
|
||||
StoredDashboardInfo: StoredDashboardInfo{
|
||||
Metadata: postable.Metadata,
|
||||
Data: postable.Data,
|
||||
},
|
||||
Tags: resolvedTags,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ToStorableDashboard packages a Dashboard into the bun row that goes into
|
||||
// the dashboard table. Tags are intentionally omitted — they live in
|
||||
// tag_relations and are inserted separately by the caller.
|
||||
func (d *Dashboard) ToStorableDashboard() (*dashboardtypes.StorableDashboard, error) {
|
||||
data, err := d.Info.StoredDashboardInfo.toStorableDashboardData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dashboardtypes.StorableDashboard{
|
||||
Identifiable: types.Identifiable{ID: d.ID},
|
||||
TimeAuditable: d.TimeAuditable,
|
||||
UserAuditable: d.UserAuditable,
|
||||
OrgID: d.OrgID,
|
||||
Locked: d.Locked,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s StoredDashboardInfo) toStorableDashboardData() (dashboardtypes.StorableDashboardData, error) {
|
||||
raw, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "marshal v2 dashboard data")
|
||||
}
|
||||
out := dashboardtypes.StorableDashboardData{}
|
||||
if err := json.Unmarshal(raw, &out); err != nil {
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "unmarshal v2 dashboard data")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
20
pkg/types/tagtypes/store.go
Normal file
20
pkg/types/tagtypes/store.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package tagtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
List(ctx context.Context, orgID valuer.UUID) ([]*Tag, error)
|
||||
|
||||
// Create upserts the given tags and returns them with authoritative IDs.
|
||||
// On conflict on (org_id, internal_name) — which happens only when a
|
||||
// concurrent insert raced ours — the returned entry carries the existing
|
||||
// row's ID rather than the pre-generated one in the input.
|
||||
Create(ctx context.Context, tags []*Tag) ([]*Tag, error)
|
||||
|
||||
// CreateRelations inserts tag-entity relations. Conflicts on the composite primary key are ignored.
|
||||
CreateRelations(ctx context.Context, relations []*TagRelation) error
|
||||
}
|
||||
168
pkg/types/tagtypes/tag.go
Normal file
168
pkg/types/tagtypes/tag.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package tagtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
const (
|
||||
// separator users type in the display name (e.g. "team/blr").
|
||||
HierarchySeparator = "/"
|
||||
|
||||
// separator used in internal_name. Different from HierarchySeparator
|
||||
// because "/" is reserved by the access control layer.
|
||||
InternalSeparator = "::"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeTagInvalidName = errors.MustNewCode("tag_invalid_name")
|
||||
ErrCodeTagNotFound = errors.MustNewCode("tag_not_found")
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
bun.BaseModel `bun:"table:tag,alias:tag"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
types.UserAuditable
|
||||
Name string `json:"name" required:"true" bun:"name,type:text,notnull"`
|
||||
InternalName string `json:"internalName" required:"true" bun:"internal_name,type:text,notnull,unique:org_id_internal_name"`
|
||||
OrgID valuer.UUID `json:"orgId" required:"true" bun:"org_id,type:text,notnull,unique:org_id_internal_name"`
|
||||
}
|
||||
|
||||
type PostableTag struct {
|
||||
Name string `json:"name" required:"true"`
|
||||
}
|
||||
|
||||
func NewTag(orgID valuer.UUID, name string, internalName string, createdBy string) *Tag {
|
||||
now := time.Now()
|
||||
return &Tag{
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
UserAuditable: types.UserAuditable{
|
||||
CreatedBy: createdBy,
|
||||
UpdatedBy: createdBy,
|
||||
},
|
||||
Name: name,
|
||||
InternalName: internalName,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve canonicalizes a batch of user-supplied tag names against the existing
|
||||
// tags for an org. Existing parent tags' casing is reused so that
|
||||
// "teams/blr/platform" inherits the "BLR" casing from a pre-existing
|
||||
// "teams/BLR". Inputs are deduped by internal name. Returns:
|
||||
// - toCreate: new Tag rows the caller should insert (with pre-generated IDs)
|
||||
// - matched: existing rows that the caller's input already pointed to. They
|
||||
// already carry authoritative IDs from the store.
|
||||
func Resolve(ctx context.Context, store Store, orgID valuer.UUID, postable []PostableTag, createdBy string) ([]*Tag, []*Tag, error) {
|
||||
if len(postable) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
existing, err := store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
internalNameToExistingTag := make(map[string]*Tag, len(existing))
|
||||
for _, t := range existing {
|
||||
internalNameToExistingTag[t.InternalName] = t
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(postable))
|
||||
toCreate := make([]*Tag, 0)
|
||||
matched := make([]*Tag, 0)
|
||||
|
||||
for _, p := range postable {
|
||||
cleanedName, err := cleanupName(p.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
matchedName, matchedInternalName := matchCasingWithExistingTags(cleanedName, existing)
|
||||
if _, dup := seen[matchedInternalName]; dup {
|
||||
continue
|
||||
}
|
||||
seen[matchedInternalName] = struct{}{}
|
||||
|
||||
if existingTag, ok := internalNameToExistingTag[matchedInternalName]; ok {
|
||||
matched = append(matched, existingTag)
|
||||
continue
|
||||
}
|
||||
toCreate = append(toCreate, NewTag(orgID, matchedName, matchedInternalName, createdBy))
|
||||
}
|
||||
|
||||
return toCreate, matched, nil
|
||||
}
|
||||
|
||||
func cleanupName(name string) (string, error) {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
raw := strings.Split(trimmed, HierarchySeparator)
|
||||
segments := make([]string, 0, len(raw))
|
||||
for _, seg := range raw {
|
||||
seg = strings.TrimSpace(seg)
|
||||
if seg == "" {
|
||||
continue
|
||||
}
|
||||
segments = append(segments, seg)
|
||||
}
|
||||
if len(segments) == 0 {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, ErrCodeTagInvalidName, "tag name cannot be empty")
|
||||
}
|
||||
|
||||
for _, seg := range segments {
|
||||
if strings.Contains(seg, InternalSeparator) {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, ErrCodeTagInvalidName, "tag name segment %q cannot contain %q", seg, InternalSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(segments, HierarchySeparator), nil
|
||||
}
|
||||
|
||||
func buildInternalName(cleanedName string) string {
|
||||
return strings.ToLower(strings.ReplaceAll(cleanedName, HierarchySeparator, InternalSeparator))
|
||||
}
|
||||
|
||||
// matchCasingWithExistingTags returns the display name and internal name to use for
|
||||
// a user-supplied tag, given the existing tags in the org. If an existing tag
|
||||
// has the same internal name, its display name (casing) is reused. If an
|
||||
// existing tag is a strict segment-prefix of the input, that prefix's casing
|
||||
// is reused for those segments and the remaining input segments are kept as
|
||||
// the user supplied them. Otherwise the input name is returned as-is.
|
||||
func matchCasingWithExistingTags(inputCleaned string, existing []*Tag) (canonicalName string, canonicalInternalName string) {
|
||||
inputInternal := buildInternalName(inputCleaned)
|
||||
|
||||
var bestPrefix *Tag
|
||||
bestPrefixLen := 0
|
||||
for _, tag := range existing {
|
||||
if tag.InternalName == inputInternal {
|
||||
return tag.Name, tag.InternalName
|
||||
}
|
||||
if strings.HasPrefix(inputInternal, tag.InternalName+InternalSeparator) {
|
||||
if len(tag.InternalName) > bestPrefixLen {
|
||||
bestPrefix = tag
|
||||
bestPrefixLen = len(tag.InternalName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bestPrefix == nil {
|
||||
return inputCleaned, inputInternal
|
||||
}
|
||||
|
||||
prefixSegments := strings.Split(bestPrefix.Name, HierarchySeparator)
|
||||
inputSegments := strings.Split(inputCleaned, HierarchySeparator)
|
||||
canonicalSegments := append(prefixSegments, inputSegments[len(prefixSegments):]...)
|
||||
canonical := strings.Join(canonicalSegments, HierarchySeparator)
|
||||
return canonical, buildInternalName(canonical)
|
||||
}
|
||||
38
pkg/types/tagtypes/tag_relation.go
Normal file
38
pkg/types/tagtypes/tag_relation.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package tagtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type EntityType struct{ valuer.String }
|
||||
|
||||
func MustNewEntityType(name string) EntityType {
|
||||
return EntityType{valuer.NewString(name)}
|
||||
}
|
||||
|
||||
type TagRelation struct {
|
||||
bun.BaseModel `bun:"table:tag_relations,alias:tag_relations"`
|
||||
|
||||
EntityType EntityType `json:"entityType" required:"true" bun:"entity_type,type:text,notnull"`
|
||||
EntityID valuer.UUID `json:"entityId" required:"true" bun:"entity_id,pk,type:text,notnull"`
|
||||
TagID valuer.UUID `json:"tagId" required:"true" bun:"tag_id,pk,type:text,notnull"`
|
||||
OrgID valuer.UUID `json:"orgId" required:"true" bun:"org_id,type:text,notnull"`
|
||||
}
|
||||
|
||||
func NewTagRelation(orgID valuer.UUID, entityType EntityType, entityID valuer.UUID, tagID valuer.UUID) *TagRelation {
|
||||
return &TagRelation{
|
||||
EntityType: entityType,
|
||||
EntityID: entityID,
|
||||
TagID: tagID,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTagRelations(orgID valuer.UUID, entityType EntityType, entityID valuer.UUID, tagIDs []valuer.UUID) []*TagRelation {
|
||||
relations := make([]*TagRelation, 0, len(tagIDs))
|
||||
for _, tagID := range tagIDs {
|
||||
relations = append(relations, NewTagRelation(orgID, entityType, entityID, tagID))
|
||||
}
|
||||
return relations
|
||||
}
|
||||
222
pkg/types/tagtypes/tag_test.go
Normal file
222
pkg/types/tagtypes/tag_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package tagtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCleanupName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
wantError bool
|
||||
}{
|
||||
{name: "single segment", input: "prod", want: "prod"},
|
||||
{name: "two segments", input: "team/blr", want: "team/blr"},
|
||||
{name: "three segments", input: "team/BLR/platform", want: "team/BLR/platform"},
|
||||
{name: "leading whitespace", input: " prod", want: "prod"},
|
||||
{name: "trailing whitespace", input: "prod ", want: "prod"},
|
||||
{name: "leading separator", input: "/prod", want: "prod"},
|
||||
{name: "trailing separator", input: "prod/", want: "prod"},
|
||||
{name: "consecutive separators collapsed", input: "team//blr", want: "team/blr"},
|
||||
{name: "many separators collapsed", input: "team///blr////platform", want: "team/blr/platform"},
|
||||
{name: "whitespace within segments", input: "team/ blr ", want: "team/blr"},
|
||||
{name: "empty rejected", input: "", wantError: true},
|
||||
{name: "only whitespace rejected", input: " ", wantError: true},
|
||||
{name: "only separators rejected", input: "///", wantError: true},
|
||||
{name: "internal separator rejected", input: "team/foo::bar", wantError: true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := cleanupName(tc.input)
|
||||
if tc.wantError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInternalName(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{input: "prod", want: "prod"},
|
||||
{input: "Prod", want: "prod"},
|
||||
{input: "team/BLR/platform", want: "team::blr::platform"},
|
||||
{input: "TEAM/BLR", want: "team::blr"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, buildInternalName(tc.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchCasingWithExistingTags(t *testing.T) {
|
||||
existing := []*Tag{
|
||||
{Name: "team/BLR", InternalName: "team::blr"},
|
||||
{Name: "team/BLR/Pulse", InternalName: "team::blr::pulse"},
|
||||
{Name: "Database", InternalName: "database"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantName string
|
||||
wantInternalName string
|
||||
}{
|
||||
{
|
||||
name: "exact match reuses casing",
|
||||
input: "team/blr",
|
||||
wantName: "team/BLR",
|
||||
wantInternalName: "team::blr",
|
||||
},
|
||||
{
|
||||
name: "exact match reuses casing for deeper tag",
|
||||
input: "TEAM/blr/pulse",
|
||||
wantName: "team/BLR/Pulse",
|
||||
wantInternalName: "team::blr::pulse",
|
||||
},
|
||||
{
|
||||
name: "prefix match reuses prefix casing and keeps remainder",
|
||||
input: "team/blr/platform",
|
||||
wantName: "team/BLR/platform",
|
||||
wantInternalName: "team::blr::platform",
|
||||
},
|
||||
{
|
||||
name: "longest prefix wins",
|
||||
input: "team/blr/pulse/sub",
|
||||
wantName: "team/BLR/Pulse/sub",
|
||||
wantInternalName: "team::blr::pulse::sub",
|
||||
},
|
||||
{
|
||||
name: "no match returns input as-is",
|
||||
input: "Brand-New/Tag",
|
||||
wantName: "Brand-New/Tag",
|
||||
wantInternalName: "brand-new::tag",
|
||||
},
|
||||
{
|
||||
name: "single segment exact match",
|
||||
input: "DATABASE",
|
||||
wantName: "Database",
|
||||
wantInternalName: "database",
|
||||
},
|
||||
{
|
||||
name: "input that shares text but not segment boundary is not a prefix match",
|
||||
input: "teams/blr",
|
||||
wantName: "teams/blr",
|
||||
wantInternalName: "teams::blr",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cleaned, err := cleanupName(tc.input)
|
||||
require.NoError(t, err)
|
||||
gotName, gotInternal := matchCasingWithExistingTags(cleaned, existing)
|
||||
assert.Equal(t, tc.wantName, gotName)
|
||||
assert.Equal(t, tc.wantInternalName, gotInternal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchCasingWithExistingTags_NoTagsExist(t *testing.T) {
|
||||
cleaned, err := cleanupName("Foo/Bar")
|
||||
require.NoError(t, err)
|
||||
name, internal := matchCasingWithExistingTags(cleaned, nil)
|
||||
assert.Equal(t, "Foo/Bar", name)
|
||||
assert.Equal(t, "foo::bar", internal)
|
||||
}
|
||||
|
||||
type fakeStore struct {
|
||||
tags []*Tag
|
||||
listCallCount int
|
||||
}
|
||||
|
||||
func (f *fakeStore) List(_ context.Context, _ valuer.UUID) ([]*Tag, error) {
|
||||
f.listCallCount++
|
||||
out := make([]*Tag, len(f.tags))
|
||||
copy(out, f.tags)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) Create(_ context.Context, tags []*Tag) ([]*Tag, error) {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) CreateRelations(_ context.Context, _ []*TagRelation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
t.Run("empty input does not hit store", func(t *testing.T) {
|
||||
store := &fakeStore{}
|
||||
toCreate, matched, err := Resolve(context.Background(), store, valuer.GenerateUUID(), nil, "u@signoz.io")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, toCreate)
|
||||
assert.Empty(t, matched)
|
||||
assert.Zero(t, store.listCallCount, "should not hit store when input is empty")
|
||||
})
|
||||
|
||||
t.Run("creates missing tags and reuses existing", func(t *testing.T) {
|
||||
orgID := valuer.GenerateUUID()
|
||||
dbTag := NewTag(orgID, "team/BLR", "team::blr", "seed")
|
||||
dbTag2 := NewTag(orgID, "Database", "database", "seed")
|
||||
store := &fakeStore{tags: []*Tag{dbTag, dbTag2}}
|
||||
|
||||
toCreate, matched, err := Resolve(context.Background(), store, orgID, []PostableTag{
|
||||
{Name: "team/blr/platform"},
|
||||
{Name: "DATABASE"},
|
||||
{Name: "Brand-New"},
|
||||
}, "u@signoz.io")
|
||||
require.NoError(t, err)
|
||||
|
||||
createdInternalNames := []string{}
|
||||
createdNames := map[string]string{}
|
||||
for _, tg := range toCreate {
|
||||
createdInternalNames = append(createdInternalNames, tg.InternalName)
|
||||
createdNames[tg.InternalName] = tg.Name
|
||||
}
|
||||
assert.ElementsMatch(t, []string{"team::blr::platform", "brand-new"}, createdInternalNames,
|
||||
"only the two missing tags should be returned for insertion")
|
||||
assert.Equal(t, "team/BLR/platform", createdNames["team::blr::platform"], "should inherit casing from existing parent")
|
||||
assert.Equal(t, "Brand-New", createdNames["brand-new"], "should keep input casing when no existing match")
|
||||
|
||||
require.Len(t, matched, 1, "DATABASE should hit the existing 'Database' tag")
|
||||
assert.Same(t, dbTag2, matched[0], "matched should return the existing pointer with its authoritative ID")
|
||||
})
|
||||
|
||||
t.Run("dedupes inputs that map to the same internal name", func(t *testing.T) {
|
||||
orgID := valuer.GenerateUUID()
|
||||
store := &fakeStore{}
|
||||
|
||||
toCreate, matched, err := Resolve(context.Background(), store, orgID, []PostableTag{
|
||||
{Name: "Foo/Bar"},
|
||||
{Name: "foo/bar"},
|
||||
{Name: "FOO/BAR"},
|
||||
}, "u@signoz.io")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, matched)
|
||||
require.Len(t, toCreate, 1, "duplicate inputs must collapse into a single insert")
|
||||
assert.Equal(t, "Foo/Bar", toCreate[0].Name)
|
||||
assert.Equal(t, "foo::bar", toCreate[0].InternalName)
|
||||
})
|
||||
|
||||
t.Run("propagates validation error from any input", func(t *testing.T) {
|
||||
store := &fakeStore{}
|
||||
_, _, err := Resolve(context.Background(), store, valuer.GenerateUUID(), []PostableTag{
|
||||
{Name: "valid"},
|
||||
{Name: ""},
|
||||
}, "u@signoz.io")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user