From 21bd47e512180001e4dfb81cc04553bdd56f8789 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Wed, 27 Nov 2024 08:58:25 +0100 Subject: [PATCH 01/41] scopes: behind its own feature flag (#97077) Signed-off-by: bergquist --- .../configure-grafana/feature-toggles/index.md | 1 + .../grafana-data/src/types/featureToggles.gen.ts | 1 + pkg/registry/apis/scope/register.go | 3 ++- pkg/services/featuremgmt/registry.go | 8 ++++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 14 ++++++++++++++ 7 files changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 30374f38aa3..ab8dad22ef3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -189,6 +189,7 @@ Experimental features might be changed or removed without prior notice. | `newFolderPicker` | Enables the nested folder picker without having nested folders enabled | | `onPremToCloudMigrationsAlerts` | Enables the migration of alerts and its child resources to your Grafana Cloud stack. Requires `onPremToCloudMigrations` to be enabled in conjunction. | | `onPremToCloudMigrationsAuthApiMig` | Enables the use of auth api instead of gcom for internal token services. Requires `onPremToCloudMigrations` to be enabled in conjunction. | +| `scopeApi` | In-development feature flag for the scope api using the app platform. | | `sqlExpressions` | Enables using SQL and DuckDB functions as Expressions. | | `nodeGraphDotLayout` | Changed the layout algorithm for the node graph | | `kubernetesAggregator` | Enable grafana's embedded kube-aggregator | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 2d78e294e6c..9c23ab2d47a 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -160,6 +160,7 @@ export interface FeatureToggles { onPremToCloudMigrationsAlerts?: boolean; onPremToCloudMigrationsAuthApiMig?: boolean; alertingSaveStatePeriodic?: boolean; + scopeApi?: boolean; promQLScope?: boolean; logQLScope?: boolean; sqlExpressions?: boolean; diff --git a/pkg/registry/apis/scope/register.go b/pkg/registry/apis/scope/register.go index 8e4965666d3..afe6eca4cfd 100644 --- a/pkg/registry/apis/scope/register.go +++ b/pkg/registry/apis/scope/register.go @@ -29,7 +29,8 @@ func NewScopeAPIBuilder() *ScopeAPIBuilder { } func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, reg prometheus.Registerer) *ScopeAPIBuilder { - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { + if !(features.IsEnabledGlobally(featuremgmt.FlagScopeApi) || + features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) { return nil // skip registration unless opting into experimental apis } builder := NewScopeAPIBuilder() diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index c8542eaea61..2c9b654252c 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1070,6 +1070,14 @@ var ( FrontendOnly: false, Owner: grafanaAlertingSquad, }, + { + Name: "scopeApi", + Description: "In-development feature flag for the scope api using the app platform.", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + HideFromAdminPage: true, + Expression: "false", + }, { Name: "promQLScope", Description: "In-development feature that will allow injection of labels into prometheus queries.", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 43c5d1b9170..2896b4d9eea 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -141,6 +141,7 @@ onPremToCloudMigrations,preview,@grafana/grafana-operator-experience-squad,false onPremToCloudMigrationsAlerts,experimental,@grafana/grafana-operator-experience-squad,false,false,false onPremToCloudMigrationsAuthApiMig,experimental,@grafana/grafana-operator-experience-squad,false,false,false alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,false,false,false +scopeApi,experimental,@grafana/grafana-app-platform-squad,false,false,false promQLScope,GA,@grafana/observability-metrics,false,false,false logQLScope,privatePreview,@grafana/observability-logs,false,false,false sqlExpressions,experimental,@grafana/grafana-app-platform-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 2f912caa947..588d9c5ff60 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -575,6 +575,10 @@ const ( // Writes the state periodically to the database, asynchronous to rule evaluation FlagAlertingSaveStatePeriodic = "alertingSaveStatePeriodic" + // FlagScopeApi + // In-development feature flag for the scope api using the app platform. + FlagScopeApi = "scopeApi" + // FlagPromQLScope // In-development feature that will allow injection of labels into prometheus queries. FlagPromQLScope = "promQLScope" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0f692b83d5a..e8ecedb79fb 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -3130,6 +3130,20 @@ "frontend": true } }, + { + "metadata": { + "name": "scopeApi", + "resourceVersion": "1732690644377", + "creationTimestamp": "2024-11-27T06:57:24Z" + }, + "spec": { + "description": "In-development feature flag for the scope api using the app platform.", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "hideFromAdminPage": true, + "expression": "false" + } + }, { "metadata": { "name": "scopeFilters", From be54c69fd41e1a3781c010e6018e8b03941df0f5 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 27 Nov 2024 09:02:29 +0100 Subject: [PATCH 02/41] Zanzana: Remove create relation from generic resources (#97042) Remove create relation from generic resources. We cant have a create relation to a resource because they don't exist yet. So in oder to check create we either have to have that permissions on a folder or the namespace --- pkg/services/authz/zanzana/common/info.go | 5 +- pkg/services/authz/zanzana/common/tuple.go | 2 +- .../authz/zanzana/schema/schema_resource.fga | 1 - .../zanzana/server/server_batch_check.go | 2 + .../zanzana/server/server_batch_check_test.go | 95 ++++++++++++------- .../zanzana/server/server_capabilities.go | 2 +- .../server/server_capabilities_test.go | 2 +- .../authz/zanzana/server/server_check.go | 44 +++++---- .../authz/zanzana/server/server_check_test.go | 42 ++++---- .../authz/zanzana/server/server_test.go | 3 +- 10 files changed, 119 insertions(+), 79 deletions(-) diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go index ad5be54940a..0517a0be84a 100644 --- a/pkg/services/authz/zanzana/common/info.go +++ b/pkg/services/authz/zanzana/common/info.go @@ -7,14 +7,15 @@ import ( ) type TypeInfo struct { - Type string + Type string + Relations []string } var typedResources = map[string]TypeInfo{ FormatGroupResource( folderalpha1.FolderResourceInfo.GroupResource().Group, folderalpha1.FolderResourceInfo.GroupResource().Resource, - ): {Type: "folder"}, + ): {Type: "folder", Relations: append(ResourceRelations, RelationCreate)}, } func GetTypeInfo(group, resource string) (TypeInfo, bool) { diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index c69b0c33d5b..2e413545232 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -50,7 +50,6 @@ const ( var ResourceRelations = []string{ RelationRead, RelationWrite, - RelationCreate, RelationDelete, RelationPermissionsRead, RelationPermissionsWrite, @@ -58,6 +57,7 @@ var ResourceRelations = []string{ var FolderRelations = append( ResourceRelations, + RelationCreate, RelationFolderResourceRead, RelationFolderResourceWrite, RelationFolderResourceCreate, diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga index 932e92181a1..951e1c151d2 100644 --- a/pkg/services/authz/zanzana/schema/schema_resource.fga +++ b/pkg/services/authz/zanzana/schema/schema_resource.fga @@ -20,7 +20,6 @@ type resource define admin: [user with group_filter, team#member with group_filter, role#assignee with group_filter] define read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or view - define create: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit define write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit define delete: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit define permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin diff --git a/pkg/services/authz/zanzana/server/server_batch_check.go b/pkg/services/authz/zanzana/server/server_batch_check.go index 7c96df5ae79..86b78634a56 100644 --- a/pkg/services/authz/zanzana/server/server_batch_check.go +++ b/pkg/services/authz/zanzana/server/server_batch_check.go @@ -60,6 +60,8 @@ func (s *Server) batchCheckItem( if err != nil { return nil, err } + + allowed = res.GetAllowed() groupResourceAccess[groupResource] = res.GetAllowed() } diff --git a/pkg/services/authz/zanzana/server/server_batch_check_test.go b/pkg/services/authz/zanzana/server/server_batch_check_test.go index 08a5644848d..ae8390ba951 100644 --- a/pkg/services/authz/zanzana/server/server_batch_check_test.go +++ b/pkg/services/authz/zanzana/server/server_batch_check_test.go @@ -13,10 +13,10 @@ import ( ) func testBatchCheck(t *testing.T, server *Server) { - newReq := func(subject, group, resource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest { + newReq := func(subject, verb, group, resource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest { for i, item := range items { items[i] = &authzextv1.BatchCheckItem{ - Verb: utils.VerbGet, + Verb: verb, Group: group, Resource: resource, Name: item.GetName(), @@ -32,89 +32,116 @@ func testBatchCheck(t *testing.T, server *Server) { } t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) - res, err := server.BatchCheck(context.Background(), newReq("user:1", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, {Name: "2", Folder: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 2) + require.Len(t, res.Groups[groupResource].Items, 2) - assert.True(t, res.Groups[groupPrefix].Items["1"]) - assert.False(t, res.Groups[groupPrefix].Items["2"]) + assert.True(t, res.Groups[groupResource].Items["1"]) + assert.False(t, res.Groups[groupResource].Items["2"]) }) t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) - res, err := server.BatchCheck(context.Background(), newReq("user:2", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, {Name: "2", Folder: "2"}, })) require.NoError(t, err) - assert.Len(t, res.Groups[groupPrefix].Items, 2) + assert.Len(t, res.Groups[groupResource].Items, 2) }) t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) - res, err := server.BatchCheck(context.Background(), newReq("user:3", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, {Name: "2", Folder: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 2) + require.Len(t, res.Groups[groupResource].Items, 2) - assert.True(t, res.Groups[groupPrefix].Items["1"]) - assert.False(t, res.Groups[groupPrefix].Items["2"]) + assert.True(t, res.Groups[groupResource].Items["1"]) + assert.False(t, res.Groups[groupResource].Items["2"]) }) t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) - res, err := server.BatchCheck(context.Background(), newReq("user:4", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, {Name: "2", Folder: "3"}, {Name: "3", Folder: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 3) + require.Len(t, res.Groups[groupResource].Items, 3) - assert.True(t, res.Groups[groupPrefix].Items["1"]) - assert.True(t, res.Groups[groupPrefix].Items["2"]) - assert.False(t, res.Groups[groupPrefix].Items["3"]) + assert.True(t, res.Groups[groupResource].Items["1"]) + assert.True(t, res.Groups[groupResource].Items["2"]) + assert.False(t, res.Groups[groupResource].Items["3"]) }) t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) - res, err := server.BatchCheck(context.Background(), newReq("user:5", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ {Name: "1", Folder: "1"}, {Name: "2", Folder: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 2) + require.Len(t, res.Groups[groupResource].Items, 2) - assert.True(t, res.Groups[groupPrefix].Items["1"]) - assert.False(t, res.Groups[groupPrefix].Items["2"]) + assert.True(t, res.Groups[groupResource].Items["1"]) + assert.False(t, res.Groups[groupResource].Items["2"]) }) t.Run("user:6 should be able to read folder 1", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(folderGroup, folderResource) - res, err := server.BatchCheck(context.Background(), newReq("user:6", folderGroup, folderResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(folderGroup, folderResource) + res, err := server.BatchCheck(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{ {Name: "1"}, {Name: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 2) + require.Len(t, res.Groups[groupResource].Items, 2) - assert.True(t, res.Groups[groupPrefix].Items["1"]) - assert.False(t, res.Groups[groupPrefix].Items["2"]) + assert.True(t, res.Groups[groupResource].Items["1"]) + assert.False(t, res.Groups[groupResource].Items["2"]) }) t.Run("user:7 should be able to read folder {1,2} through namespace access", func(t *testing.T) { - groupPrefix := zanzana.FormatGroupResource(folderGroup, folderResource) - res, err := server.BatchCheck(context.Background(), newReq("user:7", folderGroup, folderResource, []*authzextv1.BatchCheckItem{ + groupResource := zanzana.FormatGroupResource(folderGroup, folderResource) + res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, []*authzextv1.BatchCheckItem{ {Name: "1"}, {Name: "2"}, })) require.NoError(t, err) - require.Len(t, res.Groups[groupPrefix].Items, 2) + require.Len(t, res.Groups[groupResource].Items, 2) + require.True(t, res.Groups[groupResource].Items["1"]) + require.True(t, res.Groups[groupResource].Items["2"]) + }) + + t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboards in folder 6 through folder 5", func(t *testing.T) { + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "10", Folder: "6"}, + {Name: "20", Folder: "6"}, + })) + require.NoError(t, err) + require.Len(t, res.Groups[groupResource].Items, 2) + require.True(t, res.Groups[groupResource].Items["10"]) + require.True(t, res.Groups[groupResource].Items["20"]) + }) + + t.Run("user:9 should be able to create dashboards in folder 6 through folder 5", func(t *testing.T) { + groupResource := zanzana.FormatGroupResource(dashboardGroup, dashboardResource) + res, err := server.BatchCheck(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{ + {Name: "10", Folder: "6"}, + {Name: "20", Folder: "6"}, + })) + require.NoError(t, err) + t.Log(res.Groups) + require.Len(t, res.Groups[groupResource].Items, 2) + require.True(t, res.Groups[groupResource].Items["10"]) + require.True(t, res.Groups[groupResource].Items["20"]) }) } diff --git a/pkg/services/authz/zanzana/server/server_capabilities.go b/pkg/services/authz/zanzana/server/server_capabilities.go index fa62860e760..4d63841137d 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities.go +++ b/pkg/services/authz/zanzana/server/server_capabilities.go @@ -21,7 +21,7 @@ func (s *Server) Capabilities(ctx context.Context, r *authzextv1.CapabilitiesReq func (s *Server) capabilitiesTyped(ctx context.Context, r *authzextv1.CapabilitiesRequest, info common.TypeInfo, store *storeInfo) (*authzextv1.CapabilitiesResponse, error) { out := make([]string, 0, len(common.ResourceRelations)) - for _, relation := range common.ResourceRelations { + for _, relation := range info.Relations { res, err := s.checkNamespace(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) if err != nil { return nil, err diff --git a/pkg/services/authz/zanzana/server/server_capabilities_test.go b/pkg/services/authz/zanzana/server/server_capabilities_test.go index 4cf7f979ac7..c1d43d8a61a 100644 --- a/pkg/services/authz/zanzana/server/server_capabilities_test.go +++ b/pkg/services/authz/zanzana/server/server_capabilities_test.go @@ -50,7 +50,7 @@ func testCapabilities(t *testing.T, server *Server) { t.Run("user:5 should be able to read, write, create and delete resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) { res, err := server.Capabilities(context.Background(), newReq("user:5", dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) - assert.Equal(t, []string{common.RelationRead, common.RelationWrite, common.RelationCreate, common.RelationDelete}, res.GetCapabilities()) + assert.Equal(t, []string{common.RelationRead, common.RelationWrite, common.RelationDelete}, res.GetCapabilities()) }) t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) { diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index cad15cb17ef..9c4de93eb68 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -83,28 +83,32 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation, name string, func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, resource, name, folder string, store *storeInfo) (*authzv1.CheckResponse, error) { groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource)) - // Check if subject has direct access to resource - res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - TupleKey: &openfgav1.CheckRequestTupleKey{ - User: subject, - Relation: relation, - Object: common.NewResourceIdent(group, resource, name), - }, - Context: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "requested_group": groupResource, + // Create relation can only exist on namespace or folder level. + // So we skip direct resource access check. + if relation != common.RelationCreate { + // Check if subject has direct access to resource + res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ + StoreId: store.ID, + AuthorizationModelId: store.ModelID, + TupleKey: &openfgav1.CheckRequestTupleKey{ + User: subject, + Relation: relation, + Object: common.NewResourceIdent(group, resource, name), }, - }, - }) + Context: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "requested_group": groupResource, + }, + }, + }) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - if res.GetAllowed() { - return &authzv1.CheckResponse{Allowed: true}, nil + if res.GetAllowed() { + return &authzv1.CheckResponse{Allowed: true}, nil + } } if folder == "" { @@ -112,7 +116,7 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation, group, res } // Check if subject has access as a sub resource for the folder - res, err = s.openfga.Check(ctx, &openfgav1.CheckRequest{ + res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, TupleKey: &openfgav1.CheckRequestTupleKey{ diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go index 0dab21d47fb..6d4c6e49a15 100644 --- a/pkg/services/authz/zanzana/server/server_check_test.go +++ b/pkg/services/authz/zanzana/server/server_check_test.go @@ -12,11 +12,11 @@ import ( ) func testCheck(t *testing.T, server *Server) { - newRead := func(subject, group, resource, folder, name string) *authzv1.CheckRequest { + newReq := func(subject, verb, group, resource, folder, name string) *authzv1.CheckRequest { return &authzv1.CheckRequest{ Namespace: namespace, Subject: subject, - Verb: utils.VerbGet, + Verb: verb, Group: group, Resource: resource, Name: name, @@ -25,85 +25,91 @@ func testCheck(t *testing.T, server *Server) { } t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:1", dashboardGroup, dashboardResource, "1", "1")) + res, err := server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) // sanity check - res, err = server.Check(context.Background(), newRead("user:1", dashboardGroup, dashboardResource, "1", "2")) + res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2")) require.NoError(t, err) assert.False(t, res.GetAllowed()) }) t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through namespace", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:2", dashboardGroup, dashboardResource, "1", "1")) + res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) }) t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:3", dashboardGroup, dashboardResource, "1", "1")) + res, err := server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) // sanity check - res, err = server.Check(context.Background(), newRead("user:3", dashboardGroup, dashboardResource, "1", "2")) + res, err = server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2")) require.NoError(t, err) assert.False(t, res.GetAllowed()) }) t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "1", "1")) + res, err := server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) - res, err = server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "3", "2")) + res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "3", "2")) require.NoError(t, err) assert.True(t, res.GetAllowed()) // sanity check - res, err = server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "1", "2")) + res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "1", "2")) require.NoError(t, err) assert.True(t, res.GetAllowed()) - res, err = server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "2", "2")) + res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "2", "2")) require.NoError(t, err) assert.False(t, res.GetAllowed()) }) t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:5", dashboardGroup, dashboardResource, "1", "1")) + res, err := server.Check(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "1", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) }) t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:6", folderGroup, folderResource, "", "1")) + res, err := server.Check(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) }) t.Run("user:7 should be able to read folder one through namespace access", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:7", folderGroup, folderResource, "", "1")) + res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed()) - res, err = server.Check(context.Background(), newRead("user:7", folderGroup, folderResource, "", "10")) + res, err = server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "10")) require.NoError(t, err) assert.True(t, res.GetAllowed()) }) t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboar in folder 6 through folder 5", func(t *testing.T) { - res, err := server.Check(context.Background(), newRead("user:8", dashboardGroup, dashboardResource, "6", "10")) + res, err := server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "6", "10")) require.NoError(t, err) assert.True(t, res.GetAllowed()) - res, err = server.Check(context.Background(), newRead("user:8", dashboardGroup, dashboardResource, "5", "11")) + res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "5", "11")) require.NoError(t, err) assert.True(t, res.GetAllowed()) - res, err = server.Check(context.Background(), newRead("user:8", folderGroup, folderResource, "4", "12")) + res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "4", "12")) require.NoError(t, err) assert.False(t, res.GetAllowed()) }) + + t.Run("user:9 should be able to create dashboards in folder 5", func(t *testing.T) { + res, err := server.Check(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "5", "")) + require.NoError(t, err) + assert.True(t, res.GetAllowed()) + }) } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index cbbe6c955d5..7b5e1709a66 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -92,7 +92,8 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server { common.NewNamespaceResourceTuple("user:7", "read", folderGroup, folderResource), common.NewFolderParentTuple("5", "4"), common.NewFolderParentTuple("6", "5"), - common.NewFolderResourceTuple("user:8", "view", dashboardGroup, dashboardResource, "5"), + common.NewFolderResourceTuple("user:8", "edit", dashboardGroup, dashboardResource, "5"), + common.NewFolderResourceTuple("user:9", "create", dashboardGroup, dashboardResource, "5"), }, }, }) From 722af820a583978b110acbfa28942e90854a754f Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Wed, 27 Nov 2024 09:38:54 +0100 Subject: [PATCH 03/41] Scopes: Use new flag + app_mode=prod in integration tests (#97081) Signed-off-by: bergquist --- pkg/tests/apis/scopes/scopes_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/tests/apis/scopes/scopes_test.go b/pkg/tests/apis/scopes/scopes_test.go index eb380937dff..bf8fd293286 100644 --- a/pkg/tests/apis/scopes/scopes_test.go +++ b/pkg/tests/apis/scopes/scopes_test.go @@ -28,9 +28,9 @@ func TestIntegrationScopes(t *testing.T) { ctx := context.Background() helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs + AppModeProduction: true, EnableFeatureToggles: []string{ - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service + featuremgmt.FlagScopeApi, // Required to register the API }, }) From 6e2d3cae5e3b7b57b488cafd2390fbf8c85040a7 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Wed, 27 Nov 2024 10:35:35 +0100 Subject: [PATCH 04/41] AuthN: Register flags for `grpc_server_authentication` configuration (#97063) * AuthZServer: Add authenticator * Add flags --- pkg/services/authn/grpcutils/config.go | 8 ++++++++ pkg/services/authn/grpcutils/grpc_authenticator.go | 10 +++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/services/authn/grpcutils/config.go b/pkg/services/authn/grpcutils/config.go index 91929a0d1c5..8ffacacd134 100644 --- a/pkg/services/authn/grpcutils/config.go +++ b/pkg/services/authn/grpcutils/config.go @@ -3,6 +3,8 @@ package grpcutils import ( "fmt" + "github.com/spf13/pflag" + "github.com/grafana/grafana/pkg/setting" ) @@ -26,6 +28,11 @@ type GrpcServerConfig struct { AllowedAudiences []string Mode Mode LegacyFallback bool + AllowInsecure bool +} + +func (c *GrpcServerConfig) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&c.SigningKeysURL, "grpc-server-authentication.signing-keys-url", "", "gRPC server authentication signing keys URL") } func ReadGrpcServerConfig(cfg *setting.Cfg) (*GrpcServerConfig, error) { @@ -41,6 +48,7 @@ func ReadGrpcServerConfig(cfg *setting.Cfg) (*GrpcServerConfig, error) { AllowedAudiences: section.Key("allowed_audiences").Strings(","), Mode: mode, LegacyFallback: section.Key("legacy_fallback").MustBool(true), + AllowInsecure: cfg.Env == setting.Dev, }, nil } diff --git a/pkg/services/authn/grpcutils/grpc_authenticator.go b/pkg/services/authn/grpcutils/grpc_authenticator.go index 8b4086f1205..8014a62beac 100644 --- a/pkg/services/authn/grpcutils/grpc_authenticator.go +++ b/pkg/services/authn/grpcutils/grpc_authenticator.go @@ -27,11 +27,7 @@ func NewInProcGrpcAuthenticator() *authnlib.GrpcAuthenticator { ) } -func NewGrpcAuthenticator(cfg *setting.Cfg, tracer tracing.Tracer) (*authnlib.GrpcAuthenticator, error) { - authCfg, err := ReadGrpcServerConfig(cfg) - if err != nil { - return nil, err - } +func NewGrpcAuthenticator(authCfg *GrpcServerConfig, tracer tracing.Tracer) (*authnlib.GrpcAuthenticator, error) { grpcAuthCfg := authnlib.GrpcAuthenticatorConfig{ KeyRetrieverConfig: authnlib.KeyRetrieverConfig{ SigningKeysURL: authCfg.SigningKeysURL, @@ -42,7 +38,7 @@ func NewGrpcAuthenticator(cfg *setting.Cfg, tracer tracing.Tracer) (*authnlib.Gr } client := http.DefaultClient - if cfg.Env == setting.Dev { + if authCfg.AllowInsecure { // allow insecure connections in development mode to facilitate testing client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} } @@ -87,7 +83,7 @@ func NewGrpcAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registere return nil, err } - authenticator, err := NewGrpcAuthenticator(cfg, tracer) + authenticator, err := NewGrpcAuthenticator(authCfg, tracer) if err != nil { return nil, err } From 84b8296ffbedc325bef85f8c520f3f9afe8a53ac Mon Sep 17 00:00:00 2001 From: Misi Date: Wed, 27 Nov 2024 11:06:39 +0100 Subject: [PATCH 05/41] OAuth: Use the attached external session data in OAuthToken and OAuthTokenSync (#96655) * wip * wip + tests * wip * wip opt2 * Use authn.Identity struct's SessionToken * Merge fixes * Handle disabling the feature flag correctly * Fix test * Cleanup * Remove HasOAuthEntry from the OAuthTokenService interface * Remove unused function --- pkg/api/pluginproxy/ds_proxy.go | 7 +- pkg/api/pluginproxy/ds_proxy_test.go | 5 +- pkg/services/auth/auth.go | 5 +- pkg/services/auth/authimpl/auth_token.go | 8 +- .../auth/authimpl/external_session_store.go | 43 +- .../auth/authtest/auth_token_service_mock.go | 384 +++++++++++++++++ .../authtest/external_session_store_mock.go | 18 + pkg/services/auth/authtest/testing.go | 17 +- pkg/services/auth/external_session.go | 8 + pkg/services/authn/authn.go | 2 +- pkg/services/authn/authnimpl/registration.go | 6 +- pkg/services/authn/authnimpl/service.go | 2 +- pkg/services/authn/authnimpl/service_test.go | 2 +- .../authn/authnimpl/sync/oauth_token_sync.go | 13 +- .../authnimpl/sync/oauth_token_sync_test.go | 11 +- .../authn/authnimpl/sync/user_sync.go | 30 +- .../authn/authnimpl/sync/user_sync_test.go | 7 +- pkg/services/authn/authntest/mock.go | 6 +- pkg/services/authn/clients/oauth.go | 7 +- pkg/services/authn/clients/oauth_test.go | 7 +- pkg/services/login/authinfoimpl/store.go | 8 +- pkg/services/oauthtoken/oauth_token.go | 223 +++++++--- pkg/services/oauthtoken/oauth_token_test.go | 390 +++++++++++++----- .../oauthtoken/oauthtokentest/mock.go | 28 +- .../oauthtokentest/oauthtokentest.go | 12 +- .../oauthtoken/oauthtokentest/service_mock.go | 91 ++-- .../clientmiddleware/oauthtoken_middleware.go | 2 +- 27 files changed, 1032 insertions(+), 310 deletions(-) create mode 100644 pkg/services/auth/authtest/auth_token_service_mock.go diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 84ad65d5a73..1fac3d64fb6 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -19,6 +19,7 @@ import ( glog "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/contexthandler" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -57,7 +58,8 @@ type httpClient interface { func NewDataSourceProxy(ds *datasources.DataSource, pluginRoutes []*plugins.Route, ctx *contextmodel.ReqContext, proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider, oAuthTokenService oauthtoken.OAuthTokenService, dsService datasources.DataSourceService, - tracer tracing.Tracer, features featuremgmt.FeatureToggles) (*DataSourceProxy, error) { + tracer tracing.Tracer, features featuremgmt.FeatureToggles, +) (*DataSourceProxy, error) { targetURL, err := datasource.ValidateURL(ds.Type, ds.URL) if err != nil { return nil, err @@ -261,7 +263,8 @@ func (proxy *DataSourceProxy) director(req *http.Request) { } if proxy.oAuthTokenService.IsOAuthPassThruEnabled(proxy.ds) { - if token := proxy.oAuthTokenService.GetCurrentOAuthToken(req.Context(), proxy.ctx.SignedInUser); token != nil { + reqCtx := contexthandler.FromContext(req.Context()) + if token := proxy.oAuthTokenService.GetCurrentOAuthToken(req.Context(), proxy.ctx.SignedInUser, reqCtx.UserToken); token != nil { req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken)) idToken, ok := token.Extra("id_token").(string) diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index 7ba804a41be..40340bac41a 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -32,7 +32,9 @@ import ( pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authz/zanzana" + "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service" @@ -557,7 +559,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { var routes []*plugins.Route proxy, err := setupDSProxyTest(t, ctx, ds, routes, "/path/to/folder/", func(proxy *DataSourceProxy) { proxy.oAuthTokenService = &oauthtokentest.MockOauthTokenService{ - GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester) *oauth2.Token { + GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester, _ *auth.UserToken) *oauth2.Token { return (&oauth2.Token{ AccessToken: "testtoken", RefreshToken: "testrefreshtoken", @@ -573,6 +575,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { require.NoError(t, err) req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) + req = req.WithContext(context.WithValue(req.Context(), ctxkey.Key{}, &contextmodel.ReqContext{UserToken: nil})) require.NoError(t, err) proxy.director(req) diff --git a/pkg/services/auth/auth.go b/pkg/services/auth/auth.go index 88ff4d06157..cc678914b31 100644 --- a/pkg/services/auth/auth.go +++ b/pkg/services/auth/auth.go @@ -74,12 +74,15 @@ type CreateTokenCommand struct { } // UserTokenService are used for generating and validating user tokens +// +//go:generate mockery --name UserTokenService --structname MockUserAuthTokenService --outpkg authtest --filename auth_token_service_mock.go --output ./authtest/ type UserTokenService interface { CreateToken(ctx context.Context, cmd *CreateTokenCommand) (*UserToken, error) LookupToken(ctx context.Context, unhashedToken string) (*UserToken, error) GetTokenByExternalSessionID(ctx context.Context, externalSessionID int64) (*UserToken, error) - GetExternalSession(ctx context.Context, extSessionID int64) (*ExternalSession, error) + GetExternalSession(ctx context.Context, externalSessionID int64) (*ExternalSession, error) FindExternalSessions(ctx context.Context, query *ListExternalSessionQuery) ([]*ExternalSession, error) + UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *UpdateExternalSessionCommand) error // RotateToken will always rotate a valid token RotateToken(ctx context.Context, cmd RotateCommand) (*UserToken, error) diff --git a/pkg/services/auth/authimpl/auth_token.go b/pkg/services/auth/authimpl/auth_token.go index a42784d2670..837941eaf00 100644 --- a/pkg/services/auth/authimpl/auth_token.go +++ b/pkg/services/auth/authimpl/auth_token.go @@ -248,14 +248,18 @@ func (s *UserAuthTokenService) GetTokenByExternalSessionID(ctx context.Context, return &userToken, err } -func (s *UserAuthTokenService) GetExternalSession(ctx context.Context, extSessionID int64) (*auth.ExternalSession, error) { - return s.externalSessionStore.Get(ctx, extSessionID) +func (s *UserAuthTokenService) GetExternalSession(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error) { + return s.externalSessionStore.Get(ctx, externalSessionID) } func (s *UserAuthTokenService) FindExternalSessions(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) { return s.externalSessionStore.List(ctx, query) } +func (s *UserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error { + return s.externalSessionStore.Update(ctx, externalSessionID, cmd) +} + func (s *UserAuthTokenService) RotateToken(ctx context.Context, cmd auth.RotateCommand) (*auth.UserToken, error) { if cmd.UnHashedToken == "" { return nil, auth.ErrInvalidSessionToken diff --git a/pkg/services/auth/authimpl/external_session_store.go b/pkg/services/auth/authimpl/external_session_store.go index 544386673b8..95e379b79b6 100644 --- a/pkg/services/auth/authimpl/external_session_store.go +++ b/pkg/services/auth/authimpl/external_session_store.go @@ -27,11 +27,11 @@ func provideExternalSessionStore(sqlStore db.DB, secretService secrets.Service, } } -func (s *store) Get(ctx context.Context, extSessionID int64) (*auth.ExternalSession, error) { +func (s *store) Get(ctx context.Context, ID int64) (*auth.ExternalSession, error) { ctx, span := s.tracer.Start(ctx, "externalsession.Get") defer span.End() - externalSession := &auth.ExternalSession{ID: extSessionID} + externalSession := &auth.ExternalSession{ID: ID} err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { found, err := sess.Get(externalSession) @@ -150,6 +150,45 @@ func (s *store) Create(ctx context.Context, extSession *auth.ExternalSession) er return nil } +func (s *store) Update(ctx context.Context, ID int64, cmd *auth.UpdateExternalSessionCommand) error { + ctx, span := s.tracer.Start(ctx, "externalsession.Update") + defer span.End() + + var err error + externalSession := &auth.ExternalSession{} + + externalSession.AccessToken, err = s.encryptAndEncode(cmd.Token.AccessToken) + if err != nil { + return err + } + + externalSession.RefreshToken, err = s.encryptAndEncode(cmd.Token.RefreshToken) + if err != nil { + return err + } + + var secretIdToken string + if idToken, ok := cmd.Token.Extra("id_token").(string); ok && idToken != "" { + secretIdToken, err = s.encryptAndEncode(idToken) + if err != nil { + return err + } + externalSession.IDToken = secretIdToken + } + + externalSession.ExpiresAt = cmd.Token.Expiry + + err = s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + _, err := sess.ID(ID).Cols("access_token", "refresh_token", "id_token", "expires_at").Update(externalSession) + return err + }) + if err != nil { + return err + } + + return nil +} + func (s *store) Delete(ctx context.Context, ID int64) error { ctx, span := s.tracer.Start(ctx, "externalsession.Delete") defer span.End() diff --git a/pkg/services/auth/authtest/auth_token_service_mock.go b/pkg/services/auth/authtest/auth_token_service_mock.go new file mode 100644 index 00000000000..744ef1fe0b0 --- /dev/null +++ b/pkg/services/auth/authtest/auth_token_service_mock.go @@ -0,0 +1,384 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package authtest + +import ( + context "context" + + auth "github.com/grafana/grafana/pkg/services/auth" + + mock "github.com/stretchr/testify/mock" + + usertoken "github.com/grafana/grafana/pkg/models/usertoken" +) + +// MockUserAuthTokenService is an autogenerated mock type for the UserTokenService type +type MockUserAuthTokenService struct { + mock.Mock +} + +// ActiveTokenCount provides a mock function with given fields: ctx, userID +func (_m *MockUserAuthTokenService) ActiveTokenCount(ctx context.Context, userID *int64) (int64, error) { + ret := _m.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for ActiveTokenCount") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *int64) (int64, error)); ok { + return rf(ctx, userID) + } + if rf, ok := ret.Get(0).(func(context.Context, *int64) int64); ok { + r0 = rf(ctx, userID) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, userID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateToken provides a mock function with given fields: ctx, cmd +func (_m *MockUserAuthTokenService) CreateToken(ctx context.Context, cmd *auth.CreateTokenCommand) (*usertoken.UserToken, error) { + ret := _m.Called(ctx, cmd) + + if len(ret) == 0 { + panic("no return value specified for CreateToken") + } + + var r0 *usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *auth.CreateTokenCommand) (*usertoken.UserToken, error)); ok { + return rf(ctx, cmd) + } + if rf, ok := ret.Get(0).(func(context.Context, *auth.CreateTokenCommand) *usertoken.UserToken); ok { + r0 = rf(ctx, cmd) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *auth.CreateTokenCommand) error); ok { + r1 = rf(ctx, cmd) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindExternalSessions provides a mock function with given fields: ctx, query +func (_m *MockUserAuthTokenService) FindExternalSessions(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) { + ret := _m.Called(ctx, query) + + if len(ret) == 0 { + panic("no return value specified for FindExternalSessions") + } + + var r0 []*auth.ExternalSession + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error)); ok { + return rf(ctx, query) + } + if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) []*auth.ExternalSession); ok { + r0 = rf(ctx, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*auth.ExternalSession) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *auth.ListExternalSessionQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExternalSession provides a mock function with given fields: ctx, externalSessionID +func (_m *MockUserAuthTokenService) GetExternalSession(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error) { + ret := _m.Called(ctx, externalSessionID) + + if len(ret) == 0 { + panic("no return value specified for GetExternalSession") + } + + var r0 *auth.ExternalSession + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (*auth.ExternalSession, error)); ok { + return rf(ctx, externalSessionID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) *auth.ExternalSession); ok { + r0 = rf(ctx, externalSessionID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*auth.ExternalSession) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, externalSessionID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTokenByExternalSessionID provides a mock function with given fields: ctx, externalSessionID +func (_m *MockUserAuthTokenService) GetTokenByExternalSessionID(ctx context.Context, externalSessionID int64) (*usertoken.UserToken, error) { + ret := _m.Called(ctx, externalSessionID) + + if len(ret) == 0 { + panic("no return value specified for GetTokenByExternalSessionID") + } + + var r0 *usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (*usertoken.UserToken, error)); ok { + return rf(ctx, externalSessionID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) *usertoken.UserToken); ok { + r0 = rf(ctx, externalSessionID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, externalSessionID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserRevokedTokens provides a mock function with given fields: ctx, userID +func (_m *MockUserAuthTokenService) GetUserRevokedTokens(ctx context.Context, userID int64) ([]*usertoken.UserToken, error) { + ret := _m.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for GetUserRevokedTokens") + } + + var r0 []*usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*usertoken.UserToken, error)); ok { + return rf(ctx, userID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) []*usertoken.UserToken); ok { + r0 = rf(ctx, userID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, userID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserToken provides a mock function with given fields: ctx, userID, userTokenID +func (_m *MockUserAuthTokenService) GetUserToken(ctx context.Context, userID int64, userTokenID int64) (*usertoken.UserToken, error) { + ret := _m.Called(ctx, userID, userTokenID) + + if len(ret) == 0 { + panic("no return value specified for GetUserToken") + } + + var r0 *usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (*usertoken.UserToken, error)); ok { + return rf(ctx, userID, userTokenID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *usertoken.UserToken); ok { + r0 = rf(ctx, userID, userTokenID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { + r1 = rf(ctx, userID, userTokenID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserTokens provides a mock function with given fields: ctx, userID +func (_m *MockUserAuthTokenService) GetUserTokens(ctx context.Context, userID int64) ([]*usertoken.UserToken, error) { + ret := _m.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for GetUserTokens") + } + + var r0 []*usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*usertoken.UserToken, error)); ok { + return rf(ctx, userID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) []*usertoken.UserToken); ok { + r0 = rf(ctx, userID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, userID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LookupToken provides a mock function with given fields: ctx, unhashedToken +func (_m *MockUserAuthTokenService) LookupToken(ctx context.Context, unhashedToken string) (*usertoken.UserToken, error) { + ret := _m.Called(ctx, unhashedToken) + + if len(ret) == 0 { + panic("no return value specified for LookupToken") + } + + var r0 *usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*usertoken.UserToken, error)); ok { + return rf(ctx, unhashedToken) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *usertoken.UserToken); ok { + r0 = rf(ctx, unhashedToken) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, unhashedToken) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RevokeAllUserTokens provides a mock function with given fields: ctx, userID +func (_m *MockUserAuthTokenService) RevokeAllUserTokens(ctx context.Context, userID int64) error { + ret := _m.Called(ctx, userID) + + if len(ret) == 0 { + panic("no return value specified for RevokeAllUserTokens") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, userID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RevokeToken provides a mock function with given fields: ctx, token, soft +func (_m *MockUserAuthTokenService) RevokeToken(ctx context.Context, token *usertoken.UserToken, soft bool) error { + ret := _m.Called(ctx, token, soft) + + if len(ret) == 0 { + panic("no return value specified for RevokeToken") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *usertoken.UserToken, bool) error); ok { + r0 = rf(ctx, token, soft) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RotateToken provides a mock function with given fields: ctx, cmd +func (_m *MockUserAuthTokenService) RotateToken(ctx context.Context, cmd auth.RotateCommand) (*usertoken.UserToken, error) { + ret := _m.Called(ctx, cmd) + + if len(ret) == 0 { + panic("no return value specified for RotateToken") + } + + var r0 *usertoken.UserToken + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, auth.RotateCommand) (*usertoken.UserToken, error)); ok { + return rf(ctx, cmd) + } + if rf, ok := ret.Get(0).(func(context.Context, auth.RotateCommand) *usertoken.UserToken); ok { + r0 = rf(ctx, cmd) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*usertoken.UserToken) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, auth.RotateCommand) error); ok { + r1 = rf(ctx, cmd) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateExternalSession provides a mock function with given fields: ctx, externalSessionID, cmd +func (_m *MockUserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error { + ret := _m.Called(ctx, externalSessionID, cmd) + + if len(ret) == 0 { + panic("no return value specified for UpdateExternalSession") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, *auth.UpdateExternalSessionCommand) error); ok { + r0 = rf(ctx, externalSessionID, cmd) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockUserAuthTokenService creates a new instance of MockUserAuthTokenService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockUserAuthTokenService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockUserAuthTokenService { + mock := &MockUserAuthTokenService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/auth/authtest/external_session_store_mock.go b/pkg/services/auth/authtest/external_session_store_mock.go index 17694b08b3d..10894e67444 100644 --- a/pkg/services/auth/authtest/external_session_store_mock.go +++ b/pkg/services/auth/authtest/external_session_store_mock.go @@ -147,6 +147,24 @@ func (_m *MockExternalSessionStore) List(ctx context.Context, query *auth.ListEx return r0, r1 } +// Update provides a mock function with given fields: ctx, ID, cmd +func (_m *MockExternalSessionStore) Update(ctx context.Context, ID int64, cmd *auth.UpdateExternalSessionCommand) error { + ret := _m.Called(ctx, ID, cmd) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, *auth.UpdateExternalSessionCommand) error); ok { + r0 = rf(ctx, ID, cmd) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // NewMockExternalSessionStore creates a new instance of MockExternalSessionStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockExternalSessionStore(t interface { diff --git a/pkg/services/auth/authtest/testing.go b/pkg/services/auth/authtest/testing.go index b63839a139e..bbac7116910 100644 --- a/pkg/services/auth/authtest/testing.go +++ b/pkg/services/auth/authtest/testing.go @@ -14,12 +14,15 @@ import ( "github.com/grafana/grafana/pkg/services/login" ) +var _ auth.UserTokenService = (*FakeUserAuthTokenService)(nil) + type FakeUserAuthTokenService struct { CreateTokenProvider func(ctx context.Context, cmd *auth.CreateTokenCommand) (*auth.UserToken, error) RotateTokenProvider func(ctx context.Context, cmd auth.RotateCommand) (*auth.UserToken, error) GetTokenByExternalSessionIDProvider func(ctx context.Context, externalSessionID int64) (*auth.UserToken, error) GetExternalSessionProvider func(ctx context.Context, externalSessionID int64) (*auth.ExternalSession, error) FindExternalSessionsProvider func(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) + UpdateExternalSessionProvider func(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error TryRotateTokenProvider func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, *auth.UserToken, error) LookupTokenProvider func(ctx context.Context, unhashedToken string) (*auth.UserToken, error) RevokeTokenProvider func(ctx context.Context, token *auth.UserToken, soft bool) error @@ -98,6 +101,10 @@ func (s *FakeUserAuthTokenService) FindExternalSessions(ctx context.Context, que return s.FindExternalSessionsProvider(context.Background(), query) } +func (s *FakeUserAuthTokenService) UpdateExternalSession(ctx context.Context, externalSessionID int64, cmd *auth.UpdateExternalSessionCommand) error { + return s.UpdateExternalSessionProvider(context.Background(), externalSessionID, cmd) +} + func (s *FakeUserAuthTokenService) LookupToken(ctx context.Context, unhashedToken string) (*auth.UserToken, error) { return s.LookupTokenProvider(context.Background(), unhashedToken) } @@ -149,16 +156,6 @@ func (ts *FakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource) return ts.passThruEnabled } -func (ts *FakeOAuthTokenService) HasOAuthEntry(context.Context, identity.Requester) (*login.UserAuth, bool, error) { - if ts.ExpectedAuthUser != nil { - return ts.ExpectedAuthUser, true, nil - } - if error, ok := ts.ExpectedErrors["HasOAuthEntry"]; ok { - return nil, false, error - } - return nil, false, nil -} - func (ts *FakeOAuthTokenService) InvalidateOAuthTokens(ctx context.Context, usr *login.UserAuth) error { ts.ExpectedAuthUser.OAuthAccessToken = "" ts.ExpectedAuthUser.OAuthRefreshToken = "" diff --git a/pkg/services/auth/external_session.go b/pkg/services/auth/external_session.go index d49db871a10..e67b5a10259 100644 --- a/pkg/services/auth/external_session.go +++ b/pkg/services/auth/external_session.go @@ -3,6 +3,8 @@ package auth import ( "context" "time" + + "golang.org/x/oauth2" ) type ExternalSession struct { @@ -43,6 +45,10 @@ func (e *ExternalSession) Clone() *ExternalSession { } } +type UpdateExternalSessionCommand struct { + Token *oauth2.Token +} + type ListExternalSessionQuery struct { ID int64 NameID string @@ -57,6 +63,8 @@ type ExternalSessionStore interface { List(ctx context.Context, query *ListExternalSessionQuery) ([]*ExternalSession, error) // Create creates a new external session for a user Create(ctx context.Context, extSesion *ExternalSession) error + // Update updates an external session + Update(ctx context.Context, ID int64, cmd *UpdateExternalSessionCommand) error // Delete deletes an external session Delete(ctx context.Context, ID int64) error // DeleteExternalSessionsByUserID deletes an external session diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 5dcb9d45f5f..07d019e528e 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -178,7 +178,7 @@ type RedirectClient interface { // that should happen during logout and supports client specific redirect URL. type LogoutClient interface { Client - Logout(ctx context.Context, user identity.Requester) (*Redirect, bool) + Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, bool) } type SSOSettingsAwareClient interface { diff --git a/pkg/services/authn/authnimpl/registration.go b/pkg/services/authn/authnimpl/registration.go index ea47102809c..b73c051c2a1 100644 --- a/pkg/services/authn/authnimpl/registration.go +++ b/pkg/services/authn/authnimpl/registration.go @@ -37,7 +37,7 @@ func ProvideRegistration( jwtService auth.JWTVerifierService, userProtectionService login.UserProtectionService, loginAttempts loginattempt.Service, quotaService quota.Service, authInfoService login.AuthInfoService, renderService rendering.Service, - features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService, + features featuremgmt.FeatureToggles, oauthTokenService oauthtoken.OAuthTokenService, socialService social.Service, cache *remotecache.RemoteCache, ldapService service.LDAP, settingsProviderService setting.Provider, tracer tracing.Tracer, tempUserService tempuser.Service, notificationService notifications.Service, @@ -108,13 +108,13 @@ func ProvideRegistration( } // FIXME (jguer): move to User package - userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer) + userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer, features) orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg, tracer) authnSvc.RegisterPostAuthHook(userSync.SyncUserHook, 10) authnSvc.RegisterPostAuthHook(userSync.EnableUserHook, 20) authnSvc.RegisterPostAuthHook(orgSync.SyncOrgRolesHook, 30) authnSvc.RegisterPostAuthHook(userSync.SyncLastSeenHook, 130) - authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer).SyncOauthTokenHook, 60) + authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer, features).SyncOauthTokenHook, 60) authnSvc.RegisterPostAuthHook(userSync.FetchSyncedUserHook, 100) rbacSync := sync.ProvideRBACSync(accessControlService, tracer, permRegistry) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 4313cbfcc5d..b329347e113 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -322,7 +322,7 @@ func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionTo goto Default } - clientRedirect, ok := logoutClient.Logout(ctx, user) + clientRedirect, ok := logoutClient.Logout(ctx, user, sessionToken) if !ok { goto Default } diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index 50b03646abc..fe3b6ab30b3 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -507,7 +507,7 @@ func TestService_Logout(t *testing.T) { expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"}, client: &authntest.MockClient{ NameFunc: func() string { return "auth.client.azuread" }, - LogoutFunc: func(ctx context.Context, _ identity.Requester) (*authn.Redirect, bool) { + LogoutFunc: func(ctx context.Context, _ identity.Requester, sessionToken *usertoken.UserToken) (*authn.Redirect, bool) { return &authn.Redirect{URL: "http://idp.com/logout"}, true }, }, diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go index 03fcff2d286..ec6f8e926b6 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go @@ -17,12 +17,15 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/oauthtoken" ) const maxOAuthTokenCacheTTL = 5 * time.Minute -func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service, tracer tracing.Tracer) *OAuthTokenSync { +func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service, tracer tracing.Tracer, + features featuremgmt.FeatureToggles, +) *OAuthTokenSync { return &OAuthTokenSync{ log.New("oauth_token.sync"), service, @@ -31,6 +34,7 @@ func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService new(singleflight.Group), tracer, localcache.New(maxOAuthTokenCacheTTL, 15*time.Minute), + features, } } @@ -42,6 +46,7 @@ type OAuthTokenSync struct { singleflightGroup *singleflight.Group tracer tracing.Tracer cache *localcache.CacheService + features featuremgmt.FeatureToggles } func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error { @@ -72,6 +77,10 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Ident ctxLogger := s.log.FromContext(ctx).New("userID", userID) cacheKey := fmt.Sprintf("token-check-%s", id.GetID()) + if s.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + cacheKey = fmt.Sprintf("token-check-%s-%d", id.GetID(), id.SessionToken.Id) + } + if _, ok := s.cache.Get(cacheKey); ok { ctxLogger.Debug("Expiration check has been cached, no need to refresh") return nil @@ -83,7 +92,7 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Ident updateCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 15*time.Second) defer cancel() - token, refreshErr := s.service.TryTokenRefresh(updateCtx, id) + token, refreshErr := s.service.TryTokenRefresh(updateCtx, id, id.SessionToken) if refreshErr != nil { if errors.Is(refreshErr, context.Canceled) { return nil, nil diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go index e62aaa65667..c0148399d5f 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go @@ -20,6 +20,9 @@ import ( "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/oauthtoken/oauthtokentest" ) @@ -85,7 +88,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { ) service := &oauthtokentest.MockOauthTokenService{ - TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) (*oauth2.Token, error) { + TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester, _ *auth.UserToken) (*oauth2.Token, error) { tryRefreshCalled = true return nil, tt.expectedTryRefreshErr }, @@ -116,9 +119,13 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { singleflightGroup: new(singleflight.Group), tracer: tracing.InitializeTracerForTest(), cache: localcache.New(maxOAuthTokenCacheTTL, 15*time.Minute), + features: featuremgmt.WithFeatures(), } - err := sync.SyncOauthTokenHook(context.Background(), tt.identity, nil) + ctx := context.Background() + reqCtx := context.WithValue(ctx, ctxkey.Key{}, &contextmodel.ReqContext{UserToken: nil}) + + err := sync.SyncOauthTokenHook(reqCtx, tt.identity, nil) assert.ErrorIs(t, err, tt.expectedErr) assert.Equal(t, tt.expectTryRefreshTokenCalled, tryRefreshCalled) assert.Equal(t, tt.expectRevokeTokenCalled, revokeTokenCalled) diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index fa682806ea3..231356485b8 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" @@ -51,7 +52,9 @@ var ( errSignupNotAllowed = errors.New("system administrator has disabled signup") ) -func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService, quotaService quota.Service, tracer tracing.Tracer) *UserSync { +func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService, + quotaService quota.Service, tracer tracing.Tracer, features featuremgmt.FeatureToggles, +) *UserSync { return &UserSync{ userService: userService, authInfoService: authInfoService, @@ -59,6 +62,7 @@ func ProvideUserSync(userService user.Service, userProtectionService login.UserP quotaService: quotaService, log: log.New("user.sync"), tracer: tracer, + features: features, } } @@ -69,6 +73,7 @@ type UserSync struct { quotaService quota.Service log log.Logger tracer tracing.Tracer + features featuremgmt.FeatureToggles } // SyncUserHook syncs a user with the database @@ -223,21 +228,30 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident // This can happen when: using multiple auth client where the same user exists in several or // changing to new auth client if createConnection { - return s.authInfoService.SetAuthInfo(ctx, &login.SetAuthInfoCommand{ + setAuthInfoCmd := &login.SetAuthInfoCommand{ UserId: userID, AuthModule: identity.AuthenticatedBy, AuthId: identity.AuthID, - OAuthToken: identity.OAuthToken, - }) + } + + if !s.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + setAuthInfoCmd.OAuthToken = identity.OAuthToken + } + return s.authInfoService.SetAuthInfo(ctx, setAuthInfoCmd) } - s.log.FromContext(ctx).Debug("Updating auth connection for user", "id", identity.ID) - return s.authInfoService.UpdateAuthInfo(ctx, &login.UpdateAuthInfoCommand{ + updateAuthInfoCmd := &login.UpdateAuthInfoCommand{ UserId: userID, AuthId: identity.AuthID, AuthModule: identity.AuthenticatedBy, - OAuthToken: identity.OAuthToken, - }) + } + + if !s.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + updateAuthInfoCmd.OAuthToken = identity.OAuthToken + } + + s.log.FromContext(ctx).Debug("Updating auth connection for user", "id", identity.ID) + return s.authInfoService.UpdateAuthInfo(ctx, updateAuthInfoCmd) } func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id *authn.Identity, userAuth *login.UserAuth) error { diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index a03729420ab..36247ad1ea3 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/login/authinfotest" @@ -45,7 +46,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { AuthModule: "oauth", AuthId: "2032", UserId: 1, - Id: 1}} + Id: 1, + }, + } userService := &usertest.FakeUserService{ExpectedUser: &user.User{ ID: 1, @@ -434,7 +437,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService, tracing.InitializeTracerForTest()) + s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService, tracing.InitializeTracerForTest(), featuremgmt.WithFeatures()) err := s.SyncUserHook(tt.args.ctx, tt.args.id, nil) if tt.wantErr { require.Error(t, err) diff --git a/pkg/services/authn/authntest/mock.go b/pkg/services/authn/authntest/mock.go index e586d2ac8de..8c90857fa6a 100644 --- a/pkg/services/authn/authntest/mock.go +++ b/pkg/services/authn/authntest/mock.go @@ -85,7 +85,7 @@ type MockClient struct { TestFunc func(ctx context.Context, r *authn.Request) bool PriorityFunc func() uint HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error - LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) + LogoutFunc func(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*authn.Redirect, bool) IdentityTypeFunc func() claims.IdentityType ResolveIdentityFunc func(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) } @@ -133,9 +133,9 @@ func (m MockClient) Hook(ctx context.Context, identity *authn.Identity, r *authn return nil } -func (m *MockClient) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) { +func (m *MockClient) Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*authn.Redirect, bool) { if m.LogoutFunc != nil { - return m.LogoutFunc(ctx, user) + return m.LogoutFunc(ctx, user, sessionToken) } return nil, false } diff --git a/pkg/services/authn/clients/oauth.go b/pkg/services/authn/clients/oauth.go index 47601d4b548..ed469ba9f17 100644 --- a/pkg/services/authn/clients/oauth.go +++ b/pkg/services/authn/clients/oauth.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/connectors" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" @@ -257,8 +258,8 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir }, nil } -func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) { - token := c.oauthService.GetCurrentOAuthToken(ctx, user) +func (c *OAuth) Logout(ctx context.Context, user identity.Requester, sessionToken *auth.UserToken) (*authn.Redirect, bool) { + token := c.oauthService.GetCurrentOAuthToken(ctx, user, sessionToken) userID, err := identity.UserIdentifier(user.GetID()) if err != nil { @@ -268,7 +269,7 @@ func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Red ctxLogger := c.log.FromContext(ctx).New("userID", userID) - if err := c.oauthService.InvalidateOAuthTokens(ctx, user); err != nil { + if err := c.oauthService.InvalidateOAuthTokens(ctx, user, sessionToken); err != nil { ctxLogger.Error("Failed to invalidate tokens", "error", err) } diff --git a/pkg/services/authn/clients/oauth_test.go b/pkg/services/authn/clients/oauth_test.go index 288dc31db91..95595f1e5a6 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" @@ -469,7 +470,7 @@ func TestOAuth_Logout(t *testing.T) { ) mockService := &oauthtokentest.MockOauthTokenService{ - GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester) *oauth2.Token { + GetCurrentOauthTokenFunc: func(_ context.Context, _ identity.Requester, _ *auth.UserToken) *oauth2.Token { getTokenCalled = true token := &oauth2.Token{ AccessToken: "some.access.token", @@ -479,7 +480,7 @@ func TestOAuth_Logout(t *testing.T) { "id_token": "some.id.token", }) }, - InvalidateOAuthTokensFunc: func(_ context.Context, _ identity.Requester) error { + InvalidateOAuthTokensFunc: func(_ context.Context, _ identity.Requester, _ *auth.UserToken) error { invalidateTokenCalled = true return nil }, @@ -490,7 +491,7 @@ func TestOAuth_Logout(t *testing.T) { } c := ProvideOAuth(authn.ClientWithPrefix("azuread"), tt.cfg, mockService, fakeSocialSvc, &setting.OSSImpl{Cfg: tt.cfg}, featuremgmt.WithFeatures()) - redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}) + redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}, nil) assert.Equal(t, tt.expectedOK, ok) if tt.expectedOK { diff --git a/pkg/services/login/authinfoimpl/store.go b/pkg/services/login/authinfoimpl/store.go index 7c1ffad017b..b5a90fc2a8a 100644 --- a/pkg/services/login/authinfoimpl/store.go +++ b/pkg/services/login/authinfoimpl/store.go @@ -92,7 +92,6 @@ func (s *Store) GetUserLabels(ctx context.Context, query login.GetUserLabelsQuer err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { return sess.Table("user_auth").In("user_id", params).OrderBy("created").Find(&userAuths) }) - if err != nil { return nil, err } @@ -187,7 +186,9 @@ func (s *Store) UpdateAuthInfo(ctx context.Context, cmd *login.UpdateAuthInfoCom } return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - upd, err := sess.MustCols("o_auth_expiry").Where("user_id = ? AND auth_module = ?", cmd.UserId, cmd.AuthModule).Update(authUser) + upd, err := sess.MustCols("o_auth_expiry", "o_auth_access_token", "o_auth_refresh_token", "o_auth_id_token", "o_auth_token_type"). + Where("user_id = ? AND auth_module = ?", cmd.UserId, cmd.AuthModule). + Update(authUser) s.logger.Debug("Updated user_auth", "user_id", cmd.UserId, "auth_id", cmd.AuthId, "auth_module", cmd.AuthModule, "rows", upd) @@ -198,7 +199,6 @@ func (s *Store) UpdateAuthInfo(ctx context.Context, cmd *login.UpdateAuthInfoCom "SELECT id FROM user_auth WHERE user_id = ? AND auth_module = ? AND auth_id = ?", cmd.UserId, cmd.AuthModule, cmd.AuthId, ).Get(&id) - if err != nil { return err } @@ -220,7 +220,7 @@ func (s *Store) UpdateAuthInfo(ctx context.Context, cmd *login.UpdateAuthInfoCom func (s *Store) DeleteUserAuthInfo(ctx context.Context, userID int64) error { return s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { - var rawSQL = "DELETE FROM user_auth WHERE user_id = ?" + rawSQL := "DELETE FROM user_auth WHERE user_id = ?" _, err := sess.Exec(rawSQL, userID) return err }) diff --git a/pkg/services/oauthtoken/oauth_token.go b/pkg/services/oauthtoken/oauth_token.go index 284393cd455..cbb4cf67b97 100644 --- a/pkg/services/oauthtoken/oauth_token.go +++ b/pkg/services/oauthtoken/oauth_token.go @@ -19,7 +19,9 @@ import ( "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -39,28 +41,33 @@ type Service struct { Cfg *setting.Cfg SocialService social.Service AuthInfoService login.AuthInfoService + sessionService auth.UserTokenService + features featuremgmt.FeatureToggles serverLock *serverlock.ServerLockService tracer tracing.Tracer tokenRefreshDuration *prometheus.HistogramVec } +var _ OAuthTokenService = (*Service)(nil) + //go:generate mockery --name OAuthTokenService --structname MockService --outpkg oauthtokentest --filename service_mock.go --output ./oauthtokentest/ type OAuthTokenService interface { - GetCurrentOAuthToken(context.Context, identity.Requester) *oauth2.Token + GetCurrentOAuthToken(context.Context, identity.Requester, *auth.UserToken) *oauth2.Token IsOAuthPassThruEnabled(*datasources.DataSource) bool - HasOAuthEntry(context.Context, identity.Requester) (*login.UserAuth, bool, error) - TryTokenRefresh(context.Context, identity.Requester) (*oauth2.Token, error) - InvalidateOAuthTokens(context.Context, identity.Requester) error + TryTokenRefresh(context.Context, identity.Requester, *auth.UserToken) (*oauth2.Token, error) + InvalidateOAuthTokens(context.Context, identity.Requester, *auth.UserToken) error } func ProvideService(socialService social.Service, authInfoService login.AuthInfoService, cfg *setting.Cfg, registerer prometheus.Registerer, - serverLockService *serverlock.ServerLockService, tracer tracing.Tracer, + serverLockService *serverlock.ServerLockService, tracer tracing.Tracer, sessionService auth.UserTokenService, features featuremgmt.FeatureToggles, ) *Service { return &Service{ AuthInfoService: authInfoService, + sessionService: sessionService, Cfg: cfg, SocialService: socialService, + features: features, serverLock: serverLockService, tokenRefreshDuration: newTokenRefreshDurationMetric(registerer), tracer: tracer, @@ -68,7 +75,7 @@ func ProvideService(socialService social.Service, authInfoService login.AuthInfo } // GetCurrentOAuthToken returns the OAuth token, if any, for the authenticated user. Will try to refresh the token if it has expired. -func (o *Service) GetCurrentOAuthToken(ctx context.Context, usr identity.Requester) *oauth2.Token { +func (o *Service) GetCurrentOAuthToken(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) *oauth2.Token { ctx, span := o.tracer.Start(ctx, "oauthtoken.GetCurrentOAuthToken") defer span.End() @@ -93,30 +100,54 @@ func (o *Service) GetCurrentOAuthToken(ctx context.Context, usr identity.Request ctxLogger = ctxLogger.New("userID", userID) - authInfo, ok, _ := o.HasOAuthEntry(ctx, usr) - if !ok { + if !strings.HasPrefix(usr.GetAuthenticatedBy(), "oauth_") { + ctxLogger.Warn("The specified user's auth provider is not oauth", + "authmodule", usr.GetAuthenticatedBy()) return nil } - if err := checkOAuthRefreshToken(authInfo); err != nil { - if errors.Is(err, ErrNoRefreshTokenFound) { - return buildOAuthTokenFromAuthInfo(authInfo) + var persistedToken *oauth2.Token + if o.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + externalSession, err := o.sessionService.GetExternalSession(ctx, sessionToken.ExternalSessionId) + if err != nil { + if errors.Is(err, auth.ErrExternalSessionNotFound) { + return nil + } + ctxLogger.Error("Failed to fetch external session", "error", err) + return nil } - return nil - } + persistedToken = buildOAuthTokenFromExternalSession(externalSession) - persistedToken := buildOAuthTokenFromAuthInfo(authInfo) + if persistedToken.RefreshToken == "" { + return persistedToken + } + } else { + authInfo, ok, _ := o.hasOAuthEntry(ctx, usr) + if !ok { + return nil + } + + if err := checkOAuthRefreshToken(authInfo); err != nil { + if errors.Is(err, ErrNoRefreshTokenFound) { + return buildOAuthTokenFromAuthInfo(authInfo) + } + + return nil + } + + persistedToken = buildOAuthTokenFromAuthInfo(authInfo) + } refreshNeeded := needTokenRefresh(ctx, persistedToken) if !refreshNeeded { return persistedToken } - token, err := o.TryTokenRefresh(ctx, usr) + token, err := o.TryTokenRefresh(ctx, usr, sessionToken) if err != nil { if errors.Is(err, ErrNoRefreshTokenFound) { - return buildOAuthTokenFromAuthInfo(authInfo) + return persistedToken } return nil @@ -130,9 +161,9 @@ func (o *Service) IsOAuthPassThruEnabled(ds *datasources.DataSource) bool { return IsOAuthPassThruEnabled(ds) } -// HasOAuthEntry returns true and the UserAuth object when OAuth info exists for the specified User -func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) { - ctx, span := o.tracer.Start(ctx, "oauthtoken.HasOAuthEntry") +// hasOAuthEntry returns true and the UserAuth object when OAuth info exists for the specified User +func (o *Service) hasOAuthEntry(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) { + ctx, span := o.tracer.Start(ctx, "oauthtoken.hasOAuthEntry") defer span.End() if usr == nil || usr.IsNil() { @@ -167,12 +198,19 @@ func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*l if !strings.Contains(authInfo.AuthModule, "oauth") { return nil, false, nil } + + // An extra check to ensure that the user has an OAuth token + // It's required to handle the case when the `improvedExternalSessionHandling` feature flag gets disabled + if authInfo.OAuthAccessToken == "" { + ctxLogger.Debug("No access token found for user") + return nil, false, fmt.Errorf("no access token found for user %d", userID) + } return authInfo, true, nil } // TryTokenRefresh returns an error in case the OAuth token refresh was unsuccessful // It uses a server lock to prevent getting the Refresh Token multiple times for a given User -func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) (*oauth2.Token, error) { +func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) (*oauth2.Token, error) { ctx, span := o.tracer.Start(ctx, "oauthtoken.TryTokenRefresh") defer span.End() @@ -218,6 +256,9 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) ( } lockKey := fmt.Sprintf("oauth-refresh-token-%d", userID) + if o.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + lockKey = fmt.Sprintf("oauth-refresh-token-%d-%d", userID, sessionToken.ExternalSessionId) + } lockTimeConfig := serverlock.LockTimeConfig{ MaxInterval: 30 * time.Second, @@ -242,15 +283,32 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) ( ctxLogger.Debug("Serverlock request for getting a new access token", "key", lockKey) - authInfo, exists, err := o.HasOAuthEntry(ctx, usr) - if !exists { + var persistedToken *oauth2.Token + var externalSession *auth.ExternalSession + if o.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + externalSession, err = o.sessionService.GetExternalSession(ctx, sessionToken.ExternalSessionId) if err != nil { - ctxLogger.Debug("Failed to fetch oauth entry", "error", err) + if errors.Is(err, auth.ErrExternalSessionNotFound) { + ctxLogger.Error("External session was not found for user", "error", err) + return + } + ctxLogger.Error("Failed to fetch external session", "error", err) + return } - return - } - persistedToken := buildOAuthTokenFromAuthInfo(authInfo) + persistedToken = buildOAuthTokenFromExternalSession(externalSession) + } else { + authInfo, exists, err := o.hasOAuthEntry(ctx, usr) + if !exists { + if err != nil { + ctxLogger.Debug("Failed to fetch oauth entry", "error", err) + cmdErr = err + } + return + } + + persistedToken = buildOAuthTokenFromAuthInfo(authInfo) + } needRefresh := needTokenRefresh(ctx, persistedToken) if !needRefresh { @@ -259,7 +317,7 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) ( return } - newToken, cmdErr = o.tryGetOrRefreshOAuthToken(ctx, persistedToken, usr) + newToken, cmdErr = o.tryGetOrRefreshOAuthToken(ctx, persistedToken, usr, sessionToken) }, retryOpt) if lockErr != nil { ctxLogger.Error("Failed to obtain token refresh lock", "error", lockErr) @@ -274,45 +332,27 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) ( return newToken, cmdErr } -func buildOAuthTokenFromAuthInfo(authInfo *login.UserAuth) *oauth2.Token { - token := &oauth2.Token{ - AccessToken: authInfo.OAuthAccessToken, - Expiry: authInfo.OAuthExpiry, - RefreshToken: authInfo.OAuthRefreshToken, - TokenType: authInfo.OAuthTokenType, - } - - if authInfo.OAuthIdToken != "" { - token = token.WithExtra(map[string]any{"id_token": authInfo.OAuthIdToken}) - } - - return token -} - -func checkOAuthRefreshToken(authInfo *login.UserAuth) error { - if !strings.Contains(authInfo.AuthModule, "oauth") { - logger.Warn("The specified user's auth provider is not oauth", - "authmodule", authInfo.AuthModule, "userid", authInfo.UserId) - return ErrNotAnOAuthProvider - } - - if authInfo.OAuthRefreshToken == "" { - logger.Warn("No refresh token available", - "authmodule", authInfo.AuthModule, "userid", authInfo.UserId) - return ErrNoRefreshTokenFound - } - - return nil -} - // InvalidateOAuthTokens invalidates the OAuth tokens (access_token, refresh_token) and sets the Expiry to default/zero -func (o *Service) InvalidateOAuthTokens(ctx context.Context, usr identity.Requester) error { +func (o *Service) InvalidateOAuthTokens(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) error { userID, err := usr.GetInternalID() if err != nil { logger.Error("Failed to convert user id to int", "id", usr.GetID(), "error", err) return err } + ctxLogger := logger.FromContext(ctx).New("userID", userID) + + if o.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + err := o.sessionService.UpdateExternalSession(ctx, sessionToken.ExternalSessionId, &auth.UpdateExternalSessionCommand{ + Token: &oauth2.Token{}, + }) + if err != nil { + ctxLogger.Error("Failed to update external session", "error", err) + return err + } + } + + // TODO: Should this run regardless of the feature flag? return o.AuthInfoService.UpdateAuthInfo(ctx, &login.UpdateAuthInfoCommand{ UserId: userID, AuthModule: usr.GetAuthenticatedBy(), @@ -325,7 +365,7 @@ func (o *Service) InvalidateOAuthTokens(ctx context.Context, usr identity.Reques }) } -func (o *Service) tryGetOrRefreshOAuthToken(ctx context.Context, persistedToken *oauth2.Token, usr identity.Requester) (*oauth2.Token, error) { +func (o *Service) tryGetOrRefreshOAuthToken(ctx context.Context, persistedToken *oauth2.Token, usr identity.Requester, sessionToken *auth.UserToken) (*oauth2.Token, error) { ctx, span := o.tracer.Start(ctx, "oauthtoken.tryGetOrRefreshOAuthToken") defer span.End() @@ -374,7 +414,7 @@ func (o *Service) tryGetOrRefreshOAuthToken(ctx context.Context, persistedToken "provider", usr.GetAuthenticatedBy(), "error", err) // token refresh failed, invalidate the old token - if err := o.InvalidateOAuthTokens(ctx, usr); err != nil { + if err := o.InvalidateOAuthTokens(ctx, usr, sessionToken); err != nil { ctxLogger.Warn("Failed to invalidate OAuth tokens", "authID", usr.GetAuthID(), "error", err) } @@ -399,10 +439,20 @@ func (o *Service) tryGetOrRefreshOAuthToken(ctx context.Context, persistedToken ) } - if err := o.AuthInfoService.UpdateAuthInfo(ctx, updateAuthCommand); err != nil { - ctxLogger.Error("Failed to update auth info during token refresh", "authID", usr.GetAuthID(), "error", err) - return token, err + if o.features.IsEnabledGlobally(featuremgmt.FlagImprovedExternalSessionHandling) { + if err := o.sessionService.UpdateExternalSession(ctx, sessionToken.ExternalSessionId, &auth.UpdateExternalSessionCommand{ + Token: token, + }); err != nil { + ctxLogger.Error("Failed to update external session during token refresh", "error", err) + return token, err + } + } else { + if err := o.AuthInfoService.UpdateAuthInfo(ctx, updateAuthCommand); err != nil { + ctxLogger.Error("Failed to update auth info during token refresh", "authID", usr.GetAuthID(), "error", err) + return token, err + } } + ctxLogger.Debug("Updated oauth info for user") } @@ -467,6 +517,51 @@ func needTokenRefresh(ctx context.Context, persistedToken *oauth2.Token) bool { return true } +func buildOAuthTokenFromAuthInfo(authInfo *login.UserAuth) *oauth2.Token { + token := &oauth2.Token{ + AccessToken: authInfo.OAuthAccessToken, + Expiry: authInfo.OAuthExpiry, + RefreshToken: authInfo.OAuthRefreshToken, + TokenType: authInfo.OAuthTokenType, + } + + if authInfo.OAuthIdToken != "" { + token = token.WithExtra(map[string]any{"id_token": authInfo.OAuthIdToken}) + } + + return token +} + +func buildOAuthTokenFromExternalSession(externalSession *auth.ExternalSession) *oauth2.Token { + token := &oauth2.Token{ + AccessToken: externalSession.AccessToken, + Expiry: externalSession.ExpiresAt, + RefreshToken: externalSession.RefreshToken, + } + + if externalSession.IDToken != "" { + token = token.WithExtra(map[string]any{"id_token": externalSession.IDToken}) + } + + return token +} + +func checkOAuthRefreshToken(authInfo *login.UserAuth) error { + if !strings.Contains(authInfo.AuthModule, "oauth") { + logger.Warn("The specified user's auth provider is not oauth", + "authmodule", authInfo.AuthModule, "userid", authInfo.UserId) + return ErrNotAnOAuthProvider + } + + if authInfo.OAuthRefreshToken == "" { + logger.Warn("No refresh token available", + "authmodule", authInfo.AuthModule, "userid", authInfo.UserId) + return ErrNoRefreshTokenFound + } + + return nil +} + // GetIDTokenExpiry extracts the expiry time from the ID token func GetIDTokenExpiry(token *oauth2.Token) (time.Time, error) { idToken, ok := token.Extra("id_token").(string) diff --git a/pkg/services/oauthtoken/oauth_token_test.go b/pkg/services/oauthtoken/oauth_token_test.go index 203784f503f..f20128526e2 100644 --- a/pkg/services/oauthtoken/oauth_token_test.go +++ b/pkg/services/oauthtoken/oauth_token_test.go @@ -3,32 +3,29 @@ package oauthtoken import ( "context" "errors" - "reflect" "testing" "time" "github.com/grafana/authlib/claims" - "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "golang.org/x/oauth2" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" + "github.com/grafana/grafana/pkg/models/usertoken" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" - "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/login/authinfotest" - "github.com/grafana/grafana/pkg/services/secrets/fakes" - secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/oauth2" ) const EXPIRED_ID_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6InlvdXItY2xpZW50LWlkIiwiZXhwIjoxNjAwMDAwMDAwLCJpYXQiOjE2MDAwMDAwMDAsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSJ9.c2lnbmF0dXJl" // #nosec G101 not a hardcoded credential @@ -39,104 +36,6 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -func TestService_HasOAuthEntry(t *testing.T) { - testCases := []struct { - name string - user *user.SignedInUser - want *login.UserAuth - wantExist bool - wantErr bool - err error - getAuthInfoErr error - getAuthInfoUser login.UserAuth - }{ - { - name: "returns false without an error in case user is nil", - user: nil, - want: nil, - wantExist: false, - wantErr: false, - }, - { - name: "returns false and an error in case GetAuthInfo returns an error", - user: &user.SignedInUser{UserID: 1}, - want: nil, - wantExist: false, - wantErr: true, - getAuthInfoErr: errors.New("error"), - }, - { - name: "returns false without an error in case auth entry is not found", - user: &user.SignedInUser{UserID: 1}, - want: nil, - wantExist: false, - wantErr: false, - getAuthInfoErr: user.ErrUserNotFound, - }, - { - name: "returns false without an error in case the auth entry is not oauth", - user: &user.SignedInUser{UserID: 1}, - want: nil, - wantExist: false, - wantErr: false, - getAuthInfoUser: login.UserAuth{AuthModule: "auth_saml"}, - }, - { - name: "returns true when the auth entry is found", - user: &user.SignedInUser{UserID: 1}, - want: &login.UserAuth{AuthModule: login.GenericOAuthModule}, - wantExist: true, - wantErr: false, - getAuthInfoUser: login.UserAuth{AuthModule: login.GenericOAuthModule}, - }, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - srv, authInfoStore, _ := setupOAuthTokenService(t) - authInfoStore.ExpectedOAuth = &tc.getAuthInfoUser - authInfoStore.ExpectedError = tc.getAuthInfoErr - - entry, exists, err := srv.HasOAuthEntry(context.Background(), tc.user) - - if tc.wantErr { - assert.Error(t, err) - } - - if tc.want != nil { - assert.True(t, reflect.DeepEqual(tc.want, entry)) - } - assert.Equal(t, tc.wantExist, exists) - }) - } -} - -func setupOAuthTokenService(t *testing.T) (*Service, *FakeAuthInfoStore, *socialtest.MockSocialConnector) { - t.Helper() - - socialConnector := &socialtest.MockSocialConnector{} - socialService := &socialtest.FakeSocialService{ - ExpectedConnector: socialConnector, - ExpectedAuthInfoProvider: &social.OAuthInfo{ - UseRefreshToken: true, - }, - } - - authInfoStore := &FakeAuthInfoStore{ExpectedOAuth: &login.UserAuth{}} - authInfoService := authinfoimpl.ProvideService(authInfoStore, remotecache.NewFakeCacheStorage(), secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())) - - store := db.InitTestDB(t) - - return &Service{ - Cfg: setting.NewCfg(), - SocialService: socialService, - AuthInfoService: authInfoService, - serverLock: serverlock.ProvideService(store, tracing.InitializeTracerForTest()), - tokenRefreshDuration: newTokenRefreshDurationMetric(prometheus.NewRegistry()), - tracer: tracing.InitializeTracerForTest(), - }, authInfoStore, socialConnector -} - type FakeAuthInfoStore struct { login.Store ExpectedError error @@ -379,10 +278,12 @@ func TestService_TryTokenRefresh(t *testing.T) { prometheus.NewRegistry(), env.serverLock, tracing.InitializeTracerForTest(), + nil, + featuremgmt.WithFeatures(), ) // token refresh - actualToken, err := env.service.TryTokenRefresh(context.Background(), tt.identity) + actualToken, err := env.service.TryTokenRefresh(context.Background(), tt.identity, nil) if tt.expectedErr != nil { assert.ErrorIs(t, err, tt.expectedErr) @@ -407,6 +308,275 @@ func TestService_TryTokenRefresh(t *testing.T) { } } +func TestService_TryTokenRefresh_WithExternalSessions(t *testing.T) { + unexpiredToken := &oauth2.Token{ + AccessToken: "testaccess", + RefreshToken: "testrefresh", + Expiry: time.Now().Add(time.Hour), + TokenType: "Bearer", + } + unexpiredTokenWithIDToken := unexpiredToken.WithExtra(map[string]interface{}{ + "id_token": UNEXPIRED_ID_TOKEN, + }) + + expiredToken := &oauth2.Token{ + AccessToken: "testaccess", + RefreshToken: "testrefresh", + Expiry: time.Now().Add(-time.Hour), + TokenType: "Bearer", + } + + userIdentity := &authn.Identity{ + AuthenticatedBy: login.GenericOAuthModule, + ID: "1234", + Type: claims.TypeUser, + } + + type environment struct { + sessionService *authtest.MockUserAuthTokenService + serverLock *serverlock.ServerLockService + socialConnector *socialtest.MockSocialConnector + socialService *socialtest.FakeSocialService + + service *Service + } + + type testCase struct { + desc string + identity identity.Requester + setup func(env *environment) + expectedToken *oauth2.Token + expectedErr error + } + + tests := []testCase{ + { + desc: "should skip sync when identity is nil", + }, + { + desc: "should skip sync when identity is not a user", + identity: &authn.Identity{ID: "1", Type: claims.TypeServiceAccount}, + }, + { + desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID", + identity: &authn.Identity{ID: "invalid", Type: claims.TypeUser}, + }, + { + desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(nil, assert.AnError).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + }, + // Kinda impossible to happen, can only happen after the feature is enabled and logged in users don't have their external sessions set + { + desc: "should skip token refresh if the user doesn't have an external session", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(nil, auth.ErrExternalSessionNotFound).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + }, + { + desc: "should skip token refresh when no oauth provider was found", + identity: userIdentity, + setup: func(env *environment) { + env.socialService.ExpectedAuthInfoProvider = nil + }, + }, + { + desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)", + identity: userIdentity, + setup: func(env *environment) { + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: false, + } + }, + }, + { + desc: "should skip token refresh when the token is still valid and no id token is present", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(&auth.ExternalSession{ + ID: 1, + UserID: 1, + AccessToken: unexpiredTokenWithIDToken.AccessToken, + RefreshToken: unexpiredTokenWithIDToken.RefreshToken, + ExpiresAt: unexpiredTokenWithIDToken.Expiry, + }, nil).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + expectedToken: unexpiredToken, + }, + { + desc: "should not do token refresh if access token or id token have not expired yet", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(&auth.ExternalSession{ + ID: 1, + UserID: 1, + AccessToken: unexpiredTokenWithIDToken.AccessToken, + RefreshToken: unexpiredTokenWithIDToken.RefreshToken, + ExpiresAt: unexpiredTokenWithIDToken.Expiry, + IDToken: UNEXPIRED_ID_TOKEN, + }, nil).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + expectedToken: unexpiredTokenWithIDToken, + }, + { + desc: "should skip token refresh when there is no refresh token", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(&auth.ExternalSession{ + ID: 1, + UserID: 1, + AccessToken: unexpiredTokenWithIDToken.AccessToken, + RefreshToken: "", + ExpiresAt: unexpiredTokenWithIDToken.Expiry, + }, nil).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + expectedToken: &oauth2.Token{ + AccessToken: unexpiredTokenWithIDToken.AccessToken, + RefreshToken: "", + Expiry: unexpiredTokenWithIDToken.Expiry, + }, + }, + { + desc: "should refresh token when the access token is expired", + identity: &authn.Identity{ + AuthenticatedBy: login.GenericOAuthModule, + ID: "1", + Type: claims.TypeUser, + }, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(&auth.ExternalSession{ + ID: 1, + UserID: 1, + AccessToken: expiredToken.AccessToken, + IDToken: UNEXPIRED_ID_TOKEN, + RefreshToken: expiredToken.RefreshToken, + ExpiresAt: expiredToken.Expiry, + }, nil).Once() + + env.sessionService.On("UpdateExternalSession", mock.Anything, int64(1), mock.MatchedBy(verifyUpdateExternalSessionCommand(unexpiredTokenWithIDToken))).Return(nil).Once() + + env.socialConnector.On("TokenSource", mock.Anything, mock.Anything).Return(oauth2.StaticTokenSource(unexpiredTokenWithIDToken)).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + }, + expectedToken: unexpiredTokenWithIDToken, + }, + { + desc: "should refresh token when the id token is expired", + identity: userIdentity, + setup: func(env *environment) { + env.sessionService.On("GetExternalSession", mock.Anything, int64(1)).Return(&auth.ExternalSession{ + ID: 1, + UserID: 1, + AccessToken: unexpiredTokenWithIDToken.AccessToken, + RefreshToken: unexpiredTokenWithIDToken.RefreshToken, + ExpiresAt: unexpiredTokenWithIDToken.Expiry, + IDToken: EXPIRED_ID_TOKEN, + }, nil).Once() + + env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ + UseRefreshToken: true, + } + + env.sessionService.On("UpdateExternalSession", mock.Anything, int64(1), mock.MatchedBy(verifyUpdateExternalSessionCommand(unexpiredTokenWithIDToken))).Return(nil).Once() + + env.socialConnector.On("TokenSource", mock.Anything, mock.Anything).Return(oauth2.StaticTokenSource(unexpiredTokenWithIDToken)).Once() + }, + expectedToken: unexpiredTokenWithIDToken, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + socialConnector := socialtest.NewMockSocialConnector(t) + + store := db.InitTestDB(t) + + env := environment{ + sessionService: authtest.NewMockUserAuthTokenService(t), + serverLock: serverlock.ProvideService(store, tracing.InitializeTracerForTest()), + socialConnector: socialConnector, + socialService: &socialtest.FakeSocialService{ + ExpectedConnector: socialConnector, + }, + } + + if tt.setup != nil { + tt.setup(&env) + } + + env.service = ProvideService( + env.socialService, + nil, + setting.NewCfg(), + prometheus.NewRegistry(), + env.serverLock, + tracing.InitializeTracerForTest(), + env.sessionService, + featuremgmt.WithFeatures(featuremgmt.FlagImprovedExternalSessionHandling), + ) + + // token refresh + actualToken, err := env.service.TryTokenRefresh(context.Background(), tt.identity, &usertoken.UserToken{ExternalSessionId: 1}) + + if tt.expectedErr != nil { + assert.ErrorIs(t, err, tt.expectedErr) + return + } + + assert.NoError(t, err) + + if tt.expectedToken == nil { + assert.Nil(t, actualToken) + return + } + + assert.Equal(t, tt.expectedToken.AccessToken, actualToken.AccessToken) + assert.Equal(t, tt.expectedToken.RefreshToken, actualToken.RefreshToken) + assert.Equal(t, tt.expectedToken.Expiry, actualToken.Expiry) + if tt.expectedToken.Extra("id_token") != nil { + assert.Equal(t, tt.expectedToken.Extra("id_token").(string), actualToken.Extra("id_token").(string)) + } else { + assert.Nil(t, actualToken.Extra("id_token")) + } + }) + } +} + +func verifyUpdateExternalSessionCommand(token *oauth2.Token) func(*auth.UpdateExternalSessionCommand) bool { + return func(cmd *auth.UpdateExternalSessionCommand) bool { + idToken := cmd.Token.Extra("id_token") + return cmd.Token.AccessToken == token.AccessToken && + cmd.Token.RefreshToken == token.RefreshToken && + cmd.Token.Expiry == token.Expiry && + idToken == token.Extra("id_token") + } +} + func TestOAuthTokenSync_needTokenRefresh(t *testing.T) { tests := []struct { name string diff --git a/pkg/services/oauthtoken/oauthtokentest/mock.go b/pkg/services/oauthtoken/oauthtokentest/mock.go index 7c473ae11f0..39e2f6d8fd9 100644 --- a/pkg/services/oauthtoken/oauthtokentest/mock.go +++ b/pkg/services/oauthtoken/oauthtokentest/mock.go @@ -6,21 +6,20 @@ import ( "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/login" ) type MockOauthTokenService struct { - GetCurrentOauthTokenFunc func(ctx context.Context, usr identity.Requester) *oauth2.Token + GetCurrentOauthTokenFunc func(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) *oauth2.Token IsOAuthPassThruEnabledFunc func(ds *datasources.DataSource) bool - HasOAuthEntryFunc func(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) - InvalidateOAuthTokensFunc func(ctx context.Context, usr identity.Requester) error - TryTokenRefreshFunc func(ctx context.Context, usr identity.Requester) (*oauth2.Token, error) + InvalidateOAuthTokensFunc func(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) error + TryTokenRefreshFunc func(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) (*oauth2.Token, error) } -func (m *MockOauthTokenService) GetCurrentOAuthToken(ctx context.Context, usr identity.Requester) *oauth2.Token { +func (m *MockOauthTokenService) GetCurrentOAuthToken(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) *oauth2.Token { if m.GetCurrentOauthTokenFunc != nil { - return m.GetCurrentOauthTokenFunc(ctx, usr) + return m.GetCurrentOauthTokenFunc(ctx, usr, sessionToken) } return nil } @@ -32,23 +31,16 @@ func (m *MockOauthTokenService) IsOAuthPassThruEnabled(ds *datasources.DataSourc return false } -func (m *MockOauthTokenService) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) { - if m.HasOAuthEntryFunc != nil { - return m.HasOAuthEntryFunc(ctx, usr) - } - return nil, false, nil -} - -func (m *MockOauthTokenService) InvalidateOAuthTokens(ctx context.Context, usr identity.Requester) error { +func (m *MockOauthTokenService) InvalidateOAuthTokens(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) error { if m.InvalidateOAuthTokensFunc != nil { - return m.InvalidateOAuthTokensFunc(ctx, usr) + return m.InvalidateOAuthTokensFunc(ctx, usr, sessionToken) } return nil } -func (m *MockOauthTokenService) TryTokenRefresh(ctx context.Context, usr identity.Requester) (*oauth2.Token, error) { +func (m *MockOauthTokenService) TryTokenRefresh(ctx context.Context, usr identity.Requester, sessionToken *auth.UserToken) (*oauth2.Token, error) { if m.TryTokenRefreshFunc != nil { - return m.TryTokenRefreshFunc(ctx, usr) + return m.TryTokenRefreshFunc(ctx, usr, sessionToken) } return nil, nil } diff --git a/pkg/services/oauthtoken/oauthtokentest/oauthtokentest.go b/pkg/services/oauthtoken/oauthtokentest/oauthtokentest.go index f626683c5b3..8c58b43d232 100644 --- a/pkg/services/oauthtoken/oauthtokentest/oauthtokentest.go +++ b/pkg/services/oauthtoken/oauthtokentest/oauthtokentest.go @@ -6,8 +6,8 @@ import ( "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/oauthtoken" ) @@ -21,7 +21,7 @@ func ProvideService() *Service { return &Service{} } -func (s *Service) GetCurrentOAuthToken(context.Context, identity.Requester) *oauth2.Token { +func (s *Service) GetCurrentOAuthToken(context.Context, identity.Requester, *auth.UserToken) *oauth2.Token { return s.Token } @@ -29,14 +29,10 @@ func (s *Service) IsOAuthPassThruEnabled(ds *datasources.DataSource) bool { return oauthtoken.IsOAuthPassThruEnabled(ds) } -func (s *Service) HasOAuthEntry(context.Context, identity.Requester) (*login.UserAuth, bool, error) { - return nil, false, nil -} - -func (s *Service) TryTokenRefresh(context.Context, identity.Requester) (*oauth2.Token, error) { +func (s *Service) TryTokenRefresh(context.Context, identity.Requester, *auth.UserToken) (*oauth2.Token, error) { return s.Token, nil } -func (s *Service) InvalidateOAuthTokens(context.Context, identity.Requester) error { +func (s *Service) InvalidateOAuthTokens(context.Context, identity.Requester, *auth.UserToken) error { return nil } diff --git a/pkg/services/oauthtoken/oauthtokentest/service_mock.go b/pkg/services/oauthtoken/oauthtokentest/service_mock.go index f5baa7427cf..925432a6190 100644 --- a/pkg/services/oauthtoken/oauthtokentest/service_mock.go +++ b/pkg/services/oauthtoken/oauthtokentest/service_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.42.1. DO NOT EDIT. package oauthtokentest @@ -8,11 +8,11 @@ import ( identity "github.com/grafana/grafana/pkg/apimachinery/identity" datasources "github.com/grafana/grafana/pkg/services/datasources" - login "github.com/grafana/grafana/pkg/services/login" - mock "github.com/stretchr/testify/mock" oauth2 "golang.org/x/oauth2" + + usertoken "github.com/grafana/grafana/pkg/models/usertoken" ) // MockService is an autogenerated mock type for the OAuthTokenService type @@ -20,17 +20,17 @@ type MockService struct { mock.Mock } -// GetCurrentOAuthToken provides a mock function with given fields: _a0, _a1 -func (_m *MockService) GetCurrentOAuthToken(_a0 context.Context, _a1 identity.Requester) *oauth2.Token { - ret := _m.Called(_a0, _a1) +// GetCurrentOAuthToken provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockService) GetCurrentOAuthToken(_a0 context.Context, _a1 identity.Requester, _a2 *usertoken.UserToken) *oauth2.Token { + ret := _m.Called(_a0, _a1, _a2) if len(ret) == 0 { panic("no return value specified for GetCurrentOAuthToken") } var r0 *oauth2.Token - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) *oauth2.Token); ok { - r0 = rf(_a0, _a1) + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, *usertoken.UserToken) *oauth2.Token); ok { + r0 = rf(_a0, _a1, _a2) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*oauth2.Token) @@ -40,54 +40,17 @@ func (_m *MockService) GetCurrentOAuthToken(_a0 context.Context, _a1 identity.Re return r0 } -// HasOAuthEntry provides a mock function with given fields: _a0, _a1 -func (_m *MockService) HasOAuthEntry(_a0 context.Context, _a1 identity.Requester) (*login.UserAuth, bool, error) { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for HasOAuthEntry") - } - - var r0 *login.UserAuth - var r1 bool - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) (*login.UserAuth, bool, error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) *login.UserAuth); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*login.UserAuth) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) bool); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Get(1).(bool) - } - - if rf, ok := ret.Get(2).(func(context.Context, identity.Requester) error); ok { - r2 = rf(_a0, _a1) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// InvalidateOAuthTokens provides a mock function with given fields: _a0, _a1 -func (_m *MockService) InvalidateOAuthTokens(_a0 context.Context, _a1 *login.UserAuth) error { - ret := _m.Called(_a0, _a1) +// InvalidateOAuthTokens provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockService) InvalidateOAuthTokens(_a0 context.Context, _a1 identity.Requester, _a2 *usertoken.UserToken) error { + ret := _m.Called(_a0, _a1, _a2) if len(ret) == 0 { panic("no return value specified for InvalidateOAuthTokens") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *login.UserAuth) error); ok { - r0 = rf(_a0, _a1) + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, *usertoken.UserToken) error); ok { + r0 = rf(_a0, _a1, _a2) } else { r0 = ret.Error(0) } @@ -113,22 +76,34 @@ func (_m *MockService) IsOAuthPassThruEnabled(_a0 *datasources.DataSource) bool return r0 } -// TryTokenRefresh provides a mock function with given fields: _a0, _a1 -func (_m *MockService) TryTokenRefresh(_a0 context.Context, _a1 identity.Requester) error { - ret := _m.Called(_a0, _a1) +// TryTokenRefresh provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockService) TryTokenRefresh(_a0 context.Context, _a1 identity.Requester, _a2 *usertoken.UserToken) (*oauth2.Token, error) { + ret := _m.Called(_a0, _a1, _a2) if len(ret) == 0 { panic("no return value specified for TryTokenRefresh") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) error); ok { - r0 = rf(_a0, _a1) + var r0 *oauth2.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, *usertoken.UserToken) (*oauth2.Token, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, *usertoken.UserToken) *oauth2.Token); ok { + r0 = rf(_a0, _a1, _a2) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*oauth2.Token) + } } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, identity.Requester, *usertoken.UserToken) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff --git a/pkg/services/pluginsintegration/clientmiddleware/oauthtoken_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/oauthtoken_middleware.go index dcf59a50714..0585dbe3977 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/oauthtoken_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/oauthtoken_middleware.go @@ -50,7 +50,7 @@ func (m *OAuthTokenMiddleware) applyToken(ctx context.Context, pCtx backend.Plug } if m.oAuthTokenService.IsOAuthPassThruEnabled(ds) { - if token := m.oAuthTokenService.GetCurrentOAuthToken(ctx, reqCtx.SignedInUser); token != nil { + if token := m.oAuthTokenService.GetCurrentOAuthToken(ctx, reqCtx.SignedInUser, reqCtx.UserToken); token != nil { authorizationHeader := fmt.Sprintf("%s %s", token.Type(), token.AccessToken) idTokenHeader := "" From b97cfb3dc2e9b9d6008ecebd64f2b3c9458409e3 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:21:02 +0200 Subject: [PATCH 06/41] I18n: Download translations from Crowdin (#96975) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/de-DE/grafana.json | 38 ++++++++++++++++++++++++++++- public/locales/es-ES/grafana.json | 38 ++++++++++++++++++++++++++++- public/locales/fr-FR/grafana.json | 38 ++++++++++++++++++++++++++++- public/locales/pt-BR/grafana.json | 38 ++++++++++++++++++++++++++++- public/locales/zh-Hans/grafana.json | 38 ++++++++++++++++++++++++++++- 5 files changed, 185 insertions(+), 5 deletions(-) diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 84021f06b11..3059715dd6f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -280,6 +280,12 @@ "contactPointFilter": { "label": "" }, + "export": { + "subtitle": { + "formats": "", + "one-format": "" + } + }, "folderAndGroup": { "evaluation": { "modal": { @@ -301,6 +307,7 @@ "title": "" }, "grafanaManaged": { + "export-new-rule": "", "export-rules": "", "loading": "", "new-recording-rule": "", @@ -325,9 +332,16 @@ "policies": { "default-policy": { "description": "", - "title": "" + "title": "", + "update": "" + }, + "delete": { + "confirm": "", + "warning-1": "", + "warning-2": "" }, "generated-policies": "", + "matchers": "", "metadata": { "active-time": "", "delivered-to": "", @@ -358,6 +372,20 @@ "new-child": "", "new-policy": "", "no-matchers": "", + "reload-policies": "", + "save-policy": "", + "update": { + "please-wait": "", + "update-policy": "", + "updating": "" + }, + "update-errors": { + "conflict": "", + "error-code": "", + "fallback": "", + "suffix": "", + "title": "" + }, "n-more-policies_one": "", "n-more-policies_other": "" }, @@ -1714,6 +1742,10 @@ "logs": { "infinite-scroll": { "older-logs": "" + }, + "log-row-message": { + "ellipsis": "", + "more": "" } }, "migrate-to-cloud": { @@ -2073,6 +2105,9 @@ "subtitle": "Ihre verbundenen Datenquellen-Verbindungen anzeigen und verwalten", "title": "Datenquellen" }, + "databases": { + "title": "" + }, "datasources": { "subtitle": "Füge Datenquellen hinzu und konfiguriere sie", "title": "Datenquellen" @@ -3082,6 +3117,7 @@ "copy-link-button": "", "delete-button": "", "delete-description": "", + "delete-permission-tooltip": "", "delete-title": "", "deleted-alert": "", "expiration-label": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 2239c76b9bd..2f5645b0479 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -280,6 +280,12 @@ "contactPointFilter": { "label": "" }, + "export": { + "subtitle": { + "formats": "", + "one-format": "" + } + }, "folderAndGroup": { "evaluation": { "modal": { @@ -301,6 +307,7 @@ "title": "" }, "grafanaManaged": { + "export-new-rule": "", "export-rules": "", "loading": "", "new-recording-rule": "", @@ -325,9 +332,16 @@ "policies": { "default-policy": { "description": "", - "title": "" + "title": "", + "update": "" + }, + "delete": { + "confirm": "", + "warning-1": "", + "warning-2": "" }, "generated-policies": "", + "matchers": "", "metadata": { "active-time": "", "delivered-to": "", @@ -358,6 +372,20 @@ "new-child": "", "new-policy": "", "no-matchers": "", + "reload-policies": "", + "save-policy": "", + "update": { + "please-wait": "", + "update-policy": "", + "updating": "" + }, + "update-errors": { + "conflict": "", + "error-code": "", + "fallback": "", + "suffix": "", + "title": "" + }, "n-more-policies_one": "", "n-more-policies_other": "" }, @@ -1714,6 +1742,10 @@ "logs": { "infinite-scroll": { "older-logs": "" + }, + "log-row-message": { + "ellipsis": "", + "more": "" } }, "migrate-to-cloud": { @@ -2073,6 +2105,9 @@ "subtitle": "Vea y gestione las conexiones de sus fuentes de datos", "title": "Orígenes de datos" }, + "databases": { + "title": "" + }, "datasources": { "subtitle": "Añadir y configurar orígenes de datos", "title": "Orígenes de datos" @@ -3082,6 +3117,7 @@ "copy-link-button": "", "delete-button": "", "delete-description": "", + "delete-permission-tooltip": "", "delete-title": "", "deleted-alert": "", "expiration-label": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1b59210d85d..08f244ef34f 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -280,6 +280,12 @@ "contactPointFilter": { "label": "" }, + "export": { + "subtitle": { + "formats": "", + "one-format": "" + } + }, "folderAndGroup": { "evaluation": { "modal": { @@ -301,6 +307,7 @@ "title": "" }, "grafanaManaged": { + "export-new-rule": "", "export-rules": "", "loading": "", "new-recording-rule": "", @@ -325,9 +332,16 @@ "policies": { "default-policy": { "description": "", - "title": "" + "title": "", + "update": "" + }, + "delete": { + "confirm": "", + "warning-1": "", + "warning-2": "" }, "generated-policies": "", + "matchers": "", "metadata": { "active-time": "", "delivered-to": "", @@ -358,6 +372,20 @@ "new-child": "", "new-policy": "", "no-matchers": "", + "reload-policies": "", + "save-policy": "", + "update": { + "please-wait": "", + "update-policy": "", + "updating": "" + }, + "update-errors": { + "conflict": "", + "error-code": "", + "fallback": "", + "suffix": "", + "title": "" + }, "n-more-policies_one": "", "n-more-policies_other": "" }, @@ -1714,6 +1742,10 @@ "logs": { "infinite-scroll": { "older-logs": "" + }, + "log-row-message": { + "ellipsis": "", + "more": "" } }, "migrate-to-cloud": { @@ -2073,6 +2105,9 @@ "subtitle": "Voir et gérer vos connexions de sources de données connectées", "title": "Sources de données" }, + "databases": { + "title": "" + }, "datasources": { "subtitle": "Ajouter et configurer des sources de données", "title": "Sources de données" @@ -3082,6 +3117,7 @@ "copy-link-button": "", "delete-button": "", "delete-description": "", + "delete-permission-tooltip": "", "delete-title": "", "deleted-alert": "", "expiration-label": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 89b38bf695b..6d65a10367c 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -280,6 +280,12 @@ "contactPointFilter": { "label": "" }, + "export": { + "subtitle": { + "formats": "", + "one-format": "" + } + }, "folderAndGroup": { "evaluation": { "modal": { @@ -301,6 +307,7 @@ "title": "" }, "grafanaManaged": { + "export-new-rule": "", "export-rules": "", "loading": "", "new-recording-rule": "", @@ -325,9 +332,16 @@ "policies": { "default-policy": { "description": "", - "title": "" + "title": "", + "update": "" + }, + "delete": { + "confirm": "", + "warning-1": "", + "warning-2": "" }, "generated-policies": "", + "matchers": "", "metadata": { "active-time": "", "delivered-to": "", @@ -358,6 +372,20 @@ "new-child": "", "new-policy": "", "no-matchers": "", + "reload-policies": "", + "save-policy": "", + "update": { + "please-wait": "", + "update-policy": "", + "updating": "" + }, + "update-errors": { + "conflict": "", + "error-code": "", + "fallback": "", + "suffix": "", + "title": "" + }, "n-more-policies_one": "", "n-more-policies_other": "" }, @@ -1714,6 +1742,10 @@ "logs": { "infinite-scroll": { "older-logs": "" + }, + "log-row-message": { + "ellipsis": "", + "more": "" } }, "migrate-to-cloud": { @@ -2073,6 +2105,9 @@ "subtitle": "Visualize e gerencie suas conexões de fonte de dados conectadas", "title": "Fontes de dados" }, + "databases": { + "title": "" + }, "datasources": { "subtitle": "Adicione e configure fontes de dados", "title": "Fontes de dados" @@ -3082,6 +3117,7 @@ "copy-link-button": "", "delete-button": "", "delete-description": "", + "delete-permission-tooltip": "", "delete-title": "", "deleted-alert": "", "expiration-label": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 3b56dbbb109..380dc4f4230 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -278,6 +278,12 @@ "contactPointFilter": { "label": "" }, + "export": { + "subtitle": { + "formats": "", + "one-format": "" + } + }, "folderAndGroup": { "evaluation": { "modal": { @@ -299,6 +305,7 @@ "title": "" }, "grafanaManaged": { + "export-new-rule": "", "export-rules": "", "loading": "", "new-recording-rule": "", @@ -323,9 +330,16 @@ "policies": { "default-policy": { "description": "", - "title": "" + "title": "", + "update": "" + }, + "delete": { + "confirm": "", + "warning-1": "", + "warning-2": "" }, "generated-policies": "", + "matchers": "", "metadata": { "active-time": "", "delivered-to": "", @@ -355,6 +369,20 @@ "new-child": "", "new-policy": "", "no-matchers": "", + "reload-policies": "", + "save-policy": "", + "update": { + "please-wait": "", + "update-policy": "", + "updating": "" + }, + "update-errors": { + "conflict": "", + "error-code": "", + "fallback": "", + "suffix": "", + "title": "" + }, "n-more-policies_other": "" }, "provisioning": { @@ -1704,6 +1732,10 @@ "logs": { "infinite-scroll": { "older-logs": "" + }, + "log-row-message": { + "ellipsis": "", + "more": "" } }, "migrate-to-cloud": { @@ -2063,6 +2095,9 @@ "subtitle": "查看和管理您已连接的数据源连接", "title": "数据源" }, + "databases": { + "title": "" + }, "datasources": { "subtitle": "添加并配置数据源", "title": "数据源" @@ -3068,6 +3103,7 @@ "copy-link-button": "", "delete-button": "", "delete-description": "", + "delete-permission-tooltip": "", "delete-title": "", "deleted-alert": "", "expiration-label": "", From 3957b0b26ad5b2c5f37b770950eed8bdc0448b14 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:14:06 +0100 Subject: [PATCH 07/41] SCIM: Update swagger for /scim/Users (#97030) update swagger for /scim/Users --- public/api-merged.json | 26 ++++++++++++++++++++++++++ public/openapi3.json | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/public/api-merged.json b/public/api-merged.json index 6c40acebc14..348d14e02ac 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -8745,6 +8745,32 @@ } } }, + "/scim/users/": { + "post": { + "tags": [ + "users" + ], + "summary": "Creates user.", + "operationId": "createUser", + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "409": { + "$ref": "#/responses/conflictError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/search": { "get": { "tags": [ diff --git a/public/openapi3.json b/public/openapi3.json index 5b2be3da2fe..7624206850d 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -22690,6 +22690,32 @@ ] } }, + "/scim/users/": { + "post": { + "operationId": "createUser", + "responses": { + "200": { + "$ref": "#/components/responses/okResponse" + }, + "401": { + "$ref": "#/components/responses/unauthorisedError" + }, + "403": { + "$ref": "#/components/responses/forbiddenError" + }, + "409": { + "$ref": "#/components/responses/conflictError" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "summary": "Creates user.", + "tags": [ + "users" + ] + } + }, "/search": { "get": { "operationId": "search", From 5a91ab46afba15751f31579d072c3495231ed187 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 27 Nov 2024 13:05:41 +0100 Subject: [PATCH 08/41] Zanzana: Use StreamedListObjects to fetch full list of resources (#97025) * Initial streamed version of list * instantiate openfga client to use StreamedListObjects * Add config option for using streamed version * Use caching * fix cache init * Fix hashing * refactor --- pkg/services/authz/zanzana/server/server.go | 27 +++-- .../authz/zanzana/server/server_list.go | 13 ++- .../zanzana/server/server_list_streamed.go | 100 ++++++++++++++++++ .../authz/zanzana/server/server_test.go | 2 +- pkg/setting/settings_zanzana.go | 4 + 5 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 pkg/services/authz/zanzana/server/server_list_streamed.go diff --git a/pkg/services/authz/zanzana/server/server.go b/pkg/services/authz/zanzana/server/server.go index faea365502d..fb1c527e294 100644 --- a/pkg/services/authz/zanzana/server/server.go +++ b/pkg/services/authz/zanzana/server/server.go @@ -2,12 +2,15 @@ package server import ( "sync" + "time" + "github.com/fullstorydev/grpchan/inprocgrpc" authzv1 "github.com/grafana/authlib/authz/proto/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/openfga/language/pkg/go/transformer" "go.opentelemetry.io/otel" + "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" "github.com/grafana/grafana/pkg/setting" @@ -17,6 +20,8 @@ const ( resourceType = "resource" namespaceType = "namespace" folderTypePrefix = "folder:" + + cacheCleanInterval = 2 * time.Minute ) var _ authzv1.AuthzServiceServer = (*Server)(nil) @@ -28,12 +33,15 @@ type Server struct { authzv1.UnimplementedAuthzServiceServer authzextv1.UnimplementedAuthzExtentionServiceServer - openfga openfgav1.OpenFGAServiceServer + openfga openfgav1.OpenFGAServiceServer + openfgaClient openfgav1.OpenFGAServiceClient + cfg setting.ZanzanaSettings logger log.Logger modules []transformer.ModuleFile stores map[string]storeInfo storesMU *sync.Mutex + cache *localcache.CacheService } type storeInfo struct { @@ -56,14 +64,21 @@ func WithSchema(modules []transformer.ModuleFile) ServerOption { } func NewAuthzServer(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer) (*Server, error) { - return NewAuthz(openfga) + return NewAuthz(cfg, openfga) } -func NewAuthz(openfga openfgav1.OpenFGAServiceServer, opts ...ServerOption) (*Server, error) { +func NewAuthz(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer, opts ...ServerOption) (*Server, error) { + channel := &inprocgrpc.Channel{} + openfgav1.RegisterOpenFGAServiceServer(channel, openfga) + openFGAClient := openfgav1.NewOpenFGAServiceClient(channel) + s := &Server{ - openfga: openfga, - storesMU: &sync.Mutex{}, - stores: make(map[string]storeInfo), + openfga: openfga, + openfgaClient: openFGAClient, + storesMU: &sync.Mutex{}, + stores: make(map[string]storeInfo), + cfg: cfg.Zanzana, + cache: localcache.New(cfg.Zanzana.CheckQueryCacheTTL, cacheCleanInterval), } for _, o := range opts { diff --git a/pkg/services/authz/zanzana/server/server_list.go b/pkg/services/authz/zanzana/server/server_list.go index 2ee7f720370..513ebb2490c 100644 --- a/pkg/services/authz/zanzana/server/server_list.go +++ b/pkg/services/authz/zanzana/server/server_list.go @@ -47,9 +47,16 @@ func (s *Server) List(ctx context.Context, r *authzextv1.ListRequest) (*authzext return s.listGeneric(ctx, r.GetSubject(), relation, r.GetGroup(), r.GetResource(), store) } +func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) { + if s.cfg.UseStreamedListObjects { + return s.streamedListObjects(ctx, req) + } + return s.openfga.ListObjects(ctx, req) +} + func (s *Server) listTyped(ctx context.Context, subject, relation string, info common.TypeInfo, store *storeInfo) (*authzextv1.ListResponse, error) { // List all resources user has access too - listRes, err := s.openfga.ListObjects(ctx, &openfgav1.ListObjectsRequest{ + listRes, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, Type: info.Type, @@ -69,7 +76,7 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation, group, reso groupResource := structpb.NewStringValue(common.FormatGroupResource(group, resource)) // 1. List all folders subject has access to resource type in - folders, err := s.openfga.ListObjects(ctx, &openfgav1.ListObjectsRequest{ + folders, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, Type: common.TypeFolder, @@ -86,7 +93,7 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation, group, reso } // 2. List all resource directly assigned to subject - direct, err := s.openfga.ListObjects(ctx, &openfgav1.ListObjectsRequest{ + direct, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ StoreId: store.ID, AuthorizationModelId: store.ModelID, Type: common.TypeResource, diff --git a/pkg/services/authz/zanzana/server/server_list_streamed.go b/pkg/services/authz/zanzana/server/server_list_streamed.go new file mode 100644 index 00000000000..36f7b50af17 --- /dev/null +++ b/pkg/services/authz/zanzana/server/server_list_streamed.go @@ -0,0 +1,100 @@ +package server + +import ( + "context" + "encoding/base64" + "errors" + "hash/fnv" + "io" + + openfgav1 "github.com/openfga/api/proto/openfga/v1" +) + +func (s *Server) streamedListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) { + if !s.cfg.CheckQueryCache { + return s.listObjectsWithStream(ctx, req) + } + return s.streamedListObjectsCached(ctx, req) +} + +func (s *Server) streamedListObjectsCached(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) { + ctx, span := tracer.Start(ctx, "authzServer.streamedListObjectsCached") + defer span.End() + + reqHash, err := getRequestHash(req) + if err != nil { + return nil, err + } + + if res, ok := s.cache.Get(reqHash); ok { + return res.(*openfgav1.ListObjectsResponse), nil + } + + res, err := s.listObjectsWithStream(ctx, req) + if err != nil { + return nil, err + } + s.cache.Set(reqHash, res, 0) + return res, nil +} + +func (s *Server) listObjectsWithStream(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) { + ctx, span := tracer.Start(ctx, "authzServer.listObjectsWithStream") + defer span.End() + + r := &openfgav1.StreamedListObjectsRequest{ + StoreId: req.GetStoreId(), + AuthorizationModelId: req.GetAuthorizationModelId(), + Type: req.GetType(), + Relation: req.GetRelation(), + User: req.GetUser(), + Context: req.GetContext(), + } + + clientStream, err := s.openfgaClient.StreamedListObjects(ctx, r) + if err != nil { + return nil, err + } + + done := make(chan struct{}) + var streamedObjectIDs []string + var streamingErr error + var streamingResp *openfgav1.StreamedListObjectsResponse + go func() { + for { + streamingResp, streamingErr = clientStream.Recv() + if streamingErr == nil { + streamedObjectIDs = append(streamedObjectIDs, streamingResp.GetObject()) + } else { + if errors.Is(streamingErr, io.EOF) { + streamingErr = nil + } + break + } + } + done <- struct{}{} + }() + <-done + + if streamingErr != nil { + return nil, streamingErr + } + + return &openfgav1.ListObjectsResponse{ + Objects: streamedObjectIDs, + }, nil +} + +func getRequestHash(req *openfgav1.ListObjectsRequest) (string, error) { + if req == nil { + return "", errors.New("request must not be empty") + } + + hash := fnv.New64a() + _, err := hash.Write([]byte(req.String())) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil +} diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 7b5e1709a66..595da83ad0a 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -68,7 +68,7 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server { openfga, err := NewOpenFGA(&cfg.Zanzana, store, log.NewNopLogger()) require.NoError(t, err) - srv, err := NewAuthz(openfga) + srv, err := NewAuthz(cfg, openfga) require.NoError(t, err) storeInf, err := srv.getStoreInfo(context.Background(), namespace) diff --git a/pkg/setting/settings_zanzana.go b/pkg/setting/settings_zanzana.go index c9179ad9df0..e507c9706ea 100644 --- a/pkg/setting/settings_zanzana.go +++ b/pkg/setting/settings_zanzana.go @@ -34,6 +34,9 @@ type ZanzanaSettings struct { ListObjectsMaxResults uint32 // Deadline for the ListObjects() query. Default is 3 seconds. ListObjectsDeadline time.Duration + // Use streamed version of list objects. + // Returns full list of objects, but takes more time. + UseStreamedListObjects bool } func (cfg *Cfg) readZanzanaSettings() { @@ -58,6 +61,7 @@ func (cfg *Cfg) readZanzanaSettings() { s.CheckQueryCacheTTL = sec.Key("check_query_cache_ttl").MustDuration(10 * time.Second) s.ListObjectsDeadline = sec.Key("list_objects_deadline").MustDuration(3 * time.Second) s.ListObjectsMaxResults = uint32(sec.Key("list_objects_max_results").MustUint(1000)) + s.UseStreamedListObjects = sec.Key("use_streamed_list_objects").MustBool(false) cfg.Zanzana = s } From 5b0b9c9ecc11e3a6c130f6e780208159f23cdabe Mon Sep 17 00:00:00 2001 From: jackyin Date: Wed, 27 Nov 2024 20:50:44 +0800 Subject: [PATCH 09/41] ToolbarButton: Auto width on smaller screen sizes (#96023) * fix bug * optimise * delete annotation * optimise --- .../grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index 413d7b493a9..fa53277d9fe 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -159,6 +159,10 @@ const getStyles = (theme: GrafanaTheme2) => { }), }, + [theme.breakpoints.down('md')]: { + width: 'auto !important', + }, + '&:focus, &:focus-visible': { ...getFocusStyles(theme), zIndex: 1, From 6e36dfb74e3c5e5a57d03c94d35a52374920deed Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:58:31 +0100 Subject: [PATCH 10/41] Alerting docs: adds docs on new form flow (#97099) --- .../create-grafana-managed-rule.md | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index 7c3a5986f61..7ebc48b1ea3 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -181,13 +181,24 @@ It does not support absolute time ranges: `2021-12-02 00:00:00 to 2021-12-05 23: 1. Click **Set as alert condition** on the query or expression you want to set as your alert condition. {{< /collapse >}} +## Add folders and labels + +Organize your alert rule with a folder and set of labels. + +In the **Labels** section, you can optionally choose whether to add labels to organize your alert rules, make searching easier, as well as set which notification policy should handle your firing alert instance. + +1. Select a folder or click **+ New folder**. + +1. Add labels, if required. + + Add custom labels by selecting existing key-value pairs from the drop down, or add new labels by entering the new key or value. + ## Set alert evaluation behavior Use alert rule evaluation to determine how frequently an alert rule should be evaluated and how quickly it should change its state. To do this, you need to make sure that your alert rule is in the right evaluation group and set a pending period time that works best for your use case. -1. Select a folder or click **+ New folder**. 1. Select an evaluation group or click **+ New evaluation group**. If you are creating a new evaluation group, specify the interval for the group. @@ -212,19 +223,13 @@ To do this, you need to make sure that your alert rule is in the right evaluatio Use the guidelines in [No data and error handling](#configure-no-data-and-error-handling). -## Configure labels and notifications +## Configure notifications -In the **Labels** section, you can optionally choose whether to add labels to organize your alert rules, make searching easier, as well as set which notification policy should handle your firing alert instance. +Choose to select a contact point directly from the alert rule form or to use notification policy routing as well as set up mute timings and groupings. -In the **Configure notifications** section, you can choose to select a contact point directly from the alert rule form or choose to use notification policy routing as well as set up mute timings and groupings. +Complete the following steps to set up notifications. -Complete the following steps to set up labels and notifications. - -1. Add labels, if required. - - Add custom labels by selecting existing key-value pairs from the drop down, or add new labels by entering the new key or value. - -2. Configure who receives a notification when an alert rule fires by either choosing **Select contact point** or **Use notification policy**. +1. Configure who receives a notification when an alert rule fires by either choosing **Select contact point** or **Use notification policy**. **Select contact point** @@ -232,30 +237,30 @@ Complete the following steps to set up labels and notifications. All notifications for this alert rule are sent to this contact point automatically and notification policies are not used. - 2. You can also optionally select a mute timing as well as groupings and timings to define when not to send notifications. + 1. You can also optionally select a mute timing as well as groupings and timings to define when not to send notifications. {{< admonition type="note" >}} An auto-generated notification policy is generated. Only admins can view these auto-generated policies from the **Notification policies** list view. Any changes have to be made in the alert rules form. {{< /admonition >}} **Use notification policy** - 3. Choose this option to use the notification policy tree to direct your notifications. + 1. Choose this option to use the notification policy tree to direct your notifications. {{< admonition type="note" >}} All alert rules and instances, irrespective of their labels, match the default notification policy. If there are no nested policies, or no nested policies match the labels in the alert rule or alert instance, then the default notification policy is the matching policy. {{< /admonition >}} - 4. Preview your alert instance routing set up. + 1. Preview your alert instance routing set up. Based on the labels added, alert instances are routed to the following notification policies displayed. - 5. Expand each notification policy below to view more details. + 1. Expand each notification policy below to view more details. - 6. Click **See details** to view alert routing details and an email preview. + 1. Click **See details** to view alert routing details and an email preview. -## Add annotations +## Configure notification message -Add [annotations](ref:annotation-label). to provide more context on the alert in your alert notification message. +Add more context on the alert in your alert notification message. For more information, see [annotations](ref:annotation-label). Annotations add metadata to provide more information on the alert in your alert notification message. For example, add a **Summary** annotation to tell you which value caused the alert to fire or which server it happened on. From 16c78f6a98b4543ba666e4f17e520141a50b17dd Mon Sep 17 00:00:00 2001 From: Sriram <153843+yesoreyeram@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:14:44 +0000 Subject: [PATCH 11/41] Postgres: Datasource health check error message improvements #96906 (#96990) * updated postgres health check error messages * addressed review comments / added tests --- .../sqleng/handler_checkhealth.go | 55 ++++++++++++++++- .../sqleng/handler_checkhealth_test.go | 60 +++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth_test.go diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go index 0aef767f618..6dedac9bdf8 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth.go @@ -3,17 +3,22 @@ package sqleng import ( "context" "encoding/json" + "errors" + "fmt" + "net" + "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/lib/pq" ) func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { err := e.Ping() if err != nil { logCheckHealthError(ctx, e.dsInfo, err, e.log) - if req.PluginContext.User.Role == "Admin" { - return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: err.Error()}, nil + if strings.EqualFold(req.PluginContext.User.Role, "Admin") { + return ErrToHealthCheckResult(err) } errResponse := &backend.CheckHealthResult{ Status: backend.HealthStatusError, @@ -24,7 +29,51 @@ func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckH return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil } -func logCheckHealthError(ctx context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) { +// ErrToHealthCheckResult converts error into user friendly health check message +// This should be called with non nil error. If the err parameter is empty, we will send Internal Server Error +func ErrToHealthCheckResult(err error) (*backend.CheckHealthResult, error) { + if err == nil { + return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"}, nil + } + res := &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: err.Error()} + details := map[string]string{ + "verboseMessage": err.Error(), + "errorDetailsLink": "https://grafana.com/docs/grafana/latest/datasources/postgres", + } + var opErr *net.OpError + if errors.As(err, &opErr) { + res.Message = "Network error: Failed to connect to the server" + if opErr != nil && opErr.Err != nil { + res.Message += fmt.Sprintf(". Error message: %s", opErr.Err.Error()) + } + } + if errors.Is(err, pq.ErrSSLNotSupported) { + res.Message = "SSL error: Failed to connect to the server" + } + if strings.HasPrefix(err.Error(), "pq") { + res.Message = "Database error: Failed to connect to the postgres server" + if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil { + details["verboseMessage"] = unwrappedErr.Error() + } + } + var pqErr *pq.Error + if errors.As(err, &pqErr) { + if pqErr != nil { + if pqErr.Code != "" { + res.Message += fmt.Sprintf(". Postgres error code: %s", pqErr.Code.Name()) + } + details["verboseMessage"] = pqErr.Message + } + } + detailBytes, marshalErr := json.Marshal(details) + if marshalErr != nil { + return res, nil + } + res.JSONDetails = detailBytes + return res, nil +} + +func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) { configSummary := map[string]any{ "config_url_length": len(dsInfo.URL), "config_user_length": len(dsInfo.User), diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth_test.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth_test.go new file mode 100644 index 00000000000..b3f8736407d --- /dev/null +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/handler_checkhealth_test.go @@ -0,0 +1,60 @@ +package sqleng + +import ( + "errors" + "net" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestErrToHealthCheckResult(t *testing.T) { + tests := []struct { + name string + err error + want *backend.CheckHealthResult + }{ + { + name: "without error", + want: &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"}, + }, + { + name: "network error", + err: errors.Join(errors.New("foo"), &net.OpError{Op: "read", Net: "tcp", Err: errors.New("some op")}), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "Network error: Failed to connect to the server. Error message: some op", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/postgres","verboseMessage":"foo\nread tcp: some op"}`), + }, + }, + { + name: "db error", + err: errors.Join(errors.New("foo"), &pq.Error{Message: pq.ErrCouldNotDetectUsername.Error(), Code: pq.ErrorCode("28P01")}), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "foo\npq: pq: Could not detect default username. Please provide one explicitly. Postgres error code: invalid_password", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/postgres","verboseMessage":"pq: Could not detect default username. Please provide one explicitly"}`), + }, + }, + { + name: "regular error", + err: errors.New("internal server error"), + want: &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: "internal server error", + JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/postgres","verboseMessage":"internal server error"}`), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ErrToHealthCheckResult(tt.err) + require.Nil(t, err) + assert.Equal(t, string(tt.want.JSONDetails), string(got.JSONDetails)) + require.Equal(t, tt.want, got) + }) + } +} From d18cdae3e2c56c26351efb263c174ebe838cc84e Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Wed, 27 Nov 2024 14:35:37 +0100 Subject: [PATCH 12/41] Schema V2 to DashboardScene: Settings + Panels (#96074) * Transform Save mode to Scene: Settings * Transform Save mode to Scene: Panels --------- Co-authored-by: Dominik Prokop Co-authored-by: alexandra vargas Co-authored-by: Haris Rozajac --- .../dashboard/v2alpha0/dashboard.gen.ts | 26 +- .../dashboard/v2alpha0/dashboard.schema.cue | 11 +- .../src/schema/dashboard/v2alpha0/examples.ts | 355 ++++++++-- .../transformSaveModelSchemaV2ToScene.test.ts | 213 ++++++ .../transformSaveModelSchemaV2ToScene.ts | 631 ++++++++++++++++++ .../transformSaveModelToScene.ts | 2 +- .../serialization/transformToV1TypesUtils.ts | 131 ++++ .../features/dashboard/api/dashboard_api.ts | 2 +- 8 files changed, 1284 insertions(+), 87 deletions(-) create mode 100644 public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts create mode 100644 public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts create mode 100644 public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts index 23dfe83a255..0695c70cf03 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts @@ -124,6 +124,14 @@ export interface DataSourceRef { export const defaultDataSourceRef = (): DataSourceRef => ({ }); +export enum DataTopic { + AlertStates = "alertStates", + Annotations = "annotations", + Series = "series", +} + +export const defaultDataTopic = (): DataTopic => (DataTopic.AlertStates); + // Transformations allow to manipulate data returned by a query before the system applies a visualization. // Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, // use the output of one transformation as the input to another transformation, etc. @@ -135,8 +143,7 @@ export interface DataTransformerConfig { // Optional frame matcher. When missing it will be applied to all results filter?: MatcherConfig; // Where to pull DataFrames from as input to transformation - // replaced with common.DataTopic - topic?: "series" | "annotations" | "alertStates"; + topic?: DataTopic; // Options to be passed to the transformer // Valid options depend on the transformer id options: any; @@ -247,7 +254,7 @@ export const defaultMatcherConfig = (): MatcherConfig => ({ }); export interface Threshold { - value: number | null; + value: number; color: string; } @@ -283,13 +290,13 @@ export const defaultValueMapping = (): ValueMapping => (defaultValueMap()); // `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. // `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. export enum MappingType { - Value = "value", - Range = "range", - Regex = "regex", - Special = "special", + ValueToText = "value", + RangeToText = "range", + RegexToText = "regex", + SpecialValue = "special", } -export const defaultMappingType = (): MappingType => (MappingType.Value); +export const defaultMappingType = (): MappingType => (MappingType.ValueToText); // Maps text values to a color or different display text and color. // For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. @@ -376,7 +383,7 @@ export enum SpecialValueMatch { False = "false", Null = "null", NotANumber = "nan", - NullNan = "null+nan", + NullAndNaN = "null+nan", Empty = "empty", } @@ -542,6 +549,7 @@ export interface QueryOptionsSpec { queryCachingTTL?: number; interval?: string; cacheTimeout?: string; + hideTimeOverride?: boolean; } export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index e7922f46eae..026c42663db 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -102,6 +102,8 @@ DataSourceRef: { uid?: string } +DataTopic: "alertStates" | "annotations" | "series" + // Transformations allow to manipulate data returned by a query before the system applies a visualization. // Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, // use the output of one transformation as the input to another transformation, etc. @@ -113,7 +115,7 @@ DataTransformerConfig: { // Optional frame matcher. When missing it will be applied to all results filter?: MatcherConfig // Where to pull DataFrames from as input to transformation - topic?: "series" | "annotations" | "alertStates" // replaced with common.DataTopic + topic?: DataTopic // Options to be passed to the transformer // Valid options depend on the transformer id options: _ @@ -217,7 +219,7 @@ MatcherConfig: { } Threshold: { - value: number | null + value: number color: string } @@ -235,7 +237,7 @@ ValueMapping: ValueMap | RangeMap | RegexMap | SpecialValueMap // `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. // `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. // `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. -MappingType: "value" | "range" | "regex" | "special" +MappingType: "value" | "range" | "regex" | "special" @cog(kind="enum",memberNames="ValueToText|RangeToText|RegexToText|SpecialValue") // Maps text values to a color or different display text and color. // For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. @@ -287,7 +289,7 @@ SpecialValueMap: { } // Special value types supported by the `SpecialValueMap` -SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty" +SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty" @cog(kind="enum",memberNames="True|False|Null|NaN|NullAndNaN|Empty") // Result used as replacement with text and color when the value matches ValueMappingResult: { @@ -383,6 +385,7 @@ QueryOptionsSpec: { queryCachingTTL?: int interval?: string cacheTimeout?: string + hideTimeOverride?: bool } DataQueryKind: { diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index 5b98aaf0854..e1dd3ec8cb9 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -1,36 +1,38 @@ -import { DashboardCursorSync, DashboardV2Spec } from './dashboard.gen'; +import { + DashboardCursorSync, + DashboardLinkType, + DashboardV2Spec, + VariableHide, + VariableRefresh, + VariableSort, +} from './dashboard.gen'; export const handyTestingSchema: DashboardV2Spec = { - id: 1, - title: 'Default Dashboard', - description: 'This is a default dashboard', - cursorSync: DashboardCursorSync.Off, - liveNow: false, - preload: false, + title: 'Test Dashboard', + description: 'Test Description', editable: true, - links: [], - tags: [], - schemaVersion: 39, + preload: true, + schemaVersion: 40, + tags: ['tag1', 'tag2'], + liveNow: true, + cursorSync: DashboardCursorSync.Crosshair, timeSettings: { - timezone: 'browser', - from: 'now-6h', - to: 'now', - autoRefresh: '10s', - autoRefreshIntervals: ['10s', '1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'], - quickRanges: ['now/d', 'now/w', 'now/M', 'now/y'], - hideTimepicker: false, - weekStart: 'sunday', + autoRefresh: '5s', + autoRefreshIntervals: ['5s', '10s', '30s'], fiscalYearStartMonth: 1, + from: 'now-1h', + hideTimepicker: false, + nowDelay: '1m', + quickRanges: [], + timezone: 'UTC', + to: 'now', + weekStart: 'monday', }, - + annotations: [], elements: { - timeSeriesTest: { + 'test-panel-uid': { kind: 'Panel', spec: { - title: 'Time Series Test', - description: 'This is a test panel', - uid: 'timeSeriesTest', - links: [], data: { kind: 'QueryGroup', spec: { @@ -38,44 +40,63 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'PanelQuery', spec: { + refId: 'A', + datasource: { + type: 'prometheus', + uid: 'datasource1', + }, query: { kind: 'prometheus', spec: { - query: 'up', + expr: 'test-query', }, }, - datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, hidden: false, - refId: 'A', }, }, ], + queryOptions: { + timeFrom: '1h', + maxDataPoints: 100, + timeShift: '1h', + queryCachingTTL: 60, + interval: '1m', + cacheTimeout: '1m', + hideTimeOverride: false, + }, transformations: [ { kind: 'limit', spec: { - id: 'limit', // id is competing w/ kind + id: 'limit', + disabled: false, + filter: { + id: 'byValue', + options: { + reducer: 'sum', + }, + }, options: { limit: 10, }, }, }, ], - queryOptions: { - maxDataPoints: 100, - cacheTimeout: '1m', - }, }, }, + description: 'Test Description', + links: [], + title: 'Test Panel', + uid: 'test-panel-uid', vizConfig: { kind: 'timeseries', spec: { - pluginVersion: '11.0.0', - options: {}, fieldConfig: { defaults: {}, overrides: [], }, + options: {}, + pluginVersion: '7.0.0', }, }, }, @@ -88,62 +109,252 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'GridLayoutItem', spec: { - element: { kind: 'ElementReference', name: 'timeSeriesTest' }, + element: { + kind: 'ElementReference', + name: 'test-panel-uid', + }, + height: 0, + width: 0, x: 0, y: 0, - width: 12, - height: 6, }, }, ], }, }, - variables: [], - annotations: [ + links: [ { - kind: 'AnnotationQuery', + asDropdown: false, + icon: '', + includeVars: false, + keepTime: false, + tags: [], + targetBlank: false, + title: 'Test Link', + tooltip: '', + type: DashboardLinkType.Dashboards, + url: 'http://test.com', + }, + ], + + variables: [ + { + kind: 'QueryVariable', spec: { - datasource: { type: 'datasource', uid: 'grafana' }, - query: { - kind: 'grafana', - spec: { - queryType: 'timeRegions', - matchAny: false, - timeRegion: { - from: '12:27', - fromDayOfWeek: 2, - timezone: 'browser', - to: '11:30', - toDayOfWeek: 2, - }, - }, + allValue: '*', + current: { + text: 'text1', + value: 'value1', }, - enable: true, - filter: { - ids: [], + datasource: { + type: 'prometheus', + uid: 'datasource1', }, - hide: false, - iconColor: 'blue', - name: 'Grafana annotations', + definition: 'definition1', + description: 'A query variable', + hide: VariableHide.DontHide, + includeAll: true, + label: 'Query Variable', + multi: true, + name: 'queryVar', + options: [], + query: 'query1', + refresh: VariableRefresh.OnDashboardLoad, + regex: 'regex1', + skipUrlSync: false, + sort: VariableSort.Disabled, }, }, { - kind: 'AnnotationQuery', + kind: 'CustomVariable', spec: { - datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, - query: { - kind: 'prometheus', - spec: { - query: 'up', + allValue: 'All', + current: { + text: 'option1', + value: 'option1', + }, + description: 'A custom variable', + hide: VariableHide.DontHide, + includeAll: true, + label: 'Custom Variable', + multi: true, + name: 'customVar', + options: [ + { + selected: true, + text: 'option1', + value: 'option1', }, + { + selected: false, + text: 'option2', + value: 'option2', + }, + ], + query: 'option1, option2', + skipUrlSync: false, + }, + }, + { + kind: 'DatasourceVariable', + spec: { + allValue: undefined, + current: { + text: 'text1', + value: 'value1', }, - enable: true, - filter: { - ids: [], + defaultOptionEnabled: true, + description: 'A datasource variable', + hide: VariableHide.DontHide, + includeAll: false, + label: 'Datasource Variable', + multi: false, + name: 'datasourceVar', + options: [], + pluginId: 'datasource1', + refresh: VariableRefresh.OnDashboardLoad, + regex: 'regex1', + skipUrlSync: false, + }, + }, + { + kind: 'ConstantVariable', + spec: { + current: { + text: 'value4', + value: 'value4', }, - hide: false, - iconColor: 'red', - name: 'Prometheus annotations', + description: 'A constant variable', + hide: VariableHide.DontHide, + label: 'Constant Variable', + name: 'constantVar', + query: 'value4', + skipUrlSync: true, + }, + }, + { + kind: 'IntervalVariable', + spec: { + auto: false, + auto_count: 10, + auto_min: '1m', + current: { + text: '1m', + value: '1m', + }, + description: 'An interval variable', + hide: VariableHide.DontHide, + label: 'Interval Variable', + name: 'intervalVar', + options: [ + { + selected: true, + text: '1m', + value: '1m', + }, + { + selected: false, + text: '5m', + value: '5m', + }, + { + selected: false, + text: '10m', + value: '10m', + }, + ], + query: '1m,5m,10m', + refresh: VariableRefresh.OnDashboardLoad, + skipUrlSync: false, + }, + }, + { + kind: 'TextVariable', + spec: { + current: { + text: 'value6', + value: 'value6', + }, + description: 'A text variable', + hide: VariableHide.DontHide, + label: 'Text Variable', + name: 'textVar', + query: 'value6', + skipUrlSync: false, + }, + }, + { + kind: 'GroupByVariable', + spec: { + current: { + text: 'text7', + value: 'value7', + }, + datasource: { + type: 'prometheus', + uid: 'datasource2', + }, + description: 'A group by variable', + hide: VariableHide.DontHide, + includeAll: false, + label: 'Group By Variable', + multi: false, + name: 'groupByVar', + options: [ + { + text: 'option1', + value: 'option1', + }, + { + text: 'option2', + value: 'option2', + }, + ], + skipUrlSync: false, + }, + }, + { + kind: 'AdhocVariable', + spec: { + baseFilters: [ + { + condition: 'AND', + key: 'key1', + operator: '=', + value: 'value1', + }, + { + condition: 'OR', + key: 'key2', + operator: '=', + value: 'value2', + }, + ], + datasource: { + type: 'prometheus', + uid: 'datasource3', + }, + defaultKeys: [ + { + expandable: true, + group: 'defaultGroup1', + text: 'defaultKey1', + value: 'defaultKey1', + }, + ], + description: 'An adhoc variable', + filters: [ + { + condition: 'AND', + key: 'key3', + operator: '=', + value: 'value3', + }, + ], + hide: VariableHide.DontHide, + label: 'Adhoc Variable', + name: 'adhocVar', + skipUrlSync: false, }, }, ], diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts new file mode 100644 index 00000000000..28d0ceaa7f0 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -0,0 +1,213 @@ +import { cloneDeep } from 'lodash'; + +import { config } from '@grafana/runtime'; +import { behaviors, sceneGraph, SceneQueryRunner } from '@grafana/scenes'; +import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; +import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/dashboard_api'; +import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; + +import { DashboardLayoutManager } from '../scene/types'; +import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; +import { getQueryRunnerFor } from '../utils/utils'; + +import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene'; +import { transformCursorSynctoEnum } from './transformToV2TypesUtils'; + +const defaultDashboard: DashboardWithAccessInfo = { + kind: 'DashboardWithAccessInfo', + metadata: { + name: 'dashboard-uid', + namespace: 'default', + labels: {}, + resourceVersion: '', + creationTimestamp: '', + }, + spec: handyTestingSchema, + access: {}, + apiVersion: 'v2', +}; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: () => ({ + getInstanceSettings: jest.fn(), + }), +})); + +describe('transformSaveModelSchemaV2ToScene', () => { + beforeAll(() => { + config.featureToggles.groupByVariable = true; + }); + + afterAll(() => { + config.featureToggles.groupByVariable = false; + }); + + it('should initialize the DashboardScene with the model state', () => { + const scene = transformSaveModelSchemaV2ToScene(defaultDashboard); + const dashboardControls = scene.state.controls!; + const dash = defaultDashboard.spec; + + expect(scene.state.uid).toEqual(defaultDashboard.metadata.name); + expect(scene.state.title).toEqual(dash.title); + expect(scene.state.description).toEqual(dash.description); + expect(scene.state.editable).toEqual(dash.editable); + expect(scene.state.preload).toEqual(true); + expect(scene.state.version).toEqual(dash.schemaVersion); + expect(scene.state.tags).toEqual(dash.tags); + + const liveNow = scene.state.$behaviors?.find((b) => b instanceof behaviors.LiveNowTimer); + expect(liveNow?.state.enabled).toEqual(dash.liveNow); + + const cursorSync = scene.state.$behaviors?.find((b) => b instanceof behaviors.CursorSync); + expect(transformCursorSynctoEnum(cursorSync?.state.sync)).toEqual(dash.cursorSync); + + // Dashboard links + expect(scene.state.links).toHaveLength(dash.links.length); + expect(scene.state.links![0].title).toBe(dash.links[0].title); + + // Time settings + const time = dash.timeSettings; + const refreshPicker = dashboardSceneGraph.getRefreshPicker(scene)!; + const timeRange = sceneGraph.getTimeRange(scene)!; + + // Time settings + expect(refreshPicker.state.refresh).toEqual(time.autoRefresh); + expect(refreshPicker.state.intervals).toEqual(time.autoRefreshIntervals); + expect(timeRange?.state.fiscalYearStartMonth).toEqual(dash.timeSettings.fiscalYearStartMonth); + expect(timeRange?.state.value.raw).toEqual({ from: dash.timeSettings.from, to: dash.timeSettings.to }); + expect(dashboardControls.state.hideTimeControls).toEqual(dash.timeSettings.hideTimepicker); + expect(timeRange?.state.UNSAFE_nowDelay).toEqual(dash.timeSettings.nowDelay); + expect(timeRange?.state.timeZone).toEqual(dash.timeSettings.timezone); + expect(timeRange?.state.weekStart).toEqual(dash.timeSettings.weekStart); + expect(dashboardControls).toBeDefined(); + expect(dashboardControls.state.refreshPicker.state.intervals).toEqual(time.autoRefreshIntervals); + expect(dashboardControls.state.hideTimeControls).toBe(time.hideTimepicker); + + // TODO: Variables + // expect(scene.state?.$variables?.state.variables).toHaveLength(dash.variables.length); + // expect(scene.state?.$variables?.getByName(dash.variables[0].spec.name)).toBeInstanceOf(QueryVariable); + // expect(scene.state?.$variables?.getByName(dash.variables[1].spec.name)).toBeInstanceOf(TextBoxVariable); ... + + // TODO: Annotations + // expect(scene.state.annotations).toHaveLength(dash.annotations.length); + // expect(scene.state.annotations[0].text).toBe(dash.annotations[0].text); ... + + // To be implemented + // expect(timePicker.state.ranges).toEqual(dash.timeSettings.quickRanges); + + // VizPanel + const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); + expect(vizPanels).toHaveLength(1); + const vizPanel = vizPanels[0]; + expect(vizPanel.state.title).toBe(dash.elements['test-panel-uid'].spec.title); + expect(vizPanel.state.description).toBe(dash.elements['test-panel-uid'].spec.description); + expect(vizPanel.state.pluginId).toBe(dash.elements['test-panel-uid'].spec.vizConfig.kind); + expect(vizPanel.state.pluginVersion).toBe(dash.elements['test-panel-uid'].spec.vizConfig.spec.pluginVersion); + expect(vizPanel.state.options).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.options); + expect(vizPanel.state.fieldConfig).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.fieldConfig); + + // FIXME: There is an error of data being undefined + // expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer); + // const dataTransformer = vizPanel.state.$data as SceneDataTransformer; + // expect(dataTransformer.state.transformations).toEqual([{ id: 'transform1', options: {} }]); + + // expect(dataTransformer.state.$data).toBeInstanceOf(SceneQueryRunner); + const queryRunner = getQueryRunnerFor(vizPanel); + expect(queryRunner).toBeInstanceOf(SceneQueryRunner); + expect(queryRunner?.state.datasource).toBeUndefined(); + // expect(queryRunner.state.queries).toEqual([{ query: 'test-query', datasource: { uid: 'datasource1', type: 'prometheus' } }]); + // expect(queryRunner.state.maxDataPoints).toBe(100); + // expect(queryRunner.state.cacheTimeout).toBe('1m'); + // expect(queryRunner.state.queryCachingTTL).toBe(60); + // expect(queryRunner.state.minInterval).toBe('1m'); + // expect(queryRunner.state.dataLayerFilter?.panelId).toBe(1); + + // FIXME: Fix the key incompatibility since panel is not numeric anymore + // expect(vizPanel.state.key).toBe(dash.elements['test-panel-uid'].spec.uid); + + // FIXME: Tests for layout + }); + + it('should set panel ds if it is mixed DS', () => { + const dashboard = cloneDeep(defaultDashboard); + dashboard.spec.elements['test-panel-uid'].spec.data.spec.queries.push({ + kind: 'PanelQuery', + spec: { + refId: 'A', + datasource: { + type: 'graphite', + uid: 'datasource1', + }, + hidden: false, + query: { + kind: 'prometheus', + spec: { + expr: 'test-query', + }, + }, + }, + }); + + const scene = transformSaveModelSchemaV2ToScene(dashboard); + + const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); + expect(vizPanels.length).toBe(1); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.type).toBe('mixed'); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.uid).toBe(MIXED_DATASOURCE_NAME); + }); + + it('should set panel ds as undefined if it is not mixed DS', () => { + const dashboard = cloneDeep(defaultDashboard); + dashboard.spec.elements['test-panel-uid'].spec.data.spec.queries.push({ + kind: 'PanelQuery', + spec: { + refId: 'A', + datasource: { + type: 'prometheus', + uid: 'datasource1', + }, + hidden: false, + query: { + kind: 'prometheus', + spec: { + expr: 'test-query', + }, + }, + }, + }); + + const scene = transformSaveModelSchemaV2ToScene(dashboard); + + const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); + expect(vizPanels.length).toBe(1); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource).toBeUndefined(); + }); + + // Skipping the test because the schema doesn't accept the ds to be undefined. + // In future PR, we will mark it as optional so, this test should pass and the runtime code should be updated. + it.skip('should set panel ds as undefined if it is not mixed DS', () => { + const dashboard = cloneDeep(defaultDashboard); + dashboard.spec.elements['test-panel-uid'].spec.data.spec.queries.push({ + kind: 'PanelQuery', + // @ts-expect-error TODO: When marking DS as optional, this should be fixed + spec: { + refId: 'A', + hidden: false, + query: { + kind: 'prometheus', + spec: { + expr: 'test-query', + }, + }, + }, + }); + + const scene = transformSaveModelSchemaV2ToScene(dashboard); + + const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); + expect(vizPanels.length).toBe(1); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource).toBeUndefined(); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts new file mode 100644 index 00000000000..db5b251c020 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -0,0 +1,631 @@ +import { uniqueId } from 'lodash'; + +import { config, getDataSourceSrv } from '@grafana/runtime'; +import { + AdHocFiltersVariable, + behaviors, + ConstantVariable, + CustomVariable, + DataSourceVariable, + GroupByVariable, + IntervalVariable, + QueryVariable, + SceneDataLayerControls, + SceneDataProvider, + SceneDataQuery, + SceneDataTransformer, + SceneGridItemLike, + SceneGridLayout, + SceneObject, + SceneQueryRunner, + SceneRefreshPicker, + SceneTimePicker, + SceneTimeRange, + SceneVariable, + SceneVariableSet, + TextBoxVariable, + VariableValueSelectors, + VizPanel, + VizPanelMenu, + VizPanelState, +} from '@grafana/scenes'; +import { DataSourceRef } from '@grafana/schema/dist/esm/index.gen'; +import { + AdhocVariableKind, + ConstantVariableKind, + CustomVariableKind, + DashboardV2Spec, + DatasourceVariableKind, + defaultAdhocVariableKind, + defaultConstantVariableKind, + defaultCustomVariableKind, + defaultDatasourceVariableKind, + defaultGroupByVariableKind, + defaultIntervalVariableKind, + defaultQueryVariableKind, + defaultTextVariableKind, + GroupByVariableKind, + IntervalVariableKind, + PanelKind, + PanelQueryKind, + QueryVariableKind, + TextVariableKind, +} from '@grafana/schema/src/schema/dashboard/v2alpha0/dashboard.gen'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/dashboard_api'; +import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; + +import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; +import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; +import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; +import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; +import { registerDashboardMacro } from '../scene/DashboardMacro'; +import { DashboardReloadBehavior } from '../scene/DashboardReloadBehavior'; +import { DashboardScene } from '../scene/DashboardScene'; +import { DashboardScopesFacade } from '../scene/DashboardScopesFacade'; +import { panelMenuBehavior } from '../scene/PanelMenuBehavior'; +import { PanelNotices } from '../scene/PanelNotices'; +import { PanelTimeRange } from '../scene/PanelTimeRange'; +import { AngularDeprecation } from '../scene/angular/AngularDeprecation'; +import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; +import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; +import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; +import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState'; +import { getDashboardSceneFor, getIntervalsFromQueryString } from '../utils/utils'; + +import { SnapshotVariable } from './custom-variables/SnapshotVariable'; +import { registerPanelInteractionsReporter } from './transformSaveModelToScene'; +import { + transformCursorSyncV2ToV1, + transformSortVariableToEnumV1, + transformValueMappingsToV1, + transformVariableHideToEnumV1, + transformVariableRefreshToEnumV1, +} from './transformToV1TypesUtils'; + +const DEFAULT_DATASOURCE = 'default'; + +type TypedVariableModelv2 = + | QueryVariableKind + | TextVariableKind + | ConstantVariableKind + | DatasourceVariableKind + | IntervalVariableKind + | CustomVariableKind + | GroupByVariableKind + | AdhocVariableKind; + +export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo): DashboardScene { + const { spec: dashboard, metadata } = dto; + + const annotationLayers = dashboard.annotations.map((annotation) => { + return new DashboardAnnotationsDataLayer({ + key: uniqueId('annotations-'), + query: annotation.spec, + name: annotation.spec.name, + isEnabled: Boolean(annotation.spec.enable), + isHidden: Boolean(annotation.spec.hide), + }); + }); + + const dashboardScene = new DashboardScene({ + description: dashboard.description, + editable: dashboard.editable, + preload: dashboard.preload, + id: dashboard.id, + isDirty: false, + links: dashboard.links, + // TODO: Combine access and metadata to compose the V1 meta object + meta: {}, + tags: dashboard.tags, + title: dashboard.title, + uid: metadata.name, + version: dashboard.schemaVersion, + body: new DefaultGridLayoutManager({ + grid: new SceneGridLayout({ + isLazy: dashboard.preload ? false : true, + children: createSceneGridLayoutForItems(dashboard), + $behaviors: [trackIfEmpty], + }), + }), + $timeRange: new SceneTimeRange({ + from: dashboard.timeSettings.from, + to: dashboard.timeSettings.to, + fiscalYearStartMonth: dashboard.timeSettings.fiscalYearStartMonth, + timeZone: dashboard.timeSettings.timezone, + weekStart: dashboard.timeSettings.weekStart, + UNSAFE_nowDelay: dashboard.timeSettings.nowDelay, + }), + $variables: getVariables(dashboard), + $behaviors: [ + new behaviors.CursorSync({ + sync: transformCursorSyncV2ToV1(dashboard.cursorSync), + }), + new behaviors.SceneQueryController(), + registerDashboardMacro, + registerPanelInteractionsReporter, + new behaviors.LiveNowTimer({ enabled: dashboard.liveNow }), + preserveDashboardSceneStateInLocalStorage, + addPanelsOnLoadBehavior, + new DashboardScopesFacade({ + reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange, + uid: dashboard.id?.toString(), + }), + new DashboardReloadBehavior({ + reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange, + uid: dashboard.id?.toString(), + version: 1, + }), + ], + $data: new DashboardDataLayerSet({ + annotationLayers, + }), + controls: new DashboardControls({ + variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], + timePicker: new SceneTimePicker({}), + refreshPicker: new SceneRefreshPicker({ + refresh: dashboard.timeSettings.autoRefresh, + intervals: dashboard.timeSettings.autoRefreshIntervals, + withText: true, + }), + hideTimeControls: dashboard.timeSettings.hideTimepicker, + }), + }); + + return dashboardScene; +} + +function createSceneGridLayoutForItems(dashboard: DashboardV2Spec): SceneGridItemLike[] { + const gridElements = dashboard.layout.spec.items; + + return gridElements.map((element) => { + if (element.kind === 'GridLayoutItem') { + const panel = dashboard.elements[element.spec.element.name]; + + if (!panel) { + throw new Error(`Panel with uid ${element.spec.element.name} not found in the dashboard elements`); + } + + if (panel.kind === 'Panel') { + const vizPanel = buildVizPanel(panel); + + return new DashboardGridItem({ + key: `grid-item-${panel.spec.uid}`, + x: element.spec.x, + y: element.spec.y, + width: element.spec.width, + height: element.spec.height, + itemHeight: element.spec.height, + body: vizPanel, + }); + } else { + throw new Error(`Unknown element kind: ${element.kind}`); + } + } else { + throw new Error(`Unknown layout element kind: ${element.kind}`); + } + }); +} + +function buildVizPanel(panel: PanelKind): VizPanel { + const titleItems: SceneObject[] = []; + + if (config.featureToggles.angularDeprecationUI) { + titleItems.push(new AngularDeprecation()); + } + + // FIXME: Links in a panel are DashboardLinks not DataLinks + // titleItems.push( + // new VizPanelLinks({ + // rawLinks: panel.spec.links, + // menu: new VizPanelLinksMenu({ $behaviors: [panelLinksBehavior] }), + // }) + // ); + + titleItems.push(new PanelNotices()); + + const queryOptions = panel.spec.data.spec.queryOptions; + const timeOverrideShown = (queryOptions.timeFrom || queryOptions.timeShift) && !queryOptions.hideTimeOverride; + + const vizPanelState: VizPanelState = { + key: panel.spec.uid, + title: panel.spec.title, + description: panel.spec.description, + pluginId: panel.spec.vizConfig.kind, + options: panel.spec.vizConfig.spec.options, + fieldConfig: transformValueMappingsToV1(panel.spec.vizConfig.spec.fieldConfig), + pluginVersion: panel.spec.vizConfig.spec.pluginVersion, + // FIXME: Transparent is not added to the schema yet + // displayMode: panel.spec.transparent ? 'transparent' : undefined, + hoverHeader: !panel.spec.title && !timeOverrideShown, + hoverHeaderOffset: 0, + $data: createPanelDataProvider(panel), + titleItems, + $behaviors: [], + extendPanelContext: setDashboardPanelContext, + // _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), //FIXME: Angular Migration + }; + + // FIXME: Library Panel + // if (panel.spec.libraryPanel) { + // vizPanelState.$behaviors!.push( + // new LibraryPanelBehavior({ uid: panel.spec.libraryPanel.uid, name: panel.spec.libraryPanel.name }) + // ); + // vizPanelState.pluginId = LibraryPanelBehavior.LOADING_VIZ_PANEL_PLUGIN_ID; + // vizPanelState.$data = undefined; + // } + + if (!config.publicDashboardAccessToken) { + vizPanelState.menu = new VizPanelMenu({ + $behaviors: [panelMenuBehavior], + }); + } + + if (queryOptions.timeFrom || queryOptions.timeShift) { + vizPanelState.$timeRange = new PanelTimeRange({ + timeFrom: queryOptions.timeFrom, + timeShift: queryOptions.timeShift, + hideTimeOverride: queryOptions.hideTimeOverride, + }); + } + + return new VizPanel(vizPanelState); +} + +function trackIfEmpty(grid: SceneGridLayout) { + getDashboardSceneFor(grid).setState({ isEmpty: grid.state.children.length === 0 }); + + const sub = grid.subscribeToState((n, p) => { + if (n.children.length !== p.children.length || n.children !== p.children) { + getDashboardSceneFor(grid).setState({ isEmpty: n.children.length === 0 }); + } + }); + + return () => { + sub.unsubscribe(); + }; +} + +function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { + if (!panel.spec.data?.spec.queries?.length) { + return undefined; + } + + let datasource: DataSourceRef | undefined = undefined; + let isMixedDatasource = false; + + panel.spec.data.spec.queries.forEach((query) => { + if (!datasource) { + datasource = query.spec.datasource; + } else if (datasource.uid !== query.spec.datasource.uid || datasource.type !== query.spec.datasource.type) { + isMixedDatasource = true; + } + }); + + return isMixedDatasource ? { type: 'mixed', uid: MIXED_DATASOURCE_NAME } : undefined; +} + +function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { + return { + refId: query.spec.refId, + datasource: query.spec.datasource, + hide: query.spec.hidden, + ...query.spec.query.spec, + }; +} + +export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider | undefined { + const panel = panelKind.spec; + const targets = panel.data?.spec.queries ?? []; + // Skip setting query runner for panels without queries + if (!targets?.length) { + return undefined; + } + + // Skip setting query runner for panel plugins with skipDataQuery + if (config.panels[panel.vizConfig.kind]?.skipDataQuery) { + return undefined; + } + + let dataProvider: SceneDataProvider | undefined = undefined; + const datasource = getPanelDataSource(panelKind); + + dataProvider = new SceneQueryRunner({ + datasource, + queries: targets.map(panelQueryKindToSceneQuery), + maxDataPoints: panel.data.spec.queryOptions.maxDataPoints ?? undefined, + maxDataPointsFromWidth: true, + cacheTimeout: panel.data.spec.queryOptions.cacheTimeout, + queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL, + minInterval: panel.data.spec.queryOptions.interval ?? undefined, + dataLayerFilter: { + // FIXME: This is asking for a number as panel ID but here the uid of a panel is string + panelId: Number.isNaN(parseInt(panel.uid, 10)) ? 0 : parseInt(panel.uid, 10), + }, + $behaviors: [new DashboardDatasourceBehaviour({})], + }); + + // Wrap inner data provider in a data transformer + return new SceneDataTransformer({ + $data: dataProvider, + transformations: panel.data.spec.transformations.map((transformation) => transformation.spec), + }); +} + +function getVariables(dashboard: DashboardV2Spec): SceneVariableSet | undefined { + let variables: SceneVariableSet | undefined; + + if (dashboard.variables.length) { + if (false) { + // FIXME: isSnapshot is not added to the schema yet + //if (dashboard.meta?.isSnapshot) { + // in the old model we use .meta.isSnapshot but meta is not persisted + // variables = createVariablesForSnapshot(dashboard); + } else { + variables = createVariablesForDashboard(dashboard); + } + } else { + // Create empty variable set + variables = new SceneVariableSet({ + variables: [], + }); + } + + return variables; +} + +function createVariablesForDashboard(dashboard: DashboardV2Spec) { + const variableObjects = dashboard.variables + .map((v) => { + try { + return createSceneVariableFromVariableModel(v); + } catch (err) { + console.error(err); + return null; + } + }) + // TODO: Remove filter + // Added temporarily to allow skipping non-compatible variables + .filter((v): v is SceneVariable => Boolean(v)); + + return new SceneVariableSet({ + variables: variableObjects, + }); +} + +function createSceneVariableFromVariableModel(variable: TypedVariableModelv2): SceneVariable { + const commonProperties = { + name: variable.spec.name, + label: variable.spec.label, + description: variable.spec.description, + }; + if (variable.kind === defaultAdhocVariableKind().kind) { + return new AdHocFiltersVariable({ + ...commonProperties, + description: variable.spec.description, + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + datasource: variable.spec.datasource, + applyMode: 'auto', + filters: variable.spec.filters ?? [], + baseFilters: variable.spec.baseFilters ?? [], + defaultKeys: variable.spec.defaultKeys, + useQueriesAsFilterForOptions: true, + layout: config.featureToggles.newFiltersUI ? 'combobox' : undefined, + supportsMultiValueOperators: Boolean( + getDataSourceSrv().getInstanceSettings(variable.spec.datasource)?.meta.multiValueFilterOperators + ), + }); + } + if (variable.kind === defaultCustomVariableKind().kind) { + return new CustomVariable({ + ...commonProperties, + value: variable.spec.current?.value ?? '', + text: variable.spec.current?.text ?? '', + + query: variable.spec.query, + isMulti: variable.spec.multi, + allValue: variable.spec.allValue || undefined, + includeAll: variable.spec.includeAll, + defaultToAll: Boolean(variable.spec.includeAll), + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + } else if (variable.kind === defaultQueryVariableKind().kind) { + return new QueryVariable({ + ...commonProperties, + value: variable.spec.current?.value ?? '', + text: variable.spec.current?.text ?? '', + query: getDataQueryForVariable(variable), + datasource: variable.spec.datasource, + sort: transformSortVariableToEnumV1(variable.spec.sort), + refresh: transformVariableRefreshToEnumV1(variable.spec.refresh), + regex: variable.spec.regex, + allValue: variable.spec.allValue || undefined, + includeAll: variable.spec.includeAll, + defaultToAll: Boolean(variable.spec.includeAll), + isMulti: variable.spec.multi, + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + definition: variable.spec.definition, + }); + } else if (variable.kind === defaultDatasourceVariableKind().kind) { + return new DataSourceVariable({ + ...commonProperties, + value: variable.spec.current?.value ?? '', + text: variable.spec.current?.text ?? '', + regex: variable.spec.regex, + pluginId: variable.spec.pluginId, + allValue: variable.spec.allValue || undefined, + includeAll: variable.spec.includeAll, + defaultToAll: Boolean(variable.spec.includeAll), + skipUrlSync: variable.spec.skipUrlSync, + isMulti: variable.spec.multi, + hide: transformVariableHideToEnumV1(variable.spec.hide), + defaultOptionEnabled: + variable.spec.current?.value === DEFAULT_DATASOURCE && variable.spec.current?.text === 'default', + }); + } else if (variable.kind === defaultIntervalVariableKind().kind) { + const intervals = getIntervalsFromQueryString(variable.spec.query); + const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); + return new IntervalVariable({ + ...commonProperties, + value: currentInterval, + intervals: intervals, + autoEnabled: variable.spec.auto, + autoStepCount: variable.spec.auto_count, + autoMinInterval: variable.spec.auto_min, + refresh: transformVariableRefreshToEnumV1(variable.spec.refresh), + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + } else if (variable.kind === defaultConstantVariableKind().kind) { + return new ConstantVariable({ + ...commonProperties, + value: variable.spec.query, + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + } else if (variable.kind === defaultTextVariableKind().kind) { + let val; + if (!variable?.spec.current?.value) { + val = variable.spec.query; + } else { + if (typeof variable.spec.current.value === 'string') { + val = variable.spec.current.value; + } else { + val = variable.spec.current.value[0]; + } + } + + return new TextBoxVariable({ + ...commonProperties, + value: val, + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + } else if (config.featureToggles.groupByVariable && variable.kind === defaultGroupByVariableKind().kind) { + return new GroupByVariable({ + ...commonProperties, + datasource: variable.spec.datasource, + value: variable.spec.current?.value || [], + text: variable.spec.current?.text || [], + skipUrlSync: variable.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(variable.spec.hide), + // @ts-expect-error + defaultOptions: variable.options, + }); + } else { + throw new Error(`Scenes: Unsupported variable type ${variable.kind}`); + } +} + +function getDataQueryForVariable(variable: QueryVariableKind) { + return typeof variable.spec.query !== 'string' + ? { + ...variable.spec.query.spec, + refId: variable.spec.query.spec.refId ?? 'A', + } + : (variable.spec.query ?? ''); +} + +export function getCurrentValueForOldIntervalModel(variable: IntervalVariableKind, intervals: string[]): string { + const selectedInterval = Array.isArray(variable.spec.current.value) + ? variable.spec.current.value[0] + : variable.spec.current.value; + + // If the interval is the old auto format, return the new auto interval from scenes. + if (selectedInterval.startsWith('$__auto_interval_')) { + return '$__auto'; + } + + // Check if the selected interval is valid. + if (intervals.includes(selectedInterval)) { + return selectedInterval; + } + + // If the selected interval is not valid, return the first valid interval. + return intervals[0]; +} + +export function createVariablesForSnapshot(dashboard: DashboardV2Spec): SceneVariableSet { + const variableObjects = dashboard.variables + .map((v) => { + try { + // for adhoc we are using the AdHocFiltersVariable from scenes becuase of its complexity + if (v.kind === 'AdhocVariable') { + return new AdHocFiltersVariable({ + name: v.spec.name, + label: v.spec.label, + readOnly: true, + description: v.spec.description, + skipUrlSync: v.spec.skipUrlSync, + hide: transformVariableHideToEnumV1(v.spec.hide), + datasource: v.spec.datasource, + applyMode: 'auto', + filters: v.spec.filters ?? [], + baseFilters: v.spec.baseFilters ?? [], + defaultKeys: v.spec.defaultKeys, + useQueriesAsFilterForOptions: true, + layout: config.featureToggles.newFiltersUI ? 'combobox' : undefined, + supportsMultiValueOperators: Boolean( + getDataSourceSrv().getInstanceSettings(v.spec.datasource)?.meta.multiValueFilterOperators + ), + }); + } + // for other variable types we are using the SnapshotVariable + return createSnapshotVariable(v); + } catch (err) { + console.error(err); + return null; + } + }) + // TODO: Remove filter + // Added temporarily to allow skipping non-compatible variables + .filter((v): v is SceneVariable => Boolean(v)); + + return new SceneVariableSet({ + variables: variableObjects, + }); +} + +/** Snapshots variables are read-only and should not be updated */ +export function createSnapshotVariable(variable: TypedVariableModelv2): SceneVariable { + let snapshotVariable: SnapshotVariable; + let current: { value: string | string[]; text: string | string[] }; + if (variable.kind === 'IntervalVariable') { + const intervals = getIntervalsFromQueryString(variable.spec.query); + const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); + snapshotVariable = new SnapshotVariable({ + name: variable.spec.name, + label: variable.spec.label, + description: variable.spec.description, + value: currentInterval, + text: currentInterval, + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + return snapshotVariable; + } + + if (variable.kind === 'ConstantVariable' || variable.kind === 'AdhocVariable') { + current = { + value: '', + text: '', + }; + } else { + current = { + value: variable.spec.current?.value ?? '', + text: variable.spec.current?.text ?? '', + }; + } + + snapshotVariable = new SnapshotVariable({ + name: variable.spec.name, + label: variable.spec.label, + description: variable.spec.description, + value: current?.value ?? '', + text: current?.text ?? '', + hide: transformVariableHideToEnumV1(variable.spec.hide), + }); + return snapshotVariable; +} diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 8f776253450..a63312a9f81 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -359,7 +359,7 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { }); } -function registerPanelInteractionsReporter(scene: DashboardScene) { +export function registerPanelInteractionsReporter(scene: DashboardScene) { // Subscriptions set with subscribeToEvent are automatically unsubscribed when the scene deactivated scene.subscribeToEvent(UserActionEvent, (e) => { const { interaction } = e.payload; diff --git a/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts new file mode 100644 index 00000000000..26a9c5cd8bb --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts @@ -0,0 +1,131 @@ +import { FieldConfigSource as FieldConfigSourceV1, SpecialValueMatch as SpecialValueMatchV1 } from '@grafana/data'; +import { + VariableHide as VariableHideV1, + VariableRefresh as VariableRefreshV1, + VariableSort as VariableSortV1, + DashboardCursorSync as DashboardCursorSyncV1, + defaultDashboardCursorSync, +} from '@grafana/schema'; +import { + DashboardCursorSync, + MappingType, + VariableHide, + VariableRefresh, + VariableSort, + FieldConfigSource, + SpecialValueMatch, +} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; + +export function transformVariableRefreshToEnumV1(refresh?: VariableRefresh): VariableRefreshV1 { + switch (refresh) { + case VariableRefresh.Never: + return VariableRefreshV1.never; + case VariableRefresh.OnDashboardLoad: + return VariableRefreshV1.onDashboardLoad; + case VariableRefresh.OnTimeRangeChanged: + return VariableRefreshV1.onTimeRangeChanged; + default: + return VariableRefreshV1.never; + } +} + +export function transformVariableHideToEnumV1(hide?: VariableHide): VariableHideV1 { + switch (hide) { + case VariableHide.DontHide: + return VariableHideV1.dontHide; + case VariableHide.HideLabel: + return VariableHideV1.hideLabel; + case VariableHide.HideVariable: + return VariableHideV1.hideVariable; + default: + return VariableHideV1.dontHide; + } +} + +export function transformSortVariableToEnumV1(sort?: VariableSort): VariableSortV1 { + switch (sort) { + case VariableSort.Disabled: + return VariableSortV1.disabled; + case VariableSort.NumericalAsc: + return VariableSortV1.numericalAsc; + case VariableSort.NumericalDesc: + return VariableSortV1.numericalDesc; + case VariableSort.AlphabeticalAsc: + return VariableSortV1.alphabeticalAsc; + case VariableSort.AlphabeticalDesc: + return VariableSortV1.alphabeticalDesc; + default: + return VariableSortV1.disabled; + } +} + +export function transformCursorSyncV2ToV1(cursorSync: DashboardCursorSync): DashboardCursorSyncV1 { + switch (cursorSync) { + case DashboardCursorSync.Crosshair: + return DashboardCursorSyncV1.Crosshair; + case DashboardCursorSync.Tooltip: + return DashboardCursorSyncV1.Tooltip; + case DashboardCursorSync.Off: + return DashboardCursorSyncV1.Off; + default: + return defaultDashboardCursorSync; + } +} + +function transformSpecialValueMatchToV1(match: SpecialValueMatch): SpecialValueMatchV1 { + switch (match) { + case SpecialValueMatch.True: + return SpecialValueMatchV1.True; + case SpecialValueMatch.False: + return SpecialValueMatchV1.False; + case SpecialValueMatch.Null: + return SpecialValueMatchV1.Null; + case SpecialValueMatch.NotANumber: + return SpecialValueMatchV1.NaN; + case SpecialValueMatch.NullAndNaN: + return SpecialValueMatchV1.NullAndNaN; + case SpecialValueMatch.Empty: + return SpecialValueMatchV1.Empty; + default: + throw new Error(`Unknown match type: ${match}`); + } +} + +export function transformValueMappingsToV1(fieldConfig: FieldConfigSource): FieldConfigSourceV1 { + return { + ...fieldConfig, + defaults: { + ...fieldConfig.defaults, + mappings: fieldConfig.defaults.mappings?.map((mapping) => { + switch (mapping.type) { + case 'value': + return { + ...mapping, + type: MappingType.ValueToText, + }; + case 'range': + return { + ...mapping, + type: MappingType.RangeToText, + }; + case 'regex': + return { + ...mapping, + type: MappingType.RegexToText, + }; + case 'special': + return { + ...mapping, + options: { + ...mapping.options, + match: transformSpecialValueMatchToV1(mapping.options.match), + }, + type: MappingType.SpecialValue, + }; + default: + return mapping; + } + }), + }, + }; +} diff --git a/public/app/features/dashboard/api/dashboard_api.ts b/public/app/features/dashboard/api/dashboard_api.ts index 8d54bb66a0e..22988facd18 100644 --- a/public/app/features/dashboard/api/dashboard_api.ts +++ b/public/app/features/dashboard/api/dashboard_api.ts @@ -46,7 +46,7 @@ class LegacyDashboardAPI implements DashboardAPI { } } -interface DashboardWithAccessInfo extends Resource { +export interface DashboardWithAccessInfo extends Resource { access: Object; // TODO... } From b898a4540d986eb7ccf3127924f47eec3c0e1542 Mon Sep 17 00:00:00 2001 From: Adam Yeats <16296989+adamyeats@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:01:54 +0000 Subject: [PATCH 13/41] Azure Monitor: Add a feature flag to toggle user auth for Azure Monitor only (#96858) * Azure Monitor: Add a feature flag to toggle user auth for Azure Monitor only * Fix condition for userIdentityEnabled * Re-add removed test * Remove unused prop * Refactor onAuthTypeChange in AzureCredentialsForm * Add frontend unit tests * Lint --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/featuremgmt/toggles_gen.json | 16 +++++ pkg/tsdb/azuremonitor/azuremonitor.go | 5 ++ pkg/tsdb/azuremonitor/azuremonitor_test.go | 45 +++++++++++-- .../ConfigEditor/AzureCredentialsForm.tsx | 25 +++++--- .../CurrentUserFallbackCredentials.test.tsx | 1 - .../CurrentUserFallbackCredentials.tsx | 1 - .../ConfigEditor/MonitorConfig.test.tsx | 64 +++++++++++++++++-- .../components/ConfigEditor/MonitorConfig.tsx | 2 +- 13 files changed, 151 insertions(+), 22 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index ab8dad22ef3..3f7c27beb59 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -81,6 +81,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | | `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | | `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes | +| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes | ## Public preview feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 9c23ab2d47a..a4add656705 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -241,5 +241,6 @@ export interface FeatureToggles { jaegerBackendMigration?: boolean; reportingUseRawTimeRange?: boolean; alertingUIOptimizeReducer?: boolean; + azureMonitorEnableUserAuth?: boolean; alertingNotificationsStepMode?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 2c9b654252c..d0d09f33e72 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1666,6 +1666,13 @@ var ( Owner: grafanaAlertingSquad, Expression: "true", // enabled by default }, + { + Name: "azureMonitorEnableUserAuth", + Description: "Enables user auth for Azure Monitor datasource only", + Stage: FeatureStageGeneralAvailability, + Owner: grafanaPartnerPluginsSquad, + Expression: "true", // Enabled by default for now + }, { Name: "alertingNotificationsStepMode", Description: "Enables simplified step mode in the notifications section", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 2896b4d9eea..1db211b319a 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -222,4 +222,5 @@ crashDetection,experimental,@grafana/observability-traces-and-profiling,false,fa jaegerBackendMigration,experimental,@grafana/oss-big-tent,false,false,false reportingUseRawTimeRange,preview,@grafana/sharing-squad,false,false,false alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true +azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false alertingNotificationsStepMode,experimental,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 588d9c5ff60..ab64c99ac64 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -899,6 +899,10 @@ const ( // Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query FlagAlertingUIOptimizeReducer = "alertingUIOptimizeReducer" + // FlagAzureMonitorEnableUserAuth + // Enables user auth for Azure Monitor datasource only + FlagAzureMonitorEnableUserAuth = "azureMonitorEnableUserAuth" + // FlagAlertingNotificationsStepMode // Enables simplified step mode in the notifications section FlagAlertingNotificationsStepMode = "alertingNotificationsStepMode" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e8ecedb79fb..4b1cc4124d7 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -660,6 +660,22 @@ "expression": "false" } }, + { + "metadata": { + "name": "azureMonitorEnableUserAuth", + "resourceVersion": "1732189410576", + "creationTimestamp": "2024-11-21T11:42:29Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-11-21 11:43:30.576196 +0000 UTC" + } + }, + "spec": { + "description": "Enables user auth for Azure Monitor datasource only", + "stage": "GA", + "codeowner": "@grafana/partner-datasources", + "expression": "true" + } + }, { "metadata": { "name": "azureMonitorLogLimit", diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 442ef4d2cea..476386c90a2 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -10,6 +10,7 @@ import ( "net/http" "strconv" + "github.com/grafana/grafana-azure-sdk-go/v2/azcredentials" "github.com/grafana/grafana-azure-sdk-go/v2/azsettings" "github.com/grafana/grafana-azure-sdk-go/v2/azusercontext" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -139,6 +140,10 @@ func NewInstanceSettings(clientProvider *httpclient.Provider, executors map[stri return nil, err } + if credentials.AzureAuthType() == azcredentials.AzureAuthCurrentUserIdentity && !backend.GrafanaConfigFromContext(ctx).FeatureToggles().IsEnabled("azureMonitorEnableUserAuth") { + return nil, backend.DownstreamError(errors.New("current user authentication is not enabled for azure monitor")) + } + model := types.DatasourceInfo{ Credentials: credentials, Settings: azMonitorSettings, diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go index 992c5b76db2..ae5cb7f6630 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/experimental/featuretoggles" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -59,9 +60,29 @@ func TestNewInstanceSettings(t *testing.T) { tests := []struct { name string settings backend.DataSourceInstanceSettings - expectedModel types.DatasourceInfo + expectedModel *types.DatasourceInfo Err require.ErrorAssertionFunc + setupContext func(ctx context.Context) context.Context }{ + { + name: "current user authentication disabled by feature toggle", + settings: backend.DataSourceInstanceSettings{ + JSONData: []byte(`{"azureAuthType":"currentuser"}`), + DecryptedSecureJSONData: map[string]string{}, + ID: 60, + }, + expectedModel: nil, + Err: func(t require.TestingT, err error, _ ...interface{}) { + require.Error(t, err) + require.Contains(t, err.Error(), "current user authentication is not enabled for azure monitor") + }, + setupContext: func(ctx context.Context) context.Context { + featureToggles := backend.NewGrafanaCfg(map[string]string{ + featuretoggles.EnabledFeatures: "", // No enabled features + }) + return backend.WithGrafanaConfig(ctx, featureToggles) + }, + }, { name: "creates an instance", settings: backend.DataSourceInstanceSettings{ @@ -69,7 +90,7 @@ func TestNewInstanceSettings(t *testing.T) { DecryptedSecureJSONData: map[string]string{"key": "value"}, ID: 40, }, - expectedModel: types.DatasourceInfo{ + expectedModel: &types.DatasourceInfo{ Credentials: &azcredentials.AzureManagedIdentityCredentials{}, Settings: types.AzureMonitorSettings{}, Routes: testRoutes, @@ -87,7 +108,7 @@ func TestNewInstanceSettings(t *testing.T) { DecryptedSecureJSONData: map[string]string{"clientSecret": "secret"}, ID: 50, }, - expectedModel: types.DatasourceInfo{ + expectedModel: &types.DatasourceInfo{ Credentials: &azcredentials.AzureClientSecretCredentials{ AzureCloud: "AzureCustomizedCloud", ClientSecret: "secret", @@ -117,11 +138,23 @@ func TestNewInstanceSettings(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if tt.setupContext != nil { + ctx = tt.setupContext(ctx) + } + factory := NewInstanceSettings(&httpclient.Provider{}, map[string]azDatasourceExecutor{}, log.DefaultLogger) - instance, err := factory(context.Background(), tt.settings) + instance, err := factory(ctx, tt.settings) + tt.Err(t, err) - if !cmp.Equal(instance, tt.expectedModel) { - t.Errorf("Unexpected instance: %v", cmp.Diff(instance, tt.expectedModel)) + + if tt.expectedModel == nil { + require.Nil(t, instance, "Expected instance to be nil") + } else { + require.NotNil(t, instance, "Expected instance to be created") + if !cmp.Equal(instance, *tt.expectedModel) { + t.Errorf("Unexpected instance: %v", cmp.Diff(instance, *tt.expectedModel)) + } } }) } diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx index 3857dba771c..fa80caadc82 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx @@ -73,17 +73,27 @@ export const AzureCredentialsForm = (props: Props) => { }, [managedIdentityEnabled, workloadIdentityEnabled, userIdentityEnabled]); const onAuthTypeChange = (selected: SelectableValue) => { - const defaultAuthType = managedIdentityEnabled - ? 'msi' - : workloadIdentityEnabled - ? 'workloadidentity' - : userIdentityEnabled - ? 'currentuser' - : 'clientsecret'; + const defaultAuthType = (() => { + if (managedIdentityEnabled) { + return 'msi'; + } + + if (workloadIdentityEnabled) { + return 'workloadidentity'; + } + + if (userIdentityEnabled) { + return 'currentuser'; + } + + return 'clientsecret'; + })(); + const updated: AzureCredentials = { ...credentials, authType: selected.value || defaultAuthType, }; + onCredentialsChange(updated); }; @@ -122,7 +132,6 @@ export const AzureCredentialsForm = (props: Props) => { disabled={disabled} managedIdentityEnabled={managedIdentityEnabled} workloadIdentityEnabled={workloadIdentityEnabled} - userIdentityEnabled={userIdentityEnabled} /> )} diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.test.tsx index 07ca09121f8..b466a0d27ac 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.test.tsx @@ -10,7 +10,6 @@ const setup = (propsFunc?: (props: Props) => Props) => { let props: Props = { managedIdentityEnabled: true, workloadIdentityEnabled: true, - userIdentityEnabled: true, credentials: { authType: 'currentuser' }, azureCloudOptions: [ { value: 'AzureCloud', label: 'Azure' }, diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx index 9d5fbf3063f..951afa88344 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx @@ -13,7 +13,6 @@ import { AppRegistrationCredentials } from './AppRegistrationCredentials'; export interface Props { managedIdentityEnabled: boolean; workloadIdentityEnabled: boolean; - userIdentityEnabled: boolean; credentials: AadCurrentUserCredentials; azureCloudOptions?: SelectableValue[]; onCredentialsChange: (updatedCredentials: AzureCredentials) => void; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx index 992bab732f6..e7d2331d954 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx @@ -1,21 +1,37 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; + +import { config } from '@grafana/runtime'; import { createMockDatasourceSettings } from '../../__mocks__/datasourceSettings'; import { MonitorConfig, Props } from './MonitorConfig'; -const mockDatasourceSettings = createMockDatasourceSettings(); - const defaultProps: Props = { - options: mockDatasourceSettings, + options: createMockDatasourceSettings(), updateOptions: jest.fn(), getSubscriptions: jest.fn().mockResolvedValue([]), }; describe('MonitorConfig', () => { + beforeEach(() => { + config.azure = { + ...config.azure, + managedIdentityEnabled: false, + workloadIdentityEnabled: false, + userIdentityEnabled: false, + }; + config.featureToggles = { + azureMonitorEnableUserAuth: false, + }; + }); + + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + it('should render component', () => { render(); - expect(screen.getByText('Azure Cloud')).toBeInTheDocument(); }); @@ -29,6 +45,7 @@ describe('MonitorConfig', () => { expect(defaultProps.updateOptions).toHaveBeenCalled(); expect(screen.getByText('Azure Cloud')).toBeInTheDocument(); }); + expect(defaultProps.options.jsonData.azureAuthType).toBe('clientsecret'); it('should render component and set the default subscription if specified', async () => { @@ -39,4 +56,41 @@ describe('MonitorConfig', () => { expect(screen.getByText('Azure Cloud')).toBeInTheDocument(); await waitFor(() => expect(screen.getByText('Test Sub')).toBeInTheDocument()); }); + + it('should render with user identity enabled when feature toggle is true', async () => { + config.azure.userIdentityEnabled = true; + config.featureToggles.azureMonitorEnableUserAuth = true; + + const optionsWithUserAuth = createMockDatasourceSettings({ + jsonData: { azureAuthType: 'currentuser' }, + }); + + render(); + + const authDropdownInput = screen.getByTestId('data-testid auth-type').querySelector('input'); + + if (authDropdownInput) { + fireEvent.mouseDown(authDropdownInput); + } + + await waitFor(() => { + expect( + screen.getByText( + (content, element) => element?.tagName?.toLowerCase() === 'span' && /Current User/i.test(content) + ) + ).toBeInTheDocument(); + }); + }); + + it('should render with user identity disabled when feature toggle is false', async () => { + config.azure.userIdentityEnabled = true; + config.featureToggles.azureMonitorEnableUserAuth = false; + + render(); + + await waitFor(() => { + expect(screen.getByText('Authentication')).toBeInTheDocument(); + expect(screen.queryByText(/Current User/i)).not.toBeInTheDocument(); + }); + }); }); diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx index 94ea592bf3d..a989a01056d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.tsx @@ -53,7 +53,7 @@ export const MonitorConfig = (props: Props) => { Date: Wed, 27 Nov 2024 14:04:40 +0000 Subject: [PATCH 14/41] Logs: fix canKeepDisplayedFields with possibly undefined queries (#97103) --- .../features/explore/Logs/utils/logs.test.ts | 27 +++++++++++++++++++ .../app/features/explore/Logs/utils/logs.ts | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 public/app/features/explore/Logs/utils/logs.test.ts diff --git a/public/app/features/explore/Logs/utils/logs.test.ts b/public/app/features/explore/Logs/utils/logs.test.ts new file mode 100644 index 00000000000..42883ff1ad4 --- /dev/null +++ b/public/app/features/explore/Logs/utils/logs.test.ts @@ -0,0 +1,27 @@ +import { DataQuery } from '@grafana/data'; + +import { canKeepDisplayedFields } from './logs'; + +describe('canKeepDisplayedFields', () => { + test('Returns false when passing no queries', () => { + expect(canKeepDisplayedFields(undefined, [])).toBe(false); + }); + + test('Returns false when some prev queries are undefined', () => { + const logQueries: DataQuery[] = [{ refId: 'A' }, { refId: 'B' }]; + const prevLogQueries = [{ refId: 'C' }]; + expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(false); + }); + + test('Returns false when some new queries are undefined', () => { + const logQueries: DataQuery[] = [{ refId: 'A' }]; + const prevLogQueries = [{ refId: 'C' }, { refId: 'B' }]; + expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(false); + }); + + test('Returns true when the queries exactly match', () => { + const logQueries: DataQuery[] = [{ refId: 'C' }, { refId: 'B' }]; + const prevLogQueries = [{ refId: 'C' }, { refId: 'B' }]; + expect(canKeepDisplayedFields(logQueries, prevLogQueries)).toBe(true); + }); +}); diff --git a/public/app/features/explore/Logs/utils/logs.ts b/public/app/features/explore/Logs/utils/logs.ts index 61e4f9ce5d5..edcaf0fc2c4 100644 --- a/public/app/features/explore/Logs/utils/logs.ts +++ b/public/app/features/explore/Logs/utils/logs.ts @@ -17,7 +17,7 @@ export const canKeepDisplayedFields = (logsQueries: DataQuery[] | undefined, pre return false; } for (let i = 0; i < logsQueries.length; i++) { - if (!shallowCompare(logsQueries[i], prevLogsQueries[i])) { + if (!logsQueries[i] || !prevLogsQueries[i] || !shallowCompare(logsQueries[i], prevLogsQueries[i])) { return false; } } From a0de712bcaab72eb69bda3b4e78e61343f89455c Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:13:27 +0100 Subject: [PATCH 15/41] Alerting: Fix simplified query step (#97046) * Fix simplified query step * remove is advancedMode and use editorSettings.simplifiedQueryEditor * remove unnecessary Boolean conversion * fix when feature toggle is disabled * fix test * simplify code * fix when not ff is not enabled --- .../components/rule-editor/QueryWrapper.tsx | 4 +- .../alert-rule-form/AlertRuleForm.tsx | 55 ++++++++++++++---- .../QueryAndExpressionsStep.tsx | 41 +++++++------- .../determineAdvancedMode.test.ts | 56 +------------------ .../useAdvancedMode.ts | 32 ++--------- 5 files changed, 77 insertions(+), 111 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx index 62f2ea6159a..0989fada424 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx @@ -14,6 +14,7 @@ import { RelativeTimeRange, ThresholdsConfig, } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { GraphThresholdsStyleMode, Icon, InlineField, Input, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { logInfo } from 'app/features/alerting/unified/Analytics'; @@ -81,7 +82,8 @@ export const QueryWrapper = ({ const defaults = dsInstance?.getDefaultQuery ? dsInstance.getDefaultQuery(CoreApp.UnifiedAlerting) : {}; const { getValues } = useFormContext(); - const isAdvancedMode = getValues('editorSettings.simplifiedQueryEditor') !== true; + const isSwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false; + const isAdvancedMode = isSwitchModeEnabled ? getValues('editorSettings.simplifiedQueryEditor') !== true : true; const queryWithDefaults = { ...defaults, diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 185406b26ef..6de19d58e7d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -67,7 +67,11 @@ import { GrafanaFolderAndLabelsStep } from '../GrafanaFolderAndLabelsStep'; import { NotificationsStep } from '../NotificationsStep'; import { RecordingRulesNameSpaceAndGroupStep } from '../RecordingRulesNameSpaceAndGroupStep'; import { RuleInspector } from '../RuleInspector'; -import { QueryAndExpressionsStep } from '../query-and-alert-condition/QueryAndExpressionsStep'; +import { + areQueriesTransformableToSimpleCondition, + isExpressionQueryInAlert, + QueryAndExpressionsStep, +} from '../query-and-alert-condition/QueryAndExpressionsStep'; import { translateRouteParamToRuleType } from '../util'; type Props = { @@ -386,15 +390,17 @@ function formValuesFromQueryParams(ruleDefinition: string, type: RuleFormType): }; } - return setInstantOrRange( - ignoreHiddenQueries({ - ...getDefaultFormValues(), - ...ruleFromQueryParams, - annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []), - queries: ruleFromQueryParams.queries ?? getDefaultQueries(), - type: type || RuleFormType.grafana, - evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL, - }) + return setQueryEditorSettings( + setInstantOrRange( + ignoreHiddenQueries({ + ...getDefaultFormValues(), + ...ruleFromQueryParams, + annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []), + queries: ruleFromQueryParams.queries ?? getDefaultQueries(), + type: type || RuleFormType.grafana, + evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL, + }) + ) ); } @@ -405,6 +411,35 @@ function formValuesFromPrefill(rule: Partial): RuleFormValues { }); } +function setQueryEditorSettings(values: RuleFormValues): RuleFormValues { + const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false; + + if (!isQuerySwitchModeEnabled) { + return { + ...values, + editorSettings: { + simplifiedQueryEditor: false, + simplifiedNotificationEditor: true, // actually it doesn't matter in this case + }, + }; + } + + // data queries only + const dataQueries = values.queries.filter((query) => !isExpressionQuery(query.model)); + + // expression queries only + const expressionQueries = values.queries.filter((query) => isExpressionQueryInAlert(query)); + + const queryParamsAreTransformable = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); + return { + ...values, + editorSettings: { + simplifiedQueryEditor: queryParamsAreTransformable, + simplifiedNotificationEditor: true, + }, + }; +} + function setInstantOrRange(values: RuleFormValues): RuleFormValues { return { ...values, diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 7d0eaeb5b97..8185e12f65c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -32,7 +32,6 @@ import { import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler'; -import { useURLSearchParams } from '../../../hooks/useURLSearchParams'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; import { isPromOrLokiQuery, PromOrLokiQuery } from '../../../utils/rule-form'; @@ -134,9 +133,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P } = useFormContext(); const { queryPreviewData, runQueries, cancelQueries, isPreviewLoading, clearPreviewData } = useAlertQueryRunner(); - const [queryParams] = useURLSearchParams(); const isSwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false; - const isNewFromQueryParams = queryParams.has('defaults') && !editingExistingRule; const initialState = { queries: getValues('queries'), @@ -167,20 +164,23 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P const isCloudAlertRuleType = isCloudAlertingRuleByType(type); const [showResetModeModal, setShowResetModal] = useState(false); - const { isAdvancedMode, simpleCondition, setSimpleCondition } = useAdvancedMode( - editorSettings, + const simplifiedQueryInForm = editorSettings?.simplifiedQueryEditor; + + const { simpleCondition, setSimpleCondition } = useAdvancedMode( + simplifiedQueryInForm, isGrafanaAlertingType, - isNewFromQueryParams, dataQueries, expressionQueries ); + const simplifiedQueryStep = isSwitchModeEnabled ? getValues('editorSettings.simplifiedQueryEditor') : false; + // If we switch to simple mode we need to update the simple condition with the data in the queries reducer useEffect(() => { - if (!isAdvancedMode && isGrafanaAlertingType) { + if (simplifiedQueryStep && isGrafanaAlertingType) { setSimpleCondition(getSimpleConditionFromExpressions(expressionQueries)); } - }, [isAdvancedMode, expressionQueries, isGrafanaAlertingType, setSimpleCondition]); + }, [simplifiedQueryStep, expressionQueries, isGrafanaAlertingType, setSimpleCondition]); const { rulesSourcesWithRuler } = useRulesSourcesWithRuler(); @@ -192,14 +192,14 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P return; } // we need to be sure the condition is set once we switch to simple mode - if (!isAdvancedMode) { + if (simplifiedQueryStep) { setValue('condition', SimpleConditionIdentifier.thresholdId); runQueries(getValues('queries'), SimpleConditionIdentifier.thresholdId); } else { runQueries(getValues('queries'), condition || (getValues('condition') ?? '')); } }, - [isCloudAlertRuleType, runQueries, getValues, isAdvancedMode, setValue] + [isCloudAlertRuleType, runQueries, getValues, simplifiedQueryStep, setValue] ); // whenever we update the queries we have to update the form too @@ -472,13 +472,12 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P if (!type) { return null; } - const switchMode = isGrafanaAlertingType && isSwitchModeEnabled ? { - isAdvancedMode, + isAdvancedMode: !simplifiedQueryStep, setAdvancedMode: (isAdvanced: boolean) => { - if (!isAdvanced) { + if (!getValues('editorSettings.simplifiedQueryEditor')) { if (!areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries)) { setShowResetModal(true); return; @@ -573,7 +572,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P condition={condition} onSetCondition={handleSetCondition} /> - {isAdvancedMode && ( + {!simplifiedQueryStep && ( @@ -688,7 +687,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P confirmText="Deactivate" icon="exclamation-triangle" onConfirm={() => { - setValue('editorSettings.simplifiedNotificationEditor', true); + setValue('editorSettings.simplifiedQueryEditor', true); setShowResetModal(false); dispatch(resetToSimpleCondition()); }} @@ -768,7 +767,7 @@ const useSetExpressionAndDataSource = () => { }; }; -function isExpressionQueryInAlert( +export function isExpressionQueryInAlert( query: AlertQuery ): query is AlertQuery { return isExpressionQuery(query.model); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/determineAdvancedMode.test.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/determineAdvancedMode.test.ts index e82f1497399..f296957161e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/determineAdvancedMode.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/determineAdvancedMode.test.ts @@ -1,76 +1,26 @@ -import { produce } from 'immer'; - -import { dataQuery, reduceExpression, thresholdExpression } from '../../../mocks'; - import { determineAdvancedMode } from './useAdvancedMode'; -const dataQueries = [dataQuery]; -const expressionQueries = [reduceExpression, thresholdExpression]; - describe('determineAdvancedMode', () => { it('should return true if simplifiedQueryEditor is false', () => { - const editorSettings = { simplifiedQueryEditor: false, simplifiedNotificationEditor: true }; const isGrafanaAlertingType = true; - const isNewFromQueryParams = false; - const result = determineAdvancedMode( - editorSettings, - isGrafanaAlertingType, - isNewFromQueryParams, - dataQueries, - expressionQueries - ); + const result = determineAdvancedMode(false, isGrafanaAlertingType); expect(result).toBe(true); }); it('should return true if isGrafanaAlertingType is false', () => { - const editorSettings = { simplifiedQueryEditor: true, simplifiedNotificationEditor: true }; const isGrafanaAlertingType = false; - const isNewFromQueryParams = false; - const result = determineAdvancedMode( - editorSettings, - isGrafanaAlertingType, - isNewFromQueryParams, - dataQueries, - expressionQueries - ); - - expect(result).toBe(true); - }); - - const editorSettings = { simplifiedQueryEditor: true, simplifiedNotificationEditor: true }; - it('should return true if isNewFromQueryParams is true and queries are not transformable', () => { - const isGrafanaAlertingType = true; - const isNewFromQueryParams = true; - - const newQuery = produce(dataQuery, (draft) => { - draft.refId = 'whatever'; - }); - - const result = determineAdvancedMode( - editorSettings, - isGrafanaAlertingType, - isNewFromQueryParams, - [newQuery], - expressionQueries - ); + const result = determineAdvancedMode(true, isGrafanaAlertingType); expect(result).toBe(true); }); it('should return false if all conditions are false', () => { const isGrafanaAlertingType = true; - const isNewFromQueryParams = false; - const result = determineAdvancedMode( - editorSettings, - isGrafanaAlertingType, - isNewFromQueryParams, - dataQueries, - expressionQueries - ); + const result = determineAdvancedMode(true, isGrafanaAlertingType); expect(result).toBe(false); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts index 3fbf4f91874..5708efba7cb 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts @@ -5,8 +5,6 @@ import { EvalFunction } from 'app/features/alerting/state/alertDef'; import { ExpressionQuery } from 'app/features/expressions/types'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; -import { SimplifiedEditor } from '../../../types/rule-form'; - import { areQueriesTransformableToSimpleCondition } from './QueryAndExpressionsStep'; import { getSimpleConditionFromExpressions, SimpleCondition } from './SimpleCondition'; @@ -27,19 +25,8 @@ function initializeSimpleCondition( }; } } -export function determineAdvancedMode( - editorSettings: SimplifiedEditor | undefined, - isGrafanaAlertingType: boolean, - isNewFromQueryParams: boolean, - dataQueries: Array>, - expressionQueries: Array> -) { - const queryParamsAreTransformable = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries); - return ( - Boolean(editorSettings?.simplifiedQueryEditor) === false || - !isGrafanaAlertingType || - (isNewFromQueryParams && !queryParamsAreTransformable) - ); +export function determineAdvancedMode(simplifiedQueryEditor: boolean | undefined, isGrafanaAlertingType: boolean) { + return simplifiedQueryEditor === false || !isGrafanaAlertingType; } /* @@ -47,29 +34,22 @@ export function determineAdvancedMode( depending on the editor settings, the alert type, and the queries. */ export const useAdvancedMode = ( - editorSettings: SimplifiedEditor | undefined, + simplifiedQueryEditor: boolean | undefined, isGrafanaAlertingType: boolean, - isNewFromQueryParams: boolean, dataQueries: Array>, expressionQueries: Array> ) => { - const isAdvancedMode = determineAdvancedMode( - editorSettings, - isGrafanaAlertingType, - isNewFromQueryParams, - dataQueries, - expressionQueries - ); + const isAdvancedMode = determineAdvancedMode(simplifiedQueryEditor, isGrafanaAlertingType); const [simpleCondition, setSimpleCondition] = useState( initializeSimpleCondition(isGrafanaAlertingType, dataQueries, expressionQueries) ); useEffect(() => { - if (!isAdvancedMode && isGrafanaAlertingType) { + if (isGrafanaAlertingType && !isAdvancedMode) { setSimpleCondition(getSimpleConditionFromExpressions(expressionQueries)); } }, [isAdvancedMode, expressionQueries, isGrafanaAlertingType]); - return { isAdvancedMode, simpleCondition, setSimpleCondition }; + return { simpleCondition, setSimpleCondition }; }; From 33e70054835d438d2ac2180dba16ddc6e17e427b Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Wed, 27 Nov 2024 16:24:52 +0200 Subject: [PATCH 16/41] API keys: Update text when no API keys were found in DB (#97102) * update text when no API keys were found in DB * text update --- public/app/features/api-keys/MigrateToServiceAccountsCard.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx b/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx index e091aaa2833..87ff5e72ed4 100644 --- a/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx +++ b/public/app/features/api-keys/MigrateToServiceAccountsCard.tsx @@ -56,8 +56,7 @@ export const MigrateToServiceAccountsCard = ({ onMigrate, apikeysCount, disabled <>
- No API keys were found. If you reload the browser, this tab will be not available. If any API keys are - created, this tab will appear again. + No API keys were found. If you reload the browser, this page will not be available anymore.
From 2e14c20c71b2e78b9ed18ef83414b04025f4f4f0 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:59:25 +0100 Subject: [PATCH 17/41] Alerting: Fix removing reducer when inital value is instant (#97054) * Fix removing reducer when inital value is instant * use ID to allow clicking label to toggle the switch --------- Co-authored-by: Gilles De Mey --- .../rule-editor/RuleEditorSection.tsx | 1 + .../QueryAndExpressionsStep.tsx | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx index a020e50542f..fe92306e403 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx @@ -37,6 +37,7 @@ export const RuleEditorSection = ({ {switchMode && ( { @@ -151,6 +152,15 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P return queries.filter((query) => isExpressionQueryInAlert(query)); }, [queries]); + useEffect(() => { + // we only remove or add the reducer(optimize reducer) expression when creating a new alert. + // When editing an alert, we assume the user wants to manually adjust expressions and queries for more control and customization. + + if (!editingExistingRule && isOptimizeReducerEnabled) { + dispatch(optimizeReduceExpression({ updatedQueries: dataQueries, expressionQueries })); + } + }, [dataQueries, expressionQueries, editingExistingRule, isOptimizeReducerEnabled]); + const [type, condition, dataSourceName, editorSettings] = watch([ 'type', 'condition', @@ -265,7 +275,6 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P ); const updateExpressionAndDatasource = useSetExpressionAndDataSource(); - const isOptimizeReducerEnabled = config.featureToggles.alertingUIOptimizeReducer ?? false; const onChangeQueries = useCallback( (updatedQueries: AlertQuery[]) => { @@ -279,12 +288,6 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false }); updateExpressionAndDatasource(updatedQueries); - // we only remove or add the reducer(optimize reducer) expression when creating a new alert. - // When editing an alert, we assume the user wants to manually adjust expressions and queries for more control and customization. - if (!editingExistingRule && isOptimizeReducerEnabled) { - dispatch(optimizeReduceExpression({ updatedQueries, expressionQueries })); - } - dispatch(setDataQueries(updatedQueries)); dispatch(updateExpressionTimeRange()); @@ -294,7 +297,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P dispatch(rewireExpressions({ oldRefId, newRefId })); } }, - [queries, updateExpressionAndDatasource, getValues, setValue, editingExistingRule, isOptimizeReducerEnabled] + [queries, updateExpressionAndDatasource, getValues, setValue] ); const onChangeRecordingRulesQueries = useCallback( From 10837223463320317a87f5747756dd252f2de011 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:21:08 -0500 Subject: [PATCH 18/41] Add column lists to Shared dashboards page (#96941) --- .../shared-dashboards/index.md | 164 ++++++++---------- 1 file changed, 68 insertions(+), 96 deletions(-) diff --git a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md b/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md index 711127d2db4..2bfba846315 100644 --- a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md +++ b/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md @@ -215,107 +215,79 @@ Learn more about the kind of information provided in the [dashboard insights doc Externally shared dashboards _should_ work with any data source that has the properties `backend` and `alerting` both set to true in its `plugin.json`. However, this can't always be guaranteed because plugin developers can override this functionality. The following lists include data sources confirmed to work with externally shared dashboards and data sources that should work, but have not been confirmed as compatible. -### Confirmed: +### Confirmed - - - - - -
-
    -
  • ClickHouse
  • -
  • CloudWatch
  • -
  • Elasticsearch
  • -
  • Infinity
  • -
  • InfluxDB
  • -
  • Loki
  • -
  • Microsoft SQL Server
  • -
-
-
    -
  • MongoDB
  • -
  • MySQL
  • -
  • Oracle Database
  • -
  • PostgreSQL
  • -
  • Prometheus
  • -
  • Redis
  • -
  • SQLite
  • -
-
+{{< column-list >}} -### Unsupported: +- ClickHouse +- CloudWatch +- Elasticsearch +- Infinity +- InfluxDB +- Loki +- Microsoft SQL Server +- MongoDB +- MySQL +- Oracle Database +- PostgreSQL +- Prometheus +- Redis +- SQLite - - - - -
-
    -
  • Graphite
  • -
-
+{{< /column-list >}} -### Unconfirmed: +### Unsupported - - - - - - -
-
    -
  • Altinity plugin for ClickHouse
  • -
  • Amazon Athena
  • -
  • Amazon Redshift
  • -
  • Amazon Timestream
  • -
  • Apache Cassandra
  • -
  • AppDynamics
  • -
  • Azure Data Explorer Datasource
  • -
  • Azure Monitor
  • -
  • CSV
  • -
  • DB2 Datasource
  • -
  • Databricks
  • -
  • Datadog
  • -
  • Dataset
  • -
  • Druid
  • -
-
-
    -
  • Dynatrace
  • -
  • GitHub
  • -
  • Google BigQuery
  • -
  • Grafana for YNAB
  • -
  • Honeycomb
  • -
  • Jira
  • -
  • Mock
  • -
  • Neo4j Datasource
  • -
  • New Relic
  • -
  • OPC UA (Unified Architecture)
  • -
  • Open Distro for Elasticsearch
  • -
  • OpenSearch
  • -
  • OpenTSDB
  • -
-
-
    -
  • Orbit
  • -
  • SAP HANA®
  • -
  • Salesforce
  • -
  • Sentry
  • -
  • ServiceNow
  • -
  • Snowflake
  • -
  • Splunk
  • -
  • Splunk Infrastructure Monitoring
  • -
  • Sqlyze data source
  • -
  • TDengine
  • -
  • Vertica
  • -
  • Wavefront
  • -
  • X-Ray
  • -
  • kdb+
  • -
  • simple grpc data source
  • -
-
+- Graphite + +### Unconfirmed + +{{< column-list >}} + +- Altinity plugin for ClickHouse +- Amazon Athena +- Amazon Redshift +- Amazon Timestream +- Apache Cassandra +- AppDynamics +- Azure Data Explorer Datasource +- Azure Monitor +- CSV +- DB2 Datasource +- Databricks +- Datadog +- Dataset +- Druid +- Dynatrace +- GitHub +- Google BigQuery +- Grafana for YNAB +- Honeycomb +- Jira +- Mock +- Neo4j Datasource +- New Relic +- OPC UA (Unified Architecture) +- Open Distro for Elasticsearch +- OpenSearch +- OpenTSDB +- Orbit +- SAP HANA® +- Salesforce +- Sentry +- ServiceNow +- Snowflake +- Splunk +- Splunk Infrastructure Monitoring +- Sqlyze data source +- TDengine +- Vertica +- Wavefront +- X-Ray +- kdb+ +- simple grpc data source + +{{< /column-list >}} ## Limitations From 17246eb7fc549851cf4d5d2708c9b984e405de2f Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:21:33 -0500 Subject: [PATCH 19/41] Docs: Add column lists to panel config pages (#96940) --- .../configure-data-links/index.md | 28 +++++++++++++------ .../configure-overrides/index.md | 28 +++++++++++++------ .../configure-standard-options/index.md | 26 ++++++++++++----- .../configure-thresholds/index.md | 24 +++++++++++----- .../configure-tooltips/index.md | 20 ++++++++----- .../configure-value-mappings/index.md | 25 ++++++++++++----- 6 files changed, 107 insertions(+), 44 deletions(-) diff --git a/docs/sources/panels-visualizations/configure-data-links/index.md b/docs/sources/panels-visualizations/configure-data-links/index.md index 3032bd3e45b..31bf6df9b67 100644 --- a/docs/sources/panels-visualizations/configure-data-links/index.md +++ b/docs/sources/panels-visualizations/configure-data-links/index.md @@ -143,14 +143,26 @@ If there's only one data link in the visualization, clicking anywhere on the vis You can configure data links for the following visualizations: -| | | | -| ------------------------------ | ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Heatmap](ref:heatmap) | [Status history](ref:status-history) | -| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) | -| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) | -| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) | -| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) | -| [Geomap](ref:geomap) | | | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Bar gauge](ref:bar-gauge) +- [Candlestick](ref:candlestick) +- [Canvas](ref:canvas) +- [Gauge](ref:gauge) +- [Geomap](ref:geomap) +- [Heatmap](ref:heatmap) +- [Histogram](ref:histogram) +- [Pie chart](ref:pie-chart) +- [Stat](ref:stat) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Table](ref:table) +- [Time series](ref:time-series) +- [Trend](ref:trend) +- [XY chart](ref:xy-chart) + +{{< /column-list >}} ## Data link variables diff --git a/docs/sources/panels-visualizations/configure-overrides/index.md b/docs/sources/panels-visualizations/configure-overrides/index.md index 2b7264ee6d4..c01a72bd781 100644 --- a/docs/sources/panels-visualizations/configure-overrides/index.md +++ b/docs/sources/panels-visualizations/configure-overrides/index.md @@ -121,14 +121,26 @@ After you've set them, your overrides appear in both the **All** and **Overrides You can configure field overrides for the following visualizations: -| | | | -| ------------------------------ | ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Heatmap](ref:heatmap) | [Status history](ref:status-history) | -| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) | -| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) | -| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) | -| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) | -| [Geomap](ref:geomap) | | | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Bar gauge](ref:bar-gauge) +- [Candlestick](ref:candlestick) +- [Canvas](ref:canvas) +- [Gauge](ref:gauge) +- [Geomap](ref:geomap) +- [Heatmap](ref:heatmap) +- [Histogram](ref:histogram) +- [Pie chart](ref:pie-chart) +- [Stat](ref:stat) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Table](ref:table) +- [Time series](ref:time-series) +- [Trend](ref:trend) +- [XY chart](ref:xy-chart) + +{{< /column-list >}} ## Override rules diff --git a/docs/sources/panels-visualizations/configure-standard-options/index.md b/docs/sources/panels-visualizations/configure-standard-options/index.md index e39626fc2f1..cbbdfb570b6 100644 --- a/docs/sources/panels-visualizations/configure-standard-options/index.md +++ b/docs/sources/panels-visualizations/configure-standard-options/index.md @@ -126,13 +126,25 @@ For more granular control over the display of fields, refer to [Configure overri You can configure standard options for the following visualizations: -| | | | -| ------------------------------ | ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) | -| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) | -| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) | -| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) | -| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | [XY chart](ref:xy-chart) | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Bar gauge](ref:bar-gauge) +- [Candlestick](ref:candlestick) +- [Canvas](ref:canvas) +- [Gauge](ref:gauge) +- [Geomap](ref:geomap) +- [Histogram](ref:histogram) +- [Pie chart](ref:pie-chart) +- [Stat](ref:stat) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Table](ref:table) +- [Time series](ref:time-series) +- [Trend](ref:trend) +- [XY chart](ref:xy-chart) + +{{< /column-list >}} ## Standard options diff --git a/docs/sources/panels-visualizations/configure-thresholds/index.md b/docs/sources/panels-visualizations/configure-thresholds/index.md index 102b8ea4505..220f8464450 100644 --- a/docs/sources/panels-visualizations/configure-thresholds/index.md +++ b/docs/sources/panels-visualizations/configure-thresholds/index.md @@ -110,13 +110,23 @@ You can also use thresholds to: You can set thresholds in the following visualizations: -| | | | -| ------------------------------ | ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) | -| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) | -| [Candlestick](ref:candlestick) | [Stat](ref:stat) | [Time series](ref:time-series) | -| [Canvas](ref:canvas) | [State timeline](ref:state-timeline) | [Trend](ref:trend) | -| [Gauge](ref:gauge) | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Bar gauge](ref:bar-gauge) +- [Candlestick](ref:candlestick) +- [Canvas](ref:canvas) +- [Gauge](ref:gauge) +- [Geomap](ref:geomap) +- [Histogram](ref:histogram) +- [Stat](ref:stat) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Table](ref:table) +- [Time series](ref:time-series) +- [Trend](ref:trend) + +{{< /column-list >}} ## Default thresholds diff --git a/docs/sources/panels-visualizations/configure-tooltips/index.md b/docs/sources/panels-visualizations/configure-tooltips/index.md index 79f7010d6a2..4c7ee9e11e6 100644 --- a/docs/sources/panels-visualizations/configure-tooltips/index.md +++ b/docs/sources/panels-visualizations/configure-tooltips/index.md @@ -84,13 +84,19 @@ When you hover your cursor over a visualization, Grafana can display tooltips th You can configure tooltips for the following visualizations: -| | | -| ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Status history](ref:status-history) | -| [Candlestick](ref:candlestick) | [Time series](ref:time-series) | -| [Heatmap](ref:heatmap) | [Trend](ref:trend) | -| [Pie chart](ref:pie-chart) | [XY chart](ref:xy-chart) | -| [State timeline](ref:state-timeline) | | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Candlestick](ref:candlestick) +- [Heatmap](ref:heatmap) +- [Pie chart](ref:pie-chart) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Time series](ref:time-series) +- [Trend](ref:trend) +- [XY chart](ref:xy-chart) + +{{< /column-list >}} Some visualizations, for example [candlestick](ref:candlestick) and [flame graph](ref:flame-graph), have tooltips, but they aren't configurable. These visualizations don't have a **Tooltip** section in the panel editor pane. [Geomaps](ref:geomaps) provide you the option to have tooltips triggered upon click or hover under the **Map controls** options in the panel editor pane. diff --git a/docs/sources/panels-visualizations/configure-value-mappings/index.md b/docs/sources/panels-visualizations/configure-value-mappings/index.md index 754eeed29ee..82231549084 100644 --- a/docs/sources/panels-visualizations/configure-value-mappings/index.md +++ b/docs/sources/panels-visualizations/configure-value-mappings/index.md @@ -105,13 +105,24 @@ Value mappings bypass unit formatting set in the **Standard options** section of You can configure value mappings for the following visualizations: -| | | | -| ------------------------------ | ------------------------------------ | ------------------------------------ | -| [Bar chart](ref:bar-chart) | [Geomap](ref:geomap) | [Status history](ref:status-history) | -| [Bar gauge](ref:bar-gauge) | [Histogram](ref:histogram) | [Table](ref:table) | -| [Candlestick](ref:candlestick) | [Pie chart](ref:pie-chart) | [Time series](ref:time-series) | -| [Canvas](ref:canvas) | [Stat](ref:stat) | [Trend](ref:trend) | -| [Gauge](ref:gauge) | [State timeline](ref:state-timeline) | | +{{< column-list >}} + +- [Bar chart](ref:bar-chart) +- [Bar gauge](ref:bar-gauge) +- [Candlestick](ref:candlestick) +- [Canvas](ref:canvas) +- [Gauge](ref:gauge) +- [Geomap](ref:geomap) +- [Histogram](ref:histogram) +- [Pie chart](ref:pie-chart) +- [Stat](ref:stat) +- [State timeline](ref:state-timeline) +- [Status history](ref:status-history) +- [Table](ref:table) +- [Time series](ref:time-series) +- [Trend](ref:trend) + +{{< /column-list >}} ## Types of value mappings From f4e1f52a7a87cfc0d0898aa0fc8d3400850e98f9 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:14:11 -0500 Subject: [PATCH 20/41] Docs: Add note to refresh page after saving dashboards (#97112) --- .../build-dashboards/annotate-visualizations/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md index c9e0edddd0f..34d5086ea22 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md @@ -70,6 +70,7 @@ Watch the following video for a quick tutorial on creating annotations: To add an annotation, complete the following steps: +1. If you've just saved a dashboard, refresh the page. 1. Click **Edit** in the top-right corner of the dashboard. 1. Click the panel to which you're adding the annotation. @@ -85,6 +86,7 @@ Alternatively, to add an annotation, press Ctrl/Cmd and click the panel, and the ### Add a region annotation +1. If you've just saved a dashboard, refresh the page. 1. Click **Edit** in the top-right corner of the dashboard. 1. Press Ctrl/Cmd and click and drag on the panel. ![Add annotation popover](/static/img/docs/time-series-panel/time-series-annotations-add-region-annotation.gif) From 8b374b227a66ea1ff0161f34b897814df10d59ed Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:54:08 -0500 Subject: [PATCH 21/41] Docs: Fix broken link (#97119) --- .../dashboards/build-dashboards/create-dashboard/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/dashboards/build-dashboards/create-dashboard/index.md index aacf8f483ef..02cd0ab1c1c 100644 --- a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/dashboards/build-dashboards/create-dashboard/index.md @@ -52,9 +52,9 @@ refs: destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/ add-a-data-source: - pattern: /docs/grafana/ - destination: /docs/grafana//administration/data-source-management/#add-a-data-source + destination: /docs/grafana//datasources/#add-a-data-source - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//administration/data-source-management/#add-a-data-source + destination: /docs/grafana//datasources/#add-a-data-source about-users-and-permissions: - pattern: /docs/grafana/ destination: /docs/grafana//administration/roles-and-permissions/ From 54785fb5a8b5f6bc72426a91fb863a11e50d4179 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:11:17 +0000 Subject: [PATCH 22/41] Bump github.com/matryer/is from 1.4.0 to 1.4.1 (#97016) Bumps [github.com/matryer/is](https://github.com/matryer/is) from 1.4.0 to 1.4.1. - [Release notes](https://github.com/matryer/is/releases) - [Commits](https://github.com/matryer/is/compare/v1.4.0...v1.4.1) --- updated-dependencies: - dependency-name: github.com/matryer/is dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3f5148ee0ab..242e28eec22 100644 --- a/go.mod +++ b/go.mod @@ -121,7 +121,7 @@ require ( github.com/m3db/prometheus_remote_client_golang v0.4.4 // @grafana/grafana-backend-group github.com/madflojo/testcerts v1.1.1 // @grafana/alerting-backend github.com/magefile/mage v1.15.0 // @grafana/grafana-developer-enablement-squad - github.com/matryer/is v1.4.0 // @grafana/grafana-as-code + github.com/matryer/is v1.4.1 // @grafana/grafana-as-code github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend diff --git a/go.sum b/go.sum index 035c01bae9c..287dcf2e065 100644 --- a/go.sum +++ b/go.sum @@ -2680,8 +2680,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= From 4b234c47957ffe97f53565aa7983cb2ba4691f0a Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Wed, 27 Nov 2024 14:32:06 -0500 Subject: [PATCH 23/41] Add logging to legacy datasource look up path (#97065) --- pkg/registry/apis/query/parser.go | 10 +++++++++- pkg/registry/apis/query/parser_test.go | 3 ++- pkg/registry/apis/query/query_test.go | 2 +- pkg/registry/apis/query/register.go | 2 +- pkg/services/datasources/service/legacy.go | 8 ++++++++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/registry/apis/query/parser.go b/pkg/registry/apis/query/parser.go index dabd8d46566..f36642c33d8 100644 --- a/pkg/registry/apis/query/parser.go +++ b/pkg/registry/apis/query/parser.go @@ -12,6 +12,7 @@ import ( query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/datasources/service" ) @@ -48,13 +49,15 @@ type queryParser struct { legacy service.LegacyDataSourceLookup reader *expr.ExpressionQueryReader tracer tracing.Tracer + logger log.Logger } -func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer) *queryParser { +func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer, logger log.Logger) *queryParser { return &queryParser{ reader: reader, legacy: legacy, tracer: tracer, + logger: logger, } } @@ -82,6 +85,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe ds, err := p.getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID) if err != nil { + p.logger.Error("Failed to get valid datasource ref", "error", err) return rsp, err } @@ -93,14 +97,17 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe // but this approach lets us focus on well typed behavior first raw, err := json.Marshal(q) if err != nil { + p.logger.Error("Failed to marshal query for expression", "error", err) return rsp, err } iter, err := jsoniter.ParseBytes(jsoniter.ConfigDefault, raw) if err != nil { + p.logger.Error("Failed to parse bytes for expression", "error", err) return rsp, err } exp, err := p.reader.ReadQuery(q, iter) if err != nil { + p.logger.Error("Failed to read query for expression", "error", err) return rsp, NewErrorWithRefID(q.RefID, err) } exp.GraphID = int64(len(expressions) + 1) @@ -170,6 +177,7 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe // Add the sorted expressions sortedNodes, err := topo.SortStabilized(dg, nil) if err != nil { + p.logger.Error("Error when sorting nodes", "error", err) return rsp, makeCyclicError("") } for _, v := range sortedNodes { diff --git a/pkg/registry/apis/query/parser_test.go b/pkg/registry/apis/query/parser_test.go index 3476fae74a7..d775cbf55fe 100644 --- a/pkg/registry/apis/query/parser_test.go +++ b/pkg/registry/apis/query/parser_test.go @@ -15,6 +15,7 @@ import ( query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" ) @@ -29,7 +30,7 @@ type parserTestObject struct { func TestQuerySplitting(t *testing.T) { ctx := context.Background() parser := newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), - &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest()) + &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), log.NewNopLogger()) t.Run("missing datasource flavors", func(t *testing.T) { split, err := parser.parseRequest(ctx, &query.QueryDataRequest{ diff --git a/pkg/registry/apis/query/query_test.go b/pkg/registry/apis/query/query_test.go index 75e37cb8174..711b9da8ed5 100644 --- a/pkg/registry/apis/query/query_test.go +++ b/pkg/registry/apis/query/query_test.go @@ -26,7 +26,7 @@ func TestQueryRestConnectHandler(t *testing.T) { }, tracer: tracing.InitializeTracerForTest(), parser: newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), - &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest()), + &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), nil), log: log.New("test"), } qr := newQueryREST(b) diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index a08af9909cc..2354bede53a 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -75,7 +75,7 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles, log: log.New("query_apiserver"), client: client, registry: registry, - parser: newQueryParser(reader, legacy, tracer), + parser: newQueryParser(reader, legacy, tracer, log.New("query_parser")), metrics: newQueryMetrics(registerer), tracer: tracer, features: features, diff --git a/pkg/services/datasources/service/legacy.go b/pkg/services/datasources/service/legacy.go index b138a906cd6..8705e1e9dc3 100644 --- a/pkg/services/datasources/service/legacy.go +++ b/pkg/services/datasources/service/legacy.go @@ -9,6 +9,7 @@ import ( data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasources" ) @@ -38,6 +39,7 @@ type cachingLegacyDataSourceLookup struct { retriever DataSourceRetriever cache map[string]cachedValue cacheMu sync.Mutex + log log.Logger } type cachedValue struct { @@ -49,15 +51,18 @@ func ProvideLegacyDataSourceLookup(p *Service) LegacyDataSourceLookup { return &cachingLegacyDataSourceLookup{ retriever: p, cache: make(map[string]cachedValue), + log: log.New("legacy-datasource-lookup"), } } func (s *cachingLegacyDataSourceLookup) GetDataSourceFromDeprecatedFields(ctx context.Context, name string, id int64) (*data.DataSourceRef, error) { if id == 0 && name == "" { + s.log.Error("missing id and name in GetDataSourceFromDeprecatedFields") return nil, fmt.Errorf("either name or ID must be set") } user, err := identity.GetRequester(ctx) if err != nil { + s.log.Error("failed to get user from context after getRequester", "error", err) return nil, err } key := fmt.Sprintf("%d/%s/%d", user.GetOrgID(), name, id) @@ -74,6 +79,9 @@ func (s *cachingLegacyDataSourceLookup) GetDataSourceFromDeprecatedFields(ctx co Name: name, ID: id, }) + if err != nil { + s.log.Error("failed to get datasource from retriever", "error", err) + } if errors.Is(err, datasources.ErrDataSourceNotFound) && name != "" { ds, err = s.retriever.GetDataSource(ctx, &datasources.GetDataSourceQuery{ OrgID: user.GetOrgID(), From 3fc6c003e83134230102b5f52af2cb394aa9c93d Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Thu, 28 Nov 2024 09:10:02 +0200 Subject: [PATCH 24/41] chore: Pin `send-slack-message` action (#97088) Pin send-slack-message action --- .github/workflows/detect-breaking-changes-levitate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index bb71dc4cfe8..408c71e6455 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -265,7 +265,7 @@ jobs: - name: Send Slack Message via Payload id: slack if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && github.repository == 'grafana/grafana' - uses: grafana/shared-workflows/actions/send-slack-message@main + uses: grafana/shared-workflows/actions/send-slack-message@7b628e7352c2dea057c565cc4fcd5564d5f396c0 #v1.0.0 with: channel-id: "C031SLFH6G0" payload: | From feecd306870c3902f8b7dcbcd4de2f8090d5c09d Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 28 Nov 2024 09:42:58 +0200 Subject: [PATCH 25/41] API key: Fix API key migration to service account (#97100) fix api key migration to service account --- .../serviceaccounts/database/store.go | 19 +++++ .../serviceaccounts/database/store_test.go | 74 +++++++++++++++++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/pkg/services/serviceaccounts/database/store.go b/pkg/services/serviceaccounts/database/store.go index 2759fa764eb..d859817e49b 100644 --- a/pkg/services/serviceaccounts/database/store.go +++ b/pkg/services/serviceaccounts/database/store.go @@ -509,11 +509,30 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Co IsServiceAccount: true, } + // maximum number of attempts for creating a service account + attempts := 10 + return s.sqlStore.InTransaction(ctx, func(tctx context.Context) error { newSA, errCreateSA := s.userService.CreateServiceAccount(tctx, &cmd) + if errCreateSA != nil { + if errors.Is(errCreateSA, serviceaccounts.ErrServiceAccountAlreadyExists) { + // The service account we tried to create already exists with that login name. We will attempt to create + // a unique service account by adding suffixes to the initial login name (e.g. -001, -002, ... , -010). + for i := 1; errCreateSA != nil && i <= attempts; i++ { + serviceAccountName := fmt.Sprintf("%s-%03d", key.Name, i) + cmd.Login = generateLogin(prefix, key.OrgID, serviceAccountName) + newSA, errCreateSA = s.userService.CreateServiceAccount(tctx, &cmd) + if errCreateSA != nil && !errors.Is(errCreateSA, serviceaccounts.ErrServiceAccountAlreadyExists) { + break + } + } + } + } + if errCreateSA != nil { return fmt.Errorf("failed to create service account: %w", errCreateSA) } + return s.assignApiKeyToServiceAccount(tctx, key.ID, newSA.ID) }) } diff --git a/pkg/services/serviceaccounts/database/store_test.go b/pkg/services/serviceaccounts/database/store_test.go index f00cd48d4df..bc705d070b7 100644 --- a/pkg/services/serviceaccounts/database/store_test.go +++ b/pkg/services/serviceaccounts/database/store_test.go @@ -324,26 +324,85 @@ func TestIntegrationStore_MigrateApiKeys(t *testing.T) { t.Skip("skipping test in short mode") } cases := []struct { - desc string - key tests.TestApiKey - expectedErr error + desc string + serviceAccounts []user.CreateUserCommand + key tests.TestApiKey + expectedLogin string + expectedErr error }{ { - desc: "api key should be migrated to service account token", - key: tests.TestApiKey{Name: "Test1", Role: org.RoleEditor, OrgId: 1}, - expectedErr: nil, + desc: "api key should be migrated to service account token", + serviceAccounts: []user.CreateUserCommand{}, + key: tests.TestApiKey{Name: "test1", Role: org.RoleEditor, OrgId: 1}, + expectedLogin: "sa-autogen-1-test1", + expectedErr: nil, + }, + { + desc: "api key should be migrated to service account token on second attempt", + serviceAccounts: []user.CreateUserCommand{ + {Login: "sa-autogen-1-test2"}, + }, + key: tests.TestApiKey{Name: "test2", Role: org.RoleEditor, OrgId: 1}, + expectedLogin: "sa-autogen-1-test2-001", + expectedErr: nil, + }, + { + desc: "api key should be migrated to service account token on last attempt (the 10th)", + serviceAccounts: []user.CreateUserCommand{ + {Login: "sa-autogen-1-test3"}, + {Login: "sa-autogen-1-test3-001"}, + {Login: "sa-autogen-1-test3-002"}, + {Login: "sa-autogen-1-test3-003"}, + {Login: "sa-autogen-1-test3-004"}, + {Login: "sa-autogen-1-test3-005"}, + {Login: "sa-autogen-1-test3-006"}, + {Login: "sa-autogen-1-test3-007"}, + {Login: "sa-autogen-1-test3-008"}, + {Login: "sa-autogen-1-test3-009"}, + }, + key: tests.TestApiKey{Name: "test3", Role: org.RoleEditor, OrgId: 1}, + expectedLogin: "sa-autogen-1-test3-010", + expectedErr: nil, + }, + { + desc: "api key should not be migrated to service account token because all attempts failed", + serviceAccounts: []user.CreateUserCommand{ + {Login: "sa-autogen-1-test4"}, + {Login: "sa-autogen-1-test4-001"}, + {Login: "sa-autogen-1-test4-002"}, + {Login: "sa-autogen-1-test4-003"}, + {Login: "sa-autogen-1-test4-004"}, + {Login: "sa-autogen-1-test4-005"}, + {Login: "sa-autogen-1-test4-006"}, + {Login: "sa-autogen-1-test4-007"}, + {Login: "sa-autogen-1-test4-008"}, + {Login: "sa-autogen-1-test4-009"}, + {Login: "sa-autogen-1-test4-010"}, + }, + key: tests.TestApiKey{Name: "test4", Role: org.RoleEditor, OrgId: 1}, + expectedErr: serviceaccounts.ErrServiceAccountAlreadyExists, }, } for _, c := range cases { t.Run(c.desc, func(t *testing.T) { db, store := setupTestDatabase(t) + store.cfg.AutoAssignOrg = true store.cfg.AutoAssignOrgId = 1 store.cfg.AutoAssignOrgRole = "Viewer" _, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"}) require.NoError(t, err) + key := tests.SetupApiKey(t, db, store.cfg, c.key) + + for _, sa := range c.serviceAccounts { + sa.IsServiceAccount = true + sa.OrgID = key.OrgID + _, err := store.userService.CreateServiceAccount(context.Background(), &sa) + require.NoError(t, err) + } + err = store.MigrateApiKey(context.Background(), key.OrgID, key.ID) if c.expectedErr != nil { require.ErrorIs(t, err, c.expectedErr) @@ -352,7 +411,7 @@ func TestIntegrationStore_MigrateApiKeys(t *testing.T) { q := serviceaccounts.SearchOrgServiceAccountsQuery{ OrgID: key.OrgID, - Query: "", + Query: c.expectedLogin, Page: 1, Limit: 50, SignedInUser: &user.SignedInUser{ @@ -370,6 +429,7 @@ func TestIntegrationStore_MigrateApiKeys(t *testing.T) { require.Equal(t, int64(1), serviceAccounts.TotalCount) saMigrated := serviceAccounts.ServiceAccounts[0] require.Equal(t, string(key.Role), saMigrated.Role) + require.Equal(t, c.expectedLogin, saMigrated.Login) tokens, err := store.ListTokens(context.Background(), &serviceaccounts.GetSATokensQuery{ OrgID: &key.OrgID, From c8caf787d4318ae2e27b92c602a6b226c48ab7c8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 28 Nov 2024 10:41:30 +0100 Subject: [PATCH 26/41] Zanzana: handle service accounts (#97123) * add service account to the schema * sync managed permissions for service accounts * sync SA basic roles * sync SA roles * Fix endless loop in reconciler while read openfga --- .../accesscontrol/dualwrite/collectors.go | 54 ++++++++++++------- pkg/services/authz/zanzana/common/tuple.go | 13 ++--- .../authz/zanzana/schema/schema_core.fga | 26 ++++----- .../authz/zanzana/schema/schema_folder.fga | 18 +++---- .../authz/zanzana/schema/schema_resource.fga | 34 ++++++------ pkg/services/authz/zanzana/zanzana.go | 13 ++--- 6 files changed, 90 insertions(+), 68 deletions(-) diff --git a/pkg/services/accesscontrol/dualwrite/collectors.go b/pkg/services/accesscontrol/dualwrite/collectors.go index 97d5c6ee6f4..0be8336069d 100644 --- a/pkg/services/accesscontrol/dualwrite/collectors.go +++ b/pkg/services/accesscontrol/dualwrite/collectors.go @@ -115,7 +115,7 @@ func folderTreeCollector(store db.DB) legacyTupleCollector { func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name + SELECT u.uid as user_uid, u.is_service_account as is_service_account, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name FROM permission p INNER JOIN role r ON p.role_id = r.id LEFT JOIN user_role ur ON r.id = ur.role_id @@ -128,12 +128,13 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector AND p.kind = ? ` type Permission struct { - Action string `xorm:"action"` - Kind string - Identifier string - UserUID string `xorm:"user_uid"` - TeamUID string `xorm:"team_uid"` - BasicRoleName string `xorm:"basic_role_name"` + Action string `xorm:"action"` + Kind string + Identifier string + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + TeamUID string `xorm:"team_uid"` + BasicRoleName string `xorm:"basic_role_name"` } var permissions []Permission @@ -149,7 +150,9 @@ func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector for _, p := range permissions { var subject string - if len(p.UserUID) > 0 { + if len(p.UserUID) > 0 && p.IsServiceAccount { + subject = zanzana.NewTupleEntry(zanzana.TypeServiceAccount, p.UserUID, "") + } else if len(p.UserUID) > 0 { subject = zanzana.NewTupleEntry(zanzana.TypeUser, p.UserUID, "") } else if len(p.TeamUID) > 0 { subject = zanzana.NewTupleEntry(zanzana.TypeTeam, p.TeamUID, zanzana.RelationTeamMember) @@ -198,16 +201,19 @@ func tupleStringWithoutCondition(tuple *openfgav1.TupleKey) string { func basicRoleBindingsCollector(store db.DB) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT ou.org_id, u.uid as user_uid, ou.role as org_role + SELECT + ou.org_id, u.uid as user_uid, + u.is_service_account as is_service_account, + ou.role as org_role FROM org_user ou LEFT JOIN ` + store.GetDialect().Quote("user") + ` u ON u.id = ou.user_id WHERE ou.org_id = ? - AND NOT u.is_service_account ` // FIXME: handle service admin role type Binding struct { - UserUID string `xorm:"user_uid"` - OrgRole string `xorm:"org_role"` + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + OrgRole string `xorm:"org_role"` } var bindings []Binding @@ -222,8 +228,13 @@ func basicRoleBindingsCollector(store db.DB) legacyTupleCollector { tuples := make(map[string]map[string]*openfgav1.TupleKey) for _, b := range bindings { + userType := zanzana.TypeUser + if b.IsServiceAccount { + userType = zanzana.TypeServiceAccount + } + tuple := &openfgav1.TupleKey{ - User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""), + User: zanzana.NewTupleEntry(userType, b.UserUID, ""), Relation: zanzana.RelationAssignee, Object: zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(b.OrgRole), ""), } @@ -286,7 +297,7 @@ func teamRoleBindingsCollector(store db.DB) legacyTupleCollector { func userRoleBindingsCollector(store db.DB) legacyTupleCollector { return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) { query := ` - SELECT u.uid AS user_uid, r.uid AS role_uid + SELECT u.uid AS user_uid, u.is_service_account as is_service_account, r.uid AS role_uid FROM user_role ur INNER JOIN ` + store.GetDialect().Quote("user") + ` u ON ur.user_id = u.id INNER JOIN role r ON ur.role_id = r.id @@ -294,8 +305,9 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector { AND r.name NOT LIKE 'managed:%' ` type Binding struct { - UserUID string `xorm:"user_uid"` - RoleUID string `xorm:"role_uid"` + UserUID string `xorm:"user_uid"` + IsServiceAccount bool `xorm:"is_service_account"` + RoleUID string `xorm:"role_uid"` } var bindings []Binding @@ -310,8 +322,13 @@ func userRoleBindingsCollector(store db.DB) legacyTupleCollector { tuples := make(map[string]map[string]*openfgav1.TupleKey) for _, b := range bindings { + userType := zanzana.TypeUser + if b.IsServiceAccount { + userType = zanzana.TypeServiceAccount + } + tuple := &openfgav1.TupleKey{ - User: zanzana.NewTupleEntry(zanzana.TypeUser, b.UserUID, ""), + User: zanzana.NewTupleEntry(userType, b.UserUID, ""), Relation: zanzana.RelationAssignee, Object: zanzana.NewTupleEntry(zanzana.TypeRole, b.RoleUID, ""), } @@ -397,7 +414,8 @@ func zanzanaCollector(relations []string) zanzanaTupleCollector { for c != "" { res, err := client.Read(ctx, &authzextv1.ReadRequest{ - Namespace: namespace, + ContinuationToken: c, + Namespace: namespace, TupleKey: &authzextv1.ReadRequestTupleKey{ Object: object, Relation: relation, diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index 2e413545232..1105e44275a 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -10,12 +10,13 @@ import ( ) const ( - TypeUser string = "user" - TypeTeam string = "team" - TypeRole string = "role" - TypeFolder string = "folder" - TypeResource string = "resource" - TypeNamespace string = "namespace" + TypeUser string = "user" + TypeServiceAccount string = "service-account" + TypeTeam string = "team" + TypeRole string = "role" + TypeFolder string = "folder" + TypeResource string = "resource" + TypeNamespace string = "namespace" ) const ( diff --git a/pkg/services/authz/zanzana/schema/schema_core.fga b/pkg/services/authz/zanzana/schema/schema_core.fga index 1003841a9ce..5b24adb38a9 100644 --- a/pkg/services/authz/zanzana/schema/schema_core.fga +++ b/pkg/services/authz/zanzana/schema/schema_core.fga @@ -2,28 +2,30 @@ module core type namespace relations - define view: [user, team#member, role#assignee] or edit - define edit: [user, team#member, role#assignee] or admin - define admin: [user, team#member, role#assignee] + define view: [user, service-account, team#member, role#assignee] or edit + define edit: [user, service-account, team#member, role#assignee] or admin + define admin: [user, service-account, team#member, role#assignee] - define read: [user, team#member, role#assignee] or view - define create: [user, team#member, role#assignee] or edit - define write: [user, team#member, role#assignee] or edit - define delete: [user, team#member, role#assignee] or edit - define permissions_read: [user, team#member, role#assignee] or admin - define permissions_write: [user, team#member, role#assignee] or admin + define read: [user, service-account, team#member, role#assignee] or view + define create: [user, service-account, team#member, role#assignee] or edit + define write: [user, service-account, team#member, role#assignee] or edit + define delete: [user, service-account, team#member, role#assignee] or edit + define permissions_read: [user, service-account, team#member, role#assignee] or admin + define permissions_write: [user, service-account, team#member, role#assignee] or admin type user +type service-account + type role relations - define assignee: [user, team#member, role#assignee] + define assignee: [user, service-account, team#member, role#assignee] type team relations # Action sets - define admin: [user] - define member: [user] or admin + define admin: [user, service-account] + define member: [user, service-account] or admin define read: [role#assignee] or member define write: [role#assignee] or admin diff --git a/pkg/services/authz/zanzana/schema/schema_folder.fga b/pkg/services/authz/zanzana/schema/schema_folder.fga index b66f20854cb..117bff01199 100644 --- a/pkg/services/authz/zanzana/schema/schema_folder.fga +++ b/pkg/services/authz/zanzana/schema/schema_folder.fga @@ -5,13 +5,13 @@ type folder define parent: [folder] # Action sets - define view: [user, team#member, role#assignee] or edit or view from parent - define edit: [user, team#member, role#assignee] or admin or edit from parent - define admin: [user, team#member, role#assignee] or admin from parent + define view: [user, service-account, team#member, role#assignee] or edit or view from parent + define edit: [user, service-account, team#member, role#assignee] or admin or edit from parent + define admin: [user, service-account, team#member, role#assignee] or admin from parent - define read: [user, team#member, role#assignee] or view or read from parent - define create: [user, team#member, role#assignee] or edit or create from parent - define write: [user, team#member, role#assignee] or edit or write from parent - define delete: [user, team#member, role#assignee] or edit or delete from parent - define permissions_read: [user, team#member, role#assignee] or admin or permissions_read from parent - define permissions_write: [user, team#member, role#assignee] or admin or permissions_write from parent + define read: [user, service-account, team#member, role#assignee] or view or read from parent + define create: [user, service-account, team#member, role#assignee] or edit or create from parent + define write: [user, service-account, team#member, role#assignee] or edit or write from parent + define delete: [user, service-account, team#member, role#assignee] or edit or delete from parent + define permissions_read: [user, service-account, team#member, role#assignee] or admin or permissions_read from parent + define permissions_write: [user, service-account, team#member, role#assignee] or admin or permissions_write from parent diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga index 951e1c151d2..9e90bf0472b 100644 --- a/pkg/services/authz/zanzana/schema/schema_resource.fga +++ b/pkg/services/authz/zanzana/schema/schema_resource.fga @@ -2,28 +2,28 @@ module resource extend type folder relations - define resource_view: [user, team#member, role#assignee] or resource_edit or resource_view from parent - define resource_edit: [user, team#member, role#assignee] or resource_admin or resource_edit from parent - define resource_admin: [user, team#member, role#assignee] or resource_admin from parent + define resource_view: [user, service-account, team#member, role#assignee] or resource_edit or resource_view from parent + define resource_edit: [user, service-account, team#member, role#assignee] or resource_admin or resource_edit from parent + define resource_admin: [user, service-account, team#member, role#assignee] or resource_admin from parent - define resource_read: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent - define resource_create: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent - define resource_write: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent - define resource_delete: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent - define resource_permissions_read: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent - define resource_permissions_write: [user with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent + define resource_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_read from parent + define resource_create: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent + define resource_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_write from parent + define resource_delete: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent + define resource_permissions_read: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_read from parent + define resource_permissions_write: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_permissions_write from parent type resource relations - define view: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define edit: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin - define admin: [user with group_filter, team#member with group_filter, role#assignee with group_filter] + define view: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define edit: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define admin: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] - define read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or view - define write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define delete: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or edit - define permissions_read: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin - define permissions_write: [user with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view + define write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define delete: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit + define permissions_read: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin + define permissions_write: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin condition group_filter(requested_group: string, group_resource: string) { requested_group == group_resource diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index ce02d3e947e..b7af6983eda 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -11,12 +11,13 @@ import ( ) const ( - TypeUser = common.TypeUser - TypeTeam = common.TypeTeam - TypeRole = common.TypeRole - TypeFolder = common.TypeFolder - TypeResource = common.TypeResource - TypeNamespace = common.TypeNamespace + TypeUser = common.TypeUser + TypeServiceAccount = common.TypeServiceAccount + TypeTeam = common.TypeTeam + TypeRole = common.TypeRole + TypeFolder = common.TypeFolder + TypeResource = common.TypeResource + TypeNamespace = common.TypeNamespace ) const ( From c8d3ea58f8bf3aac5adc5263e4d8d78508f87e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Thu, 28 Nov 2024 10:45:31 +0100 Subject: [PATCH 27/41] Support importing local cue modules when generating TS types with cog (#96876) * Support importing local cue modules when generating TS types with cog * Generate enums as unions of values instead of an actual `enum` statement * Update transformers to use the new TS enums * Update go workspaces --------- Co-authored-by: Ivan Ortega --- apps/playlist/go.mod | 4 +- apps/playlist/go.sum | 8 +- go.mod | 10 +- go.sum | 20 +-- go.work.sum | 5 + kindsv2/gen.go | 41 ++--- kindsv2/go.mod | 12 +- kindsv2/go.sum | 20 +-- .../dashboard/v2alpha0/dashboard.gen.ts | 166 +++++------------- .../dashboard/v2alpha0/dashboard.schema.cue | 10 +- .../src/schema/dashboard/v2alpha0/examples.ts | 46 +++-- pkg/aggregator/go.mod | 10 +- pkg/aggregator/go.sum | 20 +-- pkg/apimachinery/go.mod | 4 +- pkg/apimachinery/go.sum | 8 +- pkg/apiserver/go.mod | 6 +- pkg/apiserver/go.sum | 16 +- pkg/build/go.mod | 6 +- pkg/build/go.sum | 12 +- pkg/build/wire/go.mod | 4 +- pkg/build/wire/go.sum | 8 +- pkg/promlib/go.mod | 6 +- pkg/promlib/go.sum | 12 +- pkg/tests/apis/folder/folders_test.go | 6 +- .../sceneVariablesSetToVariables.ts | 9 +- .../transformSaveModelSchemaV2ToScene.test.ts | 2 +- .../transformSaveModelSchemaV2ToScene.ts | 4 +- .../transformSceneToSaveModelSchemaV2.ts | 8 +- .../serialization/transformToV1TypesUtils.ts | 77 +++++--- .../transformToV2TypesUtils.test.ts | 83 ++------- .../serialization/transformToV2TypesUtils.ts | 52 ++---- 31 files changed, 280 insertions(+), 415 deletions(-) diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 50f9a69af47..d587ef1f468 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -59,10 +59,10 @@ require ( go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index af1e0aa37fe..91ab502e932 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -141,8 +141,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -153,8 +153,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= diff --git a/go.mod b/go.mod index 242e28eec22..c508b907901 100644 --- a/go.mod +++ b/go.mod @@ -171,15 +171,15 @@ require ( go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad - golang.org/x/crypto v0.28.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.29.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // @grafana/alerting-backend - golang.org/x/mod v0.21.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.30.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/mod v0.22.0 // indirect; @grafana/grafana-backend-group + golang.org/x/net v0.31.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team golang.org/x/sync v0.9.0 // @grafana/alerting-backend golang.org/x/text v0.20.0 // @grafana/grafana-backend-group golang.org/x/time v0.6.0 // @grafana/grafana-backend-group - golang.org/x/tools v0.26.0 // @grafana/grafana-as-code + golang.org/x/tools v0.27.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.15.1 // @grafana/observability-metrics google.golang.org/api v0.193.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.67.1 // @grafana/plugins-platform-backend @@ -431,7 +431,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // @grafana/identity-access-team golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect; @grafana/grafana-backend-group google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect diff --git a/go.sum b/go.sum index 287dcf2e065..2a47aa1c3a7 100644 --- a/go.sum +++ b/go.sum @@ -3516,8 +3516,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3595,8 +3595,8 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3695,8 +3695,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3931,8 +3931,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -4062,8 +4062,8 @@ golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index c523ff2e1f7..6925ec372b5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -989,6 +989,9 @@ github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10 h1:oDbLKM34O+JUF9 github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw= +github.com/grafana/cog v0.0.4/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs= +github.com/grafana/cog v0.0.5 h1:BCa+10i3KvV+KMSQuxlN1DS9cZEwN+EAFc7ZmXqHxQE= +github.com/grafana/cog v0.0.5/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs= github.com/grafana/cuetsy v0.1.10/go.mod h1:Ix97+CPD8ws9oSSxR3/Lf4ahU1I4Np83kjJmDVnLZvc= github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= @@ -1783,6 +1786,7 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -1796,6 +1800,7 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= diff --git a/kindsv2/gen.go b/kindsv2/gen.go index ee215e3d491..81ad33a890c 100644 --- a/kindsv2/gen.go +++ b/kindsv2/gen.go @@ -6,46 +6,47 @@ import ( "context" "os" - "cuelang.org/go/cue/cuecontext" "github.com/grafana/cog" ) type codegenTargets struct { - schemaPath string - outputPath string + modulePath string + outputPath string + cueImportsMap map[string]string + packagesImportMap map[string]string } func main() { - cueCtx := cuecontext.New() - targets := []codegenTargets{ { - schemaPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue", + modulePath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/", outputPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts", + cueImportsMap: map[string]string{ + "github.com/grafana/grafana/packages/grafana-schema/src/common": "../packages/grafana-schema/src/common", + }, + packagesImportMap: map[string]string{ + "common": "@grafana/schema", + }, }, } for _, target := range targets { - rawSchema, err := os.ReadFile(target.schemaPath) - if err != nil { - panic(err) - } - - value := cueCtx.CompileBytes(rawSchema) - if value.Err() != nil { - panic(value.Err()) - } - codegenPipeline := cog.TypesFromSchema(). - CUEValue("dashboard", value). - Typescript() + CUEModule( + target.modulePath, + cog.CUEImports(target.cueImportsMap), + ). + Typescript(cog.TypescriptConfig{ + ImportsMap: target.packagesImportMap, + EnumsAsUnionTypes: true, + }) - tsBytes, err := codegenPipeline.Run(context.Background()) + files, err := codegenPipeline.Run(context.Background()) if err != nil { panic(err) } - if err := os.WriteFile(target.outputPath, tsBytes, 0644); err != nil { + if err := os.WriteFile(target.outputPath, files[0].Data, 0644); err != nil { panic(err) } } diff --git a/kindsv2/go.mod b/kindsv2/go.mod index ecd83ce978a..820ad5a3065 100644 --- a/kindsv2/go.mod +++ b/kindsv2/go.mod @@ -2,13 +2,11 @@ module github.com/grafana/grafana/kindsv2 go 1.23.1 -require ( - cuelang.org/go v0.8.2 - github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070 -) +require github.com/grafana/cog v0.0.5 require ( cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect + cuelang.org/go v0.8.2 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/proto v1.13.2 // indirect @@ -36,11 +34,11 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.27.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/kindsv2/go.sum b/kindsv2/go.sum index c3848f5abe1..79ff5b0bee6 100644 --- a/kindsv2/go.sum +++ b/kindsv2/go.sum @@ -26,8 +26,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070 h1:ncAkFmIq3UbPiPhQ2CuBzdTuwnDqLsFFNqEIXKaiPGA= -github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070/go.mod h1:FqZi9WZ/Uzvs3tvo7l+OViThCpfvu3KDGDqHCB2LNbg= +github.com/grafana/cog v0.0.5 h1:BCa+10i3KvV+KMSQuxlN1DS9cZEwN+EAFc7ZmXqHxQE= +github.com/grafana/cog v0.0.5/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,20 +73,20 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts index 0695c70cf03..64d4ae66bbd 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts @@ -1,5 +1,8 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. +import * as common from '@grafana/schema'; + + export interface DashboardV2Spec { // Unique numeric identifier for the dashboard. // `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances. @@ -42,7 +45,7 @@ export interface DashboardV2Spec { export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ title: "", - cursorSync: DashboardCursorSync.Off, + cursorSync: "Off", preload: false, editable: true, links: [], @@ -69,13 +72,9 @@ export const defaultAnnotationPanelFilter = (): AnnotationPanelFilter => ({ // "Off" for no shared crosshair or tooltip (default). // "Crosshair" for shared crosshair. // "Tooltip" for shared crosshair AND shared tooltip. -export enum DashboardCursorSync { - Off = "Off", - Crosshair = "Crosshair", - Tooltip = "Tooltip", -} +export type DashboardCursorSync = "Off" | "Crosshair" | "Tooltip"; -export const defaultDashboardCursorSync = (): DashboardCursorSync => (DashboardCursorSync.Off); +export const defaultDashboardCursorSync = (): DashboardCursorSync => ("Off"); // Links with references to other dashboards or external resources export interface DashboardLink { @@ -104,7 +103,7 @@ export interface DashboardLink { export const defaultDashboardLink = (): DashboardLink => ({ title: "", - type: DashboardLinkType.Link, + type: "link", icon: "", tooltip: "", tags: [], @@ -124,14 +123,6 @@ export interface DataSourceRef { export const defaultDataSourceRef = (): DataSourceRef => ({ }); -export enum DataTopic { - AlertStates = "alertStates", - Annotations = "annotations", - Series = "series", -} - -export const defaultDataTopic = (): DataTopic => (DataTopic.AlertStates); - // Transformations allow to manipulate data returned by a query before the system applies a visualization. // Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, // use the output of one transformation as the input to another transformation, etc. @@ -143,7 +134,7 @@ export interface DataTransformerConfig { // Optional frame matcher. When missing it will be applied to all results filter?: MatcherConfig; // Where to pull DataFrames from as input to transformation - topic?: DataTopic; + topic?: common.DataTopic; // Options to be passed to the transformer // Valid options depend on the transformer id options: any; @@ -263,12 +254,9 @@ export const defaultThreshold = (): Threshold => ({ color: "", }); -export enum ThresholdsMode { - Absolute = "absolute", - Percentage = "percentage", -} +export type ThresholdsMode = "absolute" | "percentage"; -export const defaultThresholdsMode = (): ThresholdsMode => (ThresholdsMode.Absolute); +export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); export interface ThresholdsConfig { mode: ThresholdsMode; @@ -276,7 +264,7 @@ export interface ThresholdsConfig { } export const defaultThresholdsConfig = (): ThresholdsConfig => ({ - mode: ThresholdsMode.Absolute, + mode: "absolute", steps: [], }); @@ -289,14 +277,9 @@ export const defaultValueMapping = (): ValueMapping => (defaultValueMap()); // `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. // `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. // `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. -export enum MappingType { - ValueToText = "value", - RangeToText = "range", - RegexToText = "regex", - SpecialValue = "special", -} +export type MappingType = "value" | "range" | "regex" | "special"; -export const defaultMappingType = (): MappingType => (MappingType.ValueToText); +export const defaultMappingType = (): MappingType => ("value"); // Maps text values to a color or different display text and color. // For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. @@ -372,22 +355,15 @@ export interface SpecialValueMap { export const defaultSpecialValueMap = (): SpecialValueMap => ({ type: "special", options: { - match: SpecialValueMatch.True, + match: "true", result: defaultValueMappingResult(), }, }); // Special value types supported by the `SpecialValueMap` -export enum SpecialValueMatch { - True = "true", - False = "false", - Null = "null", - NotANumber = "nan", - NullAndNaN = "null+nan", - Empty = "empty", -} +export type SpecialValueMatch = "true" | "false" | "null" | "nan" | "null+nan" | "empty"; -export const defaultSpecialValueMatch = (): SpecialValueMatch => (SpecialValueMatch.True); +export const defaultSpecialValueMatch = (): SpecialValueMatch => ("true"); // Result used as replacement with text and color when the value matches export interface ValueMappingResult { @@ -422,34 +398,14 @@ export const defaultValueMappingResult = (): ValueMappingResult => ({ // `continuous-purples`: Continuous Purple palette mode // `shades`: Shades of a single color. Specify a single color, useful in an override rule. // `fixed`: Fixed color mode. Specify a single color, useful in an override rule. -export enum FieldColorModeId { - Thresholds = "thresholds", - PaletteClassic = "palette-classic", - PaletteClassicByName = "palette-classic-by-name", - ContinuousGrYlRd = "continuous-GrYlRd", - ContinuousRdYlGr = "continuous-RdYlGr", - ContinuousBlYlRd = "continuous-BlYlRd", - ContinuousYlRd = "continuous-YlRd", - ContinuousBlPu = "continuous-BlPu", - ContinuousYlBl = "continuous-YlBl", - ContinuousBlues = "continuous-blues", - ContinuousReds = "continuous-reds", - ContinuousGreens = "continuous-greens", - ContinuousPurples = "continuous-purples", - Fixed = "fixed", - Shades = "shades", -} +export type FieldColorModeId = "thresholds" | "palette-classic" | "palette-classic-by-name" | "continuous-GrYlRd" | "continuous-RdYlGr" | "continuous-BlYlRd" | "continuous-YlRd" | "continuous-BlPu" | "continuous-YlBl" | "continuous-blues" | "continuous-reds" | "continuous-greens" | "continuous-purples" | "fixed" | "shades"; -export const defaultFieldColorModeId = (): FieldColorModeId => (FieldColorModeId.Thresholds); +export const defaultFieldColorModeId = (): FieldColorModeId => ("thresholds"); // Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value. -export enum FieldColorSeriesByMode { - Min = "min", - Max = "max", - Last = "last", -} +export type FieldColorSeriesByMode = "min" | "max" | "last"; -export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => (FieldColorSeriesByMode.Min); +export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min"); // Map a field to a color. export interface FieldColor { @@ -462,16 +418,13 @@ export interface FieldColor { } export const defaultFieldColor = (): FieldColor => ({ - mode: FieldColorModeId.Thresholds, + mode: "thresholds", }); // Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) -export enum DashboardLinkType { - Link = "link", - Dashboards = "dashboards", -} +export type DashboardLinkType = "link" | "dashboards"; -export const defaultDashboardLinkType = (): DashboardLinkType => (DashboardLinkType.Link); +export const defaultDashboardLinkType = (): DashboardLinkType => ("link"); // --- Common types --- export interface Kind { @@ -790,7 +743,7 @@ export interface CustomFormatterVariable { export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({ name: "", - type: VariableType.Query, + type: "query", multi: false, includeAll: false, }); @@ -821,7 +774,7 @@ export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => value: {}, legacyVariableModel: { name: "", - type: VariableType.Query, + type: "query", multi: false, includeAll: false, }, @@ -836,20 +789,9 @@ export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => // `textbox`: Display a free text input field with an optional default value. // `custom`: Define the variable options manually using a comma-separated list. // `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables -export enum VariableType { - Query = "query", - Adhoc = "adhoc", - Groupby = "groupby", - Constant = "constant", - Datasource = "datasource", - Interval = "interval", - Textbox = "textbox", - Custom = "custom", - System = "system", - Snapshot = "snapshot", -} +export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot"; -export const defaultVariableType = (): VariableType => (VariableType.Query); +export const defaultVariableType = (): VariableType => ("query"); // Sort variable options // Accepted values are: @@ -863,41 +805,23 @@ export const defaultVariableType = (): VariableType => (VariableType.Query); // `naturalAsc`: Natural ASC // `naturalDesc`: Natural DESC // VariableSort enum with default value -export enum VariableSort { - Disabled = "disabled", - AlphabeticalAsc = "alphabeticalAsc", - AlphabeticalDesc = "alphabeticalDesc", - NumericalAsc = "numericalAsc", - NumericalDesc = "numericalDesc", - AlphabeticalCaseInsensitiveAsc = "alphabeticalCaseInsensitiveAsc", - AlphabeticalCaseInsensitiveDesc = "alphabeticalCaseInsensitiveDesc", - NaturalAsc = "naturalAsc", - NaturalDesc = "naturalDesc", -} +export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"; -export const defaultVariableSort = (): VariableSort => (VariableSort.Disabled); +export const defaultVariableSort = (): VariableSort => ("disabled"); // Options to config when to refresh a variable // `never`: Never refresh the variable // `onDashboardLoad`: Queries the data source every time the dashboard loads. // `onTimeRangeChanged`: Queries the data source when the dashboard time range changes. -export enum VariableRefresh { - Never = "never", - OnDashboardLoad = "onDashboardLoad", - OnTimeRangeChanged = "onTimeRangeChanged", -} +export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"; -export const defaultVariableRefresh = (): VariableRefresh => (VariableRefresh.Never); +export const defaultVariableRefresh = (): VariableRefresh => ("never"); // Determine if the variable shows on dashboard // Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). -export enum VariableHide { - DontHide = "dontHide", - HideLabel = "hideLabel", - HideVariable = "hideVariable", -} +export type VariableHide = "dontHide" | "hideLabel" | "hideVariable"; -export const defaultVariableHide = (): VariableHide => (VariableHide.DontHide); +export const defaultVariableHide = (): VariableHide => ("dontHide"); // FIXME: should we introduce this? --- Variable value option export interface VariableValueOption { @@ -950,13 +874,13 @@ export interface QueryVariableSpec { export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ name: "", current: { text: "", value: "", }, - hide: VariableHide.DontHide, - refresh: VariableRefresh.Never, + hide: "dontHide", + refresh: "never", skipUrlSync: false, datasource: defaultDataSourceRef(), query: "", regex: "", - sort: VariableSort.Disabled, + sort: "disabled", options: [], multi: false, includeAll: false, @@ -988,7 +912,7 @@ export const defaultTextVariableSpec = (): TextVariableSpec => ({ name: "", current: { text: "", value: "", }, query: "", - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); @@ -1018,7 +942,7 @@ export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({ name: "", query: "", current: { text: "", value: "", }, - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); @@ -1054,14 +978,14 @@ export interface DatasourceVariableSpec { export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ name: "", pluginId: "", - refresh: VariableRefresh.Never, + refresh: "never", regex: "", current: { text: "", value: "", }, defaultOptionEnabled: false, options: [], multi: false, includeAll: false, - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); @@ -1100,8 +1024,8 @@ export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ auto: false, auto_min: "", auto_count: 0, - refresh: VariableRefresh.Never, - hide: VariableHide.DontHide, + refresh: "never", + hide: "dontHide", skipUrlSync: false, }); @@ -1138,7 +1062,7 @@ export const defaultCustomVariableSpec = (): CustomVariableSpec => ({ options: [], multi: false, includeAll: false, - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); @@ -1175,7 +1099,7 @@ export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ options: [], multi: false, includeAll: false, - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); @@ -1209,7 +1133,7 @@ export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ baseFilters: [], filters: [], defaultKeys: [], - hide: VariableHide.DontHide, + hide: "dontHide", skipUrlSync: false, }); diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 026c42663db..236805b3a24 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -1,4 +1,8 @@ -package dashboard +package v2alpha0 + +import ( + "github.com/grafana/grafana/packages/grafana-schema/src/common" +) DashboardV2Spec: { // Unique numeric identifier for the dashboard. @@ -102,8 +106,6 @@ DataSourceRef: { uid?: string } -DataTopic: "alertStates" | "annotations" | "series" - // Transformations allow to manipulate data returned by a query before the system applies a visualization. // Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, // use the output of one transformation as the input to another transformation, etc. @@ -115,7 +117,7 @@ DataTransformerConfig: { // Optional frame matcher. When missing it will be applied to all results filter?: MatcherConfig // Where to pull DataFrames from as input to transformation - topic?: DataTopic + topic?: common.DataTopic // Options to be passed to the transformer // Valid options depend on the transformer id options: _ diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index e1dd3ec8cb9..801deb57bc9 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -1,21 +1,15 @@ -import { - DashboardCursorSync, - DashboardLinkType, - DashboardV2Spec, - VariableHide, - VariableRefresh, - VariableSort, -} from './dashboard.gen'; +import { DashboardV2Spec } from './dashboard.gen'; export const handyTestingSchema: DashboardV2Spec = { - title: 'Test Dashboard', - description: 'Test Description', + id: 1, + title: 'Default Dashboard', + description: 'This is a default dashboard', + cursorSync: 'Off', + liveNow: false, + preload: false, editable: true, - preload: true, schemaVersion: 40, tags: ['tag1', 'tag2'], - liveNow: true, - cursorSync: DashboardCursorSync.Crosshair, timeSettings: { autoRefresh: '5s', autoRefreshIntervals: ['5s', '10s', '30s'], @@ -132,7 +126,7 @@ export const handyTestingSchema: DashboardV2Spec = { targetBlank: false, title: 'Test Link', tooltip: '', - type: DashboardLinkType.Dashboards, + type: 'dashboards', url: 'http://test.com', }, ], @@ -152,17 +146,17 @@ export const handyTestingSchema: DashboardV2Spec = { }, definition: 'definition1', description: 'A query variable', - hide: VariableHide.DontHide, + hide: 'dontHide', includeAll: true, label: 'Query Variable', multi: true, name: 'queryVar', options: [], query: 'query1', - refresh: VariableRefresh.OnDashboardLoad, + refresh: 'onDashboardLoad', regex: 'regex1', skipUrlSync: false, - sort: VariableSort.Disabled, + sort: 'disabled', }, }, { @@ -174,7 +168,7 @@ export const handyTestingSchema: DashboardV2Spec = { value: 'option1', }, description: 'A custom variable', - hide: VariableHide.DontHide, + hide: 'dontHide', includeAll: true, label: 'Custom Variable', multi: true, @@ -205,14 +199,14 @@ export const handyTestingSchema: DashboardV2Spec = { }, defaultOptionEnabled: true, description: 'A datasource variable', - hide: VariableHide.DontHide, + hide: 'dontHide', includeAll: false, label: 'Datasource Variable', multi: false, name: 'datasourceVar', options: [], pluginId: 'datasource1', - refresh: VariableRefresh.OnDashboardLoad, + refresh: 'onDashboardLoad', regex: 'regex1', skipUrlSync: false, }, @@ -225,7 +219,7 @@ export const handyTestingSchema: DashboardV2Spec = { value: 'value4', }, description: 'A constant variable', - hide: VariableHide.DontHide, + hide: 'dontHide', label: 'Constant Variable', name: 'constantVar', query: 'value4', @@ -243,7 +237,7 @@ export const handyTestingSchema: DashboardV2Spec = { value: '1m', }, description: 'An interval variable', - hide: VariableHide.DontHide, + hide: 'dontHide', label: 'Interval Variable', name: 'intervalVar', options: [ @@ -264,7 +258,7 @@ export const handyTestingSchema: DashboardV2Spec = { }, ], query: '1m,5m,10m', - refresh: VariableRefresh.OnDashboardLoad, + refresh: 'onDashboardLoad', skipUrlSync: false, }, }, @@ -276,7 +270,7 @@ export const handyTestingSchema: DashboardV2Spec = { value: 'value6', }, description: 'A text variable', - hide: VariableHide.DontHide, + hide: 'dontHide', label: 'Text Variable', name: 'textVar', query: 'value6', @@ -295,7 +289,7 @@ export const handyTestingSchema: DashboardV2Spec = { uid: 'datasource2', }, description: 'A group by variable', - hide: VariableHide.DontHide, + hide: 'dontHide', includeAll: false, label: 'Group By Variable', multi: false, @@ -351,7 +345,7 @@ export const handyTestingSchema: DashboardV2Spec = { value: 'value3', }, ], - hide: VariableHide.DontHide, + hide: 'dontHide', label: 'Adhoc Variable', name: 'adhocVar', skipUrlSync: false, diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 7f0e91fd73a..d2b9e45d32a 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -134,17 +134,17 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.27.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index b54677e53a2..f4ae1a031bc 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -381,8 +381,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= @@ -392,8 +392,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -403,8 +403,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -433,8 +433,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= @@ -452,8 +452,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 93773f3f621..1f4104cbe4c 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -35,8 +35,8 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index aca8b089106..bb46339fe0a 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -77,8 +77,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -91,8 +91,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 75e7e1e9980..f3b665d5aba 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -78,13 +78,13 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.27.0 // indirect google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 0cd8916a5c0..6807a311240 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -221,8 +221,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= @@ -241,8 +241,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -259,8 +259,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= @@ -277,8 +277,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 3fd65dbcbb3..25b27e3e918 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -35,9 +35,9 @@ require ( go.opentelemetry.io/otel v1.32.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.32.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.32.0 // indirect; @grafana/grafana-backend-group - golang.org/x/crypto v0.28.0 // indirect; @grafana/grafana-backend-group - golang.org/x/mod v0.21.0 // @grafana/grafana-backend-group - golang.org/x/net v0.30.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/crypto v0.29.0 // indirect; @grafana/grafana-backend-group + golang.org/x/mod v0.22.0 // @grafana/grafana-backend-group + golang.org/x/net v0.31.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team golang.org/x/sync v0.9.0 // indirect; @grafana/alerting-backend golang.org/x/text v0.20.0 // indirect; @grafana/grafana-backend-group diff --git a/pkg/build/go.sum b/pkg/build/go.sum index 5884295ce55..42100a5bc72 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -262,8 +262,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= @@ -272,8 +272,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -283,8 +283,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 030640e048b..fb276708ea0 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,10 +6,10 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.26.0 + golang.org/x/tools v0.27.0 ) require ( - golang.org/x/mod v0.21.0 // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.9.0 // indirect ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index 1d1cc8410e9..5bb5540921c 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 96899d7eb61..dab971f2192 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -111,12 +111,12 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/tools v0.27.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index c580c242fae..624b014e14c 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -297,15 +297,15 @@ golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -337,8 +337,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index d81058f0f3a..9893a4e63d2 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -1131,8 +1131,8 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) { }, expectedCode: http.StatusOK, expectedOutput: []dtos.FolderSearchHit{ - dtos.FolderSearchHit{UID: "foo", Title: "Folder 1"}, - dtos.FolderSearchHit{UID: "qux", Title: "Folder 3"}, + {UID: "foo", Title: "Folder 1"}, + {UID: "qux", Title: "Folder 3"}, }, permissions: folderReadAndCreatePermission, }, @@ -1146,7 +1146,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) { params: "?parentUid=foo", expectedCode: http.StatusOK, expectedOutput: []dtos.FolderSearchHit{ - dtos.FolderSearchHit{UID: "bar", Title: "Folder 2", ParentUID: "foo"}, + {UID: "bar", Title: "Folder 2", ParentUID: "foo"}, }, permissions: folderReadAndCreatePermission, }, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index 3e1a1d21d05..d64a10ec40b 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -18,7 +18,6 @@ import { GroupByVariableKind, defaultVariableHide, VariableOption, - VariableRefresh, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils'; @@ -53,7 +52,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio let options: VariableOption[] = []; // Not sure if we actually have to still support this option given // that it's not exposed in the UI - if (transformVariableRefreshToEnum(variable.state.refresh) === VariableRefresh.Never || keepQueryOptions) { + if (transformVariableRefreshToEnum(variable.state.refresh) === 'never' || keepQueryOptions) { options = variableValueOptionsToVariableOptions(variable.state); } variables.push({ @@ -269,7 +268,7 @@ export function sceneVariablesSetToSchemaV2Variables( if (sceneUtils.isQueryVariable(variable)) { // Not sure if we actually have to still support this option given // that it's not exposed in the UI - if (transformVariableRefreshToEnum(variable.state.refresh) === VariableRefresh.Never || keepQueryOptions) { + if (transformVariableRefreshToEnum(variable.state.refresh) === 'never' || keepQueryOptions) { options = variableValueOptionsToVariableOptions(variable.state); } //query: DataQueryKind | string; @@ -325,7 +324,7 @@ export function sceneVariablesSetToSchemaV2Variables( current: currentVariableOption, options: [], regex: variable.state.regex, - refresh: VariableRefresh.OnDashboardLoad, + refresh: 'onDashboardLoad', pluginId: variable.state.pluginId, defaultOptionEnabled: !!variable.state.defaultOptionEnabled, multi: variable.state.isMulti || false, @@ -361,7 +360,7 @@ export function sceneVariablesSetToSchemaV2Variables( text: variable.state.value, }, query: intervals, - refresh: VariableRefresh.OnTimeRangeChanged, + refresh: 'onTimeRangeChanged', options: variable.state.intervals.map((interval) => ({ value: interval, text: interval, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index 28d0ceaa7f0..35be774d243 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -53,7 +53,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { expect(scene.state.title).toEqual(dash.title); expect(scene.state.description).toEqual(dash.description); expect(scene.state.editable).toEqual(dash.editable); - expect(scene.state.preload).toEqual(true); + expect(scene.state.preload).toEqual(false); expect(scene.state.version).toEqual(dash.schemaVersion); expect(scene.state.tags).toEqual(dash.tags); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index db5b251c020..49b99aab43d 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -78,7 +78,7 @@ import { registerPanelInteractionsReporter } from './transformSaveModelToScene'; import { transformCursorSyncV2ToV1, transformSortVariableToEnumV1, - transformValueMappingsToV1, + transformMappingsToV1, transformVariableHideToEnumV1, transformVariableRefreshToEnumV1, } from './transformToV1TypesUtils'; @@ -233,7 +233,7 @@ function buildVizPanel(panel: PanelKind): VizPanel { description: panel.spec.description, pluginId: panel.spec.vizConfig.kind, options: panel.spec.vizConfig.spec.options, - fieldConfig: transformValueMappingsToV1(panel.spec.vizConfig.spec.fieldConfig), + fieldConfig: transformMappingsToV1(panel.spec.vizConfig.spec.fieldConfig), pluginVersion: panel.spec.vizConfig.spec.pluginVersion, // FIXME: Transparent is not added to the schema yet // displayMode: panel.spec.transparent ? 'transparent' : undefined, diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index ec8ef3d1940..d550b44d3fc 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -8,7 +8,6 @@ import { TransformationKind, FieldConfigSource, DashboardLink, - DashboardCursorSync, DataTransformerConfig, PanelQuerySpec, DataQueryKind, @@ -34,7 +33,7 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getQueryRunnerFor } from '../utils/utils'; import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables'; -import { transformDashboardLinksToEnums, transformCursorSynctoEnum } from './transformToV2TypesUtils'; +import { transformCursorSynctoEnum } from './transformToV2TypesUtils'; // FIXME: This is temporary to avoid creating partial types for all the new schema, it has some performance implications, but it's fine for now type DeepPartial = T extends object @@ -58,7 +57,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps liveNow: getLiveNow(oldDash), preload: oldDash.preload, editable: oldDash.editable, - links: transformDashboardLinksToEnums(oldDash.links), + links: oldDash.links, tags: oldDash.tags, schemaVersion: DASHBOARD_SCHEMA_VERSION, // EOF dashboard settings @@ -385,9 +384,6 @@ function isDashboardSchemaV2(dash: any): dash is DashboardV2Spec { if (typeof dash.cursorSync !== 'string') { return false; } - if (!Object.values(DashboardCursorSync).includes(dash.cursorSync)) { - return false; - } if (typeof dash.liveNow !== 'boolean') { return false; } diff --git a/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts index 26a9c5cd8bb..b7b614e09aa 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts @@ -5,24 +5,26 @@ import { VariableSort as VariableSortV1, DashboardCursorSync as DashboardCursorSyncV1, defaultDashboardCursorSync, + MappingType as MappingTypeV1, + ThresholdsMode as ThresholdsModeV1, } from '@grafana/schema'; import { DashboardCursorSync, - MappingType, VariableHide, VariableRefresh, VariableSort, FieldConfigSource, SpecialValueMatch, + ThresholdsMode, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; export function transformVariableRefreshToEnumV1(refresh?: VariableRefresh): VariableRefreshV1 { switch (refresh) { - case VariableRefresh.Never: + case 'never': return VariableRefreshV1.never; - case VariableRefresh.OnDashboardLoad: + case 'onDashboardLoad': return VariableRefreshV1.onDashboardLoad; - case VariableRefresh.OnTimeRangeChanged: + case 'onTimeRangeChanged': return VariableRefreshV1.onTimeRangeChanged; default: return VariableRefreshV1.never; @@ -31,11 +33,11 @@ export function transformVariableRefreshToEnumV1(refresh?: VariableRefresh): Var export function transformVariableHideToEnumV1(hide?: VariableHide): VariableHideV1 { switch (hide) { - case VariableHide.DontHide: + case 'dontHide': return VariableHideV1.dontHide; - case VariableHide.HideLabel: + case 'hideLabel': return VariableHideV1.hideLabel; - case VariableHide.HideVariable: + case 'hideVariable': return VariableHideV1.hideVariable; default: return VariableHideV1.dontHide; @@ -44,15 +46,23 @@ export function transformVariableHideToEnumV1(hide?: VariableHide): VariableHide export function transformSortVariableToEnumV1(sort?: VariableSort): VariableSortV1 { switch (sort) { - case VariableSort.Disabled: + case 'disabled': return VariableSortV1.disabled; - case VariableSort.NumericalAsc: + case 'numericalAsc': return VariableSortV1.numericalAsc; - case VariableSort.NumericalDesc: + case 'alphabeticalCaseInsensitiveAsc': + return VariableSortV1.alphabeticalCaseInsensitiveAsc; + case 'alphabeticalCaseInsensitiveDesc': + return VariableSortV1.alphabeticalCaseInsensitiveDesc; + case 'numericalDesc': return VariableSortV1.numericalDesc; - case VariableSort.AlphabeticalAsc: + case 'naturalAsc': + return VariableSortV1.naturalAsc; + case 'naturalDesc': + return VariableSortV1.naturalDesc; + case 'alphabeticalAsc': return VariableSortV1.alphabeticalAsc; - case VariableSort.AlphabeticalDesc: + case 'alphabeticalDesc': return VariableSortV1.alphabeticalDesc; default: return VariableSortV1.disabled; @@ -61,11 +71,11 @@ export function transformSortVariableToEnumV1(sort?: VariableSort): VariableSort export function transformCursorSyncV2ToV1(cursorSync: DashboardCursorSync): DashboardCursorSyncV1 { switch (cursorSync) { - case DashboardCursorSync.Crosshair: + case 'Crosshair': return DashboardCursorSyncV1.Crosshair; - case DashboardCursorSync.Tooltip: + case 'Tooltip': return DashboardCursorSyncV1.Tooltip; - case DashboardCursorSync.Off: + case 'Off': return DashboardCursorSyncV1.Off; default: return defaultDashboardCursorSync; @@ -74,24 +84,35 @@ export function transformCursorSyncV2ToV1(cursorSync: DashboardCursorSync): Dash function transformSpecialValueMatchToV1(match: SpecialValueMatch): SpecialValueMatchV1 { switch (match) { - case SpecialValueMatch.True: + case 'true': return SpecialValueMatchV1.True; - case SpecialValueMatch.False: + case 'false': return SpecialValueMatchV1.False; - case SpecialValueMatch.Null: + case 'null': return SpecialValueMatchV1.Null; - case SpecialValueMatch.NotANumber: + case 'nan': return SpecialValueMatchV1.NaN; - case SpecialValueMatch.NullAndNaN: + case 'null+nan': return SpecialValueMatchV1.NullAndNaN; - case SpecialValueMatch.Empty: + case 'empty': return SpecialValueMatchV1.Empty; default: throw new Error(`Unknown match type: ${match}`); } } -export function transformValueMappingsToV1(fieldConfig: FieldConfigSource): FieldConfigSourceV1 { +export function transformMappingsToV1(fieldConfig: FieldConfigSource): FieldConfigSourceV1 { + const getThresholdsMode = (mode: ThresholdsMode): ThresholdsModeV1 => { + switch (mode) { + case 'absolute': + return ThresholdsModeV1.Absolute; + case 'percentage': + return ThresholdsModeV1.Percentage; + default: + return ThresholdsModeV1.Absolute; + } + }; + return { ...fieldConfig, defaults: { @@ -101,17 +122,17 @@ export function transformValueMappingsToV1(fieldConfig: FieldConfigSource): Fiel case 'value': return { ...mapping, - type: MappingType.ValueToText, + type: MappingTypeV1.ValueToText, }; case 'range': return { ...mapping, - type: MappingType.RangeToText, + type: MappingTypeV1.RangeToText, }; case 'regex': return { ...mapping, - type: MappingType.RegexToText, + type: MappingTypeV1.RegexToText, }; case 'special': return { @@ -120,12 +141,16 @@ export function transformValueMappingsToV1(fieldConfig: FieldConfigSource): Fiel ...mapping.options, match: transformSpecialValueMatchToV1(mapping.options.match), }, - type: MappingType.SpecialValue, + type: MappingTypeV1.SpecialValue, }; default: return mapping; } }), + thresholds: fieldConfig.defaults.thresholds && { + ...fieldConfig.defaults.thresholds, + mode: getThresholdsMode(fieldConfig.defaults.thresholds.mode), + }, }, }; } diff --git a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.test.ts b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.test.ts index 508d0746987..6c3ec797edb 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.test.ts @@ -1,20 +1,12 @@ -import { DashboardLink as DashboardLinkTypeV1 } from '@grafana/schema'; import { - DashboardCursorSync, - DashboardLinkType, - VariableRefresh, - VariableHide, - VariableSort, defaultVariableHide, defaultVariableSort, defaultVariableRefresh, - defaultDashboardLinkType, defaultDashboardCursorSync, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; import { transformCursorSynctoEnum, - transformDashboardLinksToEnums, transformVariableRefreshToEnum, transformVariableHideToEnum, transformSortVariableToEnum, @@ -23,86 +15,39 @@ import { describe('transformToV2TypesUtils', () => { describe('transformCursorSynctoEnum', () => { it('should return the correct enum value for cursor sync', () => { - expect(transformCursorSynctoEnum(0)).toBe(DashboardCursorSync.Off); - expect(transformCursorSynctoEnum(1)).toBe(DashboardCursorSync.Crosshair); - expect(transformCursorSynctoEnum(2)).toBe(DashboardCursorSync.Tooltip); + expect(transformCursorSynctoEnum(0)).toBe('Off'); + expect(transformCursorSynctoEnum(1)).toBe('Crosshair'); + expect(transformCursorSynctoEnum(2)).toBe('Tooltip'); expect(transformCursorSynctoEnum(undefined)).toBe(defaultDashboardCursorSync()); }); }); - - describe('transformDashboardLinksToEnums', () => { - const links: DashboardLinkTypeV1[] = [ - { - type: 'link', - asDropdown: false, - icon: '', - includeVars: false, - keepTime: false, - tags: [], - title: '', - url: '', - targetBlank: false, - tooltip: '', - }, - { - type: 'dashboards', - asDropdown: false, - icon: '', - includeVars: false, - keepTime: false, - tags: [], - title: '', - url: '', - targetBlank: false, - tooltip: '', - }, - { - // @ts-expect-error Testing invalid type - type: 'non-valid-type', - asDropdown: false, - icon: '', - includeVars: false, - keepTime: false, - tags: [], - title: '', - url: '', - targetBlank: false, - tooltip: '', - }, - ]; - - const transformedLinks = transformDashboardLinksToEnums(links); - expect(transformedLinks[0].type).toBe(DashboardLinkType.Link); - expect(transformedLinks[1].type).toBe(DashboardLinkType.Dashboards); - expect(transformedLinks[2].type).toBe(defaultDashboardLinkType()); - }); }); describe('transformVariableRefreshToEnum', () => { it('should return the correct enum value for variable refresh', () => { - expect(transformVariableRefreshToEnum(0)).toBe(VariableRefresh.Never); - expect(transformVariableRefreshToEnum(1)).toBe(VariableRefresh.OnDashboardLoad); - expect(transformVariableRefreshToEnum(2)).toBe(VariableRefresh.OnTimeRangeChanged); + expect(transformVariableRefreshToEnum(0)).toBe('never'); + expect(transformVariableRefreshToEnum(1)).toBe('onDashboardLoad'); + expect(transformVariableRefreshToEnum(2)).toBe('onTimeRangeChanged'); expect(transformVariableRefreshToEnum(undefined)).toBe(defaultVariableRefresh()); }); }); describe('transformVariableHideToEnum', () => { it('should return the correct enum value for variable hide', () => { - expect(transformVariableHideToEnum(0)).toBe(VariableHide.DontHide); - expect(transformVariableHideToEnum(1)).toBe(VariableHide.HideLabel); - expect(transformVariableHideToEnum(2)).toBe(VariableHide.HideVariable); + expect(transformVariableHideToEnum(0)).toBe('dontHide'); + expect(transformVariableHideToEnum(1)).toBe('hideLabel'); + expect(transformVariableHideToEnum(2)).toBe('hideVariable'); expect(transformVariableHideToEnum(undefined)).toBe(defaultVariableHide()); }); }); describe('transformSortVariableToEnum', () => { it('should return the correct enum value for variable sort', () => { - expect(transformSortVariableToEnum(0)).toBe(VariableSort.Disabled); - expect(transformSortVariableToEnum(1)).toBe(VariableSort.AlphabeticalAsc); - expect(transformSortVariableToEnum(2)).toBe(VariableSort.AlphabeticalDesc); - expect(transformSortVariableToEnum(3)).toBe(VariableSort.NumericalAsc); - expect(transformSortVariableToEnum(4)).toBe(VariableSort.NumericalDesc); + expect(transformSortVariableToEnum(0)).toBe('disabled'); + expect(transformSortVariableToEnum(1)).toBe('alphabeticalAsc'); + expect(transformSortVariableToEnum(2)).toBe('alphabeticalDesc'); + expect(transformSortVariableToEnum(3)).toBe('numericalAsc'); + expect(transformSortVariableToEnum(4)).toBe('numericalDesc'); expect(transformSortVariableToEnum(undefined)).toBe(defaultVariableSort()); }); }); diff --git a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts index 3088e5d6b15..6a2551c797f 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts @@ -1,65 +1,41 @@ import { - DashboardLink as DashboardLinkV1, VariableHide as VariableHideV1, VariableRefresh as VariableRefreshV1, VariableSort as VariableSortV1, DashboardCursorSync as DashboardCursorSyncV1, - DashboardLinkType as DashboardLinkTypeV1, } from '@grafana/schema'; import { DashboardCursorSync, defaultDashboardV2Spec, - DashboardLinkType, - DashboardLink, defaultVariableHide, defaultVariableRefresh, defaultVariableSort, VariableHide, VariableRefresh, VariableSort, - defaultDashboardLinkType, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; export function transformCursorSynctoEnum(cursorSync?: DashboardCursorSyncV1): DashboardCursorSync { switch (cursorSync) { case 0: - return DashboardCursorSync.Off; + return 'Off'; case 1: - return DashboardCursorSync.Crosshair; + return 'Crosshair'; case 2: - return DashboardCursorSync.Tooltip; + return 'Tooltip'; default: return defaultDashboardV2Spec().cursorSync; } } -function transformDashboardLinkTypeToEnum(linkType: DashboardLinkTypeV1): DashboardLinkType { - switch (linkType) { - case 'link': - return DashboardLinkType.Link; - case 'dashboards': - return DashboardLinkType.Dashboards; - default: - return defaultDashboardLinkType(); - } -} - -export function transformDashboardLinksToEnums(links: DashboardLinkV1[]): DashboardLink[] { - return links.map((link) => { - return { - ...link, - type: transformDashboardLinkTypeToEnum(link.type), - }; - }); -} export function transformVariableRefreshToEnum(refresh?: VariableRefreshV1): VariableRefresh { switch (refresh) { case 0: - return VariableRefresh.Never; + return 'never'; case 1: - return VariableRefresh.OnDashboardLoad; + return 'onDashboardLoad'; case 2: - return VariableRefresh.OnTimeRangeChanged; + return 'onTimeRangeChanged'; default: return defaultVariableRefresh(); } @@ -67,11 +43,11 @@ export function transformVariableRefreshToEnum(refresh?: VariableRefreshV1): Var export function transformVariableHideToEnum(hide?: VariableHideV1): VariableHide { switch (hide) { case 0: - return VariableHide.DontHide; + return 'dontHide'; case 1: - return VariableHide.HideLabel; + return 'hideLabel'; case 2: - return VariableHide.HideVariable; + return 'hideVariable'; default: return defaultVariableHide(); } @@ -79,15 +55,15 @@ export function transformVariableHideToEnum(hide?: VariableHideV1): VariableHide export function transformSortVariableToEnum(sort?: VariableSortV1): VariableSort { switch (sort) { case 0: - return VariableSort.Disabled; + return 'disabled'; case 1: - return VariableSort.AlphabeticalAsc; + return 'alphabeticalAsc'; case 2: - return VariableSort.AlphabeticalDesc; + return 'alphabeticalDesc'; case 3: - return VariableSort.NumericalAsc; + return 'numericalAsc'; case 4: - return VariableSort.NumericalDesc; + return 'numericalDesc'; default: return defaultVariableSort(); } From a4fff11438359ccebf6cbfc54988e1f12dfd5df6 Mon Sep 17 00:00:00 2001 From: Ethan <32370007+SadFaceSmith@users.noreply.github.com> Date: Thu, 28 Nov 2024 04:58:52 -0500 Subject: [PATCH 28/41] Update project URL For Security Labels (#96405) * Update project URL in .github/commands.json * update type/build-packaging project --------- Co-authored-by: tonypowa --- .github/commands.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index 1f466e4d783..e79e5e860ed 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -472,7 +472,7 @@ "name": "type/build-packaging", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/509" + "url": "https://github.com/orgs/grafana/projects/632" } }, { @@ -616,7 +616,7 @@ "name": "area/security", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/470" + "url": "https://github.com/orgs/grafana/projects/476" } }, { From 4721d5164b75f9e4e6be0e31e74ce4cc1cfecf5e Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 28 Nov 2024 03:36:09 -0700 Subject: [PATCH 29/41] Dashboard Schema v2: Transformation to v2 supports annotations (#96787) * Annotations * Default to configured default DS when datasource is undefined --------- Co-authored-by: Ivan Ortega Co-authored-by: alexandra vargas --- .../dashboard/v2alpha0/dashboard.gen.ts | 19 ++-- .../dashboard/v2alpha0/dashboard.schema.cue | 15 ++- ...sformSceneToSaveModelSchemaV2.test.ts.snap | 81 +++++++++++++++- .../transformSaveModelSchemaV2ToScene.test.ts | 9 +- .../transformSaveModelSchemaV2ToScene.ts | 7 +- .../transformSceneToSaveModelSchemaV2.test.ts | 85 ++++++++++++++++- .../transformSceneToSaveModelSchemaV2.ts | 95 +++++++++++++++++-- 7 files changed, 271 insertions(+), 40 deletions(-) diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts index 64d4ae66bbd..dd71e19ba59 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts @@ -463,11 +463,9 @@ export const defaultVizConfigKind = (): VizConfigKind => ({ }); export interface AnnotationQuerySpec { - datasource: DataSourceRef; + datasource?: DataSourceRef; query: DataQueryKind; - // TODO: Should be figured out based on datasource (Grafana ds) - // builtIn?: int - // Below are currently existing options for annotation queries + builtIn?: boolean; enable: boolean; filter: AnnotationPanelFilter; hide: boolean; @@ -476,7 +474,6 @@ export interface AnnotationQuerySpec { } export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ - datasource: defaultDataSourceRef(), query: defaultDataQueryKind(), enable: false, filter: defaultAnnotationPanelFilter(), @@ -521,14 +518,13 @@ export const defaultDataQueryKind = (): DataQueryKind => ({ export interface PanelQuerySpec { query: DataQueryKind; - datasource: DataSourceRef; + datasource?: DataSourceRef; refId: string; hidden: boolean; } export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ query: defaultDataQueryKind(), - datasource: defaultDataSourceRef(), refId: "", hidden: false, }); @@ -859,7 +855,7 @@ export interface QueryVariableSpec { refresh: VariableRefresh; skipUrlSync: boolean; description?: string; - datasource: DataSourceRef; + datasource?: DataSourceRef; query: string | DataQueryKind; regex: string; sort: VariableSort; @@ -877,7 +873,6 @@ export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ hide: "dontHide", refresh: "never", skipUrlSync: false, - datasource: defaultDataSourceRef(), query: "", regex: "", sort: "disabled", @@ -1080,7 +1075,7 @@ export const defaultCustomVariableKind = (): CustomVariableKind => ({ // GroupBy variable specification export interface GroupByVariableSpec { name: string; - datasource: DataSourceRef; + datasource?: DataSourceRef; current: VariableOption; options: VariableOption[]; multi: boolean; @@ -1094,7 +1089,6 @@ export interface GroupByVariableSpec { export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ name: "", - datasource: defaultDataSourceRef(), current: { text: "", value: "", }, options: [], multi: false, @@ -1117,7 +1111,7 @@ export const defaultGroupByVariableKind = (): GroupByVariableKind => ({ // Adhoc variable specification export interface AdhocVariableSpec { name: string; - datasource: DataSourceRef; + datasource?: DataSourceRef; baseFilters: AdHocFilterWithLabels[]; filters: AdHocFilterWithLabels[]; defaultKeys: MetricFindValue[]; @@ -1129,7 +1123,6 @@ export interface AdhocVariableSpec { export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ name: "", - datasource: defaultDataSourceRef(), baseFilters: [], filters: [], defaultKeys: [], diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 236805b3a24..6be276b73d1 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -362,12 +362,9 @@ VizConfigKind: { } AnnotationQuerySpec: { - datasource: DataSourceRef + datasource?: DataSourceRef query: DataQueryKind - - // TODO: Should be figured out based on datasource (Grafana ds) - // builtIn?: int - // Below are currently existing options for annotation queries + builtIn?: bool enable: bool filter: AnnotationPanelFilter hide: bool @@ -398,7 +395,7 @@ DataQueryKind: { PanelQuerySpec: { query: DataQueryKind - datasource: DataSourceRef + datasource?: DataSourceRef refId: string hidden: bool @@ -598,7 +595,7 @@ QueryVariableSpec: { refresh: VariableRefresh skipUrlSync: bool | *false description?: string - datasource: DataSourceRef | *{} + datasource?: DataSourceRef query: string | DataQueryKind | *"" regex: string | *"" sort: VariableSort @@ -732,7 +729,7 @@ CustomVariableKind: { // GroupBy variable specification GroupByVariableSpec: { name: string | *"" - datasource: DataSourceRef | *{} + datasource?: DataSourceRef current: VariableOption | *{ text: "" value: "" @@ -756,7 +753,7 @@ GroupByVariableKind: { // Adhoc variable specification AdhocVariableSpec: { name: string | *"" - datasource: DataSourceRef | *{} + datasource?: DataSourceRef baseFilters: [...AdHocFilterWithLabels] | *[] filters: [...AdHocFilterWithLabels] | *[] defaultKeys: [...MetricFindValue] | *[] diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap index e89b5470781..81be3748ccf 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -2,7 +2,86 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model schema v2 1`] = ` { - "annotations": [], + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "builtIn": false, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --", + }, + "enable": true, + "filter": { + "exclude": false, + "ids": [], + }, + "hide": false, + "iconColor": "red", + "name": "query1", + "query": { + "kind": "grafana", + "spec": { + "enable": true, + "iconColor": "red", + "name": "query1", + }, + }, + }, + }, + { + "kind": "AnnotationQuery", + "spec": { + "builtIn": false, + "datasource": { + "type": "prometheus", + "uid": "abcdef", + }, + "enable": true, + "filter": { + "exclude": false, + "ids": [], + }, + "hide": true, + "iconColor": "blue", + "name": "query2", + "query": { + "kind": "prometheus", + "spec": { + "enable": true, + "iconColor": "blue", + "name": "query2", + }, + }, + }, + }, + { + "kind": "AnnotationQuery", + "spec": { + "builtIn": false, + "datasource": { + "type": "loki", + "uid": "Loki", + }, + "enable": true, + "filter": { + "exclude": false, + "ids": [], + }, + "hide": true, + "iconColor": "green", + "name": "query3", + "query": { + "kind": "loki", + "spec": { + "enable": true, + "iconColor": "green", + "name": "query3", + }, + }, + }, + }, + ], "cursorSync": "Crosshair", "description": "Test Description", "editable": true, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index 35be774d243..777dbd3c675 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -185,13 +185,11 @@ describe('transformSaveModelSchemaV2ToScene', () => { expect(getQueryRunnerFor(vizPanels[0])?.state.datasource).toBeUndefined(); }); - // Skipping the test because the schema doesn't accept the ds to be undefined. - // In future PR, we will mark it as optional so, this test should pass and the runtime code should be updated. - it.skip('should set panel ds as undefined if it is not mixed DS', () => { + it('should set panel ds as mixed if one ds is undefined', () => { const dashboard = cloneDeep(defaultDashboard); + dashboard.spec.elements['test-panel-uid'].spec.data.spec.queries.push({ kind: 'PanelQuery', - // @ts-expect-error TODO: When marking DS as optional, this should be fixed spec: { refId: 'A', hidden: false, @@ -208,6 +206,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); expect(vizPanels.length).toBe(1); - expect(getQueryRunnerFor(vizPanels[0])?.state.datasource).toBeUndefined(); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.type).toBe('mixed'); + expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.uid).toBe(MIXED_DATASOURCE_NAME); }); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 49b99aab43d..22a1ef865d0 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -101,7 +101,10 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo { return new DashboardAnnotationsDataLayer({ key: uniqueId('annotations-'), - query: annotation.spec, + query: { + ...annotation.spec, + builtIn: annotation.spec.builtIn ? 1 : 0, + }, name: annotation.spec.name, isEnabled: Boolean(annotation.spec.enable), isHidden: Boolean(annotation.spec.hide), @@ -297,7 +300,7 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { panel.spec.data.spec.queries.forEach((query) => { if (!datasource) { datasource = query.spec.datasource; - } else if (datasource.uid !== query.spec.datasource.uid || datasource.type !== query.spec.datasource.type) { + } else if (datasource.uid !== query.spec.datasource?.uid || datasource.type !== query.spec.datasource?.type) { isMixedDatasource = true; } }); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 76d982b7a18..d5d411465f4 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -24,7 +24,9 @@ import { } from '@grafana/schema/dist/esm/index.gen'; import { DashboardEditPane } from '../edit-pane/DashboardEditPane'; +import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; @@ -35,6 +37,35 @@ function setupDashboardScene(state: DashboardSceneState): DashboardScene { return new DashboardScene(state); } +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + ...jest.requireActual('@grafana/runtime').config, + bootData: { + settings: { + defaultDatasource: 'loki', + datasources: { + Prometheus: { + name: 'Prometheus', + meta: { id: 'prometheus' }, + type: 'datasource', + }, + '-- Grafana --': { + name: 'Grafana', + meta: { id: 'grafana' }, + type: 'datasource', + }, + loki: { + name: 'Loki', + meta: { id: 'loki' }, + type: 'datasource', + }, + }, + }, + }, + }, +})); + describe('transformSceneToSaveModelSchemaV2', () => { let dashboardScene: DashboardScene; let prevFeatureToggleValue: boolean; @@ -52,6 +83,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { // The intention is to have a complete dashboard scene // with all the possible properties set dashboardScene = setupDashboardScene({ + $data: new DashboardDataLayerSet({ annotationLayers }), title: 'Test Dashboard', description: 'Test Description', preload: true, @@ -281,7 +313,58 @@ describe('transformSceneToSaveModelSchemaV2', () => { it('should transform scene to save model schema v2', () => { const result = transformSceneToSaveModelSchemaV2(dashboardScene); - expect(result).toMatchSnapshot(); + + // Check that the annotation layers are correctly transformed + expect(result.annotations).toHaveLength(3); + // check annotation layer 3 with no datasource has the default datasource defined as type + expect(result.annotations?.[2].spec.query.kind).toBe('loki'); }); }); + +const annotationLayer1 = new DashboardAnnotationsDataLayer({ + key: 'layer1', + query: { + datasource: { + type: 'grafana', + uid: '-- Grafana --', + }, + name: 'query1', + enable: true, + iconColor: 'red', + }, + name: 'layer1', + isEnabled: true, + isHidden: false, +}); + +const annotationLayer2 = new DashboardAnnotationsDataLayer({ + key: 'layer2', + query: { + datasource: { + type: 'prometheus', + uid: 'abcdef', + }, + name: 'query2', + enable: true, + iconColor: 'blue', + }, + name: 'layer2', + isEnabled: true, + isHidden: true, +}); + +// this could happen if a dahboard was created from code and the datasource was not defined +const annotationLayer3NoDsDefined = new DashboardAnnotationsDataLayer({ + key: 'layer3', + query: { + name: 'query3', + enable: true, + iconColor: 'green', + }, + name: 'layer3', + isEnabled: true, + isHidden: true, +}); + +const annotationLayers = [annotationLayer1, annotationLayer2, annotationLayer3NoDsDefined]; diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index d550b44d3fc..79c8a700cb8 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -1,4 +1,18 @@ -import { behaviors, SceneDataQuery, SceneDataTransformer, SceneVariableSet, VizPanel } from '@grafana/scenes'; +import { omit } from 'lodash'; + +import { AnnotationQuery } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { + behaviors, + dataLayers, + SceneDataQuery, + SceneDataTransformer, + SceneVariableSet, + VizPanel, +} from '@grafana/scenes'; +import { DataSourceRef } from '@grafana/schema'; +import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; + import { DashboardV2Spec, defaultDashboardV2Spec, @@ -11,7 +25,6 @@ import { DataTransformerConfig, PanelQuerySpec, DataQueryKind, - defaultDataSourceRef, GridLayoutItemKind, QueryOptionsSpec, QueryVariableKind, @@ -22,9 +35,11 @@ import { ConstantVariableKind, GroupByVariableKind, AdhocVariableKind, -} from '@grafana/schema/src/schema/dashboard/v2alpha0/dashboard.gen'; -import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; - + AnnotationQueryKind, + defaultAnnotationPanelFilter, + defaultAnnotationQuerySpec, +} from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen'; +import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; @@ -86,7 +101,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps // EOF elements // annotations - annotations: [], //FIXME + annotations: getAnnotations(oldDash), // EOF annotations // layout @@ -251,7 +266,7 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] { spec: query, }; const querySpec: PanelQuerySpec = { - datasource: datasource ?? defaultDataSourceRef(), + datasource: datasource ?? getDefaultDataSourceRef(), query: dataQuery, refId: query.refId, hidden: query.hidden, @@ -266,8 +281,8 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] { } export function getDataQueryKind(query: SceneDataQuery): string { - // If the query has a datasource, use the datasource type, otherwise use 'default' - return query.datasource?.type ?? 'default'; + // If the query has a datasource, use the datasource type, otherwise return empty kind + return query.datasource?.type ?? getDefaultDataSourceRef()?.type ?? ''; } export function getDataQuerySpec(query: SceneDataQuery): Record { @@ -369,6 +384,68 @@ function getVariables(oldDash: DashboardSceneState) { return variables; } +function getAnnotations(state: DashboardSceneState): AnnotationQueryKind[] { + const data = state.$data; + if (!(data instanceof DashboardDataLayerSet)) { + return []; + } + const annotations: AnnotationQueryKind[] = []; + for (const layer of data.state.annotationLayers) { + if (!(layer instanceof dataLayers.AnnotationsDataLayer)) { + continue; + } + const result: AnnotationQueryKind = { + kind: 'AnnotationQuery', + spec: { + name: layer.state.query.name, + datasource: layer.state.query.datasource || getDefaultDataSourceRef(), + query: { + kind: getAnnotationQueryKind(layer.state.query), + spec: omit(layer.state.query, 'datasource'), + }, + enable: Boolean(layer.state.isEnabled), + hide: Boolean(layer.state.isHidden), + filter: layer.state.query.filter ?? defaultAnnotationPanelFilter(), + iconColor: layer.state.query.iconColor, + builtIn: + layer.state.query.builtIn === undefined + ? Boolean(layer.state.query.builtIn) + : defaultAnnotationQuerySpec().builtIn, + }, + }; + annotations.push(result); + } + return annotations; +} + +export function getAnnotationQueryKind(annotationQuery: AnnotationQuery): string { + if (annotationQuery.datasource?.type) { + return annotationQuery.datasource.type; + } else { + const ds = getDefaultDataSourceRef(); + if (ds) { + return ds.type!; // in the datasource list from bootData "id" is the type + } + // if we can't find the default datasource, return grafana as default + return 'grafana'; + } +} + +function getDefaultDataSourceRef(): DataSourceRef | undefined { + // we need to return the default datasource configured in the BootConfig + const defaultDatasource = config.bootData.settings.defaultDatasource; + + // get default datasource type + const dsList = config.bootData.settings.datasources; + const ds = dsList[defaultDatasource]; + + if (ds) { + return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type + } + + return undefined; +} + // Function to know if the dashboard transformed is a valid DashboardV2Spec function isDashboardSchemaV2(dash: any): dash is DashboardV2Spec { if (typeof dash !== 'object' || dash === null) { From 59d4b91e4c84b1d60ef9a790a95497576aaebbeb Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:18:45 +0100 Subject: [PATCH 30/41] Alerting: Explore button in Insights view (#96496) * Alerting: Explore button in Insights view * Fix wrong insights queries * replace `$rule_group` in the query expression with the actual rule group value * Add info tooltip in `Insights` title * Remove unused `RatingModal` component * fix betterer results * remove unnecessary `React.Fragment` * Remove unnecessary text space --- .betterer.results | 11 +-- .../alerting/unified/home/Insights.tsx | 24 ++++++- ...RatingModal.tsx => InsightsMenuButton.tsx} | 68 ++++++++++++++++++- .../unified/insights/grafana/Active.tsx | 4 +- .../insights/grafana/AlertsByStateScene.tsx | 4 +- .../grafana/EvalSuccessVsFailuresScene.tsx | 8 +-- .../insights/grafana/InstanceStatusScene.tsx | 4 +- .../grafana/MissedIterationsScene.tsx | 4 +- .../grafana/MostFiredInstancesTable.tsx | 4 +- .../unified/insights/grafana/Paused.tsx | 4 +- .../insights/grafana/RulesByEvaluation.tsx | 4 +- .../grafana/RulesByEvaluationPercentage.tsx | 4 +- .../grafana/alertmanager/AlertsByState.tsx | 4 +- .../alertmanager/NotificationsScene.tsx | 4 +- .../alertmanager/SilencesByStateScene.tsx | 4 +- .../unified/insights/mimir/AlertsByState.tsx | 4 +- .../unified/insights/mimir/InvalidConfig.tsx | 4 +- .../unified/insights/mimir/Notifications.tsx | 8 +-- .../unified/insights/mimir/Silences.tsx | 4 +- ...upEvaluationDurationIntervalRatioScene.tsx | 4 +- .../RuleGroupEvaluationDurationScene.tsx | 4 +- .../perGroup/RuleGroupEvaluationsScene.tsx | 8 +-- .../mimir/perGroup/RuleGroupIntervalScene.tsx | 4 +- .../mimir/perGroup/RulesPerGroupScene.tsx | 4 +- .../rules/EvalSuccessVsFailuresScene.tsx | 8 +-- .../unified/insights/mimir/rules/Firing.tsx | 4 +- .../insights/mimir/rules/InstancesByState.tsx | 4 +- .../rules/InstancesPercentageByState.tsx | 4 +- .../mimir/rules/MissedIterationsScene.tsx | 4 +- .../insights/mimir/rules/MostFiredRules.tsx | 4 +- .../unified/insights/mimir/rules/Pending.tsx | 4 +- 31 files changed, 160 insertions(+), 71 deletions(-) rename public/app/features/alerting/unified/insights/{RatingModal.tsx => InsightsMenuButton.tsx} (53%) diff --git a/.betterer.results b/.betterer.results index 2f3246ea376..7d4f307f249 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1731,7 +1731,9 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "13"] ], "public/app/features/alerting/unified/home/Insights.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/alerting/unified/home/PluginIntegrations.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] @@ -1742,9 +1744,10 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/hooks/useControlledFieldArray.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/features/alerting/unified/insights/RatingModal.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + "public/app/features/alerting/unified/insights/InsightsMenuButton.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/alerting/unified/mocks.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], diff --git a/public/app/features/alerting/unified/home/Insights.tsx b/public/app/features/alerting/unified/home/Insights.tsx index 43765a28c02..2bf21799cc2 100644 --- a/public/app/features/alerting/unified/home/Insights.tsx +++ b/public/app/features/alerting/unified/home/Insights.tsx @@ -14,6 +14,7 @@ import { SceneVariableSet, VariableValueSelectors, } from '@grafana/scenes'; +import { Icon, Text, Tooltip } from '@grafana/ui'; import { config } from '../../../../core/config'; import { SectionFooter } from '../insights/SectionFooter'; @@ -173,7 +174,28 @@ export function getInsightsScenes() { controls: [ new SceneReactObject({ component: SectionSubheader, - props: { children:
Monitor the status of your system.
}, + props: { + children: ( + <> + + Monitor the status of your system{' '} + + Alerting insights provides pre-built dashboards to monitor your alerting data. +
+
+ You can identify patterns in why things go wrong and discover trends in alerting performance + within your organization. + + } + > + +
+
+ + ), + }, }), new SceneControlsSpacer(), new SceneTimePicker({}), diff --git a/public/app/features/alerting/unified/insights/RatingModal.tsx b/public/app/features/alerting/unified/insights/InsightsMenuButton.tsx similarity index 53% rename from public/app/features/alerting/unified/insights/RatingModal.tsx rename to public/app/features/alerting/unified/insights/InsightsMenuButton.tsx index 8611d4602e5..57883227bee 100644 --- a/public/app/features/alerting/unified/insights/RatingModal.tsx +++ b/public/app/features/alerting/unified/insights/InsightsMenuButton.tsx @@ -1,12 +1,67 @@ import { css } from '@emotion/css'; import { useState } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { ExploreUrlState, serializeStateToUrlParam, toURLRange, GrafanaTheme2 } from '@grafana/data'; +import { + SceneComponentProps, + sceneGraph, + SceneObjectBase, + SceneObjectState, + SceneTimeRangeState, + SceneVariableSetState, +} from '@grafana/scenes'; +import { DataQuery } from '@grafana/schema'; import { Button, Dropdown, Icon, IconButton, Menu, Modal, useStyles2 } from '@grafana/ui'; import { trackInsightsFeedback } from '../Analytics'; -export function InsightsRatingModal({ panel }: { panel: string }) { +type DataQueryWithExpr = DataQuery & { expr: string }; + +const getPrometheusExploreUrl = ({ + queries, + range, + variables, +}: { + queries?: DataQueryWithExpr[]; + range: SceneTimeRangeState; + variables: SceneVariableSetState; +}): string => { + // In Mimir-per-group panels, replace `$rule_group` in the query expression with the actual rule group value + const ruleGroup = variables?.variables.find((v) => v.state.name === 'rule_group')?.getValue() || null; + if (ruleGroup !== null) { + queries = queries?.map((query) => { + return { + ...query, + expr: query.expr.replace('$rule_group', String(ruleGroup)), + }; + }); + } + const urlState: ExploreUrlState = { + datasource: (queries?.length && queries[0].datasource?.uid) || null, + queries: + queries?.map(({ expr, refId }, i) => { + return { expr, refId }; + }) || [], + range: toURLRange(range ? { from: range.from, to: range.to } : { from: 'now-1h', to: 'now' }), + }; + + const param = encodeURIComponent(serializeStateToUrlParam(urlState)); + + return `/explore?left=${param}`; +}; + +const InsightsMenuButtonRenderer = ({ model }: SceneComponentProps) => { + const data = sceneGraph.getData(model).useState(); + const timeRange = sceneGraph.getTimeRange(model).useState(); + const variables = sceneGraph.getVariables(model).useState(); + const panel = model.state.panel; + + const url = getPrometheusExploreUrl({ + queries: data.data?.request?.targets as DataQueryWithExpr[], + range: timeRange, + variables: variables, + }); + const styles = useStyles2(getStyles); const [showModal, setShowModal] = useState(false); @@ -50,6 +105,7 @@ export function InsightsRatingModal({ panel }: { panel: string }) { const menu = ( + setShowModal(true)} /> ); @@ -62,6 +118,14 @@ export function InsightsRatingModal({ panel }: { panel: string }) { {modal} ); +}; + +interface InsightsMenuButtonState extends SceneObjectState { + panel: string; +} + +export class InsightsMenuButton extends SceneObjectBase { + static Component = InsightsMenuButtonRenderer; } const getStyles = (theme: GrafanaTheme2) => ({ diff --git a/public/app/features/alerting/unified/insights/grafana/Active.tsx b/public/app/features/alerting/unified/insights/grafana/Active.tsx index 46528c23d69..dfea1cc8328 100644 --- a/public/app/features/alerting/unified/insights/grafana/Active.tsx +++ b/public/app/features/alerting/unified/insights/grafana/Active.tsx @@ -3,7 +3,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getActiveGrafanaAlertsScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -41,7 +41,7 @@ export function getActiveGrafanaAlertsScene(datasource: DataSourceRef, panelTitl ], }) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/AlertsByStateScene.tsx b/public/app/features/alerting/unified/insights/grafana/AlertsByStateScene.tsx index 55ec514c57c..f9307c94442 100644 --- a/public/app/features/alerting/unified/insights/grafana/AlertsByStateScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/AlertsByStateScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneDataTransformer, SceneFlexItem, SceneQueryRunner } import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getGrafanaInstancesByStateScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -56,7 +56,7 @@ export function getGrafanaInstancesByStateScene(datasource: DataSourceRef, panel .matchFieldsWithName('nodata') .overrideColor(overrideToFixedColor('nodata')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/EvalSuccessVsFailuresScene.tsx b/public/app/features/alerting/unified/insights/grafana/EvalSuccessVsFailuresScene.tsx index b3d9921660f..6ce91ac7570 100644 --- a/public/app/features/alerting/unified/insights/grafana/EvalSuccessVsFailuresScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/EvalSuccessVsFailuresScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getGrafanaEvalSuccessVsFailuresScene(datasource: DataSourceRef, panelTitle: string) { const exprA = INSTANCE_ID @@ -18,13 +18,13 @@ export function getGrafanaEvalSuccessVsFailuresScene(datasource: DataSourceRef, queries: [ { refId: 'A', - exprA, + expr: exprA, range: true, legendFormat: 'success', }, { refId: 'B', - exprB, + expr: exprB, range: true, legendFormat: 'failed', }, @@ -46,7 +46,7 @@ export function getGrafanaEvalSuccessVsFailuresScene(datasource: DataSourceRef, .matchFieldsWithName('failed') .overrideColor(overrideToFixedColor('failed')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/InstanceStatusScene.tsx b/public/app/features/alerting/unified/insights/grafana/InstanceStatusScene.tsx index 647c27c00ff..990d3d621e5 100644 --- a/public/app/features/alerting/unified/insights/grafana/InstanceStatusScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/InstanceStatusScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getInstanceStatByStatusScene( datasource: DataSourceRef, panelTitle: string, @@ -33,7 +33,7 @@ export function getInstanceStatByStatusScene( .setData(query) .setOverrides((b) => b.matchFieldsWithName(status).overrideColor(overrideToFixedColor(status))) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/MissedIterationsScene.tsx b/public/app/features/alerting/unified/insights/grafana/MissedIterationsScene.tsx index 7c1976df590..2bc5e3d426b 100644 --- a/public/app/features/alerting/unified/insights/grafana/MissedIterationsScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/MissedIterationsScene.tsx @@ -11,7 +11,7 @@ import { import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getGrafanaMissedIterationsScene(datasource: DataSourceRef, panelTitle: string) { const expr = `sum by(rule_group) (grafanacloud_instance_rule_group_iterations_missed_total:rate5m{id="${INSTANCE_ID}"})`; @@ -62,7 +62,7 @@ export function getGrafanaMissedIterationsScene(datasource: DataSourceRef, panel .setData(transformation) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/MostFiredInstancesTable.tsx b/public/app/features/alerting/unified/insights/grafana/MostFiredInstancesTable.tsx index 0c5a0e44950..e14ba7d0d6e 100644 --- a/public/app/features/alerting/unified/insights/grafana/MostFiredInstancesTable.tsx +++ b/public/app/features/alerting/unified/insights/grafana/MostFiredInstancesTable.tsx @@ -14,7 +14,7 @@ import { Link, useStyles2 } from '@grafana/ui'; import { PANEL_STYLES } from '../../home/Insights'; import { createRelativeUrl } from '../../utils/url'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getMostFiredInstancesScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -101,7 +101,7 @@ export function getMostFiredInstancesScene(datasource: DataSourceRef, panelTitle .setDescription('The alert rule instances that have fired the most') .setData(transformation) .setNoValue('No new alerts fired last week') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/Paused.tsx b/public/app/features/alerting/unified/insights/grafana/Paused.tsx index eb7b1abb851..1607bab80f4 100644 --- a/public/app/features/alerting/unified/insights/grafana/Paused.tsx +++ b/public/app/features/alerting/unified/insights/grafana/Paused.tsx @@ -3,7 +3,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getPausedGrafanaAlertsScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -41,7 +41,7 @@ export function getPausedGrafanaAlertsScene(datasource: DataSourceRef, panelTitl ], }) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/RulesByEvaluation.tsx b/public/app/features/alerting/unified/insights/grafana/RulesByEvaluation.tsx index 57d9bb90cc8..fb700d701e5 100644 --- a/public/app/features/alerting/unified/insights/grafana/RulesByEvaluation.tsx +++ b/public/app/features/alerting/unified/insights/grafana/RulesByEvaluation.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getGrafanaRulesByEvaluationScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -32,7 +32,7 @@ export function getGrafanaRulesByEvaluationScene(datasource: DataSourceRef, pane .setOverrides((b) => b.matchFieldsWithName('active evaluation').overrideColor(overrideToFixedColor('active evaluation')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/RulesByEvaluationPercentage.tsx b/public/app/features/alerting/unified/insights/grafana/RulesByEvaluationPercentage.tsx index a6728f14953..bbb2d854702 100644 --- a/public/app/features/alerting/unified/insights/grafana/RulesByEvaluationPercentage.tsx +++ b/public/app/features/alerting/unified/insights/grafana/RulesByEvaluationPercentage.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getGrafanaRulesByEvaluationPercentageScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -35,7 +35,7 @@ export function getGrafanaRulesByEvaluationPercentageScene(datasource: DataSourc .setOverrides((b) => b.matchFieldsWithName('active evaluation').overrideColor(overrideToFixedColor('active evaluation')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/alertmanager/AlertsByState.tsx b/public/app/features/alerting/unified/insights/grafana/alertmanager/AlertsByState.tsx index 0796b90a644..60e843a80b6 100644 --- a/public/app/features/alerting/unified/insights/grafana/alertmanager/AlertsByState.tsx +++ b/public/app/features/alerting/unified/insights/grafana/alertmanager/AlertsByState.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getAlertsByStateScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -30,7 +30,7 @@ export function getAlertsByStateScene(datasource: DataSourceRef, panelTitle: str .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOverrides((b) => b.matchFieldsWithName('active').overrideColor(overrideToFixedColor('active'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/alertmanager/NotificationsScene.tsx b/public/app/features/alerting/unified/insights/grafana/alertmanager/NotificationsScene.tsx index 27f542f91b5..36f83ec7359 100644 --- a/public/app/features/alerting/unified/insights/grafana/alertmanager/NotificationsScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/alertmanager/NotificationsScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getGrafanaAlertmanagerNotificationsScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -38,7 +38,7 @@ export function getGrafanaAlertmanagerNotificationsScene(datasource: DataSourceR .matchFieldsWithName('failed') .overrideColor(overrideToFixedColor('failed')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/grafana/alertmanager/SilencesByStateScene.tsx b/public/app/features/alerting/unified/insights/grafana/alertmanager/SilencesByStateScene.tsx index a1b68a0e0ed..a5664d87fa8 100644 --- a/public/app/features/alerting/unified/insights/grafana/alertmanager/SilencesByStateScene.tsx +++ b/public/app/features/alerting/unified/insights/grafana/alertmanager/SilencesByStateScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getGrafanaAlertmanagerSilencesScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -29,7 +29,7 @@ export function getGrafanaAlertmanagerSilencesScene(datasource: DataSourceRef, p .setData(query) .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/AlertsByState.tsx b/public/app/features/alerting/unified/insights/mimir/AlertsByState.tsx index 415423fb212..ae12e43dc23 100644 --- a/public/app/features/alerting/unified/insights/mimir/AlertsByState.tsx +++ b/public/app/features/alerting/unified/insights/mimir/AlertsByState.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getAlertsByStateScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -30,7 +30,7 @@ export function getAlertsByStateScene(datasource: DataSourceRef, panelTitle: str .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOverrides((b) => b.matchFieldsWithName('active').overrideColor(overrideToFixedColor('active'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/InvalidConfig.tsx b/public/app/features/alerting/unified/insights/mimir/InvalidConfig.tsx index dba5a5b7ae3..8c9a43227cf 100644 --- a/public/app/features/alerting/unified/insights/mimir/InvalidConfig.tsx +++ b/public/app/features/alerting/unified/insights/mimir/InvalidConfig.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { BigValueGraphMode, DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getInvalidConfigScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -29,7 +29,7 @@ export function getInvalidConfigScene(datasource: DataSourceRef, panelTitle: str .setData(query) .setUnit('bool_yes_no') .setOption('graphMode', BigValueGraphMode.None) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/Notifications.tsx b/public/app/features/alerting/unified/insights/mimir/Notifications.tsx index ca7576f8e22..f21497c946e 100644 --- a/public/app/features/alerting/unified/insights/mimir/Notifications.tsx +++ b/public/app/features/alerting/unified/insights/mimir/Notifications.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getNotificationsScene(datasource: DataSourceRef, panelTitle: string) { const exprA = INSTANCE_ID @@ -18,13 +18,13 @@ export function getNotificationsScene(datasource: DataSourceRef, panelTitle: str queries: [ { refId: 'A', - exprA, + expr: exprA, range: true, legendFormat: 'success', }, { refId: 'B', - exprB, + expr: exprB, range: true, legendFormat: 'failed', }, @@ -46,7 +46,7 @@ export function getNotificationsScene(datasource: DataSourceRef, panelTitle: str .matchFieldsWithName('failed') .overrideColor(overrideToFixedColor('failed')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/Silences.tsx b/public/app/features/alerting/unified/insights/mimir/Silences.tsx index 01646a5cefe..8cbddfd0205 100644 --- a/public/app/features/alerting/unified/insights/mimir/Silences.tsx +++ b/public/app/features/alerting/unified/insights/mimir/Silences.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../home/Insights'; -import { InsightsRatingModal } from '../RatingModal'; +import { InsightsMenuButton } from '../InsightsMenuButton'; export function getSilencesScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -29,7 +29,7 @@ export function getSilencesScene(datasource: DataSourceRef, panelTitle: string) .setData(query) .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationIntervalRatioScene.tsx b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationIntervalRatioScene.tsx index bfca1bbaada..50f4084ac7c 100644 --- a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationIntervalRatioScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationIntervalRatioScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, ThresholdsMode, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getRuleGroupEvaluationDurationIntervalRatioScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -48,7 +48,7 @@ export function getRuleGroupEvaluationDurationIntervalRatioScene(datasource: Dat }, ], }) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationScene.tsx b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationScene.tsx index db28b864fba..12ff616336a 100644 --- a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationDurationScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getRuleGroupEvaluationDurationScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -37,7 +37,7 @@ export function getRuleGroupEvaluationDurationScene(datasource: DataSourceRef, p fixedColor: 'blue', }) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationsScene.tsx b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationsScene.tsx index 5c872356be6..169731864bb 100644 --- a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationsScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupEvaluationsScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getRuleGroupEvaluationsScene(datasource: DataSourceRef, panelTitle: string) { const exprA = INSTANCE_ID @@ -18,13 +18,13 @@ export function getRuleGroupEvaluationsScene(datasource: DataSourceRef, panelTit queries: [ { refId: 'A', - exprA, + expr: exprA, range: true, legendFormat: 'success', }, { refId: 'B', - exprB, + expr: exprB, range: true, legendFormat: 'failed', }, @@ -46,7 +46,7 @@ export function getRuleGroupEvaluationsScene(datasource: DataSourceRef, panelTit .matchFieldsWithName('failed') .overrideColor(overrideToFixedColor('failed')) ) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupIntervalScene.tsx b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupIntervalScene.tsx index ced1d761532..f54fc582bc4 100644 --- a/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupIntervalScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/perGroup/RuleGroupIntervalScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { BigValueGraphMode, DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getRuleGroupIntervalScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -29,7 +29,7 @@ export function getRuleGroupIntervalScene(datasource: DataSourceRef, panelTitle: .setData(query) .setUnit('s') .setOption('graphMode', BigValueGraphMode.Area) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/perGroup/RulesPerGroupScene.tsx b/public/app/features/alerting/unified/insights/mimir/perGroup/RulesPerGroupScene.tsx index 33c77d319db..c2c9ac1b44d 100644 --- a/public/app/features/alerting/unified/insights/mimir/perGroup/RulesPerGroupScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/perGroup/RulesPerGroupScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { BigValueGraphMode, DataSourceRef } from '@grafana/schema'; import { INSTANCE_ID, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getRulesPerGroupScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -36,7 +36,7 @@ export function getRulesPerGroupScene(datasource: DataSourceRef, panelTitle: str }) ) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/EvalSuccessVsFailuresScene.tsx b/public/app/features/alerting/unified/insights/mimir/rules/EvalSuccessVsFailuresScene.tsx index 28722367a73..2cda38b4acf 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/EvalSuccessVsFailuresScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/EvalSuccessVsFailuresScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getEvalSuccessVsFailuresScene(datasource: DataSourceRef, panelTitle: string) { const exprA = INSTANCE_ID @@ -18,13 +18,13 @@ export function getEvalSuccessVsFailuresScene(datasource: DataSourceRef, panelTi queries: [ { refId: 'A', - exprA, + expr: exprA, range: true, legendFormat: 'success', }, { refId: 'B', - exprB, + expr: exprB, range: true, legendFormat: 'failed', }, @@ -40,7 +40,7 @@ export function getEvalSuccessVsFailuresScene(datasource: DataSourceRef, panelTi .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOverrides((b) => b.matchFieldsWithName('failed').overrideColor(overrideToFixedColor('failed'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/Firing.tsx b/public/app/features/alerting/unified/insights/mimir/rules/Firing.tsx index 0d35c6d4ec3..2b3532924a0 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/Firing.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/Firing.tsx @@ -3,7 +3,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef } from '@grafana/schema'; import { PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getFiringCloudAlertsScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -37,7 +37,7 @@ export function getFiringCloudAlertsScene(datasource: DataSourceRef, panelTitle: ], }) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/InstancesByState.tsx b/public/app/features/alerting/unified/insights/mimir/rules/InstancesByState.tsx index b054a358c98..962cbf80cb7 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/InstancesByState.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/InstancesByState.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getInstancesByStateScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -26,7 +26,7 @@ export function getInstancesByStateScene(datasource: DataSourceRef, panelTitle: .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOverrides((b) => b.matchFieldsWithName('firing').overrideColor(overrideToFixedColor('firing'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/InstancesPercentageByState.tsx b/public/app/features/alerting/unified/insights/mimir/rules/InstancesPercentageByState.tsx index 91cfb75801a..05a709085c8 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/InstancesPercentageByState.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/InstancesPercentageByState.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getInstancesPercentageByStateScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -29,7 +29,7 @@ export function getInstancesPercentageByStateScene(datasource: DataSourceRef, pa .setMax(1) .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOverrides((b) => b.matchFieldsWithName('firing').overrideColor(overrideToFixedColor('firing'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/MissedIterationsScene.tsx b/public/app/features/alerting/unified/insights/mimir/rules/MissedIterationsScene.tsx index 59721817c19..2b5495a3e22 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/MissedIterationsScene.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/MissedIterationsScene.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef, GraphDrawStyle, TooltipDisplayMode } from '@grafana/schema'; import { INSTANCE_ID, overrideToFixedColor, PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getMissedIterationsScene(datasource: DataSourceRef, panelTitle: string) { const expr = INSTANCE_ID @@ -31,7 +31,7 @@ export function getMissedIterationsScene(datasource: DataSourceRef, panelTitle: .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) .setOption('legend', { showLegend: false }) .setOverrides((b) => b.matchFieldsWithName('missed').overrideColor(overrideToFixedColor('missed'))) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/MostFiredRules.tsx b/public/app/features/alerting/unified/insights/mimir/rules/MostFiredRules.tsx index 339c7b03651..2af5919329d 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/MostFiredRules.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/MostFiredRules.tsx @@ -2,7 +2,7 @@ import { PanelBuilders, SceneDataTransformer, SceneFlexItem, SceneQueryRunner } import { DataSourceRef } from '@grafana/schema'; import { PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getMostFiredRulesScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -43,7 +43,7 @@ export function getMostFiredRulesScene(datasource: DataSourceRef, panelTitle: st .setTitle(panelTitle) .setDescription('The alert rules that have fired the most') .setData(transformation) - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } diff --git a/public/app/features/alerting/unified/insights/mimir/rules/Pending.tsx b/public/app/features/alerting/unified/insights/mimir/rules/Pending.tsx index 7667300015a..69501ca4bbf 100644 --- a/public/app/features/alerting/unified/insights/mimir/rules/Pending.tsx +++ b/public/app/features/alerting/unified/insights/mimir/rules/Pending.tsx @@ -3,7 +3,7 @@ import { PanelBuilders, SceneFlexItem, SceneQueryRunner } from '@grafana/scenes' import { DataSourceRef } from '@grafana/schema'; import { PANEL_STYLES } from '../../../home/Insights'; -import { InsightsRatingModal } from '../../RatingModal'; +import { InsightsMenuButton } from '../../InsightsMenuButton'; export function getPendingCloudAlertsScene(datasource: DataSourceRef, panelTitle: string) { const query = new SceneQueryRunner({ @@ -37,7 +37,7 @@ export function getPendingCloudAlertsScene(datasource: DataSourceRef, panelTitle ], }) .setNoValue('0') - .setHeaderActions() + .setHeaderActions([new InsightsMenuButton({ panel: panelTitle })]) .build(), }); } From 5e5fa86b8b47a9e19cd4c299d5e02057aed69ad3 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 28 Nov 2024 12:29:45 +0100 Subject: [PATCH 31/41] Alerting: Fix go template parsing (#97145) Co-authored-by: Sonia Aguilar Co-authored-by: Gilles De Mey --- .../receivers/form/fields/utils.test.ts | 241 ++++++++++++++++++ .../components/receivers/form/fields/utils.ts | 23 +- 2 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 public/app/features/alerting/unified/components/receivers/form/fields/utils.test.ts diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/utils.test.ts b/public/app/features/alerting/unified/components/receivers/form/fields/utils.test.ts new file mode 100644 index 00000000000..0f8502defd6 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/form/fields/utils.test.ts @@ -0,0 +1,241 @@ +import { parseTemplates } from './utils'; + +describe('parseTemplates', () => { + it('should parse basic template', () => { + const templates = parseTemplates('{{ define "test" }}test{{ end }}'); + expect(templates).toEqual([{ name: 'test', content: '{{ define "test" }}test{{ end }}' }]); + }); + + it('should parse templates with multiple conditions', () => { + const originalTemplate = ` +{{ define "slack.title" }}{{ .CommonLabels.alertname -}} + | [ACTIVE:{{ .Alerts.Firing | len }}{{ + if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}] | CRI:{{.CommonLabels.criticality}} IMP:{{.CommonLabels.impact}} {{ + if eq .CommonLabels.impact "4"}}:warning:{{end}}{{ + if eq .CommonLabels.impact "5"}}:bangbang:{{end}}{{ + if match "6|7|8" .CommonLabels.criticality}}:fire:{{end}} +{{ end -}} + `.trim(); + + const [template] = parseTemplates(originalTemplate); + + expect(template).toBeDefined(); + expect(template.name).toEqual('slack.title'); + expect(template.content).toBe(originalTemplate); + }); + + it('should parse templates with unusual formatting', () => { + const originalTemplate = ` +{{ define "slack.title.small" }}{{ .CommonLabels.alertname -}} + {{ + if match "6|7|8" .CommonLabels.criticality}}:fire:{{end}} +{{ end -}}`.trim(); + + const [template] = parseTemplates(originalTemplate); + + expect(template).toBeDefined(); + expect(template.name).toEqual('slack.title.small'); + expect(template.content).toBe(originalTemplate); + }); + + it('should parse templates with nested templates', () => { + const originalTemplate = ` +{{ define "nested" }} + Main Template Content + {{ template "sub1" }} + {{ template "sub2" }} +{{ end }} +{{ define "sub1" }} + Sub Template 1 Content +{{ end }} +{{ define "sub2" }} + Sub Template 2 Content +{{ end }}`.trim(); + + const [template] = parseTemplates(originalTemplate); + + expect(template).toBeDefined(); + + expect(template.name).toEqual('nested'); + expect(template.content).toBe(originalTemplate); + }); + + it('should parse multiple unrelated templates as separate ones', () => { + const originalTemplate = ` +{{ define "template1" }}template1{{ end }} +{{ define "template2" }} + {{ .CommonLabels.alertname -}} +{{ end }} +{{ define "template3" }} + {{if eq .CommonLabels.impact "5"}}:bangbang:{{end}} +{{ end }} + `.trim(); + + const templates = parseTemplates(originalTemplate); + + expect(templates).toHaveLength(3); + + const [template1, template2, template3] = templates; + + expect(template1.name).toEqual('template1'); + expect(template2.name).toEqual('template2'); + expect(template3.name).toEqual('template3'); + + expect(template1.content).toBe('{{ define "template1" }}template1{{ end }}'); + expect(template2.content).toBe( + ` +{{ define "template2" }} + {{ .CommonLabels.alertname -}} +{{ end }}`.trim() + ); + expect(template3.content).toBe( + ` +{{ define "template3" }} + {{if eq .CommonLabels.impact "5"}}:bangbang:{{end}} +{{ end }}`.trim() + ); + }); + + it('should parse mixed nested and non-nested templates', () => { + const originalTemplate = ` +{{ define "parent" }} + {{ template "nested" }} +{{ end }} +{{ define "top-level" }} + Top Level Template Content +{{ end }} +{{ define "nested" }} + Nested Template Content +{{ end }}`.trim(); + + const templates = parseTemplates(originalTemplate); + + expect(templates).toHaveLength(2); + + const [parent, topLevel] = templates; + + expect(parent.name).toEqual('parent'); + expect(topLevel.name).toEqual('top-level'); + + expect(parent.content).toBe( + ` +{{ define "parent" }} + {{ template "nested" }} +{{ end }} +{{ define "nested" }} + Nested Template Content +{{ end }}`.trim() + ); + expect(topLevel.content).toBe( + ` +{{ define "top-level" }} + Top Level Template Content +{{ end }}`.trim() + ); + }); + + it('should handle templates with block definitions', () => { + const template = ` +{{ define "blocks" }} +{{ block "header" . }} + default header +{{ end }} +{{ block "body" . }} + default body +{{ end }} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with pipeline operations', () => { + const template = ` +{{ define "pipeline" }} + {{ .Value | printf "%.2f" | quote }} + {{ .Items | join "," | upper | trim }} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with variable declarations and scope', () => { + const template = ` +{{ define "variables" }} + {{- $var1 := "value" -}} + {{- with .Items -}} + {{- $var2 := . -}} + {{- range . -}} + {{- $var3 := . -}} + {{- end -}} + {{- end -}} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with whitespace control modifiers', () => { + const template = ` +{{- define "whitespace" -}} + {{- if .Value -}} + has-value + {{- else -}} + no-value + {{- end -}} +{{- end -}}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with complex nested actions', () => { + const template = ` +{{ define "complex" }} + {{- range $i, $v := .Items -}} + {{- if not $v.Hidden -}} + {{- with $v -}} + {{- template "item" . -}} + {{- end -}} + {{- else -}} + {{- /* skip hidden items */ -}} + {{- end -}} + {{- end -}} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with Go template comments', () => { + const template = ` +{{ define "comments" }} + {{/* single-line comment */}} + {{ printf "%q" "text" }} + {{- /* + multi-line + comment + */ -}} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with function calls and method chaining', () => { + const template = ` +{{ define "functions" }} + {{ call .Func .Arg1 .Arg2 }} + {{ .Value.Method1.Method2 "arg" }} + {{ index .Items 1 2 "key" }} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); + + it('should handle templates with complex boolean logic', () => { + const template = ` +{{ define "logic" }} + {{- if and (not .Hidden) (or (gt .Value 100) (lt .Value 0)) (has .Flags "important") -}} + special-case + {{- end -}} +{{ end }}`.trim(); + const [parsed] = parseTemplates(template); + expect(parsed.content).toBe(template); + }); +}); diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/utils.ts b/public/app/features/alerting/unified/components/receivers/form/fields/utils.ts index 3aece3f9441..6e36938a87b 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/utils.ts +++ b/public/app/features/alerting/unified/components/receivers/form/fields/utils.ts @@ -20,13 +20,13 @@ import { Template } from './TemplateSelector'; export function parseTemplates(templatesString: string): Template[] { const templates: Record = {}; const stack: Array<{ type: string; startIndex: number; name?: string }> = []; - const regex = /{{(-?\s*)(define|end|if|range|else|with|template)(\s*.*?)?(-?\s*)}}/gs; + const regex = /{{-?\s*(define|end|if|range|else|with|template|block)\b(.*?)-?}}/gs; let match; let currentIndex = 0; while ((match = regex.exec(templatesString)) !== null) { - const [, , keyword, middleContent] = match; + const [, keyword, middleContent] = match; currentIndex = match.index; if (keyword === 'define') { @@ -36,7 +36,14 @@ export function parseTemplates(templatesString: string): Template[] { } } else if (keyword === 'end') { let top = stack.pop(); - while (top && top.type !== 'define' && top.type !== 'if' && top.type !== 'range' && top.type !== 'with') { + while ( + top && + top.type !== 'define' && + top.type !== 'if' && + top.type !== 'range' && + top.type !== 'with' && + top.type !== 'block' + ) { top = stack.pop(); } if (top) { @@ -48,7 +55,13 @@ export function parseTemplates(templatesString: string): Template[] { }; } } - } else if (keyword === 'if' || keyword === 'range' || keyword === 'else' || keyword === 'with') { + } else if ( + keyword === 'if' || + keyword === 'range' || + keyword === 'else' || + keyword === 'with' || + keyword === 'block' + ) { stack.push({ type: keyword, startIndex: currentIndex }); } } @@ -78,7 +91,7 @@ export function getTemplateName(useTemplateText: string) { } /* This function checks if the a field value contains only one template usage - for example: + for example: "{{ template "templateName" . }}"" returns true but "{{ template "templateName" . }} some text {{ template "templateName" . }}"" returns false and "{{ template "templateName" . }} some text" some text returns false From f6124344ba5d63ebbede2cdc2cdd4dfcd3ac2424 Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Thu, 28 Nov 2024 13:03:54 +0100 Subject: [PATCH 32/41] authnz: Fix panic in the authenticator and rename metric (#97150) * Fix: panic * suggestion --- .../authn/grpcutils/grpc_authenticator.go | 33 +++++++++++-------- pkg/storage/unified/resource/access.go | 12 +++---- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pkg/services/authn/grpcutils/grpc_authenticator.go b/pkg/services/authn/grpcutils/grpc_authenticator.go index 8014a62beac..1b326d0d553 100644 --- a/pkg/services/authn/grpcutils/grpc_authenticator.go +++ b/pkg/services/authn/grpcutils/grpc_authenticator.go @@ -107,42 +107,49 @@ func FallbackUsed(ctx context.Context) bool { func (f *AuthenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) { ctx, span := f.tracer.Start(ctx, "grpcutils.AuthenticatorWithFallback.Authenticate") defer span.End() - span.SetAttributes(attribute.Bool("fallback_used", false)) + // Try to authenticate with the new authenticator first + span.SetAttributes(attribute.Bool("fallback_used", false)) newCtx, err := f.authenticator.Authenticate(ctx) - if err != nil { - // In case of error, fallback to the legacy authenticator - newCtx, err = f.fallback.Authenticate(ctx) - f.metrics.fallbackCounter.WithLabelValues(fmt.Sprintf("%t", err == nil)).Inc() - span.SetAttributes(attribute.Bool("fallback_used", true)) + if err == nil { + // fallback not used, authentication successful + f.metrics.requestsTotal.WithLabelValues("false", "true").Inc() + return newCtx, nil + } + + // In case of error, fallback to the legacy authenticator + span.SetAttributes(attribute.Bool("fallback_used", true)) + newCtx, err = f.fallback.Authenticate(ctx) + if newCtx != nil { newCtx = context.WithValue(newCtx, contextFallbackKey{}, true) } + f.metrics.requestsTotal.WithLabelValues("true", fmt.Sprintf("%t", err == nil)).Inc() return newCtx, err } const ( metricsNamespace = "grafana" - metricsSubSystem = "grpc_authenticator" + metricsSubSystem = "grpc_authenticator_with_fallback" ) type metrics struct { - fallbackCounter *prometheus.CounterVec + requestsTotal *prometheus.CounterVec } func newMetrics(reg prometheus.Registerer) *metrics { m := &metrics{ - fallbackCounter: prometheus.NewCounterVec( + requestsTotal: prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metricsNamespace, Subsystem: metricsSubSystem, - Name: "fallback_total", - Help: "Number of times the fallback authenticator was used", - }, []string{"result"}), + Name: "requests_total", + Help: "Number requests using the authenticator with fallback", + }, []string{"fallback_used", "result"}), } if reg != nil { once.Do(func() { - reg.MustRegister(m.fallbackCounter) + reg.MustRegister(m.requestsTotal) }) } diff --git a/pkg/storage/unified/resource/access.go b/pkg/storage/unified/resource/access.go index 97a8082373e..da6aad7fc2b 100644 --- a/pkg/storage/unified/resource/access.go +++ b/pkg/storage/unified/resource/access.go @@ -39,8 +39,8 @@ type groupResource map[string]map[string]interface{} // For now, it makes one call to the authz service for each list items. This is known to be inefficient. type authzLimitedClient struct { client authz.AccessChecker - // whitelist is a map of group to resources that are compatible with RBAC. - whitelist groupResource + // allowlist is a map of group to resources that are compatible with RBAC. + allowlist groupResource logger *slog.Logger tracer trace.Tracer } @@ -57,7 +57,7 @@ func NewAuthzLimitedClient(client authz.AccessChecker, opts AuthzOptions) authz. } return &authzLimitedClient{ client: client, - whitelist: groupResource{ + allowlist: groupResource{ "dashboard.grafana.app": map[string]interface{}{"dashboards": nil}, "folder.grafana.app": map[string]interface{}{"folders": nil}, }, @@ -107,7 +107,7 @@ func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req )) defer span.End() if grpcutils.FallbackUsed(ctx) { - c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "fallback", true, "rbac", false, "allowed", true) + c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "fallback", true, "rbac", false, "allowed", true) return true } // TODO: Implement For now we perform the check for each item. @@ -134,8 +134,8 @@ func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req } func (c authzLimitedClient) IsCompatibleWithRBAC(group, resource string) bool { - if _, ok := c.whitelist[group]; ok { - if _, ok := c.whitelist[group][resource]; ok { + if _, ok := c.allowlist[group]; ok { + if _, ok := c.allowlist[group][resource]; ok { return true } } From 0bf9d68070ff5d1b267328563afba1568a422d6b Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:26:36 +0200 Subject: [PATCH 33/41] DashboardScene: Fix layout issues with repeated panels when `repeatDirection` is missing (#97149) fix --- .../transformSaveModelToScene.test.ts | 20 +++++++++++++++++++ .../transformSaveModelToScene.ts | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 39f84f3480b..ad063c17fb1 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -660,6 +660,26 @@ describe('transformSaveModelToScene', () => { expect(vizPanel.state.$data).toBeUndefined(); }); + it('When repeat is set but repeatDirection is not it should default to horizontal repeat', () => { + const panel = { + title: '', + type: 'text-plugin-34', + gridPos: { x: 0, y: 0, w: 8, h: 8 }, + repeat: 'server', + maxPerRow: 8, + }; + + const gridItem = buildGridItemForPanel(new PanelModel(panel)); + const repeater = gridItem as DashboardGridItem; + + expect(repeater.state.maxPerRow).toBe(8); + expect(repeater.state.variableName).toBe('server'); + expect(repeater.state.width).toBe(24); + expect(repeater.state.height).toBe(8); + expect(repeater.state.repeatDirection).toBe('h'); + expect(repeater.state.maxPerRow).toBe(8); + }); + it('When repeat is set should build PanelRepeaterGridItem', () => { const panel = { title: '', diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index a63312a9f81..ba05dff82c9 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -283,7 +283,7 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { const repeatOptions: Partial<{ variableName: string; repeatDirection: RepeatDirection }> = panel.repeat ? { variableName: panel.repeat, - repeatDirection: panel.repeatDirection === 'h' ? 'h' : 'v', + repeatDirection: panel.repeatDirection === 'v' ? 'v' : 'h', } : {}; From f2b96593ead071e938f7e6b8e3882ec7ba6d9f27 Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Thu, 28 Nov 2024 14:28:55 +0100 Subject: [PATCH 34/41] SQL: close rows to release connection (#97147) * SQL: close rows to release connection * dont return err from rows.Close() --- pkg/services/accesscontrol/database/policies.go | 3 +++ pkg/services/playlist/playlistimpl/xorm_store.go | 5 +++++ pkg/storage/unified/search/document.go | 5 +++++ pkg/storage/unified/sql/backend.go | 10 +++++++--- pkg/storage/unified/sql/dbutil/dbutil.go | 4 ++++ pkg/storage/unified/sql/queries.go | 2 +- 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pkg/services/accesscontrol/database/policies.go b/pkg/services/accesscontrol/database/policies.go index 6fec2c2c483..235d301b69c 100644 --- a/pkg/services/accesscontrol/database/policies.go +++ b/pkg/services/accesscontrol/database/policies.go @@ -42,6 +42,9 @@ func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, if err != nil { return nil, err } + defer func() { + _ = rows.Close() + }() created := time.Now() updated := time.Now() diff --git a/pkg/services/playlist/playlistimpl/xorm_store.go b/pkg/services/playlist/playlistimpl/xorm_store.go index 07c1ce548fd..8970ba15747 100644 --- a/pkg/services/playlist/playlistimpl/xorm_store.go +++ b/pkg/services/playlist/playlistimpl/xorm_store.go @@ -222,6 +222,11 @@ func (s *sqlStore) ListAll(ctx context.Context, orgId int64) ([]playlist.Playlis if err != nil { return nil, err } + + defer func() { + _ = rows.Close() + }() + for rows.Next() { err = rows.Scan(&playlistId, &itemType, &itemValue) if err != nil { diff --git a/pkg/storage/unified/search/document.go b/pkg/storage/unified/search/document.go index b2d6bf01f77..78836bef06e 100644 --- a/pkg/storage/unified/search/document.go +++ b/pkg/storage/unified/search/document.go @@ -29,6 +29,11 @@ func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBui if err != nil { return nil, err } + + defer func() { + _ = rows.Close() + }() + for rows.Next() { info := &dashboard.DatasourceQueryResult{} err = rows.Scan(&info.UID, &info.Type, &info.Name, &info.IsDefault) diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index e11bbdcefcc..0e8f45d173a 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -146,6 +146,11 @@ func (b *backend) Namespaces(ctx context.Context) ([]string, error) { if err != nil { return err } + + defer func() { + _ = rows.Close() + }() + for rows.Next() { var ns string err = rows.Scan(&ns) @@ -155,8 +160,7 @@ func (b *backend) Namespaces(ctx context.Context) ([]string, error) { namespaces = append(namespaces, ns) } - err = rows.Close() - return err + return nil }) return namespaces, err @@ -679,7 +683,7 @@ func (b *backend) poll(ctx context.Context, grp string, res string, since int64, nextRV = rec.ResourceVersion prevRV := rec.PreviousRV if prevRV == nil { - *prevRV = int64(0) + prevRV = new(int64) } stream <- &resource.WrittenEvent{ WriteEvent: resource.WriteEvent{ diff --git a/pkg/storage/unified/sql/dbutil/dbutil.go b/pkg/storage/unified/sql/dbutil/dbutil.go index 03e6fbe0be7..f17b83cf335 100644 --- a/pkg/storage/unified/sql/dbutil/dbutil.go +++ b/pkg/storage/unified/sql/dbutil/dbutil.go @@ -165,6 +165,10 @@ func Query[T any](ctx context.Context, x db.ContextExecer, tmpl *template.Templa return nil, err } + defer func() { + _ = rows.Close() + }() + var ret []T for rows.Next() { v, err := scanRow(rows, req) diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 78afce6439d..7ce060e07a7 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -101,7 +101,7 @@ func (r *sqlResourceHistoryPollRequest) Validate() error { func (r *sqlResourceHistoryPollRequest) Results() (*historyPollResponse, error) { prevRV := r.Response.PreviousRV if prevRV == nil { - *prevRV = int64(0) + prevRV = new(int64) } return &historyPollResponse{ Key: resource.ResourceKey{ From b2626a2d65dd749c0af8719bfdab70df11eb484a Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Thu, 28 Nov 2024 14:33:18 +0100 Subject: [PATCH 35/41] LoginAttempt: Add setting to control max number of attempts before user login gets locked (#97091) * Add setting to adjust number of login attempts before user login gets locked * Ensure at least one attempt can be made * Update documentation with new setting * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Eric Leijonmarck --- conf/defaults.ini | 3 ++ conf/sample.ini | 3 ++ .../setup-grafana/configure-grafana/_index.md | 6 +++- .../loginattemptimpl/login_attempt.go | 7 ++-- .../loginattemptimpl/login_attempt_test.go | 4 +++ pkg/setting/setting.go | 32 ++++++++++++------- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index c9ef90d5f3d..c52788da583 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -351,6 +351,9 @@ data_source_proxy_whitelist = # disable protection against brute force login attempts disable_brute_force_login_protection = false +# max number of failed login attempts before user gets locked +brute_force_login_protection_max_attempts = 5 + # set to true if you host Grafana behind HTTPS. default is false. cookie_secure = false diff --git a/conf/sample.ini b/conf/sample.ini index 740f13e394b..687f4052053 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -350,6 +350,9 @@ # disable protection against brute force login attempts ;disable_brute_force_login_protection = false +# max number of failed login attempts before user gets locked +;brute_force_login_protection_max_attempts = 5 + # set to true if you host Grafana behind HTTPS. default is false. ;cookie_secure = false diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index bd96e5514a2..ac5c27f35bc 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -630,7 +630,11 @@ Define a whitelist of allowed IP addresses or domains, with ports, to be used in ### disable_brute_force_login_protection -Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. An existing user's account will be locked after 5 attempts in 5 minutes. +Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout). Default is `false`. An existing user's account will be unable to login for 5 minutes if all login attempts are spent within a 5 minute window. + +### brute_force_login_protection_max_attempts + +Configure how many login attempts a user have within a 5 minute window before the account will be locked. Default is `5`. ### cookie_secure diff --git a/pkg/services/loginattempt/loginattemptimpl/login_attempt.go b/pkg/services/loginattempt/loginattemptimpl/login_attempt.go index 611d83b0d5f..f55b8fe34b1 100644 --- a/pkg/services/loginattempt/loginattemptimpl/login_attempt.go +++ b/pkg/services/loginattempt/loginattemptimpl/login_attempt.go @@ -11,10 +11,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -const ( - maxInvalidLoginAttempts int64 = 5 - loginAttemptsWindow = time.Minute * 5 -) +const loginAttemptsWindow = time.Minute * 5 func ProvideService(db db.DB, cfg *setting.Cfg, lock *serverlock.ServerLockService) *Service { return &Service{ @@ -80,7 +77,7 @@ func (s *Service) Validate(ctx context.Context, username string) (bool, error) { return false, err } - if count >= maxInvalidLoginAttempts { + if count >= s.cfg.BruteForceLoginProtectionMaxAttempts { return false, nil } diff --git a/pkg/services/loginattempt/loginattemptimpl/login_attempt_test.go b/pkg/services/loginattempt/loginattemptimpl/login_attempt_test.go index 13b1bc2706f..7015dff01e9 100644 --- a/pkg/services/loginattempt/loginattemptimpl/login_attempt_test.go +++ b/pkg/services/loginattempt/loginattemptimpl/login_attempt_test.go @@ -12,6 +12,8 @@ import ( ) func TestService_Validate(t *testing.T) { + const maxInvalidLoginAttempts = 5 + testCases := []struct { name string loginAttempts int64 @@ -64,6 +66,7 @@ func TestService_Validate(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := setting.NewCfg() + cfg.BruteForceLoginProtectionMaxAttempts = maxInvalidLoginAttempts cfg.DisableBruteForceLoginProtection = tt.disabled service := &Service{ store: fakeStore{ @@ -84,6 +87,7 @@ func TestLoginAttempts(t *testing.T) { ctx := context.Background() cfg := setting.NewCfg() cfg.DisableBruteForceLoginProtection = false + cfg.BruteForceLoginProtectionMaxAttempts = 5 db := db.InitTestDB(t) service := ProvideService(db, cfg, nil) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1a4cd3f9b4d..9dcd3bcde78 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -154,18 +154,19 @@ type Cfg struct { RendererDefaultImageScale float64 // Security - DisableInitAdminCreation bool - DisableBruteForceLoginProtection bool - CookieSecure bool - CookieSameSiteDisabled bool - CookieSameSiteMode http.SameSite - AllowEmbedding bool - XSSProtectionHeader bool - ContentTypeProtectionHeader bool - StrictTransportSecurity bool - StrictTransportSecurityMaxAge int - StrictTransportSecurityPreload bool - StrictTransportSecuritySubDomains bool + DisableInitAdminCreation bool + DisableBruteForceLoginProtection bool + BruteForceLoginProtectionMaxAttempts int64 + CookieSecure bool + CookieSameSiteDisabled bool + CookieSameSiteMode http.SameSite + AllowEmbedding bool + XSSProtectionHeader bool + ContentTypeProtectionHeader bool + StrictTransportSecurity bool + StrictTransportSecurityMaxAge int + StrictTransportSecurityPreload bool + StrictTransportSecuritySubDomains bool // CSPEnabled toggles Content Security Policy support. CSPEnabled bool // CSPTemplate contains the Content Security Policy template. @@ -1498,7 +1499,14 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error { security := iniFile.Section("security") cfg.SecretKey = valueAsString(security, "secret_key", "") cfg.DisableGravatar = security.Key("disable_gravatar").MustBool(true) + cfg.DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false) + cfg.BruteForceLoginProtectionMaxAttempts = security.Key("brute_force_login_protection_max_attempts").MustInt64(5) + + // Ensure at least one login attempt can be performed. + if cfg.BruteForceLoginProtectionMaxAttempts <= 0 { + cfg.BruteForceLoginProtectionMaxAttempts = 1 + } CookieSecure = security.Key("cookie_secure").MustBool(false) cfg.CookieSecure = CookieSecure From 10eec834780b7b8646c6738dc17e362aa6b52031 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Thu, 28 Nov 2024 14:51:20 +0100 Subject: [PATCH 36/41] Grafana UI: Compress grot-cta image (#97052) * chore(grafana-ui): reduce size of grot-cta * chore(grafana-ui): apply less aggressive compression on grot-cta --- .../EmptyState/GrotCTA/grot-cta.svg | 65 +++++++------------ 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg b/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg index 7e44c92839c..04c111dbe8e 100644 --- a/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg +++ b/packages/grafana-ui/src/components/EmptyState/GrotCTA/grot-cta.svg @@ -1,46 +1,31 @@ - - + + - - - + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From 25da5f080645a78cf0507608cfafdbc92287080a Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Thu, 28 Nov 2024 14:58:36 +0100 Subject: [PATCH 37/41] Chore: Bump Storybook to 8.4.x (#96128) * feat(storybook): upgrade to version 8.4 * chore(grafana-ui): replace all usage of preview with canvas * chore(grafana-ui): add fs-extra as dev dependency * feat(storybook): copy required assets to temp static directory due to 8.4 not supporting file paths * chore(yarn): fix up lock file so swc-loader doesnt break for decoupled plugins * Add ExampleFrame component to render grafana-ui examples * Prevent Storybook from styling in ExampleFrame * Use global styles in Storybook docs * Update mdx docs to use ExampleFrame or correct Canvas usage * update AutoSizeInput * Update Index mdx * remove the gfm mdx package * silence sass warnings * fix(storybook): add missing imports to fix failed rendering of stories/docs * remove empty docs --------- Co-authored-by: joshhunt --- .betterer.results | 3 + .github/renovate.json5 | 2 +- .gitignore | 3 +- ...torybook-blocks-npm-8.1.6-892f57a6d7.patch | 134 - package.json | 1 - packages/grafana-ui/.storybook/copyAssets.ts | 62 + packages/grafana-ui/.storybook/main.ts | 54 +- packages/grafana-ui/.storybook/preview.ts | 2 +- packages/grafana-ui/package.json | 33 +- .../AutoSaveField/AutoSaveField.mdx | 8 +- .../src/components/Button/Button.mdx | 133 +- .../Combobox/MultiCombobox.internal.story.tsx | 6 - .../src/components/Combobox/MultiCombobox.mdx | 0 .../src/components/Input/AutoSizeInput.mdx | 14 +- .../grafana-ui/src/components/Input/Input.mdx | 17 +- .../src/components/Layout/Layout.mdx | 76 +- .../src/components/Layout/Layout.story.tsx | 6 +- .../src/components/Link/TextLink.mdx | 37 +- .../src/components/Link/TextLink.story.tsx | 25 +- .../src/components/LoadingBar/LoadingBar.mdx | 15 +- .../components/PanelChrome/PanelChrome.mdx | 116 +- .../PluginSignatureBadge.mdx | 23 +- .../RefreshPicker/RefreshPicker.mdx | 9 +- .../RenderUserContentAsHTML.mdx | 2 +- .../src/components/Spinner/Spinner.mdx | 10 +- .../grafana-ui/src/components/Tags/Tag.mdx | 17 +- .../src/components/Tags/TagList.mdx | 16 +- .../grafana-ui/src/components/Text/Text.mdx | 21 +- .../src/components/TextArea/TextArea.mdx | 18 +- .../components/ThemeDemos/EmotionPerfTest.tsx | 2 +- .../src/components/UnitPicker/UnitPicker.mdx | 6 - .../UnitPicker/UnitPicker.story.tsx | 2 - .../src/utils/storybook/ExampleFrame.tsx | 39 + .../utils/storybook/ThemedDocsContainer.tsx | 2 + yarn.lock | 2877 +++-------------- 35 files changed, 774 insertions(+), 3017 deletions(-) delete mode 100644 .yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch create mode 100644 packages/grafana-ui/.storybook/copyAssets.ts delete mode 100644 packages/grafana-ui/src/components/Combobox/MultiCombobox.mdx delete mode 100644 packages/grafana-ui/src/components/UnitPicker/UnitPicker.mdx create mode 100644 packages/grafana-ui/src/utils/storybook/ExampleFrame.tsx diff --git a/.betterer.results b/.betterer.results index 7d4f307f249..dc61ba89ee0 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5400,6 +5400,9 @@ exports[`no undocumented stories`] = { "packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], + "packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx:5381": [ + [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] + ], "packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b26cb9ff77d..c42512cdd61 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -13,7 +13,7 @@ "slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate "@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate "@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate - + "storybook-dark-mode", // 4.0.2 causes storybook 8.4 to break with react hooks errors // Temporarily pause updating lerna and nx until we resolve build issues "lerna", "nx" diff --git a/.gitignore b/.gitignore index 3b2d8f1d5d7..ce35f5d61f8 100644 --- a/.gitignore +++ b/.gitignore @@ -141,6 +141,7 @@ pkg/services/quota/quotaimpl/storage/storage.json # Ignoring frontend packages specifics /packages/grafana-ui/.yarn/.cache +/packages/grafana-ui/.storybook/static /packages/**/dist /packages/**/compiled /packages/**/.rpt2_cache @@ -225,4 +226,4 @@ public/app/plugins/**/dist/ # Mock service worker used for fake API responses in frontend development public/mockServiceWorker.js -/e2e/test-plugins/*/dist \ No newline at end of file +/e2e/test-plugins/*/dist diff --git a/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch b/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch deleted file mode 100644 index 541b3c6082a..00000000000 --- a/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch +++ /dev/null @@ -1,134 +0,0 @@ -diff --git a/dist/index.d.ts b/dist/index.d.ts -index baa055bbb796e750fe1badfdfbc4fedbbcbf9f04..5f389f7e1d200dd6a951d8ccfa4cd71befcbceba 100644 ---- a/dist/index.d.ts -+++ b/dist/index.d.ts -@@ -53,6 +53,12 @@ interface PreviewProps { - children?: ReactNode; - } - type Layout = 'padded' | 'fullscreen' | 'centered'; -+/** -+ * A preview component for showing one or more component `Story` -+ * items. The preview also shows the source for the component -+ * as a drop-down. -+ */ -+declare const Preview: FC; - - interface ArgType { - name?: string; -@@ -643,4 +649,4 @@ type ColorProps = ColorControlProps; - declare const LazyColorControl: React.LazyExoticComponent>; - declare const ColorControl: (props: ComponentProps) => React.JSX.Element; - --export { AddContext, Anchor, AnchorMdx, AnchorProps, ArgTypes, BooleanConfig, BooleanControl, BooleanProps, BooleanValue, Canvas, CodeOrSourceMdx, ColorConfig, ColorControl, ColorItem, ColorPalette, ColorProps, ColorValue, Component, Control, ControlProps, ControlType, Controls, DateConfig, DateControl, DateProps, DateValue, DescriptionContainer as Description, DescriptionType, Docs, DocsContainer, DocsContainerProps, DocsContext, DocsPage, DocsProps, DocsStory, DocsStoryProps, ExternalDocs, ExternalDocsContainer, ExternalDocsProps, FilesControl, FilesControlProps, HeaderMdx, HeadersMdx, Heading, HeadingProps, IconGallery, IconItem, Markdown, Meta, NormalizedOptionsConfig, NumberConfig, NumberControl, NumberValue, ObjectConfig, ObjectControl, ObjectProps, ObjectValue, Of, Options, OptionsArray, OptionsConfig, OptionsControl, OptionsControlType, OptionsMultiSelection, OptionsObject, OptionsProps, OptionsSelection, OptionsSingleSelection, PRIMARY_STORY, PresetColor, Primary, ArgsTable as PureArgsTable, RangeConfig, RangeControl, SortType, Source, SourceContainer, SourceContext, SourceContextProps, SourceItem, SourceProps, Stories, Story, StoryProps, StorySources, Subheading, Subtitle, TextConfig, TextControl, TextProps, TextValue, Title, Typeset, UNKNOWN_ARGS_HASH, Unstyled, Wrapper, anchorBlockIdFromId, argsHash, assertIsFn, extractTitle, format, formatDate, formatTime, getStoryId, getStoryProps, parse, parseDate, parseTime, useOf, useSourceProps }; -+export { AddContext, Anchor, AnchorMdx, AnchorProps, ArgTypes, BooleanConfig, BooleanControl, BooleanProps, BooleanValue, Canvas, CodeOrSourceMdx, ColorConfig, ColorControl, ColorItem, ColorPalette, ColorProps, ColorValue, Component, Control, ControlProps, ControlType, Controls, DateConfig, DateControl, DateProps, DateValue, DescriptionContainer as Description, DescriptionType, Docs, DocsContainer, DocsContainerProps, DocsContext, DocsPage, DocsProps, DocsStory, DocsStoryProps, ExternalDocs, ExternalDocsContainer, ExternalDocsProps, FilesControl, FilesControlProps, HeaderMdx, HeadersMdx, Heading, HeadingProps, IconGallery, IconItem, Markdown, Meta, NormalizedOptionsConfig, NumberConfig, NumberControl, NumberValue, ObjectConfig, ObjectControl, ObjectProps, ObjectValue, Of, Options, OptionsArray, OptionsConfig, OptionsControl, OptionsControlType, OptionsMultiSelection, OptionsObject, OptionsProps, OptionsSelection, OptionsSingleSelection, PRIMARY_STORY, PresetColor, Preview, Primary, ArgsTable as PureArgsTable, RangeConfig, RangeControl, SortType, Source, SourceContainer, SourceContext, SourceContextProps, SourceItem, SourceProps, Stories, Story, StoryProps, StorySources, Subheading, Subtitle, TextConfig, TextControl, TextProps, TextValue, Title, Typeset, UNKNOWN_ARGS_HASH, Unstyled, Wrapper, anchorBlockIdFromId, argsHash, assertIsFn, extractTitle, format, formatDate, formatTime, getStoryId, getStoryProps, parse, parseDate, parseTime, useOf, useSourceProps }; -diff --git a/dist/index.js b/dist/index.js -index a20435ad1d5645c0f3846365938bc71a98068ae7..4974b0639bcb8579da5fe47916223d23f099c9a2 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -1,24 +1,24 @@ --var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var getControlId,getControlSetterButtonId,init_helpers=__esm({"src/controls/helpers.ts"(){getControlId=value2=>`control-${value2.replace(/\s+/g,"-")}`,getControlSetterButtonId=value2=>`set-${value2.replace(/\s+/g,"-")}`}});var Color_exports={};__export(Color_exports,{ColorControl:()=>ColorControl,default:()=>Color_default});var import_react24,import_react_colorful,import_color_convert,import_throttle,import_theming18,import_components15,import_icons5,Wrapper8,PickerTooltip,TooltipContent,Note,Swatches,SwatchColor,swatchBackground,Swatch,Input2,ToggleIcon,ColorSpace,COLOR_SPACES,COLOR_REGEXP,RGB_REGEXP,HSL_REGEXP,HEX_REGEXP,SHORTHEX_REGEXP,ColorPicker,fallbackColor,stringToArgs,parseValue,getRealValue,useColorInput,id,usePresets,ColorControl,Color_default,init_Color=__esm({"src/controls/Color.tsx"(){import_react24=__toESM(require("react")),import_react_colorful=require("react-colorful"),import_color_convert=__toESM(require("color-convert")),import_throttle=__toESM(require("lodash/throttle.js")),import_theming18=require("@storybook/theming"),import_components15=require("@storybook/components");init_helpers();import_icons5=require("@storybook/icons"),Wrapper8=import_theming18.styled.div({position:"relative",maxWidth:250,'&[aria-readonly="true"]':{opacity:.5}}),PickerTooltip=(0,import_theming18.styled)(import_components15.WithTooltip)({position:"absolute",zIndex:1,top:4,left:4,"[aria-readonly=true] &":{cursor:"not-allowed"}}),TooltipContent=import_theming18.styled.div({width:200,margin:5,".react-colorful__saturation":{borderRadius:"4px 4px 0 0"},".react-colorful__hue":{boxShadow:"inset 0 0 0 1px rgb(0 0 0 / 5%)"},".react-colorful__last-control":{borderRadius:"0 0 4px 4px"}}),Note=(0,import_theming18.styled)(import_components15.TooltipNote)(({theme})=>({fontFamily:theme.typography.fonts.base})),Swatches=import_theming18.styled.div({display:"grid",gridTemplateColumns:"repeat(9, 16px)",gap:6,padding:3,marginTop:5,width:200}),SwatchColor=import_theming18.styled.div(({theme,active})=>({width:16,height:16,boxShadow:active?`${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px`:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:theme.appBorderRadius})),swatchBackground=`url('data:image/svg+xml;charset=utf-8,')`,Swatch=({value:value2,style,...props})=>{let backgroundImage=`linear-gradient(${value2}, ${value2}), ${swatchBackground}, linear-gradient(#fff, #fff)`;return import_react24.default.createElement(SwatchColor,{...props,style:{...style,backgroundImage}})},Input2=(0,import_theming18.styled)(import_components15.Form.Input)(({theme,readOnly})=>({width:"100%",paddingLeft:30,paddingRight:30,boxSizing:"border-box",fontFamily:theme.typography.fonts.base})),ToggleIcon=(0,import_theming18.styled)(import_icons5.MarkupIcon)(({theme})=>({position:"absolute",zIndex:1,top:6,right:7,width:20,height:20,padding:4,boxSizing:"border-box",cursor:"pointer",color:theme.input.color})),ColorSpace=(ColorSpace2=>(ColorSpace2.RGB="rgb",ColorSpace2.HSL="hsl",ColorSpace2.HEX="hex",ColorSpace2))(ColorSpace||{}),COLOR_SPACES=Object.values(ColorSpace),COLOR_REGEXP=/\(([0-9]+),\s*([0-9]+)%?,\s*([0-9]+)%?,?\s*([0-9.]+)?\)/,RGB_REGEXP=/^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i,HSL_REGEXP=/^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i,HEX_REGEXP=/^\s*#?([0-9a-f]{3}|[0-9a-f]{6})\s*$/i,SHORTHEX_REGEXP=/^\s*#?([0-9a-f]{3})\s*$/i,ColorPicker={hex:import_react_colorful.HexColorPicker,rgb:import_react_colorful.RgbaStringColorPicker,hsl:import_react_colorful.HslaStringColorPicker},fallbackColor={hex:"transparent",rgb:"rgba(0, 0, 0, 0)",hsl:"hsla(0, 0%, 0%, 0)"},stringToArgs=value2=>{let match=value2?.match(COLOR_REGEXP);if(!match)return[0,0,0,1];let[,x,y,z,a=1]=match;return[x,y,z,a].map(Number)},parseValue=value2=>{if(!value2)return;let valid=!0;if(RGB_REGEXP.test(value2)){let[r,g,b,a]=stringToArgs(value2),[h,s,l]=import_color_convert.default.rgb.hsl([r,g,b])||[0,0,0];return{valid,value:value2,keyword:import_color_convert.default.rgb.keyword([r,g,b]),colorSpace:"rgb",rgb:value2,hsl:`hsla(${h}, ${s}%, ${l}%, ${a})`,hex:`#${import_color_convert.default.rgb.hex([r,g,b]).toLowerCase()}`}}if(HSL_REGEXP.test(value2)){let[h,s,l,a]=stringToArgs(value2),[r,g,b]=import_color_convert.default.hsl.rgb([h,s,l])||[0,0,0];return{valid,value:value2,keyword:import_color_convert.default.hsl.keyword([h,s,l]),colorSpace:"hsl",rgb:`rgba(${r}, ${g}, ${b}, ${a})`,hsl:value2,hex:`#${import_color_convert.default.hsl.hex([h,s,l]).toLowerCase()}`}}let plain=value2.replace("#",""),rgb=import_color_convert.default.keyword.rgb(plain)||import_color_convert.default.hex.rgb(plain),hsl=import_color_convert.default.rgb.hsl(rgb),mapped=value2;if(/[^#a-f0-9]/i.test(value2)?mapped=plain:HEX_REGEXP.test(value2)&&(mapped=`#${plain}`),mapped.startsWith("#"))valid=HEX_REGEXP.test(mapped);else try{import_color_convert.default.keyword.hex(mapped)}catch{valid=!1}return{valid,value:mapped,keyword:import_color_convert.default.rgb.keyword(rgb),colorSpace:"hex",rgb:`rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 1)`,hsl:`hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, 1)`,hex:mapped}},getRealValue=(value2,color,colorSpace)=>{if(!value2||!color?.valid)return fallbackColor[colorSpace];if(colorSpace!=="hex")return color?.[colorSpace]||fallbackColor[colorSpace];if(!color.hex.startsWith("#"))try{return`#${import_color_convert.default.keyword.hex(color.hex)}`}catch{return fallbackColor.hex}let short=color.hex.match(SHORTHEX_REGEXP);if(!short)return HEX_REGEXP.test(color.hex)?color.hex:fallbackColor.hex;let[r,g,b]=short[1].split("");return`#${r}${r}${g}${g}${b}${b}`},useColorInput=(initialValue,onChange)=>{let[value2,setValue]=(0,import_react24.useState)(initialValue||""),[color,setColor]=(0,import_react24.useState)(()=>parseValue(value2)),[colorSpace,setColorSpace]=(0,import_react24.useState)(color?.colorSpace||"hex");(0,import_react24.useEffect)(()=>{let nextValue=initialValue||"",nextColor=parseValue(nextValue);setValue(nextValue),setColor(nextColor),setColorSpace(nextColor?.colorSpace||"hex")},[initialValue]);let realValue=(0,import_react24.useMemo)(()=>getRealValue(value2,color,colorSpace).toLowerCase(),[value2,color,colorSpace]),updateValue=(0,import_react24.useCallback)(update=>{let parsed=parseValue(update),v=parsed?.value||update||"";setValue(v),v===""&&(setColor(void 0),onChange(void 0)),parsed&&(setColor(parsed),setColorSpace(parsed.colorSpace),onChange(parsed.value))},[onChange]),cycleColorSpace=(0,import_react24.useCallback)(()=>{let next=COLOR_SPACES.indexOf(colorSpace)+1;next>=COLOR_SPACES.length&&(next=0),setColorSpace(COLOR_SPACES[next]);let update=color?.[COLOR_SPACES[next]]||"";setValue(update),onChange(update)},[color,colorSpace,onChange]);return{value:value2,realValue,updateValue,color,colorSpace,cycleColorSpace}},id=value2=>value2.replace(/\s*/,"").toLowerCase(),usePresets=(presetColors,currentColor,colorSpace)=>{let[selectedColors,setSelectedColors]=(0,import_react24.useState)(currentColor?.valid?[currentColor]:[]);(0,import_react24.useEffect)(()=>{currentColor===void 0&&setSelectedColors([])},[currentColor]);let presets=(0,import_react24.useMemo)(()=>(presetColors||[]).map(preset=>typeof preset=="string"?parseValue(preset):preset.title?{...parseValue(preset.color),keyword:preset.title}:parseValue(preset.color)).concat(selectedColors).filter(Boolean).slice(-27),[presetColors,selectedColors]),addPreset=(0,import_react24.useCallback)(color=>{color?.valid&&(presets.some(preset=>id(preset[colorSpace])===id(color[colorSpace]))||setSelectedColors(arr=>arr.concat(color)))},[colorSpace,presets]);return{presets,addPreset}},ColorControl=({name,value:initialValue,onChange,onFocus,onBlur,presetColors,startOpen=!1,argType})=>{let throttledOnChange=(0,import_react24.useCallback)((0,import_throttle.default)(onChange,200),[onChange]),{value:value2,realValue,updateValue,color,colorSpace,cycleColorSpace}=useColorInput(initialValue,throttledOnChange),{presets,addPreset}=usePresets(presetColors,color,colorSpace),Picker=ColorPicker[colorSpace],readonly=!!argType?.table?.readonly;return import_react24.default.createElement(Wrapper8,{"aria-readonly":readonly},import_react24.default.createElement(PickerTooltip,{startOpen,trigger:readonly?[null]:void 0,closeOnOutsideClick:!0,onVisibleChange:()=>addPreset(color),tooltip:import_react24.default.createElement(TooltipContent,null,import_react24.default.createElement(Picker,{color:realValue==="transparent"?"#000000":realValue,onChange:updateValue,onFocus,onBlur}),presets.length>0&&import_react24.default.createElement(Swatches,null,presets.map((preset,index)=>import_react24.default.createElement(import_components15.WithTooltip,{key:`${preset.value}-${index}`,hasChrome:!1,tooltip:import_react24.default.createElement(Note,{note:preset.keyword||preset.value})},import_react24.default.createElement(Swatch,{value:preset[colorSpace],active:color&&id(preset[colorSpace])===id(color[colorSpace]),onClick:()=>updateValue(preset.value)})))))},import_react24.default.createElement(Swatch,{value:realValue,style:{margin:4}})),import_react24.default.createElement(Input2,{id:getControlId(name),value:value2,onChange:e=>updateValue(e.target.value),onFocus:e=>e.target.select(),readOnly:readonly,placeholder:"Choose color..."}),value2?import_react24.default.createElement(ToggleIcon,{onClick:cycleColorSpace}):null)},Color_default=ColorControl}});var src_exports={};__export(src_exports,{AddContext:()=>AddContext,Anchor:()=>Anchor,AnchorMdx:()=>AnchorMdx,ArgTypes:()=>ArgTypes,BooleanControl:()=>BooleanControl,Canvas:()=>Canvas,CodeOrSourceMdx:()=>CodeOrSourceMdx,ColorControl:()=>ColorControl2,ColorItem:()=>ColorItem,ColorPalette:()=>ColorPalette,Controls:()=>Controls3,DateControl:()=>DateControl,Description:()=>DescriptionContainer,DescriptionType:()=>DescriptionType,Docs:()=>Docs,DocsContainer:()=>DocsContainer,DocsContext:()=>DocsContext,DocsPage:()=>DocsPage,DocsStory:()=>DocsStory,ExternalDocs:()=>ExternalDocs,ExternalDocsContainer:()=>ExternalDocsContainer,FilesControl:()=>FilesControl,HeaderMdx:()=>HeaderMdx,HeadersMdx:()=>HeadersMdx,Heading:()=>Heading2,IconGallery:()=>IconGallery,IconItem:()=>IconItem,Markdown:()=>Markdown2,Meta:()=>Meta,NumberControl:()=>NumberControl,ObjectControl:()=>ObjectControl,OptionsControl:()=>OptionsControl,PRIMARY_STORY:()=>PRIMARY_STORY,Primary:()=>Primary,PureArgsTable:()=>ArgsTable,RangeControl:()=>RangeControl,Source:()=>Source2,SourceContainer:()=>SourceContainer,SourceContext:()=>SourceContext,Stories:()=>Stories,Story:()=>Story2,Subheading:()=>Subheading,Subtitle:()=>Subtitle2,TextControl:()=>TextControl,Title:()=>Title2,Typeset:()=>Typeset,UNKNOWN_ARGS_HASH:()=>UNKNOWN_ARGS_HASH,Unstyled:()=>Unstyled,Wrapper:()=>Wrapper12,anchorBlockIdFromId:()=>anchorBlockIdFromId,argsHash:()=>argsHash,assertIsFn:()=>assertIsFn,extractTitle:()=>extractTitle,format:()=>format,formatDate:()=>formatDate,formatTime:()=>formatTime,getStoryId:()=>getStoryId2,getStoryProps:()=>getStoryProps,parse:()=>parse2,parseDate:()=>parseDate,parseTime:()=>parseTime,useOf:()=>useOf,useSourceProps:()=>useSourceProps});module.exports=__toCommonJS(src_exports);var import_react2=__toESM(require("react")),import_theming2=require("@storybook/theming"),import_components2=require("@storybook/components");var import_react=__toESM(require("react")),import_polished=require("polished"),import_theming=require("@storybook/theming"),import_components=require("@storybook/components"),Wrapper=import_theming.styled.div(import_components.withReset,({theme})=>({backgroundColor:theme.base==="light"?"rgba(0,0,0,.01)":"rgba(255,255,255,.01)",borderRadius:theme.appBorderRadius,border:`1px dashed ${theme.appBorderColor}`,display:"flex",alignItems:"center",justifyContent:"center",padding:20,margin:"25px 0 40px",color:(0,import_polished.transparentize)(.3,theme.color.defaultText),fontSize:theme.typography.size.s2})),EmptyBlock=props=>import_react.default.createElement(Wrapper,{...props,className:"docblock-emptyblock sb-unstyled"});var StyledSyntaxHighlighter=(0,import_theming2.styled)(import_components2.SyntaxHighlighter)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,lineHeight:"19px",margin:"25px 0 40px",borderRadius:theme.appBorderRadius,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0","pre.prismjs":{padding:20,background:"inherit"}}));var SourceSkeletonWrapper=import_theming2.styled.div(({theme})=>({background:theme.background.content,borderRadius:theme.appBorderRadius,border:`1px solid ${theme.appBorderColor}`,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",margin:"25px 0 40px",padding:"20px 20px 20px 22px"})),SourceSkeletonPlaceholder=import_theming2.styled.div(({theme})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,height:17,marginTop:1,width:"60%",[`&:first-child${import_theming2.ignoreSsrWarning}`]:{margin:0}})),SourceSkeleton=()=>import_react2.default.createElement(SourceSkeletonWrapper,null,import_react2.default.createElement(SourceSkeletonPlaceholder,null),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}}),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"30%"}}),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}})),Source=({isLoading,error,language,code,dark,format:format2=!1,...rest})=>{let{typography}=(0,import_theming2.useTheme)();if(isLoading)return import_react2.default.createElement(SourceSkeleton,null);if(error)return import_react2.default.createElement(EmptyBlock,null,error);let syntaxHighlighter=import_react2.default.createElement(StyledSyntaxHighlighter,{bordered:!0,copyable:!0,format:format2,language,className:"docblock-source sb-unstyled",...rest},code);if(typeof dark>"u")return syntaxHighlighter;let overrideTheme=dark?import_theming2.themes.dark:import_theming2.themes.light;return import_react2.default.createElement(import_theming2.ThemeProvider,{theme:(0,import_theming2.convert)({...overrideTheme,fontCode:typography.fonts.mono,fontBase:typography.fonts.base})},syntaxHighlighter)};var import_react3=__toESM(require("react")),import_polished2=require("polished"),import_components3=require("@storybook/components"),import_theming3=require("@storybook/theming"),toGlobalSelector=element=>`& :where(${element}:not(.sb-anchor, .sb-unstyled, .sb-unstyled ${element}))`,breakpoint=600,Title=import_theming3.styled.h1(import_components3.withReset,({theme})=>({color:theme.color.defaultText,fontSize:theme.typography.size.m3,fontWeight:theme.typography.weight.bold,lineHeight:"32px",[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.l1,lineHeight:"36px",marginBottom:"16px"}})),Subtitle=import_theming3.styled.h2(import_components3.withReset,({theme})=>({fontWeight:theme.typography.weight.regular,fontSize:theme.typography.size.s3,lineHeight:"20px",borderBottom:"none",marginBottom:15,[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.m1,lineHeight:"28px",marginBottom:24},color:(0,import_polished2.transparentize)(.25,theme.color.defaultText)})),DocsContent=import_theming3.styled.div(({theme})=>{let reset={fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s3,margin:0,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch"},headers={margin:"20px 0 8px",padding:0,cursor:"text",position:"relative",color:theme.color.defaultText,"&:first-of-type":{marginTop:0,paddingTop:0},"&:hover a.anchor":{textDecoration:"none"},"& code":{fontSize:"inherit"}},code={lineHeight:1,margin:"0 2px",padding:"3px 5px",whiteSpace:"nowrap",borderRadius:3,fontSize:theme.typography.size.s2-1,border:theme.base==="light"?`1px solid ${theme.color.mediumlight}`:`1px solid ${theme.color.darker}`,color:theme.base==="light"?(0,import_polished2.transparentize)(.1,theme.color.defaultText):(0,import_polished2.transparentize)(.3,theme.color.defaultText),backgroundColor:theme.base==="light"?theme.color.lighter:theme.color.border};return{maxWidth:1e3,width:"100%",[toGlobalSelector("a")]:{...reset,fontSize:"inherit",lineHeight:"24px",color:theme.color.secondary,textDecoration:"none","&.absent":{color:"#cc0000"},"&.anchor":{display:"block",paddingLeft:30,marginLeft:-30,cursor:"pointer",position:"absolute",top:0,left:0,bottom:0}},[toGlobalSelector("blockquote")]:{...reset,margin:"16px 0",borderLeft:`4px solid ${theme.color.medium}`,padding:"0 15px",color:theme.color.dark,"& > :first-of-type":{marginTop:0},"& > :last-child":{marginBottom:0}},[toGlobalSelector("div")]:reset,[toGlobalSelector("dl")]:{...reset,margin:"16px 0",padding:0,"& dt":{fontSize:"14px",fontWeight:"bold",fontStyle:"italic",padding:0,margin:"16px 0 4px"},"& dt:first-of-type":{padding:0},"& dt > :first-of-type":{marginTop:0},"& dt > :last-child":{marginBottom:0},"& dd":{margin:"0 0 16px",padding:"0 15px"},"& dd > :first-of-type":{marginTop:0},"& dd > :last-child":{marginBottom:0}},[toGlobalSelector("h1")]:{...reset,...headers,fontSize:`${theme.typography.size.l1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h2")]:{...reset,...headers,fontSize:`${theme.typography.size.m2}px`,paddingBottom:4,borderBottom:`1px solid ${theme.appBorderColor}`},[toGlobalSelector("h3")]:{...reset,...headers,fontSize:`${theme.typography.size.m1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h4")]:{...reset,...headers,fontSize:`${theme.typography.size.s3}px`},[toGlobalSelector("h5")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`},[toGlobalSelector("h6")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`,color:theme.color.dark},[toGlobalSelector("hr")]:{border:"0 none",borderTop:`1px solid ${theme.appBorderColor}`,height:4,padding:0},[toGlobalSelector("img")]:{maxWidth:"100%"},[toGlobalSelector("li")]:{...reset,fontSize:theme.typography.size.s2,color:theme.color.defaultText,lineHeight:"24px","& + li":{marginTop:".25em"},"& ul, & ol":{marginTop:".25em",marginBottom:0},"& code":code},[toGlobalSelector("ol")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0}},[toGlobalSelector("p")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",color:theme.color.defaultText,"& code":code},[toGlobalSelector("pre")]:{...reset,fontFamily:theme.typography.fonts.mono,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",lineHeight:"18px",padding:"11px 1rem",whiteSpace:"pre-wrap",color:"inherit",borderRadius:3,margin:"1rem 0","&:not(.prismjs)":{background:"transparent",border:"none",borderRadius:0,padding:0,margin:0},"& pre, &.prismjs":{padding:15,margin:0,whiteSpace:"pre-wrap",color:"inherit",fontSize:"13px",lineHeight:"19px",code:{color:"inherit",fontSize:"inherit"}},"& code":{whiteSpace:"pre"},"& code, & tt":{border:"none"}},[toGlobalSelector("span")]:{...reset,"&.frame":{display:"block",overflow:"hidden","& > span":{border:`1px solid ${theme.color.medium}`,display:"block",float:"left",overflow:"hidden",margin:"13px 0 0",padding:7,width:"auto"},"& span img":{display:"block",float:"left"},"& span span":{clear:"both",color:theme.color.darkest,display:"block",padding:"5px 0 0"}},"&.align-center":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"center"},"& span img":{margin:"0 auto",textAlign:"center"}},"&.align-right":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px 0 0",textAlign:"right"},"& span img":{margin:0,textAlign:"right"}},"&.float-left":{display:"block",marginRight:13,overflow:"hidden",float:"left","& span":{margin:"13px 0 0"}},"&.float-right":{display:"block",marginLeft:13,overflow:"hidden",float:"right","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"right"}}},[toGlobalSelector("table")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",padding:0,borderCollapse:"collapse","& tr":{borderTop:`1px solid ${theme.appBorderColor}`,backgroundColor:theme.appContentBg,margin:0,padding:0},"& tr:nth-of-type(2n)":{backgroundColor:theme.base==="dark"?theme.color.darker:theme.color.lighter},"& tr th":{fontWeight:"bold",color:theme.color.defaultText,border:`1px solid ${theme.appBorderColor}`,margin:0,padding:"6px 13px"},"& tr td":{border:`1px solid ${theme.appBorderColor}`,color:theme.color.defaultText,margin:0,padding:"6px 13px"},"& tr th :first-of-type, & tr td :first-of-type":{marginTop:0},"& tr th :last-child, & tr td :last-child":{marginBottom:0}},[toGlobalSelector("ul")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0},listStyle:"disc"}}}),DocsWrapper=import_theming3.styled.div(({theme})=>({background:theme.background.content,display:"flex",justifyContent:"center",padding:"4rem 20px",minHeight:"100vh",boxSizing:"border-box",gap:"3rem",[`@media (min-width: ${breakpoint}px)`]:{}})),DocsPageWrapper=({children,toc})=>import_react3.default.createElement(DocsWrapper,{className:"sbdocs sbdocs-wrapper"},import_react3.default.createElement(DocsContent,{className:"sbdocs sbdocs-content"},children),toc);var import_react8=__toESM(require("react")),import_polished3=require("polished"),import_theming5=require("@storybook/theming"),import_global3=require("@storybook/global"),import_components6=require("@storybook/components");var getBlockBackgroundStyle=theme=>({borderRadius:theme.appBorderRadius,background:theme.background.content,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",border:`1px solid ${theme.appBorderColor}`});var import_react4=__toESM(require("react")),import_theming4=require("@storybook/theming"),import_components4=require("@storybook/components"),import_icons=require("@storybook/icons"),Bar=(0,import_theming4.styled)(import_components4.FlexBar)({position:"absolute",left:0,right:0,top:0,transition:"transform .2s linear"}),Wrapper2=import_theming4.styled.div({display:"flex",alignItems:"center",gap:4}),IconPlaceholder=import_theming4.styled.div(({theme})=>({width:14,height:14,borderRadius:2,margin:"0 7px",backgroundColor:theme.appBorderColor,animation:`${theme.animation.glow} 1.5s ease-in-out infinite`})),Toolbar=({isLoading,storyId,baseUrl,zoom,resetZoom,...rest})=>import_react4.default.createElement(Bar,{...rest},import_react4.default.createElement(Wrapper2,{key:"left"},isLoading?[1,2,3].map(key=>import_react4.default.createElement(IconPlaceholder,{key})):import_react4.default.createElement(import_react4.default.Fragment,null,import_react4.default.createElement(import_components4.IconButton,{key:"zoomin",onClick:e=>{e.preventDefault(),zoom(.8)},title:"Zoom in"},import_react4.default.createElement(import_icons.ZoomIcon,null)),import_react4.default.createElement(import_components4.IconButton,{key:"zoomout",onClick:e=>{e.preventDefault(),zoom(1.25)},title:"Zoom out"},import_react4.default.createElement(import_icons.ZoomOutIcon,null)),import_react4.default.createElement(import_components4.IconButton,{key:"zoomreset",onClick:e=>{e.preventDefault(),resetZoom()},title:"Reset zoom"},import_react4.default.createElement(import_icons.ZoomResetIcon,null)))));var import_react5=require("react"),ZoomContext=(0,import_react5.createContext)({scale:1});var import_global2=require("@storybook/global"),import_react7=__toESM(require("react")),import_components5=require("@storybook/components");var import_react6=__toESM(require("react")),import_global=require("@storybook/global"),{window:globalWindow}=import_global.global,IFrame=class extends import_react6.Component{constructor(){super(...arguments);this.iframe=null}componentDidMount(){let{id:id2}=this.props;this.iframe=globalWindow.document.getElementById(id2)}shouldComponentUpdate(nextProps){let{scale}=nextProps;return scale!==this.props.scale&&this.setIframeBodyStyle({width:`${scale*100}%`,height:`${scale*100}%`,transform:`scale(${1/scale})`,transformOrigin:"top left"}),!1}setIframeBodyStyle(style){return Object.assign(this.iframe.contentDocument.body.style,style)}render(){let{id:id2,title,src,allowFullScreen,scale,...rest}=this.props;return import_react6.default.createElement("iframe",{id:id2,title,src,...allowFullScreen?{allow:"fullscreen"}:{},loading:"lazy",...rest})}};var{PREVIEW_URL}=import_global2.global,BASE_URL=PREVIEW_URL||"iframe.html",storyBlockIdFromId=({story,primary})=>`story--${story.id}${primary?"--primary":""}`,InlineStory=props=>{let storyRef=(0,import_react7.useRef)(),[showLoader,setShowLoader]=(0,import_react7.useState)(!0),[error,setError]=(0,import_react7.useState)(),{story,height,autoplay,forceInitialArgs,renderStoryToElement}=props;return(0,import_react7.useEffect)(()=>{if(!(story&&storyRef.current))return()=>{};let element=storyRef.current,cleanup=renderStoryToElement(story,element,{showMain:()=>{},showError:({title,description})=>setError(new Error(`${title} - ${description}`)),showException:err=>setError(err)},{autoplay,forceInitialArgs});return setShowLoader(!1),()=>{Promise.resolve().then(()=>cleanup())}},[autoplay,renderStoryToElement,story]),error?import_react7.default.createElement("pre",null,import_react7.default.createElement(import_components5.ErrorFormatter,{error})):import_react7.default.createElement(import_react7.default.Fragment,null,height?import_react7.default.createElement("style",null,`#${storyBlockIdFromId(props)} { min-height: ${height}; transform: translateZ(0); overflow: auto }`):null,showLoader&&import_react7.default.createElement(StorySkeleton,null),import_react7.default.createElement("div",{ref:storyRef,id:`${storyBlockIdFromId(props)}-inner`,"data-name":story.name}))},IFrameStory=({story,height="500px"})=>import_react7.default.createElement("div",{style:{width:"100%",height}},import_react7.default.createElement(ZoomContext.Consumer,null,({scale})=>import_react7.default.createElement(IFrame,{key:"iframe",id:`iframe--${story.id}`,title:story.name,src:(0,import_components5.getStoryHref)(BASE_URL,story.id,{viewMode:"story"}),allowFullScreen:!0,scale,style:{width:"100%",height:"100%",border:"0 none"}}))),Story=props=>{let{inline}=props;return import_react7.default.createElement("div",{id:storyBlockIdFromId(props),className:"sb-story sb-unstyled","data-story-block":"true"},inline?import_react7.default.createElement(InlineStory,{...props}):import_react7.default.createElement(IFrameStory,{...props}))},StorySkeleton=()=>import_react7.default.createElement(import_components5.Loader,null);var ChildrenContainer=import_theming5.styled.div(({isColumn,columns,layout})=>({display:isColumn||!columns?"block":"flex",position:"relative",flexWrap:"wrap",overflow:"auto",flexDirection:isColumn?"column":"row","& .innerZoomElementWrapper > *":isColumn?{width:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"block"}:{maxWidth:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"inline-block"}}),({layout="padded"})=>layout==="centered"||layout==="padded"?{padding:"30px 20px","& .innerZoomElementWrapper > *":{width:"auto",border:"10px solid transparent!important"}}:{},({layout="padded"})=>layout==="centered"?{display:"flex",justifyContent:"center",justifyItems:"center",alignContent:"center",alignItems:"center"}:{},({columns})=>columns&&columns>1?{".innerZoomElementWrapper > *":{minWidth:`calc(100% / ${columns} - 20px)`}}:{}),StyledSource=(0,import_theming5.styled)(Source)(({theme})=>({margin:0,borderTopLeftRadius:0,borderTopRightRadius:0,borderBottomLeftRadius:theme.appBorderRadius,borderBottomRightRadius:theme.appBorderRadius,border:"none",background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":(0,import_polished3.darken)(.05,theme.background.content),color:theme.color.lightest,button:{background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":(0,import_polished3.darken)(.05,theme.background.content)}})),PreviewContainer=import_theming5.styled.div(({theme,withSource,isExpanded})=>({position:"relative",overflow:"hidden",margin:"25px 0 40px",...getBlockBackgroundStyle(theme),borderBottomLeftRadius:withSource&&isExpanded&&0,borderBottomRightRadius:withSource&&isExpanded&&0,borderBottomWidth:isExpanded&&0,"h3 + &":{marginTop:"16px"}}),({withToolbar})=>withToolbar&&{paddingTop:40}),getSource=(withSource,expanded,setExpanded)=>{switch(!0){case!!(withSource&&withSource.error):return{source:null,actionItem:{title:"No code available",className:"docblock-code-toggle docblock-code-toggle--disabled",disabled:!0,onClick:()=>setExpanded(!1)}};case expanded:return{source:import_react8.default.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Hide code",className:"docblock-code-toggle docblock-code-toggle--expanded",onClick:()=>setExpanded(!1)}};default:return{source:import_react8.default.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Show code",className:"docblock-code-toggle",onClick:()=>setExpanded(!0)}}}};function getStoryId(children){if(import_react8.Children.count(children)===1){let elt=children;if(elt.props)return elt.props.id}return null}var PositionedToolbar=(0,import_theming5.styled)(Toolbar)({position:"absolute",top:0,left:0,right:0,height:40}),Relative=import_theming5.styled.div({overflow:"hidden",position:"relative"}),Preview=({isLoading,isColumn,columns,children,withSource,withToolbar=!1,isExpanded=!1,additionalActions,className,layout="padded",...props})=>{let[expanded,setExpanded]=(0,import_react8.useState)(isExpanded),{source,actionItem}=getSource(withSource,expanded,setExpanded),[scale,setScale]=(0,import_react8.useState)(1),previewClasses=[className].concat(["sbdocs","sbdocs-preview","sb-unstyled"]),defaultActionItems=withSource?[actionItem]:[],[additionalActionItems,setAdditionalActionItems]=(0,import_react8.useState)(additionalActions?[...additionalActions]:[]),actionItems=[...defaultActionItems,...additionalActionItems],{window:globalWindow4}=import_global3.global,copyToClipboard=(0,import_react8.useCallback)(async text=>{let{createCopyToClipboardFunction}=await import("@storybook/components");createCopyToClipboardFunction()},[]),onCopyCapture=e=>{let selection=globalWindow4.getSelection();selection&&selection.type==="Range"||(e.preventDefault(),additionalActionItems.filter(item=>item.title==="Copied").length===0&©ToClipboard(source.props.code).then(()=>{setAdditionalActionItems([...additionalActionItems,{title:"Copied",onClick:()=>{}}]),globalWindow4.setTimeout(()=>setAdditionalActionItems(additionalActionItems.filter(item=>item.title!=="Copied")),1500)}))};return import_react8.default.createElement(PreviewContainer,{withSource,withToolbar,...props,className:previewClasses.join(" ")},withToolbar&&import_react8.default.createElement(PositionedToolbar,{isLoading,border:!0,zoom:z=>setScale(scale*z),resetZoom:()=>setScale(1),storyId:getStoryId(children),baseUrl:"./iframe.html"}),import_react8.default.createElement(ZoomContext.Provider,{value:{scale}},import_react8.default.createElement(Relative,{className:"docs-story",onCopyCapture:withSource&&onCopyCapture},import_react8.default.createElement(ChildrenContainer,{isColumn:isColumn||!Array.isArray(children),columns,layout},import_react8.default.createElement(import_components6.Zoom.Element,{scale},Array.isArray(children)?children.map((child,i)=>import_react8.default.createElement("div",{key:i},child)):import_react8.default.createElement("div",null,children))),import_react8.default.createElement(import_components6.ActionBar,{actionItems}))),withSource&&expanded&&source)},StyledPreview=(0,import_theming5.styled)(Preview)(()=>({".docs-story":{paddingTop:32,paddingBottom:40}}));var import_react31=__toESM(require("react")),import_pickBy=__toESM(require("lodash/pickBy.js")),import_theming23=require("@storybook/theming"),import_polished8=require("polished"),import_csf=require("@storybook/csf"),import_client_logger4=require("@storybook/client-logger"),import_components19=require("@storybook/components"),import_icons8=require("@storybook/icons");var import_react27=__toESM(require("react")),import_markdown_to_jsx=__toESM(require("markdown-to-jsx")),import_polished6=require("polished"),import_theming19=require("@storybook/theming"),import_components17=require("@storybook/components");var import_react9=__toESM(require("react")),import_theming6=require("@storybook/theming"),import_components7=require("@storybook/components"),Table=import_theming6.styled.table(({theme})=>({"&&":{borderCollapse:"collapse",borderSpacing:0,border:"none",tr:{border:"none !important",background:"none"},"td, th":{padding:0,border:"none",width:"auto!important"},marginTop:0,marginBottom:0,"th:first-of-type, td:first-of-type":{paddingLeft:0},"th:last-of-type, td:last-of-type":{paddingRight:0},td:{paddingTop:0,paddingBottom:4,"&:not(:first-of-type)":{paddingLeft:10,paddingRight:0}},tbody:{boxShadow:"none",border:"none"},code:(0,import_components7.codeCommon)({theme}),div:{span:{fontWeight:"bold"}},"& code":{margin:0,display:"inline-block",fontSize:theme.typography.size.s1}}})),ArgJsDoc=({tags})=>{let params=(tags.params||[]).filter(x=>x.description),hasDisplayableParams=params.length!==0,hasDisplayableDeprecated=tags.deprecated!=null,hasDisplayableReturns=tags.returns!=null&&tags.returns.description!=null;return!hasDisplayableParams&&!hasDisplayableReturns&&!hasDisplayableDeprecated?null:import_react9.default.createElement(import_react9.default.Fragment,null,import_react9.default.createElement(Table,null,import_react9.default.createElement("tbody",null,hasDisplayableDeprecated&&import_react9.default.createElement("tr",{key:"deprecated"},import_react9.default.createElement("td",{colSpan:2},import_react9.default.createElement("strong",null,"Deprecated"),": ",tags.deprecated.toString())),hasDisplayableParams&¶ms.map(x=>import_react9.default.createElement("tr",{key:x.name},import_react9.default.createElement("td",null,import_react9.default.createElement("code",null,x.name)),import_react9.default.createElement("td",null,x.description))),hasDisplayableReturns&&import_react9.default.createElement("tr",{key:"returns"},import_react9.default.createElement("td",null,import_react9.default.createElement("code",null,"Returns")),import_react9.default.createElement("td",null,tags.returns.description)))))};var import_react10=__toESM(require("react")),import_memoizerific=__toESM(require("memoizerific")),import_uniq=__toESM(require("lodash/uniq.js")),import_theming7=require("@storybook/theming"),import_components8=require("@storybook/components"),import_icons2=require("@storybook/icons"),ITEMS_BEFORE_EXPANSION=8,Summary=import_theming7.styled.div(({isExpanded})=>({display:"flex",flexDirection:isExpanded?"column":"row",flexWrap:"wrap",alignItems:"flex-start",marginBottom:"-4px",minWidth:100})),Text=import_theming7.styled.span(import_components8.codeCommon,({theme,simple=!1})=>({flex:"0 0 auto",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,wordBreak:"break-word",whiteSpace:"normal",maxWidth:"100%",margin:0,marginRight:"4px",marginBottom:"4px",paddingTop:"2px",paddingBottom:"2px",lineHeight:"13px",...simple&&{background:"transparent",border:"0 none",paddingLeft:0}})),ExpandButton=import_theming7.styled.button(({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,marginBottom:"4px",background:"none",border:"none"})),Expandable=import_theming7.styled.div(import_components8.codeCommon,({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,fontSize:theme.typography.size.s1,margin:0,whiteSpace:"nowrap",display:"flex",alignItems:"center"})),Detail=import_theming7.styled.div(({theme,width})=>({width,minWidth:200,maxWidth:800,padding:15,fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,boxSizing:"content-box","& code":{padding:"0 !important"}})),ChevronUpIcon=(0,import_theming7.styled)(import_icons2.ChevronSmallUpIcon)({marginLeft:4}),ChevronDownIcon=(0,import_theming7.styled)(import_icons2.ChevronSmallDownIcon)({marginLeft:4}),EmptyArg=()=>import_react10.default.createElement("span",null,"-"),ArgText=({text,simple})=>import_react10.default.createElement(Text,{simple},text),calculateDetailWidth=(0,import_memoizerific.default)(1e3)(detail=>{let lines=detail.split(/\r?\n/);return`${Math.max(...lines.map(x=>x.length))}ch`}),getSummaryItems=summary=>{if(!summary)return[summary];let summaryItems=summary.split("|").map(value2=>value2.trim());return(0,import_uniq.default)(summaryItems)},renderSummaryItems=(summaryItems,isExpanded=!0)=>{let items=summaryItems;return isExpanded||(items=summaryItems.slice(0,ITEMS_BEFORE_EXPANSION)),items.map(item=>import_react10.default.createElement(ArgText,{key:item,text:item===""?'""':item}))},ArgSummary=({value:value2,initialExpandedArgs})=>{let{summary,detail}=value2,[isOpen,setIsOpen]=(0,import_react10.useState)(!1),[isExpanded,setIsExpanded]=(0,import_react10.useState)(initialExpandedArgs||!1);if(summary==null)return null;let summaryAsString=typeof summary.toString=="function"?summary.toString():summary;if(detail==null){if(/[(){}[\]<>]/.test(summaryAsString))return import_react10.default.createElement(ArgText,{text:summaryAsString});let summaryItems=getSummaryItems(summaryAsString),itemsCount=summaryItems.length;return itemsCount>ITEMS_BEFORE_EXPANSION?import_react10.default.createElement(Summary,{isExpanded},renderSummaryItems(summaryItems,isExpanded),import_react10.default.createElement(ExpandButton,{onClick:()=>setIsExpanded(!isExpanded)},isExpanded?"Show less...":`Show ${itemsCount-ITEMS_BEFORE_EXPANSION} more...`)):import_react10.default.createElement(Summary,null,renderSummaryItems(summaryItems))}return import_react10.default.createElement(import_components8.WithTooltipPure,{closeOnOutsideClick:!0,placement:"bottom",visible:isOpen,onVisibleChange:isVisible=>{setIsOpen(isVisible)},tooltip:import_react10.default.createElement(Detail,{width:calculateDetailWidth(detail)},import_react10.default.createElement(import_components8.SyntaxHighlighter,{language:"jsx",format:!1},detail))},import_react10.default.createElement(Expandable,{className:"sbdocs-expandable"},import_react10.default.createElement("span",null,summaryAsString),isOpen?import_react10.default.createElement(ChevronUpIcon,null):import_react10.default.createElement(ChevronDownIcon,null)))},ArgValue=({value:value2,initialExpandedArgs})=>value2==null?import_react10.default.createElement(EmptyArg,null):import_react10.default.createElement(ArgSummary,{value:value2,initialExpandedArgs});var import_react26=__toESM(require("react")),import_components16=require("@storybook/components");var import_react25=__toESM(require("react"));var import_react11=__toESM(require("react")),import_polished4=require("polished"),import_theming8=require("@storybook/theming"),import_components9=require("@storybook/components");init_helpers();var Label=import_theming8.styled.label(({theme})=>({lineHeight:"18px",alignItems:"center",marginBottom:8,display:"inline-block",position:"relative",whiteSpace:"nowrap",background:theme.boolean.background,borderRadius:"3em",padding:1,'&[aria-disabled="true"]':{opacity:.5,input:{cursor:"not-allowed"}},input:{appearance:"none",width:"100%",height:"100%",position:"absolute",left:0,top:0,margin:0,padding:0,border:"none",background:"transparent",cursor:"pointer",borderRadius:"3em","&:focus":{outline:"none",boxShadow:`${theme.color.secondary} 0 0 0 1px inset !important`}},span:{textAlign:"center",fontSize:theme.typography.size.s1,fontWeight:theme.typography.weight.bold,lineHeight:"1",cursor:"pointer",display:"inline-block",padding:"7px 15px",transition:"all 100ms ease-out",userSelect:"none",borderRadius:"3em",color:(0,import_polished4.transparentize)(.5,theme.color.defaultText),background:"transparent","&:hover":{boxShadow:`${(0,import_polished4.opacify)(.3,theme.appBorderColor)} 0 0 0 1px inset`},"&:active":{boxShadow:`${(0,import_polished4.opacify)(.05,theme.appBorderColor)} 0 0 0 2px inset`,color:(0,import_polished4.opacify)(1,theme.appBorderColor)},"&:first-of-type":{paddingRight:8},"&:last-of-type":{paddingLeft:8}},"input:checked ~ span:last-of-type, input:not(:checked) ~ span:first-of-type":{background:theme.boolean.selectedBackground,boxShadow:theme.base==="light"?`${(0,import_polished4.opacify)(.1,theme.appBorderColor)} 0 0 2px`:`${theme.appBorderColor} 0 0 0 1px`,color:theme.color.defaultText,padding:"7px 15px"}})),parse=value2=>value2==="true",BooleanControl=({name,value:value2,onChange,onBlur,onFocus,argType})=>{let onSetFalse=(0,import_react11.useCallback)(()=>onChange(!1),[onChange]),readonly=!!argType?.table?.readonly;if(value2===void 0)return import_react11.default.createElement(import_components9.Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onSetFalse,disabled:readonly},"Set boolean");let controlId=getControlId(name),parsedValue=typeof value2=="string"?parse(value2):value2;return import_react11.default.createElement(Label,{"aria-disabled":readonly,htmlFor:controlId,"aria-label":name},import_react11.default.createElement("input",{id:controlId,type:"checkbox",onChange:e=>onChange(e.target.checked),checked:parsedValue,role:"switch",disabled:readonly,name,onBlur,onFocus}),import_react11.default.createElement("span",{"aria-hidden":"true"},"False"),import_react11.default.createElement("span",{"aria-hidden":"true"},"True"))};var import_react12=__toESM(require("react")),import_theming9=require("@storybook/theming"),import_components10=require("@storybook/components");init_helpers();var parseDate=value2=>{let[year,month,day]=value2.split("-"),result=new Date;return result.setFullYear(parseInt(year,10),parseInt(month,10)-1,parseInt(day,10)),result},parseTime=value2=>{let[hours,minutes]=value2.split(":"),result=new Date;return result.setHours(parseInt(hours,10)),result.setMinutes(parseInt(minutes,10)),result},formatDate=value2=>{let date=new Date(value2),year=`000${date.getFullYear()}`.slice(-4),month=`0${date.getMonth()+1}`.slice(-2),day=`0${date.getDate()}`.slice(-2);return`${year}-${month}-${day}`},formatTime=value2=>{let date=new Date(value2),hours=`0${date.getHours()}`.slice(-2),minutes=`0${date.getMinutes()}`.slice(-2);return`${hours}:${minutes}`},FormInput=(0,import_theming9.styled)(import_components10.Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),FlexSpaced=import_theming9.styled.div(({theme})=>({flex:1,display:"flex",input:{marginLeft:10,flex:1,height:32,"&::-webkit-calendar-picker-indicator":{opacity:.5,height:12,filter:theme.base==="light"?void 0:"invert(1)"}},"input:first-of-type":{marginLeft:0,flexGrow:4},"input:last-of-type":{flexGrow:3}})),DateControl=({name,value:value2,onChange,onFocus,onBlur,argType})=>{let[valid,setValid]=(0,import_react12.useState)(!0),dateRef=(0,import_react12.useRef)(),timeRef=(0,import_react12.useRef)(),readonly=!!argType?.table?.readonly;(0,import_react12.useEffect)(()=>{valid!==!1&&(dateRef&&dateRef.current&&(dateRef.current.value=formatDate(value2)),timeRef&&timeRef.current&&(timeRef.current.value=formatTime(value2)))},[value2]);let onDateChange=e=>{let parsed=parseDate(e.target.value),result=new Date(value2);result.setFullYear(parsed.getFullYear(),parsed.getMonth(),parsed.getDate());let time=result.getTime();time&&onChange(time),setValid(!!time)},onTimeChange=e=>{let parsed=parseTime(e.target.value),result=new Date(value2);result.setHours(parsed.getHours()),result.setMinutes(parsed.getMinutes());let time=result.getTime();time&&onChange(time),setValid(!!time)},controlId=getControlId(name);return import_react12.default.createElement(FlexSpaced,null,import_react12.default.createElement(FormInput,{type:"date",max:"9999-12-31",ref:dateRef,id:`${controlId}-date`,name:`${controlId}-date`,readOnly:readonly,onChange:onDateChange,onFocus,onBlur}),import_react12.default.createElement(FormInput,{type:"time",id:`${controlId}-time`,name:`${controlId}-time`,ref:timeRef,onChange:onTimeChange,readOnly:readonly,onFocus,onBlur}),valid?null:import_react12.default.createElement("div",null,"invalid"))};var import_react13=__toESM(require("react")),import_theming10=require("@storybook/theming"),import_components11=require("@storybook/components");init_helpers();var Wrapper3=import_theming10.styled.label({display:"flex"}),parse2=value2=>{let result=parseFloat(value2);return Number.isNaN(result)?void 0:result},format=value2=>value2!=null?String(value2):"",FormInput2=(0,import_theming10.styled)(import_components11.Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),NumberControl=({name,value:value2,onChange,min,max,step,onBlur,onFocus,argType})=>{let[inputValue,setInputValue]=(0,import_react13.useState)(typeof value2=="number"?value2:""),[forceVisible,setForceVisible]=(0,import_react13.useState)(!1),[parseError,setParseError]=(0,import_react13.useState)(null),readonly=!!argType?.table?.readonly,handleChange=(0,import_react13.useCallback)(event=>{setInputValue(event.target.value);let result=parseFloat(event.target.value);Number.isNaN(result)?setParseError(new Error(`'${event.target.value}' is not a number`)):(onChange(result),setParseError(null))},[onChange,setParseError]),onForceVisible=(0,import_react13.useCallback)(()=>{setInputValue("0"),onChange(0),setForceVisible(!0)},[setForceVisible]),htmlElRef=(0,import_react13.useRef)(null);return(0,import_react13.useEffect)(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select()},[forceVisible]),(0,import_react13.useEffect)(()=>{inputValue!==(typeof value2=="number"?value2:"")&&setInputValue(value2)},[value2]),value2===void 0?import_react13.default.createElement(import_components11.Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onForceVisible,disabled:readonly},"Set number"):import_react13.default.createElement(Wrapper3,null,import_react13.default.createElement(FormInput2,{ref:htmlElRef,id:getControlId(name),type:"number",onChange:handleChange,size:"flex",placeholder:"Edit number...",value:inputValue,valid:parseError?"error":null,autoFocus:forceVisible,readOnly:readonly,name,min,max,step,onFocus,onBlur}))};var import_react17=__toESM(require("react"));var import_react14=__toESM(require("react")),import_theming11=require("@storybook/theming"),import_client_logger=require("@storybook/client-logger");var selectedKey=(value2,options)=>{let entry=options&&Object.entries(options).find(([_key,val])=>val===value2);return entry?entry[0]:void 0},selectedKeys=(value2,options)=>value2&&options?Object.entries(options).filter(entry=>value2.includes(entry[1])).map(entry=>entry[0]):[],selectedValues=(keys,options)=>keys&&options&&keys.map(key=>options[key]);init_helpers();var Wrapper4=import_theming11.styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return{input:{cursor:"not-allowed"}}}),Text2=import_theming11.styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label2=import_theming11.styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),CheckboxControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return import_client_logger.logger.warn(`Checkbox with no options: ${name}`),import_react14.default.createElement(import_react14.default.Fragment,null,"-");let initial=selectedKeys(value2,options),[selected,setSelected]=(0,import_react14.useState)(initial),readonly=!!argType?.table?.readonly,handleChange=e=>{let option=e.target.value,updated=[...selected];updated.includes(option)?updated.splice(updated.indexOf(option),1):updated.push(option),onChange(selectedValues(updated,options)),setSelected(updated)};(0,import_react14.useEffect)(()=>{setSelected(selectedKeys(value2,options))},[value2]);let controlId=getControlId(name);return import_react14.default.createElement(Wrapper4,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id2=`${controlId}-${index}`;return import_react14.default.createElement(Label2,{key:id2,htmlFor:id2},import_react14.default.createElement("input",{type:"checkbox",disabled:readonly,id:id2,name:id2,value:key,onChange:handleChange,checked:selected?.includes(key)}),import_react14.default.createElement(Text2,null,key))}))};var import_react15=__toESM(require("react")),import_theming12=require("@storybook/theming"),import_client_logger2=require("@storybook/client-logger");init_helpers();var Wrapper5=import_theming12.styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return{input:{cursor:"not-allowed"}}}),Text3=import_theming12.styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label3=import_theming12.styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),RadioControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return import_client_logger2.logger.warn(`Radio with no options: ${name}`),import_react15.default.createElement(import_react15.default.Fragment,null,"-");let selection=selectedKey(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react15.default.createElement(Wrapper5,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id2=`${controlId}-${index}`;return import_react15.default.createElement(Label3,{key:id2,htmlFor:id2},import_react15.default.createElement("input",{type:"radio",id:id2,name:id2,disabled:readonly,value:key,onChange:e=>onChange(options[e.currentTarget.value]),checked:key===selection}),import_react15.default.createElement(Text3,null,key))}))};var import_react16=__toESM(require("react")),import_theming13=require("@storybook/theming"),import_client_logger3=require("@storybook/client-logger");init_helpers();var import_icons3=require("@storybook/icons"),styleResets={appearance:"none",border:"0 none",boxSizing:"inherit",display:" block",margin:" 0",background:"transparent",padding:0,fontSize:"inherit",position:"relative"},OptionsSelect=import_theming13.styled.select(styleResets,({theme})=>({boxSizing:"border-box",position:"relative",padding:"6px 10px",width:"100%",color:theme.input.color||"inherit",background:theme.input.background,borderRadius:theme.input.borderRadius,boxShadow:`${theme.input.border} 0 0 0 1px inset`,fontSize:theme.typography.size.s2-1,lineHeight:"20px","&:focus":{boxShadow:`${theme.color.secondary} 0 0 0 1px inset`,outline:"none"},"&[disabled]":{cursor:"not-allowed",opacity:.5},"::placeholder":{color:theme.textMutedColor},"&[multiple]":{overflow:"auto",padding:0,option:{display:"block",padding:"6px 10px",marginLeft:1,marginRight:1}}})),SelectWrapper=import_theming13.styled.span(({theme})=>({display:"inline-block",lineHeight:"normal",overflow:"hidden",position:"relative",verticalAlign:"top",width:"100%",svg:{position:"absolute",zIndex:1,pointerEvents:"none",height:"12px",marginTop:"-6px",right:"12px",top:"50%",fill:theme.textMutedColor,path:{fill:theme.textMutedColor}}})),NO_SELECTION="Choose option...",SingleSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{onChange(options[e.currentTarget.value])},selection=selectedKey(value2,options)||NO_SELECTION,controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react16.default.createElement(SelectWrapper,null,import_react16.default.createElement(import_icons3.ChevronSmallDownIcon,null),import_react16.default.createElement(OptionsSelect,{disabled:readonly,id:controlId,value:selection,onChange:handleChange},import_react16.default.createElement("option",{key:"no-selection",disabled:!0},NO_SELECTION),Object.keys(options).map(key=>import_react16.default.createElement("option",{key,value:key},key))))},MultiSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{let selection2=Array.from(e.currentTarget.options).filter(option=>option.selected).map(option=>option.value);onChange(selectedValues(selection2,options))},selection=selectedKeys(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react16.default.createElement(SelectWrapper,null,import_react16.default.createElement(OptionsSelect,{disabled:readonly,id:controlId,multiple:!0,value:selection,onChange:handleChange},Object.keys(options).map(key=>import_react16.default.createElement("option",{key,value:key},key))))},SelectControl=props=>{let{name,options}=props;return options?props.isMulti?import_react16.default.createElement(MultiSelect,{...props}):import_react16.default.createElement(SingleSelect,{...props}):(import_client_logger3.logger.warn(`Select with no options: ${name}`),import_react16.default.createElement(import_react16.default.Fragment,null,"-"))};var normalizeOptions=(options,labels)=>Array.isArray(options)?options.reduce((acc,item)=>(acc[labels?.[item]||String(item)]=item,acc),{}):options,Controls={check:CheckboxControl,"inline-check":CheckboxControl,radio:RadioControl,"inline-radio":RadioControl,select:SelectControl,"multi-select":SelectControl},OptionsControl=props=>{let{type="select",labels,argType}=props,normalized={...props,argType,options:argType?normalizeOptions(argType.options,labels):{},isInline:type.includes("inline"),isMulti:type.includes("multi")},Control=Controls[type];if(Control)return import_react17.default.createElement(Control,{...normalized});throw new Error(`Unknown options type: ${type}`)};var import_global4=require("@storybook/global"),import_cloneDeep=__toESM(require("lodash/cloneDeep.js")),import_react20=__toESM(require("react")),import_theming14=require("@storybook/theming"),import_components12=require("@storybook/components"),import_icons4=require("@storybook/icons");var import_react19=__toESM(require("react"));var import_react18=__toESM(require("react"));var VALUE="value",KEY="key";var ERROR="Error",OBJECT="Object",ARRAY="Array",STRING="String",NUMBER="Number",BOOLEAN="Boolean",DATE="Date",NULL="Null",UNDEFINED="Undefined",FUNCTION="Function",SYMBOL="Symbol";var ADD_DELTA_TYPE="ADD_DELTA_TYPE",REMOVE_DELTA_TYPE="REMOVE_DELTA_TYPE",UPDATE_DELTA_TYPE="UPDATE_DELTA_TYPE";function getObjectType(obj){return obj!==null&&typeof obj=="object"&&!Array.isArray(obj)&&typeof obj[Symbol.iterator]=="function"?"Iterable":Object.prototype.toString.call(obj).slice(8,-1)}function isComponentWillChange(oldValue,newValue){let oldType=getObjectType(oldValue),newType=getObjectType(newValue);return(oldType==="Function"||newType==="Function")&&newType!==oldType}var JsonAddValue=class extends import_react18.Component{constructor(props){super(props),this.state={inputRefKey:null,inputRefValue:null},this.refInputValue=this.refInputValue.bind(this),this.refInputKey=this.refInputKey.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onSubmit=this.onSubmit.bind(this)}componentDidMount(){let{inputRefKey,inputRefValue}=this.state,{onlyValue}=this.props;inputRefKey&&typeof inputRefKey.focus=="function"&&inputRefKey.focus(),onlyValue&&inputRefValue&&typeof inputRefValue.focus=="function"&&inputRefValue.focus(),document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.onSubmit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.props.handleCancel()))}onSubmit(){let{handleAdd,onlyValue,onSubmitValueParser,keyPath,deep}=this.props,{inputRefKey,inputRefValue}=this.state,result={};if(!onlyValue){if(!inputRefKey.value)return;result.key=inputRefKey.value}result.newValue=onSubmitValueParser(!1,keyPath,deep,result.key,inputRefValue.value),handleAdd(result)}refInputKey(node){this.state.inputRefKey=node}refInputValue(node){this.state.inputRefValue=node}render(){let{handleCancel,onlyValue,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep}=this.props,addButtonElementLayout=(0,import_react18.cloneElement)(addButtonElement,{onClick:this.onSubmit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:handleCancel}),inputElementValue=inputElementGenerator(VALUE,keyPath,deep),inputElementValueLayout=(0,import_react18.cloneElement)(inputElementValue,{placeholder:"Value",ref:this.refInputValue}),inputElementKeyLayout=null;if(!onlyValue){let inputElementKey=inputElementGenerator(KEY,keyPath,deep);inputElementKeyLayout=(0,import_react18.cloneElement)(inputElementKey,{placeholder:"Key",ref:this.refInputKey})}return import_react18.default.createElement("span",{className:"rejt-add-value-node"},inputElementKeyLayout,inputElementValueLayout,cancelButtonElementLayout,addButtonElementLayout)}};JsonAddValue.defaultProps={onlyValue:!1,addButtonElement:import_react18.default.createElement("button",null,"+"),cancelButtonElement:import_react18.default.createElement("button",null,"c")};var JsonArray=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={data:props.data,name:props.name,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveItem=this.handleRemoveItem.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data)}handleAddMode(){this.setState({addFormVisible:!0})}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}))}handleRemoveItem(index){return()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[index];beforeRemoveAction(index,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key:index,oldValue,type:REMOVE_DELTA_TYPE};data.splice(index,1),this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult)}).catch(logger4.error)}}handleAddValueAdd({newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(data.length,keyPath,deep,newValue).then(()=>{let newData=[...data,newValue];this.setState({data:newData}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],newData),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key:newData.length-1,newValue})}).catch(logger4.error)}handleAddValueCancel(){this.setState({addFormVisible:!1})}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve(void 0)}).catch(reject)})}renderCollapsed(){let{name,data,keyPath,deep}=this.state,{handleRemove,readOnly,getStyle,dataType,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-collapsed"},import_react18.default.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"[...] ",data.length," ",data.length===1?"item":"items"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,addFormVisible,nextDeep}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,delimiter,ul,addForm}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=(0,import_react18.cloneElement)(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-not-collapsed"},import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"["),!addFormVisible&&addItemButton,import_react18.default.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},data.map((item,index)=>import_react18.default.createElement(JsonNode,{key:index,name:index.toString(),data:item,keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveItem(index),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}))),!isReadOnly&&addFormVisible&&import_react18.default.createElement("div",{className:"rejt-add-form",style:addForm},import_react18.default.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,onlyValue:!0,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"]"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{dataType,getStyle}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return import_react18.default.createElement("div",{className:"rejt-array-node"},import_react18.default.createElement("span",{onClick:this.handleCollapseMode},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonArray.defaultProps={keyPath:[],deep:0,minusMenuElement:import_react18.default.createElement("span",null," - "),plusMenuElement:import_react18.default.createElement("span",null," + ")};var JsonFunctionValue=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this)}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,readOnlyResult=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!readOnlyResult&&typeof inputRef.focus=="function"&&inputRef.focus()}componentDidMount(){document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()))}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit()}).catch(logger4.error)}handleEditMode(){this.setState({editEnabled:!0})}refInput(node){this.state.inputRef=node}handleCancelEdit(){this.setState({editEnabled:!1})}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,textareaElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),result=null,minusElement=null,resultOnlyResult=readOnly(name,originalValue,keyPath,deep,dataType);if(editEnabled&&!resultOnlyResult){let textareaElement=textareaElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=(0,import_react18.cloneElement)(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:this.handleCancelEdit}),textareaElementLayout=(0,import_react18.cloneElement)(textareaElement,{ref:this.refInput,defaultValue:originalValue});result=import_react18.default.createElement("span",{className:"rejt-edit-form",style:style.editForm},textareaElementLayout," ",cancelButtonElementLayout,editButtonElementLayout),minusElement=null}else{result=import_react18.default.createElement("span",{className:"rejt-value",style:style.value,onClick:resultOnlyResult?null:this.handleEditMode},value2);let minusMenuLayout=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});minusElement=resultOnlyResult?null:minusMenuLayout}return import_react18.default.createElement("li",{className:"rejt-function-value-node",style:style.li},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," "),result,minusElement)}};JsonFunctionValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>{},editButtonElement:import_react18.default.createElement("button",null,"e"),cancelButtonElement:import_react18.default.createElement("button",null,"c"),minusMenuElement:import_react18.default.createElement("span",null," - ")};var JsonNode=class extends import_react18.Component{constructor(props){super(props),this.state={data:props.data,name:props.name,keyPath:props.keyPath,deep:props.deep}}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}render(){let{data,name,keyPath,deep}=this.state,{isCollapsed,handleRemove,handleUpdateValue,onUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,readOnlyTrue=()=>!0,dataType=getObjectType(data);switch(dataType){case ERROR:return import_react18.default.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly:readOnlyTrue,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case OBJECT:return import_react18.default.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case ARRAY:return import_react18.default.createElement(JsonArray,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case STRING:return import_react18.default.createElement(JsonValue,{name,value:`"${data}"`,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NUMBER:return import_react18.default.createElement(JsonValue,{name,value:data,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case BOOLEAN:return import_react18.default.createElement(JsonValue,{name,value:data?"true":"false",originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case DATE:return import_react18.default.createElement(JsonValue,{name,value:data.toISOString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NULL:return import_react18.default.createElement(JsonValue,{name,value:"null",originalValue:"null",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case UNDEFINED:return import_react18.default.createElement(JsonValue,{name,value:"undefined",originalValue:"undefined",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case FUNCTION:return import_react18.default.createElement(JsonFunctionValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,textareaElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case SYMBOL:return import_react18.default.createElement(JsonValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});default:return null}}};JsonNode.defaultProps={keyPath:[],deep:0};var JsonObject=class extends import_react18.Component{constructor(props){super(props);let keyPath=props.deep===-1?[]:[...props.keyPath,props.name];this.state={name:props.name,data:props.data,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveValue=this.handleRemoveValue.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data)}handleAddMode(){this.setState({addFormVisible:!0})}handleAddValueCancel(){this.setState({addFormVisible:!1})}handleAddValueAdd({key,newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(key,keyPath,deep,newValue).then(()=>{data[key]=newValue,this.setState({data}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key,newValue})}).catch(logger4.error)}handleRemoveValue(key){return()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeRemoveAction(key,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key,oldValue,type:REMOVE_DELTA_TYPE};delete data[key],this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult)}).catch(logger4.error)}}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}))}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve()}).catch(reject)})}renderCollapsed(){let{name,keyPath,deep,data}=this.state,{handleRemove,readOnly,dataType,getStyle,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-collapsed"},import_react18.default.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"{...}"," ",keyList.length," ",keyList.length===1?"key":"keys"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,nextDeep,addFormVisible}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,addForm,ul,delimiter}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=(0,import_react18.cloneElement)(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus}),list=keyList.map(key=>import_react18.default.createElement(JsonNode,{key,name:key,data:data[key],keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveValue(key),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}));return import_react18.default.createElement("span",{className:"rejt-not-collapsed"},import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"{"),!isReadOnly&&addItemButton,import_react18.default.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},list),!isReadOnly&&addFormVisible&&import_react18.default.createElement("div",{className:"rejt-add-form",style:addForm},import_react18.default.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"}"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{getStyle,dataType}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return import_react18.default.createElement("div",{className:"rejt-object-node"},import_react18.default.createElement("span",{onClick:this.handleCollapseMode},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonObject.defaultProps={keyPath:[],deep:0,minusMenuElement:import_react18.default.createElement("span",null," - "),plusMenuElement:import_react18.default.createElement("span",null," + ")};var JsonValue=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this)}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,isReadOnly=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!isReadOnly&&typeof inputRef.focus=="function"&&inputRef.focus()}componentDidMount(){document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()))}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit()}).catch(logger4.error)}handleEditMode(){this.setState({editEnabled:!0})}refInput(node){this.state.inputRef=node}handleCancelEdit(){this.setState({editEnabled:!1})}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,inputElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),isReadOnly=readOnly(name,originalValue,keyPath,deep,dataType),isEditing=editEnabled&&!isReadOnly,inputElement=inputElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=(0,import_react18.cloneElement)(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:this.handleCancelEdit}),inputElementLayout=(0,import_react18.cloneElement)(inputElement,{ref:this.refInput,defaultValue:JSON.stringify(originalValue)}),minusMenuLayout=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});return import_react18.default.createElement("li",{className:"rejt-value-node",style:style.li},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," : "),isEditing?import_react18.default.createElement("span",{className:"rejt-edit-form",style:style.editForm},inputElementLayout," ",cancelButtonElementLayout,editButtonElementLayout):import_react18.default.createElement("span",{className:"rejt-value",style:style.value,onClick:isReadOnly?null:this.handleEditMode},String(value2)),!isReadOnly&&!isEditing&&minusMenuLayout)}};JsonValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>Promise.resolve(),editButtonElement:import_react18.default.createElement("button",null,"e"),cancelButtonElement:import_react18.default.createElement("button",null,"c"),minusMenuElement:import_react18.default.createElement("span",null," - ")};var object={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},array={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},value={minus:{color:"red"},editForm:{},value:{color:"#7bba3d"},li:{minHeight:"22px",lineHeight:"22px",outline:"0px"},name:{color:"#2287CD"}};function parse3(string){let result=string;if(result.indexOf("function")===0)return(0,eval)(`(${result})`);try{result=JSON.parse(string)}catch{}return result}var JsonTree=class extends import_react19.Component{constructor(props){super(props),this.state={data:props.data,rootName:props.rootName},this.onUpdate=this.onUpdate.bind(this),this.removeRoot=this.removeRoot.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data||props.rootName!==state.rootName?{data:props.data,rootName:props.rootName}:null}onUpdate(key,data){this.setState({data}),this.props.onFullyUpdate(data)}removeRoot(){this.onUpdate(null,null)}render(){let{data,rootName}=this.state,{isCollapsed,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElement,textareaElement,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser,fallback=null}=this.props,dataType=getObjectType(data),readOnlyFunction=readOnly;getObjectType(readOnly)==="Boolean"&&(readOnlyFunction=()=>readOnly);let inputElementFunction=inputElement;inputElement&&getObjectType(inputElement)!=="Function"&&(inputElementFunction=()=>inputElement);let textareaElementFunction=textareaElement;return textareaElement&&getObjectType(textareaElement)!=="Function"&&(textareaElementFunction=()=>textareaElement),dataType==="Object"||dataType==="Array"?import_react19.default.createElement("div",{className:"rejt-tree"},import_react19.default.createElement(JsonNode,{data,name:rootName,deep:-1,isCollapsed,onUpdate:this.onUpdate,onDeltaUpdate,readOnly:readOnlyFunction,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator:inputElementFunction,textareaElementGenerator:textareaElementFunction,minusMenuElement,plusMenuElement,handleRemove:this.removeRoot,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser})):fallback}};JsonTree.defaultProps={rootName:"root",isCollapsed:(keyPath,deep)=>deep!==-1,getStyle:(keyName,data,keyPath,deep,dataType)=>{switch(dataType){case"Object":case"Error":return object;case"Array":return array;default:return value}},readOnly:()=>!1,onFullyUpdate:()=>{},onDeltaUpdate:()=>{},beforeRemoveAction:()=>Promise.resolve(),beforeAddAction:()=>Promise.resolve(),beforeUpdateAction:()=>Promise.resolve(),logger:{error:()=>{}},onSubmitValueParser:(isEditMode,keyPath,deep,name,rawValue)=>parse3(rawValue),inputElement:()=>import_react19.default.createElement("input",null),textareaElement:()=>import_react19.default.createElement("textarea",null),fallback:null};init_helpers();var{window:globalWindow2}=import_global4.global,Wrapper6=import_theming14.styled.div(({theme})=>({position:"relative",display:"flex",'&[aria-readonly="true"]':{opacity:.5},".rejt-tree":{marginLeft:"1rem",fontSize:"13px"},".rejt-value-node, .rejt-object-node > .rejt-collapsed, .rejt-array-node > .rejt-collapsed, .rejt-object-node > .rejt-not-collapsed, .rejt-array-node > .rejt-not-collapsed":{"& > svg":{opacity:0,transition:"opacity 0.2s"}},".rejt-value-node:hover, .rejt-object-node:hover > .rejt-collapsed, .rejt-array-node:hover > .rejt-collapsed, .rejt-object-node:hover > .rejt-not-collapsed, .rejt-array-node:hover > .rejt-not-collapsed":{"& > svg":{opacity:1}},".rejt-edit-form button":{display:"none"},".rejt-add-form":{marginLeft:10},".rejt-add-value-node":{display:"inline-flex",alignItems:"center"},".rejt-name":{lineHeight:"22px"},".rejt-not-collapsed-delimiter":{lineHeight:"22px"},".rejt-plus-menu":{marginLeft:5},".rejt-object-node > span > *, .rejt-array-node > span > *":{position:"relative",zIndex:2},".rejt-object-node, .rejt-array-node":{position:"relative"},".rejt-object-node > span:first-of-type::after, .rejt-array-node > span:first-of-type::after, .rejt-collapsed::before, .rejt-not-collapsed::before":{content:'""',position:"absolute",top:0,display:"block",width:"100%",marginLeft:"-1rem",padding:"0 4px 0 1rem",height:22},".rejt-collapsed::before, .rejt-not-collapsed::before":{zIndex:1,background:"transparent",borderRadius:4,transition:"background 0.2s",pointerEvents:"none",opacity:.1},".rejt-object-node:hover, .rejt-array-node:hover":{"& > .rejt-collapsed::before, & > .rejt-not-collapsed::before":{background:theme.color.secondary}},".rejt-collapsed::after, .rejt-not-collapsed::after":{content:'""',position:"absolute",display:"inline-block",pointerEvents:"none",width:0,height:0},".rejt-collapsed::after":{left:-8,top:8,borderTop:"3px solid transparent",borderBottom:"3px solid transparent",borderLeft:"3px solid rgba(153,153,153,0.6)"},".rejt-not-collapsed::after":{left:-10,top:10,borderTop:"3px solid rgba(153,153,153,0.6)",borderLeft:"3px solid transparent",borderRight:"3px solid transparent"},".rejt-value":{display:"inline-block",border:"1px solid transparent",borderRadius:4,margin:"1px 0",padding:"0 4px",cursor:"text",color:theme.color.defaultText},".rejt-value-node:hover > .rejt-value":{background:theme.color.lighter,borderColor:theme.appBorderColor}})),ButtonInline=import_theming14.styled.button(({theme,primary})=>({border:0,height:20,margin:1,borderRadius:4,background:primary?theme.color.secondary:"transparent",color:primary?theme.color.lightest:theme.color.dark,fontWeight:primary?"bold":"normal",cursor:"pointer",order:primary?"initial":9})),ActionAddIcon=(0,import_theming14.styled)(import_icons4.AddIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.ancillary},"svg + &":{marginLeft:0}})),ActionSubstractIcon=(0,import_theming14.styled)(import_icons4.SubtractIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.negative},"svg + &":{marginLeft:0}})),Input=import_theming14.styled.input(({theme,placeholder})=>({outline:0,margin:placeholder?1:"1px 0",padding:"3px 4px",color:theme.color.defaultText,background:theme.background.app,border:`1px solid ${theme.appBorderColor}`,borderRadius:4,lineHeight:"14px",width:placeholder==="Key"?80:120,"&:focus":{border:`1px solid ${theme.color.secondary}`}})),RawButton=(0,import_theming14.styled)(import_components12.IconButton)(({theme})=>({position:"absolute",zIndex:2,top:2,right:2,height:21,padding:"0 3px",background:theme.background.bar,border:`1px solid ${theme.appBorderColor}`,borderRadius:3,color:theme.textMutedColor,fontSize:"9px",fontWeight:"bold",textDecoration:"none",span:{marginLeft:3,marginTop:1}})),RawInput=(0,import_theming14.styled)(import_components12.Form.Textarea)(({theme})=>({flex:1,padding:"7px 6px",fontFamily:theme.typography.fonts.mono,fontSize:"12px",lineHeight:"18px","&::placeholder":{fontFamily:theme.typography.fonts.base,fontSize:"13px"},"&:placeholder-shown":{padding:"7px 10px"}})),ENTER_EVENT={bubbles:!0,cancelable:!0,key:"Enter",code:"Enter",keyCode:13},dispatchEnterKey=event=>{event.currentTarget.dispatchEvent(new globalWindow2.KeyboardEvent("keydown",ENTER_EVENT))},selectValue=event=>{event.currentTarget.select()},getCustomStyleFunction=theme=>()=>({name:{color:theme.color.secondary},collapsed:{color:theme.color.dark},ul:{listStyle:"none",margin:"0 0 0 1rem",padding:0},li:{outline:0}}),ObjectControl=({name,value:value2,onChange,argType})=>{let theme=(0,import_theming14.useTheme)(),data=(0,import_react20.useMemo)(()=>value2&&(0,import_cloneDeep.default)(value2),[value2]),hasData=data!=null,[showRaw,setShowRaw]=(0,import_react20.useState)(!hasData),[parseError,setParseError]=(0,import_react20.useState)(null),readonly=!!argType?.table?.readonly,updateRaw=(0,import_react20.useCallback)(raw=>{try{raw&&onChange(JSON.parse(raw)),setParseError(void 0)}catch(e){setParseError(e)}},[onChange]),[forceVisible,setForceVisible]=(0,import_react20.useState)(!1),onForceVisible=(0,import_react20.useCallback)(()=>{onChange({}),setForceVisible(!0)},[setForceVisible]),htmlElRef=(0,import_react20.useRef)(null);if((0,import_react20.useEffect)(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select()},[forceVisible]),!hasData)return import_react20.default.createElement(import_components12.Button,{disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set object");let rawJSONForm=import_react20.default.createElement(RawInput,{ref:htmlElRef,id:getControlId(name),name,defaultValue:value2===null?"":JSON.stringify(value2,null,2),onBlur:event=>updateRaw(event.target.value),placeholder:"Edit JSON string...",autoFocus:forceVisible,valid:parseError?"error":null,readOnly:readonly}),isObjectOrArray=Array.isArray(value2)||typeof value2=="object"&&value2?.constructor===Object;return import_react20.default.createElement(Wrapper6,{"aria-readonly":readonly},isObjectOrArray&&import_react20.default.createElement(RawButton,{onClick:e=>{e.preventDefault(),setShowRaw(v=>!v)}},showRaw?import_react20.default.createElement(import_icons4.EyeCloseIcon,null):import_react20.default.createElement(import_icons4.EyeIcon,null),import_react20.default.createElement("span",null,"RAW")),showRaw?rawJSONForm:import_react20.default.createElement(JsonTree,{readOnly:readonly||!isObjectOrArray,isCollapsed:isObjectOrArray?void 0:()=>!0,data,rootName:name,onFullyUpdate:onChange,getStyle:getCustomStyleFunction(theme),cancelButtonElement:import_react20.default.createElement(ButtonInline,{type:"button"},"Cancel"),editButtonElement:import_react20.default.createElement(ButtonInline,{type:"submit"},"Save"),addButtonElement:import_react20.default.createElement(ButtonInline,{type:"submit",primary:!0},"Save"),plusMenuElement:import_react20.default.createElement(ActionAddIcon,null),minusMenuElement:import_react20.default.createElement(ActionSubstractIcon,null),inputElement:(_,__,___,key)=>key?import_react20.default.createElement(Input,{onFocus:selectValue,onBlur:dispatchEnterKey}):import_react20.default.createElement(Input,null),fallback:rawJSONForm}))};var import_react21=__toESM(require("react")),import_theming15=require("@storybook/theming"),import_polished5=require("polished");init_helpers();var RangeInput=import_theming15.styled.input(({theme,min,max,value:value2,disabled})=>({"&":{width:"100%",backgroundColor:"transparent",appearance:"none"},"&::-webkit-slider-runnable-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.lighten)(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer"},"&::-webkit-slider-thumb":{marginTop:"-6px",width:16,height:16,border:`1px solid ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grab",appearance:"none",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${(0,import_polished5.darken)(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:disabled?"not-allowed":"grab"}},"&:focus":{outline:"none","&::-webkit-slider-runnable-track":{borderColor:(0,import_polished5.rgba)(theme.color.secondary,.4)},"&::-webkit-slider-thumb":{borderColor:theme.color.secondary,boxShadow:`0 0px 5px 0px ${theme.color.secondary}`}},"&::-moz-range-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.lighten)(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer",outline:"none"},"&::-moz-range-thumb":{width:16,height:16,border:`1px solid ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grap",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${(0,import_polished5.darken)(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:"grabbing"}},"&::-ms-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var getControlId,getControlSetterButtonId,init_helpers=__esm({"src/controls/helpers.ts"(){getControlId=value2=>`control-${value2.replace(/\s+/g,"-")}`,getControlSetterButtonId=value2=>`set-${value2.replace(/\s+/g,"-")}`}});var Color_exports={};__export(Color_exports,{ColorControl:()=>ColorControl,default:()=>Color_default});var import_react24,import_react_colorful,import_color_convert,import_throttle,import_theming18,import_components15,import_icons5,Wrapper8,PickerTooltip,TooltipContent,Note,Swatches,SwatchColor,swatchBackground,Swatch,Input2,ToggleIcon,ColorSpace,COLOR_SPACES,COLOR_REGEXP,RGB_REGEXP,HSL_REGEXP,HEX_REGEXP,SHORTHEX_REGEXP,ColorPicker,fallbackColor,stringToArgs,parseValue,getRealValue,useColorInput,id,usePresets,ColorControl,Color_default,init_Color=__esm({"src/controls/Color.tsx"(){import_react24=__toESM(require("react")),import_react_colorful=require("react-colorful"),import_color_convert=__toESM(require("color-convert")),import_throttle=__toESM(require("lodash/throttle.js")),import_theming18=require("@storybook/theming"),import_components15=require("@storybook/components");init_helpers();import_icons5=require("@storybook/icons"),Wrapper8=import_theming18.styled.div({position:"relative",maxWidth:250,'&[aria-readonly="true"]':{opacity:.5}}),PickerTooltip=(0,import_theming18.styled)(import_components15.WithTooltip)({position:"absolute",zIndex:1,top:4,left:4,"[aria-readonly=true] &":{cursor:"not-allowed"}}),TooltipContent=import_theming18.styled.div({width:200,margin:5,".react-colorful__saturation":{borderRadius:"4px 4px 0 0"},".react-colorful__hue":{boxShadow:"inset 0 0 0 1px rgb(0 0 0 / 5%)"},".react-colorful__last-control":{borderRadius:"0 0 4px 4px"}}),Note=(0,import_theming18.styled)(import_components15.TooltipNote)(({theme})=>({fontFamily:theme.typography.fonts.base})),Swatches=import_theming18.styled.div({display:"grid",gridTemplateColumns:"repeat(9, 16px)",gap:6,padding:3,marginTop:5,width:200}),SwatchColor=import_theming18.styled.div(({theme,active})=>({width:16,height:16,boxShadow:active?`${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px`:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:theme.appBorderRadius})),swatchBackground=`url('data:image/svg+xml;charset=utf-8,')`,Swatch=({value:value2,style,...props})=>{let backgroundImage=`linear-gradient(${value2}, ${value2}), ${swatchBackground}, linear-gradient(#fff, #fff)`;return import_react24.default.createElement(SwatchColor,{...props,style:{...style,backgroundImage}})},Input2=(0,import_theming18.styled)(import_components15.Form.Input)(({theme,readOnly})=>({width:"100%",paddingLeft:30,paddingRight:30,boxSizing:"border-box",fontFamily:theme.typography.fonts.base})),ToggleIcon=(0,import_theming18.styled)(import_icons5.MarkupIcon)(({theme})=>({position:"absolute",zIndex:1,top:6,right:7,width:20,height:20,padding:4,boxSizing:"border-box",cursor:"pointer",color:theme.input.color})),ColorSpace=(ColorSpace2=>(ColorSpace2.RGB="rgb",ColorSpace2.HSL="hsl",ColorSpace2.HEX="hex",ColorSpace2))(ColorSpace||{}),COLOR_SPACES=Object.values(ColorSpace),COLOR_REGEXP=/\(([0-9]+),\s*([0-9]+)%?,\s*([0-9]+)%?,?\s*([0-9.]+)?\)/,RGB_REGEXP=/^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i,HSL_REGEXP=/^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i,HEX_REGEXP=/^\s*#?([0-9a-f]{3}|[0-9a-f]{6})\s*$/i,SHORTHEX_REGEXP=/^\s*#?([0-9a-f]{3})\s*$/i,ColorPicker={hex:import_react_colorful.HexColorPicker,rgb:import_react_colorful.RgbaStringColorPicker,hsl:import_react_colorful.HslaStringColorPicker},fallbackColor={hex:"transparent",rgb:"rgba(0, 0, 0, 0)",hsl:"hsla(0, 0%, 0%, 0)"},stringToArgs=value2=>{let match=value2?.match(COLOR_REGEXP);if(!match)return[0,0,0,1];let[,x,y,z,a=1]=match;return[x,y,z,a].map(Number)},parseValue=value2=>{if(!value2)return;let valid=!0;if(RGB_REGEXP.test(value2)){let[r,g,b,a]=stringToArgs(value2),[h,s,l]=import_color_convert.default.rgb.hsl([r,g,b])||[0,0,0];return{valid,value:value2,keyword:import_color_convert.default.rgb.keyword([r,g,b]),colorSpace:"rgb",rgb:value2,hsl:`hsla(${h}, ${s}%, ${l}%, ${a})`,hex:`#${import_color_convert.default.rgb.hex([r,g,b]).toLowerCase()}`}}if(HSL_REGEXP.test(value2)){let[h,s,l,a]=stringToArgs(value2),[r,g,b]=import_color_convert.default.hsl.rgb([h,s,l])||[0,0,0];return{valid,value:value2,keyword:import_color_convert.default.hsl.keyword([h,s,l]),colorSpace:"hsl",rgb:`rgba(${r}, ${g}, ${b}, ${a})`,hsl:value2,hex:`#${import_color_convert.default.hsl.hex([h,s,l]).toLowerCase()}`}}let plain=value2.replace("#",""),rgb=import_color_convert.default.keyword.rgb(plain)||import_color_convert.default.hex.rgb(plain),hsl=import_color_convert.default.rgb.hsl(rgb),mapped=value2;if(/[^#a-f0-9]/i.test(value2)?mapped=plain:HEX_REGEXP.test(value2)&&(mapped=`#${plain}`),mapped.startsWith("#"))valid=HEX_REGEXP.test(mapped);else try{import_color_convert.default.keyword.hex(mapped)}catch{valid=!1}return{valid,value:mapped,keyword:import_color_convert.default.rgb.keyword(rgb),colorSpace:"hex",rgb:`rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 1)`,hsl:`hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, 1)`,hex:mapped}},getRealValue=(value2,color,colorSpace)=>{if(!value2||!color?.valid)return fallbackColor[colorSpace];if(colorSpace!=="hex")return color?.[colorSpace]||fallbackColor[colorSpace];if(!color.hex.startsWith("#"))try{return`#${import_color_convert.default.keyword.hex(color.hex)}`}catch{return fallbackColor.hex}let short=color.hex.match(SHORTHEX_REGEXP);if(!short)return HEX_REGEXP.test(color.hex)?color.hex:fallbackColor.hex;let[r,g,b]=short[1].split("");return`#${r}${r}${g}${g}${b}${b}`},useColorInput=(initialValue,onChange)=>{let[value2,setValue]=(0,import_react24.useState)(initialValue||""),[color,setColor]=(0,import_react24.useState)(()=>parseValue(value2)),[colorSpace,setColorSpace]=(0,import_react24.useState)(color?.colorSpace||"hex");(0,import_react24.useEffect)(()=>{let nextValue=initialValue||"",nextColor=parseValue(nextValue);setValue(nextValue),setColor(nextColor),setColorSpace(nextColor?.colorSpace||"hex")},[initialValue]);let realValue=(0,import_react24.useMemo)(()=>getRealValue(value2,color,colorSpace).toLowerCase(),[value2,color,colorSpace]),updateValue=(0,import_react24.useCallback)(update=>{let parsed=parseValue(update),v=parsed?.value||update||"";setValue(v),v===""&&(setColor(void 0),onChange(void 0)),parsed&&(setColor(parsed),setColorSpace(parsed.colorSpace),onChange(parsed.value))},[onChange]),cycleColorSpace=(0,import_react24.useCallback)(()=>{let next=COLOR_SPACES.indexOf(colorSpace)+1;next>=COLOR_SPACES.length&&(next=0),setColorSpace(COLOR_SPACES[next]);let update=color?.[COLOR_SPACES[next]]||"";setValue(update),onChange(update)},[color,colorSpace,onChange]);return{value:value2,realValue,updateValue,color,colorSpace,cycleColorSpace}},id=value2=>value2.replace(/\s*/,"").toLowerCase(),usePresets=(presetColors,currentColor,colorSpace)=>{let[selectedColors,setSelectedColors]=(0,import_react24.useState)(currentColor?.valid?[currentColor]:[]);(0,import_react24.useEffect)(()=>{currentColor===void 0&&setSelectedColors([])},[currentColor]);let presets=(0,import_react24.useMemo)(()=>(presetColors||[]).map(preset=>typeof preset=="string"?parseValue(preset):preset.title?{...parseValue(preset.color),keyword:preset.title}:parseValue(preset.color)).concat(selectedColors).filter(Boolean).slice(-27),[presetColors,selectedColors]),addPreset=(0,import_react24.useCallback)(color=>{color?.valid&&(presets.some(preset=>id(preset[colorSpace])===id(color[colorSpace]))||setSelectedColors(arr=>arr.concat(color)))},[colorSpace,presets]);return{presets,addPreset}},ColorControl=({name,value:initialValue,onChange,onFocus,onBlur,presetColors,startOpen=!1,argType})=>{let throttledOnChange=(0,import_react24.useCallback)((0,import_throttle.default)(onChange,200),[onChange]),{value:value2,realValue,updateValue,color,colorSpace,cycleColorSpace}=useColorInput(initialValue,throttledOnChange),{presets,addPreset}=usePresets(presetColors,color,colorSpace),Picker=ColorPicker[colorSpace],readonly=!!argType?.table?.readonly;return import_react24.default.createElement(Wrapper8,{"aria-readonly":readonly},import_react24.default.createElement(PickerTooltip,{startOpen,trigger:readonly?[null]:void 0,closeOnOutsideClick:!0,onVisibleChange:()=>addPreset(color),tooltip:import_react24.default.createElement(TooltipContent,null,import_react24.default.createElement(Picker,{color:realValue==="transparent"?"#000000":realValue,onChange:updateValue,onFocus,onBlur}),presets.length>0&&import_react24.default.createElement(Swatches,null,presets.map((preset,index)=>import_react24.default.createElement(import_components15.WithTooltip,{key:`${preset.value}-${index}`,hasChrome:!1,tooltip:import_react24.default.createElement(Note,{note:preset.keyword||preset.value})},import_react24.default.createElement(Swatch,{value:preset[colorSpace],active:color&&id(preset[colorSpace])===id(color[colorSpace]),onClick:()=>updateValue(preset.value)})))))},import_react24.default.createElement(Swatch,{value:realValue,style:{margin:4}})),import_react24.default.createElement(Input2,{id:getControlId(name),value:value2,onChange:e=>updateValue(e.target.value),onFocus:e=>e.target.select(),readOnly:readonly,placeholder:"Choose color..."}),value2?import_react24.default.createElement(ToggleIcon,{onClick:cycleColorSpace}):null)},Color_default=ColorControl}});var src_exports={};__export(src_exports,{AddContext:()=>AddContext,Anchor:()=>Anchor,AnchorMdx:()=>AnchorMdx,ArgTypes:()=>ArgTypes,BooleanControl:()=>BooleanControl,Canvas:()=>Canvas,CodeOrSourceMdx:()=>CodeOrSourceMdx,ColorControl:()=>ColorControl2,ColorItem:()=>ColorItem,ColorPalette:()=>ColorPalette,Controls:()=>Controls3,DateControl:()=>DateControl,Description:()=>DescriptionContainer,DescriptionType:()=>DescriptionType,Docs:()=>Docs,DocsContainer:()=>DocsContainer,DocsContext:()=>DocsContext,DocsPage:()=>DocsPage,DocsStory:()=>DocsStory,ExternalDocs:()=>ExternalDocs,ExternalDocsContainer:()=>ExternalDocsContainer,FilesControl:()=>FilesControl,HeaderMdx:()=>HeaderMdx,HeadersMdx:()=>HeadersMdx,Heading:()=>Heading2,IconGallery:()=>IconGallery,IconItem:()=>IconItem,Markdown:()=>Markdown2,Meta:()=>Meta,NumberControl:()=>NumberControl,ObjectControl:()=>ObjectControl,OptionsControl:()=>OptionsControl,PRIMARY_STORY:()=>PRIMARY_STORY,Preview:()=>Preview,Primary:()=>Primary,PureArgsTable:()=>ArgsTable,RangeControl:()=>RangeControl,Source:()=>Source2,SourceContainer:()=>SourceContainer,SourceContext:()=>SourceContext,Stories:()=>Stories,Story:()=>Story2,Subheading:()=>Subheading,Subtitle:()=>Subtitle2,TextControl:()=>TextControl,Title:()=>Title2,Typeset:()=>Typeset,UNKNOWN_ARGS_HASH:()=>UNKNOWN_ARGS_HASH,Unstyled:()=>Unstyled,Wrapper:()=>Wrapper12,anchorBlockIdFromId:()=>anchorBlockIdFromId,argsHash:()=>argsHash,assertIsFn:()=>assertIsFn,extractTitle:()=>extractTitle,format:()=>format,formatDate:()=>formatDate,formatTime:()=>formatTime,getStoryId:()=>getStoryId2,getStoryProps:()=>getStoryProps,parse:()=>parse2,parseDate:()=>parseDate,parseTime:()=>parseTime,useOf:()=>useOf,useSourceProps:()=>useSourceProps});module.exports=__toCommonJS(src_exports);var import_react2=__toESM(require("react")),import_theming2=require("@storybook/theming"),import_components2=require("@storybook/components");var import_react=__toESM(require("react")),import_polished=require("polished"),import_theming=require("@storybook/theming"),import_components=require("@storybook/components"),Wrapper=import_theming.styled.div(import_components.withReset,({theme})=>({backgroundColor:theme.base==="light"?"rgba(0,0,0,.01)":"rgba(255,255,255,.01)",borderRadius:theme.appBorderRadius,border:`1px dashed ${theme.appBorderColor}`,display:"flex",alignItems:"center",justifyContent:"center",padding:20,margin:"25px 0 40px",color:(0,import_polished.transparentize)(.3,theme.color.defaultText),fontSize:theme.typography.size.s2})),EmptyBlock=props=>import_react.default.createElement(Wrapper,{...props,className:"docblock-emptyblock sb-unstyled"});var StyledSyntaxHighlighter=(0,import_theming2.styled)(import_components2.SyntaxHighlighter)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,lineHeight:"19px",margin:"25px 0 40px",borderRadius:theme.appBorderRadius,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0","pre.prismjs":{padding:20,background:"inherit"}}));var SourceSkeletonWrapper=import_theming2.styled.div(({theme})=>({background:theme.background.content,borderRadius:theme.appBorderRadius,border:`1px solid ${theme.appBorderColor}`,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",margin:"25px 0 40px",padding:"20px 20px 20px 22px"})),SourceSkeletonPlaceholder=import_theming2.styled.div(({theme})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,height:17,marginTop:1,width:"60%",[`&:first-child${import_theming2.ignoreSsrWarning}`]:{margin:0}})),SourceSkeleton=()=>import_react2.default.createElement(SourceSkeletonWrapper,null,import_react2.default.createElement(SourceSkeletonPlaceholder,null),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}}),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"30%"}}),import_react2.default.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}})),Source=({isLoading,error,language,code,dark,format:format2=!1,...rest})=>{let{typography}=(0,import_theming2.useTheme)();if(isLoading)return import_react2.default.createElement(SourceSkeleton,null);if(error)return import_react2.default.createElement(EmptyBlock,null,error);let syntaxHighlighter=import_react2.default.createElement(StyledSyntaxHighlighter,{bordered:!0,copyable:!0,format:format2,language,className:"docblock-source sb-unstyled",...rest},code);if(typeof dark>"u")return syntaxHighlighter;let overrideTheme=dark?import_theming2.themes.dark:import_theming2.themes.light;return import_react2.default.createElement(import_theming2.ThemeProvider,{theme:(0,import_theming2.convert)({...overrideTheme,fontCode:typography.fonts.mono,fontBase:typography.fonts.base})},syntaxHighlighter)};var import_react3=__toESM(require("react")),import_polished2=require("polished"),import_components3=require("@storybook/components"),import_theming3=require("@storybook/theming"),toGlobalSelector=element=>`& :where(${element}:not(.sb-anchor, .sb-unstyled, .sb-unstyled ${element}))`,breakpoint=600,Title=import_theming3.styled.h1(import_components3.withReset,({theme})=>({color:theme.color.defaultText,fontSize:theme.typography.size.m3,fontWeight:theme.typography.weight.bold,lineHeight:"32px",[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.l1,lineHeight:"36px",marginBottom:"16px"}})),Subtitle=import_theming3.styled.h2(import_components3.withReset,({theme})=>({fontWeight:theme.typography.weight.regular,fontSize:theme.typography.size.s3,lineHeight:"20px",borderBottom:"none",marginBottom:15,[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.m1,lineHeight:"28px",marginBottom:24},color:(0,import_polished2.transparentize)(.25,theme.color.defaultText)})),DocsContent=import_theming3.styled.div(({theme})=>{let reset={fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s3,margin:0,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch"},headers={margin:"20px 0 8px",padding:0,cursor:"text",position:"relative",color:theme.color.defaultText,"&:first-of-type":{marginTop:0,paddingTop:0},"&:hover a.anchor":{textDecoration:"none"},"& code":{fontSize:"inherit"}},code={lineHeight:1,margin:"0 2px",padding:"3px 5px",whiteSpace:"nowrap",borderRadius:3,fontSize:theme.typography.size.s2-1,border:theme.base==="light"?`1px solid ${theme.color.mediumlight}`:`1px solid ${theme.color.darker}`,color:theme.base==="light"?(0,import_polished2.transparentize)(.1,theme.color.defaultText):(0,import_polished2.transparentize)(.3,theme.color.defaultText),backgroundColor:theme.base==="light"?theme.color.lighter:theme.color.border};return{maxWidth:1e3,width:"100%",[toGlobalSelector("a")]:{...reset,fontSize:"inherit",lineHeight:"24px",color:theme.color.secondary,textDecoration:"none","&.absent":{color:"#cc0000"},"&.anchor":{display:"block",paddingLeft:30,marginLeft:-30,cursor:"pointer",position:"absolute",top:0,left:0,bottom:0}},[toGlobalSelector("blockquote")]:{...reset,margin:"16px 0",borderLeft:`4px solid ${theme.color.medium}`,padding:"0 15px",color:theme.color.dark,"& > :first-of-type":{marginTop:0},"& > :last-child":{marginBottom:0}},[toGlobalSelector("div")]:reset,[toGlobalSelector("dl")]:{...reset,margin:"16px 0",padding:0,"& dt":{fontSize:"14px",fontWeight:"bold",fontStyle:"italic",padding:0,margin:"16px 0 4px"},"& dt:first-of-type":{padding:0},"& dt > :first-of-type":{marginTop:0},"& dt > :last-child":{marginBottom:0},"& dd":{margin:"0 0 16px",padding:"0 15px"},"& dd > :first-of-type":{marginTop:0},"& dd > :last-child":{marginBottom:0}},[toGlobalSelector("h1")]:{...reset,...headers,fontSize:`${theme.typography.size.l1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h2")]:{...reset,...headers,fontSize:`${theme.typography.size.m2}px`,paddingBottom:4,borderBottom:`1px solid ${theme.appBorderColor}`},[toGlobalSelector("h3")]:{...reset,...headers,fontSize:`${theme.typography.size.m1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h4")]:{...reset,...headers,fontSize:`${theme.typography.size.s3}px`},[toGlobalSelector("h5")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`},[toGlobalSelector("h6")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`,color:theme.color.dark},[toGlobalSelector("hr")]:{border:"0 none",borderTop:`1px solid ${theme.appBorderColor}`,height:4,padding:0},[toGlobalSelector("img")]:{maxWidth:"100%"},[toGlobalSelector("li")]:{...reset,fontSize:theme.typography.size.s2,color:theme.color.defaultText,lineHeight:"24px","& + li":{marginTop:".25em"},"& ul, & ol":{marginTop:".25em",marginBottom:0},"& code":code},[toGlobalSelector("ol")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0}},[toGlobalSelector("p")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",color:theme.color.defaultText,"& code":code},[toGlobalSelector("pre")]:{...reset,fontFamily:theme.typography.fonts.mono,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",lineHeight:"18px",padding:"11px 1rem",whiteSpace:"pre-wrap",color:"inherit",borderRadius:3,margin:"1rem 0","&:not(.prismjs)":{background:"transparent",border:"none",borderRadius:0,padding:0,margin:0},"& pre, &.prismjs":{padding:15,margin:0,whiteSpace:"pre-wrap",color:"inherit",fontSize:"13px",lineHeight:"19px",code:{color:"inherit",fontSize:"inherit"}},"& code":{whiteSpace:"pre"},"& code, & tt":{border:"none"}},[toGlobalSelector("span")]:{...reset,"&.frame":{display:"block",overflow:"hidden","& > span":{border:`1px solid ${theme.color.medium}`,display:"block",float:"left",overflow:"hidden",margin:"13px 0 0",padding:7,width:"auto"},"& span img":{display:"block",float:"left"},"& span span":{clear:"both",color:theme.color.darkest,display:"block",padding:"5px 0 0"}},"&.align-center":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"center"},"& span img":{margin:"0 auto",textAlign:"center"}},"&.align-right":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px 0 0",textAlign:"right"},"& span img":{margin:0,textAlign:"right"}},"&.float-left":{display:"block",marginRight:13,overflow:"hidden",float:"left","& span":{margin:"13px 0 0"}},"&.float-right":{display:"block",marginLeft:13,overflow:"hidden",float:"right","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"right"}}},[toGlobalSelector("table")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",padding:0,borderCollapse:"collapse","& tr":{borderTop:`1px solid ${theme.appBorderColor}`,backgroundColor:theme.appContentBg,margin:0,padding:0},"& tr:nth-of-type(2n)":{backgroundColor:theme.base==="dark"?theme.color.darker:theme.color.lighter},"& tr th":{fontWeight:"bold",color:theme.color.defaultText,border:`1px solid ${theme.appBorderColor}`,margin:0,padding:"6px 13px"},"& tr td":{border:`1px solid ${theme.appBorderColor}`,color:theme.color.defaultText,margin:0,padding:"6px 13px"},"& tr th :first-of-type, & tr td :first-of-type":{marginTop:0},"& tr th :last-child, & tr td :last-child":{marginBottom:0}},[toGlobalSelector("ul")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0},listStyle:"disc"}}}),DocsWrapper=import_theming3.styled.div(({theme})=>({background:theme.background.content,display:"flex",justifyContent:"center",padding:"4rem 20px",minHeight:"100vh",boxSizing:"border-box",gap:"3rem",[`@media (min-width: ${breakpoint}px)`]:{}})),DocsPageWrapper=({children,toc})=>import_react3.default.createElement(DocsWrapper,{className:"sbdocs sbdocs-wrapper"},import_react3.default.createElement(DocsContent,{className:"sbdocs sbdocs-content"},children),toc);var import_react8=__toESM(require("react")),import_polished3=require("polished"),import_theming5=require("@storybook/theming"),import_global3=require("@storybook/global"),import_components6=require("@storybook/components");var getBlockBackgroundStyle=theme=>({borderRadius:theme.appBorderRadius,background:theme.background.content,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",border:`1px solid ${theme.appBorderColor}`});var import_react4=__toESM(require("react")),import_theming4=require("@storybook/theming"),import_components4=require("@storybook/components"),import_icons=require("@storybook/icons"),Bar=(0,import_theming4.styled)(import_components4.FlexBar)({position:"absolute",left:0,right:0,top:0,transition:"transform .2s linear"}),Wrapper2=import_theming4.styled.div({display:"flex",alignItems:"center",gap:4}),IconPlaceholder=import_theming4.styled.div(({theme})=>({width:14,height:14,borderRadius:2,margin:"0 7px",backgroundColor:theme.appBorderColor,animation:`${theme.animation.glow} 1.5s ease-in-out infinite`})),Toolbar=({isLoading,storyId,baseUrl,zoom,resetZoom,...rest})=>import_react4.default.createElement(Bar,{...rest},import_react4.default.createElement(Wrapper2,{key:"left"},isLoading?[1,2,3].map(key=>import_react4.default.createElement(IconPlaceholder,{key})):import_react4.default.createElement(import_react4.default.Fragment,null,import_react4.default.createElement(import_components4.IconButton,{key:"zoomin",onClick:e=>{e.preventDefault(),zoom(.8)},title:"Zoom in"},import_react4.default.createElement(import_icons.ZoomIcon,null)),import_react4.default.createElement(import_components4.IconButton,{key:"zoomout",onClick:e=>{e.preventDefault(),zoom(1.25)},title:"Zoom out"},import_react4.default.createElement(import_icons.ZoomOutIcon,null)),import_react4.default.createElement(import_components4.IconButton,{key:"zoomreset",onClick:e=>{e.preventDefault(),resetZoom()},title:"Reset zoom"},import_react4.default.createElement(import_icons.ZoomResetIcon,null)))));var import_react5=require("react"),ZoomContext=(0,import_react5.createContext)({scale:1});var import_global2=require("@storybook/global"),import_react7=__toESM(require("react")),import_components5=require("@storybook/components");var import_react6=__toESM(require("react")),import_global=require("@storybook/global"),{window:globalWindow}=import_global.global,IFrame=class extends import_react6.Component{constructor(){super(...arguments);this.iframe=null}componentDidMount(){let{id:id2}=this.props;this.iframe=globalWindow.document.getElementById(id2)}shouldComponentUpdate(nextProps){let{scale}=nextProps;return scale!==this.props.scale&&this.setIframeBodyStyle({width:`${scale*100}%`,height:`${scale*100}%`,transform:`scale(${1/scale})`,transformOrigin:"top left"}),!1}setIframeBodyStyle(style){return Object.assign(this.iframe.contentDocument.body.style,style)}render(){let{id:id2,title,src,allowFullScreen,scale,...rest}=this.props;return import_react6.default.createElement("iframe",{id:id2,title,src,...allowFullScreen?{allow:"fullscreen"}:{},loading:"lazy",...rest})}};var{PREVIEW_URL}=import_global2.global,BASE_URL=PREVIEW_URL||"iframe.html",storyBlockIdFromId=({story,primary})=>`story--${story.id}${primary?"--primary":""}`,InlineStory=props=>{let storyRef=(0,import_react7.useRef)(),[showLoader,setShowLoader]=(0,import_react7.useState)(!0),[error,setError]=(0,import_react7.useState)(),{story,height,autoplay,forceInitialArgs,renderStoryToElement}=props;return(0,import_react7.useEffect)(()=>{if(!(story&&storyRef.current))return()=>{};let element=storyRef.current,cleanup=renderStoryToElement(story,element,{showMain:()=>{},showError:({title,description})=>setError(new Error(`${title} - ${description}`)),showException:err=>setError(err)},{autoplay,forceInitialArgs});return setShowLoader(!1),()=>{Promise.resolve().then(()=>cleanup())}},[autoplay,renderStoryToElement,story]),error?import_react7.default.createElement("pre",null,import_react7.default.createElement(import_components5.ErrorFormatter,{error})):import_react7.default.createElement(import_react7.default.Fragment,null,height?import_react7.default.createElement("style",null,`#${storyBlockIdFromId(props)} { min-height: ${height}; transform: translateZ(0); overflow: auto }`):null,showLoader&&import_react7.default.createElement(StorySkeleton,null),import_react7.default.createElement("div",{ref:storyRef,id:`${storyBlockIdFromId(props)}-inner`,"data-name":story.name}))},IFrameStory=({story,height="500px"})=>import_react7.default.createElement("div",{style:{width:"100%",height}},import_react7.default.createElement(ZoomContext.Consumer,null,({scale})=>import_react7.default.createElement(IFrame,{key:"iframe",id:`iframe--${story.id}`,title:story.name,src:(0,import_components5.getStoryHref)(BASE_URL,story.id,{viewMode:"story"}),allowFullScreen:!0,scale,style:{width:"100%",height:"100%",border:"0 none"}}))),Story=props=>{let{inline}=props;return import_react7.default.createElement("div",{id:storyBlockIdFromId(props),className:"sb-story sb-unstyled","data-story-block":"true"},inline?import_react7.default.createElement(InlineStory,{...props}):import_react7.default.createElement(IFrameStory,{...props}))},StorySkeleton=()=>import_react7.default.createElement(import_components5.Loader,null);var ChildrenContainer=import_theming5.styled.div(({isColumn,columns,layout})=>({display:isColumn||!columns?"block":"flex",position:"relative",flexWrap:"wrap",overflow:"auto",flexDirection:isColumn?"column":"row","& .innerZoomElementWrapper > *":isColumn?{width:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"block"}:{maxWidth:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"inline-block"}}),({layout="padded"})=>layout==="centered"||layout==="padded"?{padding:"30px 20px","& .innerZoomElementWrapper > *":{width:"auto",border:"10px solid transparent!important"}}:{},({layout="padded"})=>layout==="centered"?{display:"flex",justifyContent:"center",justifyItems:"center",alignContent:"center",alignItems:"center"}:{},({columns})=>columns&&columns>1?{".innerZoomElementWrapper > *":{minWidth:`calc(100% / ${columns} - 20px)`}}:{}),StyledSource=(0,import_theming5.styled)(Source)(({theme})=>({margin:0,borderTopLeftRadius:0,borderTopRightRadius:0,borderBottomLeftRadius:theme.appBorderRadius,borderBottomRightRadius:theme.appBorderRadius,border:"none",background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":(0,import_polished3.darken)(.05,theme.background.content),color:theme.color.lightest,button:{background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":(0,import_polished3.darken)(.05,theme.background.content)}})),PreviewContainer=import_theming5.styled.div(({theme,withSource,isExpanded})=>({position:"relative",overflow:"hidden",margin:"25px 0 40px",...getBlockBackgroundStyle(theme),borderBottomLeftRadius:withSource&&isExpanded&&0,borderBottomRightRadius:withSource&&isExpanded&&0,borderBottomWidth:isExpanded&&0,"h3 + &":{marginTop:"16px"}}),({withToolbar})=>withToolbar&&{paddingTop:40}),getSource=(withSource,expanded,setExpanded)=>{switch(!0){case!!(withSource&&withSource.error):return{source:null,actionItem:{title:"No code available",className:"docblock-code-toggle docblock-code-toggle--disabled",disabled:!0,onClick:()=>setExpanded(!1)}};case expanded:return{source:import_react8.default.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Hide code",className:"docblock-code-toggle docblock-code-toggle--expanded",onClick:()=>setExpanded(!1)}};default:return{source:import_react8.default.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Show code",className:"docblock-code-toggle",onClick:()=>setExpanded(!0)}}}};function getStoryId(children){if(import_react8.Children.count(children)===1){let elt=children;if(elt.props)return elt.props.id}return null}var PositionedToolbar=(0,import_theming5.styled)(Toolbar)({position:"absolute",top:0,left:0,right:0,height:40}),Relative=import_theming5.styled.div({overflow:"hidden",position:"relative"}),Preview=({isLoading,isColumn,columns,children,withSource,withToolbar=!1,isExpanded=!1,additionalActions,className,layout="padded",...props})=>{let[expanded,setExpanded]=(0,import_react8.useState)(isExpanded),{source,actionItem}=getSource(withSource,expanded,setExpanded),[scale,setScale]=(0,import_react8.useState)(1),previewClasses=[className].concat(["sbdocs","sbdocs-preview","sb-unstyled"]),defaultActionItems=withSource?[actionItem]:[],[additionalActionItems,setAdditionalActionItems]=(0,import_react8.useState)(additionalActions?[...additionalActions]:[]),actionItems=[...defaultActionItems,...additionalActionItems],{window:globalWindow4}=import_global3.global,copyToClipboard=(0,import_react8.useCallback)(async text=>{let{createCopyToClipboardFunction}=await import("@storybook/components");createCopyToClipboardFunction()},[]),onCopyCapture=e=>{let selection=globalWindow4.getSelection();selection&&selection.type==="Range"||(e.preventDefault(),additionalActionItems.filter(item=>item.title==="Copied").length===0&©ToClipboard(source.props.code).then(()=>{setAdditionalActionItems([...additionalActionItems,{title:"Copied",onClick:()=>{}}]),globalWindow4.setTimeout(()=>setAdditionalActionItems(additionalActionItems.filter(item=>item.title!=="Copied")),1500)}))};return import_react8.default.createElement(PreviewContainer,{withSource,withToolbar,...props,className:previewClasses.join(" ")},withToolbar&&import_react8.default.createElement(PositionedToolbar,{isLoading,border:!0,zoom:z=>setScale(scale*z),resetZoom:()=>setScale(1),storyId:getStoryId(children),baseUrl:"./iframe.html"}),import_react8.default.createElement(ZoomContext.Provider,{value:{scale}},import_react8.default.createElement(Relative,{className:"docs-story",onCopyCapture:withSource&&onCopyCapture},import_react8.default.createElement(ChildrenContainer,{isColumn:isColumn||!Array.isArray(children),columns,layout},import_react8.default.createElement(import_components6.Zoom.Element,{scale},Array.isArray(children)?children.map((child,i)=>import_react8.default.createElement("div",{key:i},child)):import_react8.default.createElement("div",null,children))),import_react8.default.createElement(import_components6.ActionBar,{actionItems}))),withSource&&expanded&&source)},StyledPreview=(0,import_theming5.styled)(Preview)(()=>({".docs-story":{paddingTop:32,paddingBottom:40}}));var import_react31=__toESM(require("react")),import_pickBy=__toESM(require("lodash/pickBy.js")),import_theming23=require("@storybook/theming"),import_polished8=require("polished"),import_csf=require("@storybook/csf"),import_client_logger4=require("@storybook/client-logger"),import_components19=require("@storybook/components"),import_icons8=require("@storybook/icons");var import_react27=__toESM(require("react")),import_markdown_to_jsx=__toESM(require("markdown-to-jsx")),import_polished6=require("polished"),import_theming19=require("@storybook/theming"),import_components17=require("@storybook/components");var import_react9=__toESM(require("react")),import_theming6=require("@storybook/theming"),import_components7=require("@storybook/components"),Table=import_theming6.styled.table(({theme})=>({"&&":{borderCollapse:"collapse",borderSpacing:0,border:"none",tr:{border:"none !important",background:"none"},"td, th":{padding:0,border:"none",width:"auto!important"},marginTop:0,marginBottom:0,"th:first-of-type, td:first-of-type":{paddingLeft:0},"th:last-of-type, td:last-of-type":{paddingRight:0},td:{paddingTop:0,paddingBottom:4,"&:not(:first-of-type)":{paddingLeft:10,paddingRight:0}},tbody:{boxShadow:"none",border:"none"},code:(0,import_components7.codeCommon)({theme}),div:{span:{fontWeight:"bold"}},"& code":{margin:0,display:"inline-block",fontSize:theme.typography.size.s1}}})),ArgJsDoc=({tags})=>{let params=(tags.params||[]).filter(x=>x.description),hasDisplayableParams=params.length!==0,hasDisplayableDeprecated=tags.deprecated!=null,hasDisplayableReturns=tags.returns!=null&&tags.returns.description!=null;return!hasDisplayableParams&&!hasDisplayableReturns&&!hasDisplayableDeprecated?null:import_react9.default.createElement(import_react9.default.Fragment,null,import_react9.default.createElement(Table,null,import_react9.default.createElement("tbody",null,hasDisplayableDeprecated&&import_react9.default.createElement("tr",{key:"deprecated"},import_react9.default.createElement("td",{colSpan:2},import_react9.default.createElement("strong",null,"Deprecated"),": ",tags.deprecated.toString())),hasDisplayableParams&¶ms.map(x=>import_react9.default.createElement("tr",{key:x.name},import_react9.default.createElement("td",null,import_react9.default.createElement("code",null,x.name)),import_react9.default.createElement("td",null,x.description))),hasDisplayableReturns&&import_react9.default.createElement("tr",{key:"returns"},import_react9.default.createElement("td",null,import_react9.default.createElement("code",null,"Returns")),import_react9.default.createElement("td",null,tags.returns.description)))))};var import_react10=__toESM(require("react")),import_memoizerific=__toESM(require("memoizerific")),import_uniq=__toESM(require("lodash/uniq.js")),import_theming7=require("@storybook/theming"),import_components8=require("@storybook/components"),import_icons2=require("@storybook/icons"),ITEMS_BEFORE_EXPANSION=8,Summary=import_theming7.styled.div(({isExpanded})=>({display:"flex",flexDirection:isExpanded?"column":"row",flexWrap:"wrap",alignItems:"flex-start",marginBottom:"-4px",minWidth:100})),Text=import_theming7.styled.span(import_components8.codeCommon,({theme,simple=!1})=>({flex:"0 0 auto",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,wordBreak:"break-word",whiteSpace:"normal",maxWidth:"100%",margin:0,marginRight:"4px",marginBottom:"4px",paddingTop:"2px",paddingBottom:"2px",lineHeight:"13px",...simple&&{background:"transparent",border:"0 none",paddingLeft:0}})),ExpandButton=import_theming7.styled.button(({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,marginBottom:"4px",background:"none",border:"none"})),Expandable=import_theming7.styled.div(import_components8.codeCommon,({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,fontSize:theme.typography.size.s1,margin:0,whiteSpace:"nowrap",display:"flex",alignItems:"center"})),Detail=import_theming7.styled.div(({theme,width})=>({width,minWidth:200,maxWidth:800,padding:15,fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,boxSizing:"content-box","& code":{padding:"0 !important"}})),ChevronUpIcon=(0,import_theming7.styled)(import_icons2.ChevronSmallUpIcon)({marginLeft:4}),ChevronDownIcon=(0,import_theming7.styled)(import_icons2.ChevronSmallDownIcon)({marginLeft:4}),EmptyArg=()=>import_react10.default.createElement("span",null,"-"),ArgText=({text,simple})=>import_react10.default.createElement(Text,{simple},text),calculateDetailWidth=(0,import_memoizerific.default)(1e3)(detail=>{let lines=detail.split(/\r?\n/);return`${Math.max(...lines.map(x=>x.length))}ch`}),getSummaryItems=summary=>{if(!summary)return[summary];let summaryItems=summary.split("|").map(value2=>value2.trim());return(0,import_uniq.default)(summaryItems)},renderSummaryItems=(summaryItems,isExpanded=!0)=>{let items=summaryItems;return isExpanded||(items=summaryItems.slice(0,ITEMS_BEFORE_EXPANSION)),items.map(item=>import_react10.default.createElement(ArgText,{key:item,text:item===""?'""':item}))},ArgSummary=({value:value2,initialExpandedArgs})=>{let{summary,detail}=value2,[isOpen,setIsOpen]=(0,import_react10.useState)(!1),[isExpanded,setIsExpanded]=(0,import_react10.useState)(initialExpandedArgs||!1);if(summary==null)return null;let summaryAsString=typeof summary.toString=="function"?summary.toString():summary;if(detail==null){if(/[(){}[\]<>]/.test(summaryAsString))return import_react10.default.createElement(ArgText,{text:summaryAsString});let summaryItems=getSummaryItems(summaryAsString),itemsCount=summaryItems.length;return itemsCount>ITEMS_BEFORE_EXPANSION?import_react10.default.createElement(Summary,{isExpanded},renderSummaryItems(summaryItems,isExpanded),import_react10.default.createElement(ExpandButton,{onClick:()=>setIsExpanded(!isExpanded)},isExpanded?"Show less...":`Show ${itemsCount-ITEMS_BEFORE_EXPANSION} more...`)):import_react10.default.createElement(Summary,null,renderSummaryItems(summaryItems))}return import_react10.default.createElement(import_components8.WithTooltipPure,{closeOnOutsideClick:!0,placement:"bottom",visible:isOpen,onVisibleChange:isVisible=>{setIsOpen(isVisible)},tooltip:import_react10.default.createElement(Detail,{width:calculateDetailWidth(detail)},import_react10.default.createElement(import_components8.SyntaxHighlighter,{language:"jsx",format:!1},detail))},import_react10.default.createElement(Expandable,{className:"sbdocs-expandable"},import_react10.default.createElement("span",null,summaryAsString),isOpen?import_react10.default.createElement(ChevronUpIcon,null):import_react10.default.createElement(ChevronDownIcon,null)))},ArgValue=({value:value2,initialExpandedArgs})=>value2==null?import_react10.default.createElement(EmptyArg,null):import_react10.default.createElement(ArgSummary,{value:value2,initialExpandedArgs});var import_react26=__toESM(require("react")),import_components16=require("@storybook/components");var import_react25=__toESM(require("react"));var import_react11=__toESM(require("react")),import_polished4=require("polished"),import_theming8=require("@storybook/theming"),import_components9=require("@storybook/components");init_helpers();var Label=import_theming8.styled.label(({theme})=>({lineHeight:"18px",alignItems:"center",marginBottom:8,display:"inline-block",position:"relative",whiteSpace:"nowrap",background:theme.boolean.background,borderRadius:"3em",padding:1,'&[aria-disabled="true"]':{opacity:.5,input:{cursor:"not-allowed"}},input:{appearance:"none",width:"100%",height:"100%",position:"absolute",left:0,top:0,margin:0,padding:0,border:"none",background:"transparent",cursor:"pointer",borderRadius:"3em","&:focus":{outline:"none",boxShadow:`${theme.color.secondary} 0 0 0 1px inset !important`}},span:{textAlign:"center",fontSize:theme.typography.size.s1,fontWeight:theme.typography.weight.bold,lineHeight:"1",cursor:"pointer",display:"inline-block",padding:"7px 15px",transition:"all 100ms ease-out",userSelect:"none",borderRadius:"3em",color:(0,import_polished4.transparentize)(.5,theme.color.defaultText),background:"transparent","&:hover":{boxShadow:`${(0,import_polished4.opacify)(.3,theme.appBorderColor)} 0 0 0 1px inset`},"&:active":{boxShadow:`${(0,import_polished4.opacify)(.05,theme.appBorderColor)} 0 0 0 2px inset`,color:(0,import_polished4.opacify)(1,theme.appBorderColor)},"&:first-of-type":{paddingRight:8},"&:last-of-type":{paddingLeft:8}},"input:checked ~ span:last-of-type, input:not(:checked) ~ span:first-of-type":{background:theme.boolean.selectedBackground,boxShadow:theme.base==="light"?`${(0,import_polished4.opacify)(.1,theme.appBorderColor)} 0 0 2px`:`${theme.appBorderColor} 0 0 0 1px`,color:theme.color.defaultText,padding:"7px 15px"}})),parse=value2=>value2==="true",BooleanControl=({name,value:value2,onChange,onBlur,onFocus,argType})=>{let onSetFalse=(0,import_react11.useCallback)(()=>onChange(!1),[onChange]),readonly=!!argType?.table?.readonly;if(value2===void 0)return import_react11.default.createElement(import_components9.Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onSetFalse,disabled:readonly},"Set boolean");let controlId=getControlId(name),parsedValue=typeof value2=="string"?parse(value2):value2;return import_react11.default.createElement(Label,{"aria-disabled":readonly,htmlFor:controlId,"aria-label":name},import_react11.default.createElement("input",{id:controlId,type:"checkbox",onChange:e=>onChange(e.target.checked),checked:parsedValue,role:"switch",disabled:readonly,name,onBlur,onFocus}),import_react11.default.createElement("span",{"aria-hidden":"true"},"False"),import_react11.default.createElement("span",{"aria-hidden":"true"},"True"))};var import_react12=__toESM(require("react")),import_theming9=require("@storybook/theming"),import_components10=require("@storybook/components");init_helpers();var parseDate=value2=>{let[year,month,day]=value2.split("-"),result=new Date;return result.setFullYear(parseInt(year,10),parseInt(month,10)-1,parseInt(day,10)),result},parseTime=value2=>{let[hours,minutes]=value2.split(":"),result=new Date;return result.setHours(parseInt(hours,10)),result.setMinutes(parseInt(minutes,10)),result},formatDate=value2=>{let date=new Date(value2),year=`000${date.getFullYear()}`.slice(-4),month=`0${date.getMonth()+1}`.slice(-2),day=`0${date.getDate()}`.slice(-2);return`${year}-${month}-${day}`},formatTime=value2=>{let date=new Date(value2),hours=`0${date.getHours()}`.slice(-2),minutes=`0${date.getMinutes()}`.slice(-2);return`${hours}:${minutes}`},FormInput=(0,import_theming9.styled)(import_components10.Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),FlexSpaced=import_theming9.styled.div(({theme})=>({flex:1,display:"flex",input:{marginLeft:10,flex:1,height:32,"&::-webkit-calendar-picker-indicator":{opacity:.5,height:12,filter:theme.base==="light"?void 0:"invert(1)"}},"input:first-of-type":{marginLeft:0,flexGrow:4},"input:last-of-type":{flexGrow:3}})),DateControl=({name,value:value2,onChange,onFocus,onBlur,argType})=>{let[valid,setValid]=(0,import_react12.useState)(!0),dateRef=(0,import_react12.useRef)(),timeRef=(0,import_react12.useRef)(),readonly=!!argType?.table?.readonly;(0,import_react12.useEffect)(()=>{valid!==!1&&(dateRef&&dateRef.current&&(dateRef.current.value=formatDate(value2)),timeRef&&timeRef.current&&(timeRef.current.value=formatTime(value2)))},[value2]);let onDateChange=e=>{let parsed=parseDate(e.target.value),result=new Date(value2);result.setFullYear(parsed.getFullYear(),parsed.getMonth(),parsed.getDate());let time=result.getTime();time&&onChange(time),setValid(!!time)},onTimeChange=e=>{let parsed=parseTime(e.target.value),result=new Date(value2);result.setHours(parsed.getHours()),result.setMinutes(parsed.getMinutes());let time=result.getTime();time&&onChange(time),setValid(!!time)},controlId=getControlId(name);return import_react12.default.createElement(FlexSpaced,null,import_react12.default.createElement(FormInput,{type:"date",max:"9999-12-31",ref:dateRef,id:`${controlId}-date`,name:`${controlId}-date`,readOnly:readonly,onChange:onDateChange,onFocus,onBlur}),import_react12.default.createElement(FormInput,{type:"time",id:`${controlId}-time`,name:`${controlId}-time`,ref:timeRef,onChange:onTimeChange,readOnly:readonly,onFocus,onBlur}),valid?null:import_react12.default.createElement("div",null,"invalid"))};var import_react13=__toESM(require("react")),import_theming10=require("@storybook/theming"),import_components11=require("@storybook/components");init_helpers();var Wrapper3=import_theming10.styled.label({display:"flex"}),parse2=value2=>{let result=parseFloat(value2);return Number.isNaN(result)?void 0:result},format=value2=>value2!=null?String(value2):"",FormInput2=(0,import_theming10.styled)(import_components11.Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),NumberControl=({name,value:value2,onChange,min,max,step,onBlur,onFocus,argType})=>{let[inputValue,setInputValue]=(0,import_react13.useState)(typeof value2=="number"?value2:""),[forceVisible,setForceVisible]=(0,import_react13.useState)(!1),[parseError,setParseError]=(0,import_react13.useState)(null),readonly=!!argType?.table?.readonly,handleChange=(0,import_react13.useCallback)(event=>{setInputValue(event.target.value);let result=parseFloat(event.target.value);Number.isNaN(result)?setParseError(new Error(`'${event.target.value}' is not a number`)):(onChange(result),setParseError(null))},[onChange,setParseError]),onForceVisible=(0,import_react13.useCallback)(()=>{setInputValue("0"),onChange(0),setForceVisible(!0)},[setForceVisible]),htmlElRef=(0,import_react13.useRef)(null);return(0,import_react13.useEffect)(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select()},[forceVisible]),(0,import_react13.useEffect)(()=>{inputValue!==(typeof value2=="number"?value2:"")&&setInputValue(value2)},[value2]),value2===void 0?import_react13.default.createElement(import_components11.Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onForceVisible,disabled:readonly},"Set number"):import_react13.default.createElement(Wrapper3,null,import_react13.default.createElement(FormInput2,{ref:htmlElRef,id:getControlId(name),type:"number",onChange:handleChange,size:"flex",placeholder:"Edit number...",value:inputValue,valid:parseError?"error":null,autoFocus:forceVisible,readOnly:readonly,name,min,max,step,onFocus,onBlur}))};var import_react17=__toESM(require("react"));var import_react14=__toESM(require("react")),import_theming11=require("@storybook/theming"),import_client_logger=require("@storybook/client-logger");var selectedKey=(value2,options)=>{let entry=options&&Object.entries(options).find(([_key,val])=>val===value2);return entry?entry[0]:void 0},selectedKeys=(value2,options)=>value2&&options?Object.entries(options).filter(entry=>value2.includes(entry[1])).map(entry=>entry[0]):[],selectedValues=(keys,options)=>keys&&options&&keys.map(key=>options[key]);init_helpers();var Wrapper4=import_theming11.styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return{input:{cursor:"not-allowed"}}}),Text2=import_theming11.styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label2=import_theming11.styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),CheckboxControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return import_client_logger.logger.warn(`Checkbox with no options: ${name}`),import_react14.default.createElement(import_react14.default.Fragment,null,"-");let initial=selectedKeys(value2,options),[selected,setSelected]=(0,import_react14.useState)(initial),readonly=!!argType?.table?.readonly,handleChange=e=>{let option=e.target.value,updated=[...selected];updated.includes(option)?updated.splice(updated.indexOf(option),1):updated.push(option),onChange(selectedValues(updated,options)),setSelected(updated)};(0,import_react14.useEffect)(()=>{setSelected(selectedKeys(value2,options))},[value2]);let controlId=getControlId(name);return import_react14.default.createElement(Wrapper4,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id2=`${controlId}-${index}`;return import_react14.default.createElement(Label2,{key:id2,htmlFor:id2},import_react14.default.createElement("input",{type:"checkbox",disabled:readonly,id:id2,name:id2,value:key,onChange:handleChange,checked:selected?.includes(key)}),import_react14.default.createElement(Text2,null,key))}))};var import_react15=__toESM(require("react")),import_theming12=require("@storybook/theming"),import_client_logger2=require("@storybook/client-logger");init_helpers();var Wrapper5=import_theming12.styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return{input:{cursor:"not-allowed"}}}),Text3=import_theming12.styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label3=import_theming12.styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),RadioControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return import_client_logger2.logger.warn(`Radio with no options: ${name}`),import_react15.default.createElement(import_react15.default.Fragment,null,"-");let selection=selectedKey(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react15.default.createElement(Wrapper5,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id2=`${controlId}-${index}`;return import_react15.default.createElement(Label3,{key:id2,htmlFor:id2},import_react15.default.createElement("input",{type:"radio",id:id2,name:id2,disabled:readonly,value:key,onChange:e=>onChange(options[e.currentTarget.value]),checked:key===selection}),import_react15.default.createElement(Text3,null,key))}))};var import_react16=__toESM(require("react")),import_theming13=require("@storybook/theming"),import_client_logger3=require("@storybook/client-logger");init_helpers();var import_icons3=require("@storybook/icons"),styleResets={appearance:"none",border:"0 none",boxSizing:"inherit",display:" block",margin:" 0",background:"transparent",padding:0,fontSize:"inherit",position:"relative"},OptionsSelect=import_theming13.styled.select(styleResets,({theme})=>({boxSizing:"border-box",position:"relative",padding:"6px 10px",width:"100%",color:theme.input.color||"inherit",background:theme.input.background,borderRadius:theme.input.borderRadius,boxShadow:`${theme.input.border} 0 0 0 1px inset`,fontSize:theme.typography.size.s2-1,lineHeight:"20px","&:focus":{boxShadow:`${theme.color.secondary} 0 0 0 1px inset`,outline:"none"},"&[disabled]":{cursor:"not-allowed",opacity:.5},"::placeholder":{color:theme.textMutedColor},"&[multiple]":{overflow:"auto",padding:0,option:{display:"block",padding:"6px 10px",marginLeft:1,marginRight:1}}})),SelectWrapper=import_theming13.styled.span(({theme})=>({display:"inline-block",lineHeight:"normal",overflow:"hidden",position:"relative",verticalAlign:"top",width:"100%",svg:{position:"absolute",zIndex:1,pointerEvents:"none",height:"12px",marginTop:"-6px",right:"12px",top:"50%",fill:theme.textMutedColor,path:{fill:theme.textMutedColor}}})),NO_SELECTION="Choose option...",SingleSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{onChange(options[e.currentTarget.value])},selection=selectedKey(value2,options)||NO_SELECTION,controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react16.default.createElement(SelectWrapper,null,import_react16.default.createElement(import_icons3.ChevronSmallDownIcon,null),import_react16.default.createElement(OptionsSelect,{disabled:readonly,id:controlId,value:selection,onChange:handleChange},import_react16.default.createElement("option",{key:"no-selection",disabled:!0},NO_SELECTION),Object.keys(options).map(key=>import_react16.default.createElement("option",{key,value:key},key))))},MultiSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{let selection2=Array.from(e.currentTarget.options).filter(option=>option.selected).map(option=>option.value);onChange(selectedValues(selection2,options))},selection=selectedKeys(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return import_react16.default.createElement(SelectWrapper,null,import_react16.default.createElement(OptionsSelect,{disabled:readonly,id:controlId,multiple:!0,value:selection,onChange:handleChange},Object.keys(options).map(key=>import_react16.default.createElement("option",{key,value:key},key))))},SelectControl=props=>{let{name,options}=props;return options?props.isMulti?import_react16.default.createElement(MultiSelect,{...props}):import_react16.default.createElement(SingleSelect,{...props}):(import_client_logger3.logger.warn(`Select with no options: ${name}`),import_react16.default.createElement(import_react16.default.Fragment,null,"-"))};var normalizeOptions=(options,labels)=>Array.isArray(options)?options.reduce((acc,item)=>(acc[labels?.[item]||String(item)]=item,acc),{}):options,Controls={check:CheckboxControl,"inline-check":CheckboxControl,radio:RadioControl,"inline-radio":RadioControl,select:SelectControl,"multi-select":SelectControl},OptionsControl=props=>{let{type="select",labels,argType}=props,normalized={...props,argType,options:argType?normalizeOptions(argType.options,labels):{},isInline:type.includes("inline"),isMulti:type.includes("multi")},Control=Controls[type];if(Control)return import_react17.default.createElement(Control,{...normalized});throw new Error(`Unknown options type: ${type}`)};var import_global4=require("@storybook/global"),import_cloneDeep=__toESM(require("lodash/cloneDeep.js")),import_react20=__toESM(require("react")),import_theming14=require("@storybook/theming"),import_components12=require("@storybook/components"),import_icons4=require("@storybook/icons");var import_react19=__toESM(require("react"));var import_react18=__toESM(require("react"));var VALUE="value",KEY="key";var ERROR="Error",OBJECT="Object",ARRAY="Array",STRING="String",NUMBER="Number",BOOLEAN="Boolean",DATE="Date",NULL="Null",UNDEFINED="Undefined",FUNCTION="Function",SYMBOL="Symbol";var ADD_DELTA_TYPE="ADD_DELTA_TYPE",REMOVE_DELTA_TYPE="REMOVE_DELTA_TYPE",UPDATE_DELTA_TYPE="UPDATE_DELTA_TYPE";function getObjectType(obj){return obj!==null&&typeof obj=="object"&&!Array.isArray(obj)&&typeof obj[Symbol.iterator]=="function"?"Iterable":Object.prototype.toString.call(obj).slice(8,-1)}function isComponentWillChange(oldValue,newValue){let oldType=getObjectType(oldValue),newType=getObjectType(newValue);return(oldType==="Function"||newType==="Function")&&newType!==oldType}var JsonAddValue=class extends import_react18.Component{constructor(props){super(props),this.state={inputRefKey:null,inputRefValue:null},this.refInputValue=this.refInputValue.bind(this),this.refInputKey=this.refInputKey.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onSubmit=this.onSubmit.bind(this)}componentDidMount(){let{inputRefKey,inputRefValue}=this.state,{onlyValue}=this.props;inputRefKey&&typeof inputRefKey.focus=="function"&&inputRefKey.focus(),onlyValue&&inputRefValue&&typeof inputRefValue.focus=="function"&&inputRefValue.focus(),document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.onSubmit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.props.handleCancel()))}onSubmit(){let{handleAdd,onlyValue,onSubmitValueParser,keyPath,deep}=this.props,{inputRefKey,inputRefValue}=this.state,result={};if(!onlyValue){if(!inputRefKey.value)return;result.key=inputRefKey.value}result.newValue=onSubmitValueParser(!1,keyPath,deep,result.key,inputRefValue.value),handleAdd(result)}refInputKey(node){this.state.inputRefKey=node}refInputValue(node){this.state.inputRefValue=node}render(){let{handleCancel,onlyValue,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep}=this.props,addButtonElementLayout=(0,import_react18.cloneElement)(addButtonElement,{onClick:this.onSubmit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:handleCancel}),inputElementValue=inputElementGenerator(VALUE,keyPath,deep),inputElementValueLayout=(0,import_react18.cloneElement)(inputElementValue,{placeholder:"Value",ref:this.refInputValue}),inputElementKeyLayout=null;if(!onlyValue){let inputElementKey=inputElementGenerator(KEY,keyPath,deep);inputElementKeyLayout=(0,import_react18.cloneElement)(inputElementKey,{placeholder:"Key",ref:this.refInputKey})}return import_react18.default.createElement("span",{className:"rejt-add-value-node"},inputElementKeyLayout,inputElementValueLayout,cancelButtonElementLayout,addButtonElementLayout)}};JsonAddValue.defaultProps={onlyValue:!1,addButtonElement:import_react18.default.createElement("button",null,"+"),cancelButtonElement:import_react18.default.createElement("button",null,"c")};var JsonArray=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={data:props.data,name:props.name,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveItem=this.handleRemoveItem.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data)}handleAddMode(){this.setState({addFormVisible:!0})}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}))}handleRemoveItem(index){return()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[index];beforeRemoveAction(index,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key:index,oldValue,type:REMOVE_DELTA_TYPE};data.splice(index,1),this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult)}).catch(logger4.error)}}handleAddValueAdd({newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(data.length,keyPath,deep,newValue).then(()=>{let newData=[...data,newValue];this.setState({data:newData}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],newData),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key:newData.length-1,newValue})}).catch(logger4.error)}handleAddValueCancel(){this.setState({addFormVisible:!1})}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve(void 0)}).catch(reject)})}renderCollapsed(){let{name,data,keyPath,deep}=this.state,{handleRemove,readOnly,getStyle,dataType,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-collapsed"},import_react18.default.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"[...] ",data.length," ",data.length===1?"item":"items"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,addFormVisible,nextDeep}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,delimiter,ul,addForm}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=(0,import_react18.cloneElement)(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-not-collapsed"},import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"["),!addFormVisible&&addItemButton,import_react18.default.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},data.map((item,index)=>import_react18.default.createElement(JsonNode,{key:index,name:index.toString(),data:item,keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveItem(index),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}))),!isReadOnly&&addFormVisible&&import_react18.default.createElement("div",{className:"rejt-add-form",style:addForm},import_react18.default.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,onlyValue:!0,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"]"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{dataType,getStyle}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return import_react18.default.createElement("div",{className:"rejt-array-node"},import_react18.default.createElement("span",{onClick:this.handleCollapseMode},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonArray.defaultProps={keyPath:[],deep:0,minusMenuElement:import_react18.default.createElement("span",null," - "),plusMenuElement:import_react18.default.createElement("span",null," + ")};var JsonFunctionValue=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this)}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,readOnlyResult=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!readOnlyResult&&typeof inputRef.focus=="function"&&inputRef.focus()}componentDidMount(){document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()))}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit()}).catch(logger4.error)}handleEditMode(){this.setState({editEnabled:!0})}refInput(node){this.state.inputRef=node}handleCancelEdit(){this.setState({editEnabled:!1})}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,textareaElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),result=null,minusElement=null,resultOnlyResult=readOnly(name,originalValue,keyPath,deep,dataType);if(editEnabled&&!resultOnlyResult){let textareaElement=textareaElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=(0,import_react18.cloneElement)(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:this.handleCancelEdit}),textareaElementLayout=(0,import_react18.cloneElement)(textareaElement,{ref:this.refInput,defaultValue:originalValue});result=import_react18.default.createElement("span",{className:"rejt-edit-form",style:style.editForm},textareaElementLayout," ",cancelButtonElementLayout,editButtonElementLayout),minusElement=null}else{result=import_react18.default.createElement("span",{className:"rejt-value",style:style.value,onClick:resultOnlyResult?null:this.handleEditMode},value2);let minusMenuLayout=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});minusElement=resultOnlyResult?null:minusMenuLayout}return import_react18.default.createElement("li",{className:"rejt-function-value-node",style:style.li},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," "),result,minusElement)}};JsonFunctionValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>{},editButtonElement:import_react18.default.createElement("button",null,"e"),cancelButtonElement:import_react18.default.createElement("button",null,"c"),minusMenuElement:import_react18.default.createElement("span",null," - ")};var JsonNode=class extends import_react18.Component{constructor(props){super(props),this.state={data:props.data,name:props.name,keyPath:props.keyPath,deep:props.deep}}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}render(){let{data,name,keyPath,deep}=this.state,{isCollapsed,handleRemove,handleUpdateValue,onUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,readOnlyTrue=()=>!0,dataType=getObjectType(data);switch(dataType){case ERROR:return import_react18.default.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly:readOnlyTrue,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case OBJECT:return import_react18.default.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case ARRAY:return import_react18.default.createElement(JsonArray,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case STRING:return import_react18.default.createElement(JsonValue,{name,value:`"${data}"`,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NUMBER:return import_react18.default.createElement(JsonValue,{name,value:data,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case BOOLEAN:return import_react18.default.createElement(JsonValue,{name,value:data?"true":"false",originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case DATE:return import_react18.default.createElement(JsonValue,{name,value:data.toISOString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NULL:return import_react18.default.createElement(JsonValue,{name,value:"null",originalValue:"null",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case UNDEFINED:return import_react18.default.createElement(JsonValue,{name,value:"undefined",originalValue:"undefined",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case FUNCTION:return import_react18.default.createElement(JsonFunctionValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,textareaElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case SYMBOL:return import_react18.default.createElement(JsonValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});default:return null}}};JsonNode.defaultProps={keyPath:[],deep:0};var JsonObject=class extends import_react18.Component{constructor(props){super(props);let keyPath=props.deep===-1?[]:[...props.keyPath,props.name];this.state={name:props.name,data:props.data,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveValue=this.handleRemoveValue.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data)}handleAddMode(){this.setState({addFormVisible:!0})}handleAddValueCancel(){this.setState({addFormVisible:!1})}handleAddValueAdd({key,newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(key,keyPath,deep,newValue).then(()=>{data[key]=newValue,this.setState({data}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key,newValue})}).catch(logger4.error)}handleRemoveValue(key){return()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeRemoveAction(key,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key,oldValue,type:REMOVE_DELTA_TYPE};delete data[key],this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult)}).catch(logger4.error)}}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}))}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve()}).catch(reject)})}renderCollapsed(){let{name,keyPath,deep,data}=this.state,{handleRemove,readOnly,dataType,getStyle,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return import_react18.default.createElement("span",{className:"rejt-collapsed"},import_react18.default.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"{...}"," ",keyList.length," ",keyList.length===1?"key":"keys"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,nextDeep,addFormVisible}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,addForm,ul,delimiter}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=(0,import_react18.cloneElement)(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus}),list=keyList.map(key=>import_react18.default.createElement(JsonNode,{key,name:key,data:data[key],keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveValue(key),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}));return import_react18.default.createElement("span",{className:"rejt-not-collapsed"},import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"{"),!isReadOnly&&addItemButton,import_react18.default.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},list),!isReadOnly&&addFormVisible&&import_react18.default.createElement("div",{className:"rejt-add-form",style:addForm},import_react18.default.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),import_react18.default.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"}"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{getStyle,dataType}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return import_react18.default.createElement("div",{className:"rejt-object-node"},import_react18.default.createElement("span",{onClick:this.handleCollapseMode},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonObject.defaultProps={keyPath:[],deep:0,minusMenuElement:import_react18.default.createElement("span",null," - "),plusMenuElement:import_react18.default.createElement("span",null," + ")};var JsonValue=class extends import_react18.Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this)}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,isReadOnly=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!isReadOnly&&typeof inputRef.focus=="function"&&inputRef.focus()}componentDidMount(){document.addEventListener("keydown",this.onKeydown)}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown)}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()))}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit()}).catch(logger4.error)}handleEditMode(){this.setState({editEnabled:!0})}refInput(node){this.state.inputRef=node}handleCancelEdit(){this.setState({editEnabled:!1})}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,inputElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),isReadOnly=readOnly(name,originalValue,keyPath,deep,dataType),isEditing=editEnabled&&!isReadOnly,inputElement=inputElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=(0,import_react18.cloneElement)(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=(0,import_react18.cloneElement)(cancelButtonElement,{onClick:this.handleCancelEdit}),inputElementLayout=(0,import_react18.cloneElement)(inputElement,{ref:this.refInput,defaultValue:JSON.stringify(originalValue)}),minusMenuLayout=(0,import_react18.cloneElement)(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});return import_react18.default.createElement("li",{className:"rejt-value-node",style:style.li},import_react18.default.createElement("span",{className:"rejt-name",style:style.name},name," : "),isEditing?import_react18.default.createElement("span",{className:"rejt-edit-form",style:style.editForm},inputElementLayout," ",cancelButtonElementLayout,editButtonElementLayout):import_react18.default.createElement("span",{className:"rejt-value",style:style.value,onClick:isReadOnly?null:this.handleEditMode},String(value2)),!isReadOnly&&!isEditing&&minusMenuLayout)}};JsonValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>Promise.resolve(),editButtonElement:import_react18.default.createElement("button",null,"e"),cancelButtonElement:import_react18.default.createElement("button",null,"c"),minusMenuElement:import_react18.default.createElement("span",null," - ")};var object={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},array={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},value={minus:{color:"red"},editForm:{},value:{color:"#7bba3d"},li:{minHeight:"22px",lineHeight:"22px",outline:"0px"},name:{color:"#2287CD"}};function parse3(string){let result=string;if(result.indexOf("function")===0)return(0,eval)(`(${result})`);try{result=JSON.parse(string)}catch{}return result}var JsonTree=class extends import_react19.Component{constructor(props){super(props),this.state={data:props.data,rootName:props.rootName},this.onUpdate=this.onUpdate.bind(this),this.removeRoot=this.removeRoot.bind(this)}static getDerivedStateFromProps(props,state){return props.data!==state.data||props.rootName!==state.rootName?{data:props.data,rootName:props.rootName}:null}onUpdate(key,data){this.setState({data}),this.props.onFullyUpdate(data)}removeRoot(){this.onUpdate(null,null)}render(){let{data,rootName}=this.state,{isCollapsed,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElement,textareaElement,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser,fallback=null}=this.props,dataType=getObjectType(data),readOnlyFunction=readOnly;getObjectType(readOnly)==="Boolean"&&(readOnlyFunction=()=>readOnly);let inputElementFunction=inputElement;inputElement&&getObjectType(inputElement)!=="Function"&&(inputElementFunction=()=>inputElement);let textareaElementFunction=textareaElement;return textareaElement&&getObjectType(textareaElement)!=="Function"&&(textareaElementFunction=()=>textareaElement),dataType==="Object"||dataType==="Array"?import_react19.default.createElement("div",{className:"rejt-tree"},import_react19.default.createElement(JsonNode,{data,name:rootName,deep:-1,isCollapsed,onUpdate:this.onUpdate,onDeltaUpdate,readOnly:readOnlyFunction,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator:inputElementFunction,textareaElementGenerator:textareaElementFunction,minusMenuElement,plusMenuElement,handleRemove:this.removeRoot,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser})):fallback}};JsonTree.defaultProps={rootName:"root",isCollapsed:(keyPath,deep)=>deep!==-1,getStyle:(keyName,data,keyPath,deep,dataType)=>{switch(dataType){case"Object":case"Error":return object;case"Array":return array;default:return value}},readOnly:()=>!1,onFullyUpdate:()=>{},onDeltaUpdate:()=>{},beforeRemoveAction:()=>Promise.resolve(),beforeAddAction:()=>Promise.resolve(),beforeUpdateAction:()=>Promise.resolve(),logger:{error:()=>{}},onSubmitValueParser:(isEditMode,keyPath,deep,name,rawValue)=>parse3(rawValue),inputElement:()=>import_react19.default.createElement("input",null),textareaElement:()=>import_react19.default.createElement("textarea",null),fallback:null};init_helpers();var{window:globalWindow2}=import_global4.global,Wrapper6=import_theming14.styled.div(({theme})=>({position:"relative",display:"flex",'&[aria-readonly="true"]':{opacity:.5},".rejt-tree":{marginLeft:"1rem",fontSize:"13px"},".rejt-value-node, .rejt-object-node > .rejt-collapsed, .rejt-array-node > .rejt-collapsed, .rejt-object-node > .rejt-not-collapsed, .rejt-array-node > .rejt-not-collapsed":{"& > svg":{opacity:0,transition:"opacity 0.2s"}},".rejt-value-node:hover, .rejt-object-node:hover > .rejt-collapsed, .rejt-array-node:hover > .rejt-collapsed, .rejt-object-node:hover > .rejt-not-collapsed, .rejt-array-node:hover > .rejt-not-collapsed":{"& > svg":{opacity:1}},".rejt-edit-form button":{display:"none"},".rejt-add-form":{marginLeft:10},".rejt-add-value-node":{display:"inline-flex",alignItems:"center"},".rejt-name":{lineHeight:"22px"},".rejt-not-collapsed-delimiter":{lineHeight:"22px"},".rejt-plus-menu":{marginLeft:5},".rejt-object-node > span > *, .rejt-array-node > span > *":{position:"relative",zIndex:2},".rejt-object-node, .rejt-array-node":{position:"relative"},".rejt-object-node > span:first-of-type::after, .rejt-array-node > span:first-of-type::after, .rejt-collapsed::before, .rejt-not-collapsed::before":{content:'""',position:"absolute",top:0,display:"block",width:"100%",marginLeft:"-1rem",padding:"0 4px 0 1rem",height:22},".rejt-collapsed::before, .rejt-not-collapsed::before":{zIndex:1,background:"transparent",borderRadius:4,transition:"background 0.2s",pointerEvents:"none",opacity:.1},".rejt-object-node:hover, .rejt-array-node:hover":{"& > .rejt-collapsed::before, & > .rejt-not-collapsed::before":{background:theme.color.secondary}},".rejt-collapsed::after, .rejt-not-collapsed::after":{content:'""',position:"absolute",display:"inline-block",pointerEvents:"none",width:0,height:0},".rejt-collapsed::after":{left:-8,top:8,borderTop:"3px solid transparent",borderBottom:"3px solid transparent",borderLeft:"3px solid rgba(153,153,153,0.6)"},".rejt-not-collapsed::after":{left:-10,top:10,borderTop:"3px solid rgba(153,153,153,0.6)",borderLeft:"3px solid transparent",borderRight:"3px solid transparent"},".rejt-value":{display:"inline-block",border:"1px solid transparent",borderRadius:4,margin:"1px 0",padding:"0 4px",cursor:"text",color:theme.color.defaultText},".rejt-value-node:hover > .rejt-value":{background:theme.color.lighter,borderColor:theme.appBorderColor}})),ButtonInline=import_theming14.styled.button(({theme,primary})=>({border:0,height:20,margin:1,borderRadius:4,background:primary?theme.color.secondary:"transparent",color:primary?theme.color.lightest:theme.color.dark,fontWeight:primary?"bold":"normal",cursor:"pointer",order:primary?"initial":9})),ActionAddIcon=(0,import_theming14.styled)(import_icons4.AddIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.ancillary},"svg + &":{marginLeft:0}})),ActionSubstractIcon=(0,import_theming14.styled)(import_icons4.SubtractIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.negative},"svg + &":{marginLeft:0}})),Input=import_theming14.styled.input(({theme,placeholder})=>({outline:0,margin:placeholder?1:"1px 0",padding:"3px 4px",color:theme.color.defaultText,background:theme.background.app,border:`1px solid ${theme.appBorderColor}`,borderRadius:4,lineHeight:"14px",width:placeholder==="Key"?80:120,"&:focus":{border:`1px solid ${theme.color.secondary}`}})),RawButton=(0,import_theming14.styled)(import_components12.IconButton)(({theme})=>({position:"absolute",zIndex:2,top:2,right:2,height:21,padding:"0 3px",background:theme.background.bar,border:`1px solid ${theme.appBorderColor}`,borderRadius:3,color:theme.textMutedColor,fontSize:"9px",fontWeight:"bold",textDecoration:"none",span:{marginLeft:3,marginTop:1}})),RawInput=(0,import_theming14.styled)(import_components12.Form.Textarea)(({theme})=>({flex:1,padding:"7px 6px",fontFamily:theme.typography.fonts.mono,fontSize:"12px",lineHeight:"18px","&::placeholder":{fontFamily:theme.typography.fonts.base,fontSize:"13px"},"&:placeholder-shown":{padding:"7px 10px"}})),ENTER_EVENT={bubbles:!0,cancelable:!0,key:"Enter",code:"Enter",keyCode:13},dispatchEnterKey=event=>{event.currentTarget.dispatchEvent(new globalWindow2.KeyboardEvent("keydown",ENTER_EVENT))},selectValue=event=>{event.currentTarget.select()},getCustomStyleFunction=theme=>()=>({name:{color:theme.color.secondary},collapsed:{color:theme.color.dark},ul:{listStyle:"none",margin:"0 0 0 1rem",padding:0},li:{outline:0}}),ObjectControl=({name,value:value2,onChange,argType})=>{let theme=(0,import_theming14.useTheme)(),data=(0,import_react20.useMemo)(()=>value2&&(0,import_cloneDeep.default)(value2),[value2]),hasData=data!=null,[showRaw,setShowRaw]=(0,import_react20.useState)(!hasData),[parseError,setParseError]=(0,import_react20.useState)(null),readonly=!!argType?.table?.readonly,updateRaw=(0,import_react20.useCallback)(raw=>{try{raw&&onChange(JSON.parse(raw)),setParseError(void 0)}catch(e){setParseError(e)}},[onChange]),[forceVisible,setForceVisible]=(0,import_react20.useState)(!1),onForceVisible=(0,import_react20.useCallback)(()=>{onChange({}),setForceVisible(!0)},[setForceVisible]),htmlElRef=(0,import_react20.useRef)(null);if((0,import_react20.useEffect)(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select()},[forceVisible]),!hasData)return import_react20.default.createElement(import_components12.Button,{disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set object");let rawJSONForm=import_react20.default.createElement(RawInput,{ref:htmlElRef,id:getControlId(name),name,defaultValue:value2===null?"":JSON.stringify(value2,null,2),onBlur:event=>updateRaw(event.target.value),placeholder:"Edit JSON string...",autoFocus:forceVisible,valid:parseError?"error":null,readOnly:readonly}),isObjectOrArray=Array.isArray(value2)||typeof value2=="object"&&value2?.constructor===Object;return import_react20.default.createElement(Wrapper6,{"aria-readonly":readonly},isObjectOrArray&&import_react20.default.createElement(RawButton,{onClick:e=>{e.preventDefault(),setShowRaw(v=>!v)}},showRaw?import_react20.default.createElement(import_icons4.EyeCloseIcon,null):import_react20.default.createElement(import_icons4.EyeIcon,null),import_react20.default.createElement("span",null,"RAW")),showRaw?rawJSONForm:import_react20.default.createElement(JsonTree,{readOnly:readonly||!isObjectOrArray,isCollapsed:isObjectOrArray?void 0:()=>!0,data,rootName:name,onFullyUpdate:onChange,getStyle:getCustomStyleFunction(theme),cancelButtonElement:import_react20.default.createElement(ButtonInline,{type:"button"},"Cancel"),editButtonElement:import_react20.default.createElement(ButtonInline,{type:"submit"},"Save"),addButtonElement:import_react20.default.createElement(ButtonInline,{type:"submit",primary:!0},"Save"),plusMenuElement:import_react20.default.createElement(ActionAddIcon,null),minusMenuElement:import_react20.default.createElement(ActionSubstractIcon,null),inputElement:(_,__,___,key)=>key?import_react20.default.createElement(Input,{onFocus:selectValue,onBlur:dispatchEnterKey}):import_react20.default.createElement(Input,null),fallback:rawJSONForm}))};var import_react21=__toESM(require("react")),import_theming15=require("@storybook/theming"),import_polished5=require("polished");init_helpers();var RangeInput=import_theming15.styled.input(({theme,min,max,value:value2,disabled})=>({"&":{width:"100%",backgroundColor:"transparent",appearance:"none"},"&::-webkit-slider-runnable-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.lighten)(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer"},"&::-webkit-slider-thumb":{marginTop:"-6px",width:16,height:16,border:`1px solid ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grab",appearance:"none",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${(0,import_polished5.darken)(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:disabled?"not-allowed":"grab"}},"&:focus":{outline:"none","&::-webkit-slider-runnable-track":{borderColor:(0,import_polished5.rgba)(theme.color.secondary,.4)},"&::-webkit-slider-thumb":{borderColor:theme.color.secondary,boxShadow:`0 0px 5px 0px ${theme.color.secondary}`}},"&::-moz-range-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.lighten)(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer",outline:"none"},"&::-moz-range-thumb":{width:16,height:16,border:`1px solid ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grap",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${(0,import_polished5.darken)(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:"grabbing"}},"&::-ms-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.darken)(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${(0,import_polished5.lighten)(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, - ${(0,import_polished5.lighten)(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,color:"transparent",width:"100%",height:"6px",cursor:"pointer"},"&::-ms-fill-lower":{borderRadius:6},"&::-ms-fill-upper":{borderRadius:6},"&::-ms-thumb":{width:16,height:16,background:`${theme.input.background}`,border:`1px solid ${(0,import_polished5.rgba)(theme.appBorderColor,.2)}`,borderRadius:50,cursor:"grab",marginTop:0},"@supports (-ms-ime-align:auto)":{"input[type=range]":{margin:"0"}}})),RangeLabel=import_theming15.styled.span({paddingLeft:5,paddingRight:5,fontSize:12,whiteSpace:"nowrap",fontFeatureSettings:"tnum",fontVariantNumeric:"tabular-nums","[aria-readonly=true] &":{opacity:.5}}),RangeCurrentAndMaxLabel=(0,import_theming15.styled)(RangeLabel)(({numberOFDecimalsPlaces,max})=>({width:`${numberOFDecimalsPlaces+max.toString().length*2+3}ch`,textAlign:"right",flexShrink:0})),RangeWrapper=import_theming15.styled.div({display:"flex",alignItems:"center",width:"100%"});function getNumberOfDecimalPlaces(number){let match=number.toString().match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return match?Math.max(0,(match[1]?match[1].length:0)-(match[2]?+match[2]:0)):0}var RangeControl=({name,value:value2,onChange,min=0,max=100,step=1,onBlur,onFocus,argType})=>{let handleChange=event=>{onChange(parse2(event.target.value))},hasValue=value2!==void 0,numberOFDecimalsPlaces=(0,import_react21.useMemo)(()=>getNumberOfDecimalPlaces(step),[step]),readonly=!!argType?.table?.readonly;return import_react21.default.createElement(RangeWrapper,{"aria-readonly":readonly},import_react21.default.createElement(RangeLabel,null,min),import_react21.default.createElement(RangeInput,{id:getControlId(name),type:"range",disabled:readonly,onChange:handleChange,name,value:value2,min,max,step,onFocus,onBlur}),import_react21.default.createElement(RangeCurrentAndMaxLabel,{numberOFDecimalsPlaces,max},hasValue?value2.toFixed(numberOFDecimalsPlaces):"--"," / ",max))};var import_react22=__toESM(require("react")),import_theming16=require("@storybook/theming"),import_components13=require("@storybook/components");init_helpers();var Wrapper7=import_theming16.styled.label({display:"flex"}),MaxLength=import_theming16.styled.div(({isMaxed})=>({marginLeft:"0.75rem",paddingTop:"0.35rem",color:isMaxed?"red":void 0})),TextControl=({name,value:value2,onChange,onFocus,onBlur,maxLength,argType})=>{let handleChange=event=>{onChange(event.target.value)},readonly=!!argType?.table?.readonly,[forceVisible,setForceVisible]=(0,import_react22.useState)(!1),onForceVisible=(0,import_react22.useCallback)(()=>{onChange(""),setForceVisible(!0)},[setForceVisible]);if(value2===void 0)return import_react22.default.createElement(import_components13.Button,{variant:"outline",size:"medium",disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set string");let isValid=typeof value2=="string";return import_react22.default.createElement(Wrapper7,null,import_react22.default.createElement(import_components13.Form.Textarea,{id:getControlId(name),maxLength,onChange:handleChange,disabled:readonly,size:"flex",placeholder:"Edit string...",autoFocus:forceVisible,valid:isValid?null:"error",name,value:isValid?value2:"",onFocus,onBlur}),maxLength&&import_react22.default.createElement(MaxLength,{isMaxed:value2?.length===maxLength},value2?.length??0," / ",maxLength))};var import_react23=__toESM(require("react")),import_theming17=require("@storybook/theming"),import_components14=require("@storybook/components");init_helpers();var FileInput=(0,import_theming17.styled)(import_components14.Form.Input)({padding:10});function revokeOldUrls(urls){urls.forEach(url=>{url.startsWith("blob:")&&URL.revokeObjectURL(url)})}var FilesControl=({onChange,name,accept="image/*",value:value2,argType})=>{let inputElement=(0,import_react23.useRef)(null),readonly=argType?.control?.readOnly;function handleFileChange(e){if(!e.target.files)return;let fileUrls=Array.from(e.target.files).map(file=>URL.createObjectURL(file));onChange(fileUrls),revokeOldUrls(value2)}return(0,import_react23.useEffect)(()=>{value2==null&&inputElement.current&&(inputElement.current.value=null)},[value2,name]),import_react23.default.createElement(FileInput,{ref:inputElement,id:getControlId(name),type:"file",name,multiple:!0,disabled:readonly,onChange:handleFileChange,accept,size:"flex"})};var LazyColorControl=(0,import_react25.lazy)(()=>Promise.resolve().then(()=>(init_Color(),Color_exports))),ColorControl2=props=>import_react25.default.createElement(import_react25.Suspense,{fallback:import_react25.default.createElement("div",null)},import_react25.default.createElement(LazyColorControl,{...props}));var Controls2={array:ObjectControl,object:ObjectControl,boolean:BooleanControl,color:ColorControl2,date:DateControl,number:NumberControl,check:OptionsControl,"inline-check":OptionsControl,radio:OptionsControl,"inline-radio":OptionsControl,select:OptionsControl,"multi-select":OptionsControl,range:RangeControl,text:TextControl,file:FilesControl},NoControl=()=>import_react26.default.createElement(import_react26.default.Fragment,null,"-"),ArgControl=({row,arg,updateArgs,isHovered})=>{let{key,control}=row,[isFocused,setFocused]=(0,import_react26.useState)(!1),[boxedValue,setBoxedValue]=(0,import_react26.useState)({value:arg});(0,import_react26.useEffect)(()=>{isFocused||setBoxedValue({value:arg})},[isFocused,arg]);let onChange=(0,import_react26.useCallback)(argVal=>(setBoxedValue({value:argVal}),updateArgs({[key]:argVal}),argVal),[updateArgs,key]),onBlur=(0,import_react26.useCallback)(()=>setFocused(!1),[]),onFocus=(0,import_react26.useCallback)(()=>setFocused(!0),[]);if(!control||control.disable){let canBeSetup=control?.disable!==!0&&row?.type?.name!=="function";return isHovered&&canBeSetup?import_react26.default.createElement(import_components16.Link,{href:"https://storybook.js.org/docs/react/essentials/controls",target:"_blank",withArrow:!0},"Setup controls"):import_react26.default.createElement(NoControl,null)}let props={name:key,argType:row,value:boxedValue.value,onChange,onBlur,onFocus},Control=Controls2[control.type]||NoControl;return import_react26.default.createElement(Control,{...props,...control,controlType:control.type})};var Name=import_theming19.styled.span({fontWeight:"bold"}),Required=import_theming19.styled.span(({theme})=>({color:theme.color.negative,fontFamily:theme.typography.fonts.mono,cursor:"help"})),Description=import_theming19.styled.div(({theme})=>({"&&":{p:{margin:"0 0 10px 0"},a:{color:theme.color.secondary}},code:{...(0,import_components17.codeCommon)({theme}),fontSize:12,fontFamily:theme.typography.fonts.mono},"& code":{margin:0,display:"inline-block"},"& pre > code":{whiteSpace:"pre-wrap"}})),Type=import_theming19.styled.div(({theme,hasDescription})=>({color:theme.base==="light"?(0,import_polished6.transparentize)(.1,theme.color.defaultText):(0,import_polished6.transparentize)(.2,theme.color.defaultText),marginTop:hasDescription?4:0})),TypeWithJsDoc=import_theming19.styled.div(({theme,hasDescription})=>({color:theme.base==="light"?(0,import_polished6.transparentize)(.1,theme.color.defaultText):(0,import_polished6.transparentize)(.2,theme.color.defaultText),marginTop:hasDescription?12:0,marginBottom:12})),StyledTd=import_theming19.styled.td(({theme,expandable})=>({paddingLeft:expandable?"40px !important":"20px !important"})),toSummary=value2=>value2&&{summary:typeof value2=="string"?value2:value2.name},ArgRow=props=>{let[isHovered,setIsHovered]=(0,import_react27.useState)(!1),{row,updateArgs,compact,expandable,initialExpandedArgs}=props,{name,description}=row,table=row.table||{},type=table.type||toSummary(row.type),defaultValue=table.defaultValue||row.defaultValue,required=row.type?.required,hasDescription=description!=null&&description!=="";return import_react27.default.createElement("tr",{onMouseEnter:()=>setIsHovered(!0),onMouseLeave:()=>setIsHovered(!1)},import_react27.default.createElement(StyledTd,{expandable},import_react27.default.createElement(Name,null,name),required?import_react27.default.createElement(Required,{title:"Required"},"*"):null),compact?null:import_react27.default.createElement("td",null,hasDescription&&import_react27.default.createElement(Description,null,import_react27.default.createElement(import_markdown_to_jsx.default,null,description)),table.jsDocTags!=null?import_react27.default.createElement(import_react27.default.Fragment,null,import_react27.default.createElement(TypeWithJsDoc,{hasDescription},import_react27.default.createElement(ArgValue,{value:type,initialExpandedArgs})),import_react27.default.createElement(ArgJsDoc,{tags:table.jsDocTags})):import_react27.default.createElement(Type,{hasDescription},import_react27.default.createElement(ArgValue,{value:type,initialExpandedArgs}))),compact?null:import_react27.default.createElement("td",null,import_react27.default.createElement(ArgValue,{value:defaultValue,initialExpandedArgs})),updateArgs?import_react27.default.createElement("td",null,import_react27.default.createElement(ArgControl,{...props,isHovered})):null)};var import_react28=__toESM(require("react")),import_polished7=require("polished"),import_theming20=require("@storybook/theming"),import_icons6=require("@storybook/icons"),ExpanderIconDown=(0,import_theming20.styled)(import_icons6.ChevronDownIcon)(({theme})=>({marginRight:8,marginLeft:-10,marginTop:-2,height:12,width:12,color:theme.base==="light"?(0,import_polished7.transparentize)(.25,theme.color.defaultText):(0,import_polished7.transparentize)(.3,theme.color.defaultText),border:"none",display:"inline-block"})),ExpanderIconRight=(0,import_theming20.styled)(import_icons6.ChevronRightIcon)(({theme})=>({marginRight:8,marginLeft:-10,marginTop:-2,height:12,width:12,color:theme.base==="light"?(0,import_polished7.transparentize)(.25,theme.color.defaultText):(0,import_polished7.transparentize)(.3,theme.color.defaultText),border:"none",display:"inline-block"})),FlexWrapper=import_theming20.styled.span(({theme})=>({display:"flex",lineHeight:"20px",alignItems:"center"})),Section=import_theming20.styled.td(({theme})=>({position:"relative",letterSpacing:"0.35em",textTransform:"uppercase",fontWeight:theme.typography.weight.bold,fontSize:theme.typography.size.s1-1,color:theme.base==="light"?(0,import_polished7.transparentize)(.4,theme.color.defaultText):(0,import_polished7.transparentize)(.6,theme.color.defaultText),background:`${theme.background.app} !important`,"& ~ td":{background:`${theme.background.app} !important`}})),Subsection=import_theming20.styled.td(({theme})=>({position:"relative",fontWeight:theme.typography.weight.bold,fontSize:theme.typography.size.s2-1,background:theme.background.app})),StyledTd2=import_theming20.styled.td(()=>({position:"relative"})),StyledTr=import_theming20.styled.tr(({theme})=>({"&:hover > td":{backgroundColor:`${(0,import_polished7.lighten)(.005,theme.background.app)} !important`,boxShadow:`${theme.color.mediumlight} 0 - 1px 0 0 inset`,cursor:"row-resize"}})),ClickIntercept=import_theming20.styled.button(()=>({background:"none",border:"none",padding:"0",font:"inherit",position:"absolute",top:0,bottom:0,left:0,right:0,height:"100%",width:"100%",color:"transparent",cursor:"row-resize !important"})),SectionRow=({level="section",label,children,initialExpanded=!0,colSpan=3})=>{let[expanded,setExpanded]=(0,import_react28.useState)(initialExpanded),Level=level==="subsection"?Subsection:Section,itemCount=children?.length||0,caption=level==="subsection"?`${itemCount} item${itemCount!==1?"s":""}`:"",helperText=`${expanded?"Hide":"Show"} ${level==="subsection"?itemCount:label} item${itemCount!==1?"s":""}`;return import_react28.default.createElement(import_react28.default.Fragment,null,import_react28.default.createElement(StyledTr,{title:helperText},import_react28.default.createElement(Level,{colSpan:1},import_react28.default.createElement(ClickIntercept,{onClick:e=>setExpanded(!expanded),tabIndex:0},helperText),import_react28.default.createElement(FlexWrapper,null,expanded?import_react28.default.createElement(ExpanderIconDown,null):import_react28.default.createElement(ExpanderIconRight,null),label)),import_react28.default.createElement(StyledTd2,{colSpan:colSpan-1},import_react28.default.createElement(ClickIntercept,{onClick:e=>setExpanded(!expanded),tabIndex:-1,style:{outline:"none"}},helperText),expanded?null:caption)),expanded?children:null)};var import_theming21=require("@storybook/theming"),import_react29=__toESM(require("react")),Row=import_theming21.styled.div(({theme})=>({display:"flex",gap:16,borderBottom:`1px solid ${theme.appBorderColor}`,"&:last-child":{borderBottom:0}})),Column=import_theming21.styled.div(({numColumn})=>({display:"flex",flexDirection:"column",flex:numColumn||1,gap:5,padding:"12px 20px"})),SkeletonText=import_theming21.styled.div(({theme,width,height})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,width:width||"100%",height:height||16,borderRadius:3})),columnWidth=[2,4,2,2],Skeleton=()=>import_react29.default.createElement(import_react29.default.Fragment,null,import_react29.default.createElement(Row,null,import_react29.default.createElement(Column,{numColumn:columnWidth[0]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[1]},import_react29.default.createElement(SkeletonText,{width:"30%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[2]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[3]},import_react29.default.createElement(SkeletonText,{width:"60%"}))),import_react29.default.createElement(Row,null,import_react29.default.createElement(Column,{numColumn:columnWidth[0]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[1]},import_react29.default.createElement(SkeletonText,{width:"80%"}),import_react29.default.createElement(SkeletonText,{width:"30%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[2]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[3]},import_react29.default.createElement(SkeletonText,{width:"60%"}))),import_react29.default.createElement(Row,null,import_react29.default.createElement(Column,{numColumn:columnWidth[0]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[1]},import_react29.default.createElement(SkeletonText,{width:"80%"}),import_react29.default.createElement(SkeletonText,{width:"30%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[2]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[3]},import_react29.default.createElement(SkeletonText,{width:"60%"}))),import_react29.default.createElement(Row,null,import_react29.default.createElement(Column,{numColumn:columnWidth[0]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[1]},import_react29.default.createElement(SkeletonText,{width:"80%"}),import_react29.default.createElement(SkeletonText,{width:"30%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[2]},import_react29.default.createElement(SkeletonText,{width:"60%"})),import_react29.default.createElement(Column,{numColumn:columnWidth[3]},import_react29.default.createElement(SkeletonText,{width:"60%"}))));var import_react30=__toESM(require("react")),import_theming22=require("@storybook/theming"),import_components18=require("@storybook/components"),import_icons7=require("@storybook/icons"),Wrapper9=import_theming22.styled.div(({inAddonPanel,theme})=>({height:inAddonPanel?"100%":"auto",display:"flex",border:inAddonPanel?"none":`1px solid ${theme.appBorderColor}`,borderRadius:inAddonPanel?0:theme.appBorderRadius,padding:inAddonPanel?0:40,alignItems:"center",justifyContent:"center",flexDirection:"column",gap:15,background:theme.background.content,boxShadow:"rgba(0, 0, 0, 0.10) 0 1px 3px 0"})),Links=import_theming22.styled.div(({theme})=>({display:"flex",fontSize:theme.typography.size.s2-1,gap:25})),Divider=import_theming22.styled.div(({theme})=>({width:1,height:16,backgroundColor:theme.appBorderColor})),Empty=({inAddonPanel})=>{let[isLoading,setIsLoading]=(0,import_react30.useState)(!0);return(0,import_react30.useEffect)(()=>{let load=setTimeout(()=>{setIsLoading(!1)},100);return()=>clearTimeout(load)},[]),isLoading?null:import_react30.default.createElement(Wrapper9,{inAddonPanel},import_react30.default.createElement(import_components18.EmptyTabContent,{title:inAddonPanel?"Interactive story playground":"Args table with interactive controls couldn't be auto-generated",description:import_react30.default.createElement(import_react30.default.Fragment,null,"Controls give you an easy to use interface to test your components. Set your story args and you'll see controls appearing here automatically."),footer:import_react30.default.createElement(Links,null,inAddonPanel&&import_react30.default.createElement(import_react30.default.Fragment,null,import_react30.default.createElement(import_components18.Link,{href:"https://youtu.be/0gOfS6K0x0E",target:"_blank",withArrow:!0},import_react30.default.createElement(import_icons7.VideoIcon,null)," Watch 5m video"),import_react30.default.createElement(Divider,null),import_react30.default.createElement(import_components18.Link,{href:"https://storybook.js.org/docs/essentials/controls",target:"_blank",withArrow:!0},import_react30.default.createElement(import_icons7.DocumentIcon,null)," Read docs")),!inAddonPanel&&import_react30.default.createElement(import_components18.Link,{href:"https://storybook.js.org/docs/essentials/controls",target:"_blank",withArrow:!0},import_react30.default.createElement(import_icons7.DocumentIcon,null)," Learn how to set that up"))}))};var TableWrapper=import_theming23.styled.table(({theme,compact,inAddonPanel})=>({"&&":{borderSpacing:0,color:theme.color.defaultText,"td, th":{padding:0,border:"none",verticalAlign:"top",textOverflow:"ellipsis"},fontSize:theme.typography.size.s2-1,lineHeight:"20px",textAlign:"left",width:"100%",marginTop:inAddonPanel?0:25,marginBottom:inAddonPanel?0:40,"thead th:first-of-type, td:first-of-type":{width:"25%"},"th:first-of-type, td:first-of-type":{paddingLeft:20},"th:nth-of-type(2), td:nth-of-type(2)":{...compact?null:{width:"35%"}},"td:nth-of-type(3)":{...compact?null:{width:"15%"}},"th:last-of-type, td:last-of-type":{paddingRight:20,...compact?null:{width:"25%"}},th:{color:theme.base==="light"?(0,import_polished8.transparentize)(.25,theme.color.defaultText):(0,import_polished8.transparentize)(.45,theme.color.defaultText),paddingTop:10,paddingBottom:10,paddingLeft:15,paddingRight:15},td:{paddingTop:"10px",paddingBottom:"10px","&:not(:first-of-type)":{paddingLeft:15,paddingRight:15},"&:last-of-type":{paddingRight:20}},marginLeft:inAddonPanel?0:1,marginRight:inAddonPanel?0:1,tbody:{...inAddonPanel?null:{filter:theme.base==="light"?"drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.10))":"drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.20))"},"> tr > *":{background:theme.background.content,borderTop:`1px solid ${theme.appBorderColor}`},...inAddonPanel?null:{"> tr:first-of-type > *":{borderBlockStart:`1px solid ${theme.appBorderColor}`},"> tr:last-of-type > *":{borderBlockEnd:`1px solid ${theme.appBorderColor}`},"> tr > *:first-of-type":{borderInlineStart:`1px solid ${theme.appBorderColor}`},"> tr > *:last-of-type":{borderInlineEnd:`1px solid ${theme.appBorderColor}`},"> tr:first-of-type > td:first-of-type":{borderTopLeftRadius:theme.appBorderRadius},"> tr:first-of-type > td:last-of-type":{borderTopRightRadius:theme.appBorderRadius},"> tr:last-of-type > td:first-of-type":{borderBottomLeftRadius:theme.appBorderRadius},"> tr:last-of-type > td:last-of-type":{borderBottomRightRadius:theme.appBorderRadius}}}}})),StyledIconButton=(0,import_theming23.styled)(import_components19.IconButton)(({theme})=>({margin:"-4px -12px -4px 0"})),ControlHeadingWrapper=import_theming23.styled.span({display:"flex",justifyContent:"space-between"});var sortFns={alpha:(a,b)=>a.name.localeCompare(b.name),requiredFirst:(a,b)=>+!!b.type?.required-+!!a.type?.required||a.name.localeCompare(b.name),none:void 0},groupRows=(rows,sort)=>{let sections={ungrouped:[],ungroupedSubsections:{},sections:{}};if(!rows)return sections;Object.entries(rows).forEach(([key,row])=>{let{category,subcategory}=row?.table||{};if(category){let section=sections.sections[category]||{ungrouped:[],subsections:{}};if(!subcategory)section.ungrouped.push({key,...row});else{let subsection=section.subsections[subcategory]||[];subsection.push({key,...row}),section.subsections[subcategory]=subsection}sections.sections[category]=section}else if(subcategory){let subsection=sections.ungroupedSubsections[subcategory]||[];subsection.push({key,...row}),sections.ungroupedSubsections[subcategory]=subsection}else sections.ungrouped.push({key,...row})});let sortFn=sortFns[sort],sortSubsection=record=>sortFn?Object.keys(record).reduce((acc,cur)=>({...acc,[cur]:record[cur].sort(sortFn)}),{}):record;return{ungrouped:sections.ungrouped.sort(sortFn),ungroupedSubsections:sortSubsection(sections.ungroupedSubsections),sections:Object.keys(sections.sections).reduce((acc,cur)=>({...acc,[cur]:{ungrouped:sections.sections[cur].ungrouped.sort(sortFn),subsections:sortSubsection(sections.sections[cur].subsections)}}),{})}},safeIncludeConditionalArg=(row,args,globals)=>{try{return(0,import_csf.includeConditionalArg)(row,args,globals)}catch(err){return import_client_logger4.once.warn(err.message),!1}},ArgsTable=props=>{let{updateArgs,resetArgs,compact,inAddonPanel,initialExpandedArgs,sort="none",isLoading}=props;if("error"in props){let{error}=props;return import_react31.default.createElement(EmptyBlock,null,error,"\xA0",import_react31.default.createElement(import_components19.Link,{href:"http://storybook.js.org/docs/",target:"_blank",withArrow:!0},import_react31.default.createElement(import_icons8.DocumentIcon,null)," Read the docs"))}if(isLoading)return import_react31.default.createElement(Skeleton,null);let{rows,args,globals}="rows"in props&&props,groups=groupRows((0,import_pickBy.default)(rows,row=>!row?.table?.disable&&safeIncludeConditionalArg(row,args||{},globals||{})),sort),hasNoUngrouped=groups.ungrouped.length===0,hasNoSections=Object.entries(groups.sections).length===0,hasNoUngroupedSubsections=Object.entries(groups.ungroupedSubsections).length===0;if(hasNoUngrouped&&hasNoSections&&hasNoUngroupedSubsections)return import_react31.default.createElement(Empty,{inAddonPanel});let colSpan=1;updateArgs&&(colSpan+=1),compact||(colSpan+=2);let expandable=Object.keys(groups.sections).length>0,common={updateArgs,compact,inAddonPanel,initialExpandedArgs};return import_react31.default.createElement(import_components19.ResetWrapper,null,import_react31.default.createElement(TableWrapper,{compact,inAddonPanel,className:"docblock-argstable sb-unstyled"},import_react31.default.createElement("thead",{className:"docblock-argstable-head"},import_react31.default.createElement("tr",null,import_react31.default.createElement("th",null,import_react31.default.createElement("span",null,"Name")),compact?null:import_react31.default.createElement("th",null,import_react31.default.createElement("span",null,"Description")),compact?null:import_react31.default.createElement("th",null,import_react31.default.createElement("span",null,"Default")),updateArgs?import_react31.default.createElement("th",null,import_react31.default.createElement(ControlHeadingWrapper,null,"Control"," ",!isLoading&&resetArgs&&import_react31.default.createElement(StyledIconButton,{onClick:()=>resetArgs(),title:"Reset controls"},import_react31.default.createElement(import_icons8.UndoIcon,{"aria-hidden":!0})))):null)),import_react31.default.createElement("tbody",{className:"docblock-argstable-body"},groups.ungrouped.map(row=>import_react31.default.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],...common})),Object.entries(groups.ungroupedSubsections).map(([subcategory,subsection])=>import_react31.default.createElement(SectionRow,{key:subcategory,label:subcategory,level:"subsection",colSpan},subsection.map(row=>import_react31.default.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],expandable,...common})))),Object.entries(groups.sections).map(([category,section])=>import_react31.default.createElement(SectionRow,{key:category,label:category,level:"section",colSpan},section.ungrouped.map(row=>import_react31.default.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],...common})),Object.entries(section.subsections).map(([subcategory,subsection])=>import_react31.default.createElement(SectionRow,{key:subcategory,label:subcategory,level:"subsection",colSpan},subsection.map(row=>import_react31.default.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],expandable,...common})))))))))};var import_react32=__toESM(require("react")),import_components20=require("@storybook/components");var TabbedArgsTable=({tabs,...props})=>{let entries=Object.entries(tabs);return entries.length===1?import_react32.default.createElement(ArgsTable,{...entries[0][1],...props}):import_react32.default.createElement(import_components20.TabsState,null,entries.map((entry,index)=>{let[label,table]=entry,id2=`prop_table_div_${label}`,Component4="div",argsTableProps=index===0?props:{sort:props.sort};return import_react32.default.createElement(Component4,{key:id2,id:id2,title:label},({active})=>active?import_react32.default.createElement(ArgsTable,{key:`prop_table_${label}`,...table,...argsTableProps}):null)}))};var import_react33=__toESM(require("react")),import_polished9=require("polished"),import_theming24=require("@storybook/theming"),import_components21=require("@storybook/components");var Label4=import_theming24.styled.div(({theme})=>({marginRight:30,fontSize:`${theme.typography.size.s1}px`,color:theme.base==="light"?(0,import_polished9.transparentize)(.4,theme.color.defaultText):(0,import_polished9.transparentize)(.6,theme.color.defaultText)})),Sample=import_theming24.styled.div({overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"}),TypeSpecimen=import_theming24.styled.div({display:"flex",flexDirection:"row",alignItems:"baseline","&:not(:last-child)":{marginBottom:"1rem"}}),Wrapper10=import_theming24.styled.div(import_components21.withReset,({theme})=>({...getBlockBackgroundStyle(theme),margin:"25px 0 40px",padding:"30px 20px"})),Typeset=({fontFamily,fontSizes,fontWeight,sampleText,...props})=>import_react33.default.createElement(Wrapper10,{...props,className:"docblock-typeset sb-unstyled"},fontSizes.map(size=>import_react33.default.createElement(TypeSpecimen,{key:size},import_react33.default.createElement(Label4,null,size),import_react33.default.createElement(Sample,{style:{fontFamily,fontSize:size,fontWeight,lineHeight:1.2}},sampleText||"Was he a beast if music could move him so?"))));var import_react34=__toESM(require("react")),import_polished10=require("polished"),import_theming25=require("@storybook/theming"),import_components22=require("@storybook/components");var ItemTitle=import_theming25.styled.div(({theme})=>({fontWeight:theme.typography.weight.bold,color:theme.color.defaultText})),ItemSubtitle=import_theming25.styled.div(({theme})=>({color:theme.base==="light"?(0,import_polished10.transparentize)(.2,theme.color.defaultText):(0,import_polished10.transparentize)(.6,theme.color.defaultText)})),ItemDescription=import_theming25.styled.div({flex:"0 0 30%",lineHeight:"20px",marginTop:5}),SwatchLabel=import_theming25.styled.div(({theme})=>({flex:1,textAlign:"center",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,lineHeight:1,overflow:"hidden",color:theme.base==="light"?(0,import_polished10.transparentize)(.4,theme.color.defaultText):(0,import_polished10.transparentize)(.6,theme.color.defaultText),"> div":{display:"inline-block",overflow:"hidden",maxWidth:"100%",textOverflow:"ellipsis"},span:{display:"block",marginTop:2}})),SwatchLabels=import_theming25.styled.div({display:"flex",flexDirection:"row"}),Swatch2=import_theming25.styled.div(({background})=>({position:"relative",flex:1,"&::before":{position:"absolute",top:0,left:0,width:"100%",height:"100%",background,content:'""'}})),SwatchColors=import_theming25.styled.div(({theme})=>({...getBlockBackgroundStyle(theme),display:"flex",flexDirection:"row",height:50,marginBottom:5,overflow:"hidden",backgroundColor:"white",backgroundImage:"repeating-linear-gradient(-45deg, #ccc, #ccc 1px, #fff 1px, #fff 16px)",backgroundClip:"padding-box"})),SwatchSpecimen=import_theming25.styled.div({display:"flex",flexDirection:"column",flex:1,position:"relative",marginBottom:30}),Swatches2=import_theming25.styled.div({flex:1,display:"flex",flexDirection:"row"}),Item=import_theming25.styled.div({display:"flex",alignItems:"flex-start"}),ListName=import_theming25.styled.div({flex:"0 0 30%"}),ListSwatches=import_theming25.styled.div({flex:1}),ListHeading=import_theming25.styled.div(({theme})=>({display:"flex",flexDirection:"row",alignItems:"center",paddingBottom:20,fontWeight:theme.typography.weight.bold,color:theme.base==="light"?(0,import_polished10.transparentize)(.4,theme.color.defaultText):(0,import_polished10.transparentize)(.6,theme.color.defaultText)})),List=import_theming25.styled.div(({theme})=>({fontSize:theme.typography.size.s2,lineHeight:"20px",display:"flex",flexDirection:"column"}));function renderSwatch(color,index){return import_react34.default.createElement(Swatch2,{key:`${color}-${index}`,title:color,background:color})}function renderSwatchLabel(color,index,colorDescription){return import_react34.default.createElement(SwatchLabel,{key:`${color}-${index}`,title:color},import_react34.default.createElement("div",null,color,colorDescription&&import_react34.default.createElement("span",null,colorDescription)))}function renderSwatchSpecimen(colors){if(Array.isArray(colors))return import_react34.default.createElement(SwatchSpecimen,null,import_react34.default.createElement(SwatchColors,null,colors.map((color,index)=>renderSwatch(color,index))),import_react34.default.createElement(SwatchLabels,null,colors.map((color,index)=>renderSwatchLabel(color,index))));let swatchElements=[],labelElements=[];for(let colorKey in colors){let colorValue=colors[colorKey];swatchElements.push(renderSwatch(colorValue,swatchElements.length)),labelElements.push(renderSwatchLabel(colorKey,labelElements.length,colorValue))}return import_react34.default.createElement(SwatchSpecimen,null,import_react34.default.createElement(SwatchColors,null,swatchElements),import_react34.default.createElement(SwatchLabels,null,labelElements))}var ColorItem=({title,subtitle,colors})=>import_react34.default.createElement(Item,null,import_react34.default.createElement(ItemDescription,null,import_react34.default.createElement(ItemTitle,null,title),import_react34.default.createElement(ItemSubtitle,null,subtitle)),import_react34.default.createElement(Swatches2,null,renderSwatchSpecimen(colors))),ColorPalette=({children,...props})=>import_react34.default.createElement(import_components22.ResetWrapper,null,import_react34.default.createElement(List,{...props,className:"docblock-colorpalette sb-unstyled"},import_react34.default.createElement(ListHeading,null,import_react34.default.createElement(ListName,null,"Name"),import_react34.default.createElement(ListSwatches,null,"Swatches")),children));var import_react35=__toESM(require("react")),import_theming26=require("@storybook/theming"),import_components23=require("@storybook/components");var ItemLabel=import_theming26.styled.div(({theme})=>({fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s2,color:theme.color.defaultText,marginLeft:10,lineHeight:1.2})),ItemSpecimen=import_theming26.styled.div(({theme})=>({...getBlockBackgroundStyle(theme),overflow:"hidden",height:40,width:40,display:"flex",alignItems:"center",justifyContent:"center",flex:"none","> img, > svg":{width:20,height:20}})),Item2=import_theming26.styled.div({display:"inline-flex",flexDirection:"row",alignItems:"center",flex:"0 1 calc(20% - 10px)",minWidth:120,margin:"0px 10px 30px 0"}),List2=import_theming26.styled.div({display:"flex",flexFlow:"row wrap"}),IconItem=({name,children})=>import_react35.default.createElement(Item2,null,import_react35.default.createElement(ItemSpecimen,null,children),import_react35.default.createElement(ItemLabel,null,name)),IconGallery=({children,...props})=>import_react35.default.createElement(import_components23.ResetWrapper,null,import_react35.default.createElement(List2,{...props,className:"docblock-icongallery sb-unstyled"},children));var import_react36=__toESM(require("react")),anchorBlockIdFromId=storyId=>`anchor--${storyId}`,Anchor=({storyId,children})=>import_react36.default.createElement("div",{id:anchorBlockIdFromId(storyId),className:"sb-anchor"},children);var import_preview_api=require("@storybook/preview-api"),import_react39=__toESM(require("react"));var import_react38=require("react");var import_react37=require("react"),import_global5=require("@storybook/global");import_global5.global&&import_global5.global.__DOCS_CONTEXT__===void 0&&(import_global5.global.__DOCS_CONTEXT__=(0,import_react37.createContext)(null),import_global5.global.__DOCS_CONTEXT__.displayName="DocsContext");var DocsContext=import_global5.global?import_global5.global.__DOCS_CONTEXT__:(0,import_react37.createContext)(null);var useOf=(moduleExportOrType,validTypes)=>(0,import_react38.useContext)(DocsContext).resolveOf(moduleExportOrType,validTypes);var titleCase=str=>str.split("-").map(part=>part.charAt(0).toUpperCase()+part.slice(1)).join(""),getComponentName=component=>{if(component)return typeof component=="string"?component.includes("-")?titleCase(component):component:component.__docgenInfo&&component.__docgenInfo.displayName?component.__docgenInfo.displayName:component.name};function scrollToElement(element,block="start"){element.scrollIntoView({behavior:"smooth",block,inline:"nearest"})}function extractComponentArgTypes(component,parameters){let{extractArgTypes}=parameters.docs||{};if(!extractArgTypes)throw new Error("Args unsupported. See Args documentation for your framework.");return extractArgTypes(component)}function getArgTypesFromResolved(resolved){if(resolved.type==="component"){let{component:component2,projectAnnotations:{parameters:parameters2}}=resolved;return{argTypes:extractComponentArgTypes(component2,parameters2),parameters:parameters2,component:component2}}if(resolved.type==="meta"){let{preparedMeta:{argTypes:argTypes2,parameters:parameters2,component:component2,subcomponents:subcomponents2}}=resolved;return{argTypes:argTypes2,parameters:parameters2,component:component2,subcomponents:subcomponents2}}let{story:{argTypes,parameters,component,subcomponents}}=resolved;return{argTypes,parameters,component,subcomponents}}var ArgTypes=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let resolved=useOf(of||"meta"),{argTypes,parameters,component,subcomponents}=getArgTypesFromResolved(resolved),argTypesParameters=parameters.docs?.argTypes||{},include=props.include??argTypesParameters.include,exclude=props.exclude??argTypesParameters.exclude,sort=props.sort??argTypesParameters.sort,filteredArgTypes=(0,import_preview_api.filterArgTypes)(argTypes,include,exclude);if(!(!!subcomponents&&Object.keys(subcomponents).length>0))return import_react39.default.createElement(ArgsTable,{rows:filteredArgTypes,sort});let mainComponentName=getComponentName(component),subcomponentTabs=Object.fromEntries(Object.entries(subcomponents).map(([key,comp])=>[key,{rows:(0,import_preview_api.filterArgTypes)(extractComponentArgTypes(comp,parameters),include,exclude),sort}])),tabs={[mainComponentName]:{rows:filteredArgTypes,sort},...subcomponentTabs};return import_react39.default.createElement(TabbedArgsTable,{tabs,sort})};var import_react44=__toESM(require("react"));var import_react40=__toESM(require("react")),import_docs_tools=require("@storybook/docs-tools"),import_telejson=require("telejson");function argsHash(args){return(0,import_telejson.stringify)(args,{allowFunction:!1})}var SourceContext=(0,import_react40.createContext)({sources:{}}),UNKNOWN_ARGS_HASH="--unknown--",SourceContainer=({children,channel})=>{let[sources,setSources]=(0,import_react40.useState)({});return(0,import_react40.useEffect)(()=>{let handleSnippetRendered=(idOrEvent,inputSource=null,inputFormat=!1)=>{let{id:id2,args=void 0,source,format:format2}=typeof idOrEvent=="string"?{id:idOrEvent,source:inputSource,format:inputFormat}:idOrEvent,hash=args?argsHash(args):UNKNOWN_ARGS_HASH;setSources(current=>({...current,[id2]:{...current[id2],[hash]:{code:source,format:format2}}}))};return channel.on(import_docs_tools.SNIPPET_RENDERED,handleSnippetRendered),()=>channel.off(import_docs_tools.SNIPPET_RENDERED,handleSnippetRendered)},[]),import_react40.default.createElement(SourceContext.Provider,{value:{sources}},children)};var import_react41=__toESM(require("react")),import_docs_tools2=require("@storybook/docs-tools");var getStorySource=(storyId,args,sourceContext)=>{let{sources}=sourceContext,sourceMap=sources?.[storyId];return sourceMap?.[argsHash(args)]||sourceMap?.[UNKNOWN_ARGS_HASH]||{code:""}},getSnippet=({snippet,storyContext,typeFromProps,transformFromProps})=>{let{__isArgsStory:isArgsStory}=storyContext.parameters,sourceParameters=storyContext.parameters.docs?.source||{},type=typeFromProps||sourceParameters.type||import_docs_tools2.SourceType.AUTO;if(sourceParameters.code!==void 0)return sourceParameters.code;let code=type===import_docs_tools2.SourceType.DYNAMIC||type===import_docs_tools2.SourceType.AUTO&&snippet&&isArgsStory?snippet:sourceParameters.originalSource||"";return(transformFromProps??sourceParameters.transform)?.(code,storyContext)||code},useSourceProps=(props,docsContext,sourceContext)=>{let story,{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");if(of)story=docsContext.resolveOf(of,["story"]).story;else try{story=docsContext.storyById()}catch{}let sourceParameters=story?.parameters?.docs?.source||{},{code}=props,format2=props.format??sourceParameters.format,language=props.language??sourceParameters.language??"jsx",dark=props.dark??sourceParameters.dark??!1;if(!code&&!story)return{error:"Oh no! The source is not available."};if(code)return{code,format:format2,language,dark};let storyContext=docsContext.getStoryContext(story),argsForSource=props.__forceInitialArgs?storyContext.initialArgs:storyContext.unmappedArgs,source=getStorySource(story.id,argsForSource,sourceContext);return format2=source.format??story.parameters.docs?.source?.format??!1,{code:getSnippet({snippet:source.code,storyContext:{...storyContext,args:argsForSource},typeFromProps:props.type,transformFromProps:props.transform}),format:format2,language,dark}},Source2=props=>{let sourceContext=(0,import_react41.useContext)(SourceContext),docsContext=(0,import_react41.useContext)(DocsContext),sourceProps=useSourceProps(props,docsContext,sourceContext);return import_react41.default.createElement(Source,{...sourceProps})};var import_react43=__toESM(require("react"));var import_react42=require("react");function useStory(storyId,context){let stories=useStories([storyId],context);return stories&&stories[0]}function useStories(storyIds,context){let[storiesById,setStories]=(0,import_react42.useState)({});return(0,import_react42.useEffect)(()=>{Promise.all(storyIds.map(async storyId=>{let story=await context.loadStory(storyId);setStories(current=>current[storyId]===story?current:{...current,[storyId]:story})}))}),storyIds.map(storyId=>{if(storiesById[storyId])return storiesById[storyId];try{return context.storyById(storyId)}catch{return null}})}var getStoryId2=(props,context)=>{let{of,meta}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");return meta&&context.referenceMeta(meta,!1),context.resolveOf(of||"story",["story"]).story.id},getStoryProps=(props,story,context)=>{let{parameters={}}=story||{},{docs={}}=parameters,storyParameters=docs.story||{};if(docs.disable)return null;if(props.inline??storyParameters.inline??!1){let height2=props.height??storyParameters.height,autoplay=props.autoplay??storyParameters.autoplay??!1;return{story,inline:!0,height:height2,autoplay,forceInitialArgs:!!props.__forceInitialArgs,primary:!!props.__primary,renderStoryToElement:context.renderStoryToElement}}let height=props.height??storyParameters.height??storyParameters.iframeHeight??"100px";return{story,inline:!1,height,primary:!!props.__primary}},Story2=(props={__forceInitialArgs:!1,__primary:!1})=>{let context=(0,import_react43.useContext)(DocsContext),storyId=getStoryId2(props,context),story=useStory(storyId,context);if(!story)return import_react43.default.createElement(StorySkeleton,null);let storyProps=getStoryProps(props,story,context);return storyProps?import_react43.default.createElement(Story,{...storyProps}):null};var Canvas=props=>{let docsContext=(0,import_react44.useContext)(DocsContext),sourceContext=(0,import_react44.useContext)(SourceContext),{of,source}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let{story}=useOf(of||"story",["story"]),sourceProps=useSourceProps({...source,...of&&{of}},docsContext,sourceContext),layout=props.layout??story.parameters.layout??story.parameters.docs?.canvas?.layout??"padded",withToolbar=props.withToolbar??story.parameters.docs?.canvas?.withToolbar??!1,additionalActions=props.additionalActions??story.parameters.docs?.canvas?.additionalActions,sourceState=props.sourceState??story.parameters.docs?.canvas?.sourceState??"hidden",className=props.className??story.parameters.docs?.canvas?.className;return import_react44.default.createElement(Preview,{withSource:sourceState==="none"?void 0:sourceProps,isExpanded:sourceState==="shown",withToolbar,additionalActions,className,layout},import_react44.default.createElement(Story2,{of:of||story.moduleExport,meta:props.meta,...props.story}))};var import_react47=__toESM(require("react")),import_preview_api2=require("@storybook/preview-api");var import_react45=require("react"),import_core_events=require("@storybook/core-events"),useGlobals=(story,context)=>{let storyContext=context.getStoryContext(story),[globals,setGlobals]=(0,import_react45.useState)(storyContext.globals);return(0,import_react45.useEffect)(()=>{let onGlobalsUpdated=changed=>{setGlobals(changed.globals)};return context.channel.on(import_core_events.GLOBALS_UPDATED,onGlobalsUpdated),()=>context.channel.off(import_core_events.GLOBALS_UPDATED,onGlobalsUpdated)},[context.channel]),[globals]};var import_react46=require("react"),import_core_events2=require("@storybook/core-events"),useArgs=(story,context)=>{let result=useArgsIfDefined(story,context);if(!result)throw new Error("No result when story was defined");return result},useArgsIfDefined=(story,context)=>{let storyContext=story?context.getStoryContext(story):{args:{}},{id:storyId}=story||{id:"none"},[args,setArgs]=(0,import_react46.useState)(storyContext.args);(0,import_react46.useEffect)(()=>{let onArgsUpdated=changed=>{changed.storyId===storyId&&setArgs(changed.args)};return context.channel.on(import_core_events2.STORY_ARGS_UPDATED,onArgsUpdated),()=>context.channel.off(import_core_events2.STORY_ARGS_UPDATED,onArgsUpdated)},[storyId,context.channel]);let updateArgs=(0,import_react46.useCallback)(updatedArgs=>context.channel.emit(import_core_events2.UPDATE_STORY_ARGS,{storyId,updatedArgs}),[storyId,context.channel]),resetArgs=(0,import_react46.useCallback)(argNames=>context.channel.emit(import_core_events2.RESET_STORY_ARGS,{storyId,argNames}),[storyId,context.channel]);return story&&[args,updateArgs,resetArgs]};function extractComponentArgTypes2(component,parameters){let{extractArgTypes}=parameters.docs||{};if(!extractArgTypes)throw new Error("Args unsupported. See Args documentation for your framework.");return extractArgTypes(component)}var Controls3=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let context=(0,import_react47.useContext)(DocsContext),{story}=context.resolveOf(of||"story",["story"]),{parameters,argTypes,component,subcomponents}=story,controlsParameters=parameters.docs?.controls||{},include=props.include??controlsParameters.include,exclude=props.exclude??controlsParameters.exclude,sort=props.sort??controlsParameters.sort,[args,updateArgs,resetArgs]=useArgs(story,context),[globals]=useGlobals(story,context),filteredArgTypes=(0,import_preview_api2.filterArgTypes)(argTypes,include,exclude);if(!(!!subcomponents&&Object.keys(subcomponents).length>0))return Object.keys(filteredArgTypes).length>0||Object.keys(args).length>0?import_react47.default.createElement(ArgsTable,{rows:filteredArgTypes,sort,args,globals,updateArgs,resetArgs}):null;let mainComponentName=getComponentName(component),subcomponentTabs=Object.fromEntries(Object.entries(subcomponents).map(([key,comp])=>[key,{rows:(0,import_preview_api2.filterArgTypes)(extractComponentArgTypes2(comp,parameters),include,exclude),sort}])),tabs={[mainComponentName]:{rows:filteredArgTypes,sort},...subcomponentTabs};return import_react47.default.createElement(TabbedArgsTable,{tabs,sort,args,globals,updateArgs,resetArgs})};var import_react50=__toESM(require("react"));var import_react49=__toESM(require("react")),import_markdown_to_jsx2=__toESM(require("markdown-to-jsx")),import_ts_dedent=__toESM(require("ts-dedent"));var import_react48=__toESM(require("react")),import_core_events3=require("@storybook/core-events"),import_components28=require("@storybook/components"),import_global6=require("@storybook/global"),import_theming27=require("@storybook/theming"),import_icons9=require("@storybook/icons");var{document:document2}=import_global6.global,assertIsFn=val=>{if(typeof val!="function")throw new Error(`Expected story function, got: ${val}`);return val},AddContext=props=>{let{children,...rest}=props,parentContext=import_react48.default.useContext(DocsContext);return import_react48.default.createElement(DocsContext.Provider,{value:{...parentContext,...rest}},children)},CodeOrSourceMdx=({className,children,...rest})=>{if(typeof className!="string"&&(typeof children!="string"||!children.match(/[\n\r]/g)))return import_react48.default.createElement(import_components28.Code,null,children);let language=className&&className.split("-");return import_react48.default.createElement(Source,{language:language&&language[1]||"text",format:!1,code:children,...rest})};function navigate(context,url){context.channel.emit(import_core_events3.NAVIGATE_URL,url)}var A=import_components28.components.a,AnchorInPage=({hash,children})=>{let context=(0,import_react48.useContext)(DocsContext);return import_react48.default.createElement(A,{href:hash,target:"_self",onClick:event=>{let id2=hash.substring(1);document2.getElementById(id2)&&navigate(context,hash)}},children)},AnchorMdx=props=>{let{href,target,children,...rest}=props,context=(0,import_react48.useContext)(DocsContext);return!href||target==="_blank"||/^https?:\/\//.test(href)?import_react48.default.createElement(A,{...props}):href.startsWith("#")?import_react48.default.createElement(AnchorInPage,{hash:href},children):import_react48.default.createElement(A,{href,onClick:event=>{event.button===0&&!event.altKey&&!event.ctrlKey&&!event.metaKey&&!event.shiftKey&&(event.preventDefault(),navigate(context,event.currentTarget.getAttribute("href")))},target,...rest},children)},SUPPORTED_MDX_HEADERS=["h1","h2","h3","h4","h5","h6"],OcticonHeaders=SUPPORTED_MDX_HEADERS.reduce((acc,headerType)=>({...acc,[headerType]:(0,import_theming27.styled)(headerType)({"& svg":{position:"relative",top:"-0.1em",visibility:"hidden"},"&:hover svg":{visibility:"visible"}})}),{}),OcticonAnchor=import_theming27.styled.a(()=>({float:"left",lineHeight:"inherit",paddingRight:"10px",marginLeft:"-24px",color:"inherit"})),HeaderWithOcticonAnchor=({as,id:id2,children,...rest})=>{let context=(0,import_react48.useContext)(DocsContext),OcticonHeader=OcticonHeaders[as],hash=`#${id2}`;return import_react48.default.createElement(OcticonHeader,{id:id2,...rest},import_react48.default.createElement(OcticonAnchor,{"aria-hidden":"true",href:hash,tabIndex:-1,target:"_self",onClick:event=>{document2.getElementById(id2)&&navigate(context,hash)}},import_react48.default.createElement(import_icons9.LinkIcon,null)),children)},HeaderMdx=props=>{let{as,id:id2,children,...rest}=props;if(id2)return import_react48.default.createElement(HeaderWithOcticonAnchor,{as,id:id2,...rest},children);let Component4=as,{as:omittedAs,...withoutAs}=props;return import_react48.default.createElement(Component4,{...(0,import_components28.nameSpaceClassNames)(withoutAs,as)})},HeadersMdx=SUPPORTED_MDX_HEADERS.reduce((acc,headerType)=>({...acc,[headerType]:props=>import_react48.default.createElement(HeaderMdx,{as:headerType,...props})}),{});var Markdown2=props=>{if(!props.children)return null;if(typeof props.children!="string")throw new Error(import_ts_dedent.default`The Markdown block only accepts children as a single string, but children were of type: '${typeof props.children}' - This is often caused by not wrapping the child in a template string. -- -+ - This is invalid: - - # Some heading -@@ -32,4 +32,4 @@ var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropD - A paragraph - \`} - -- `);return import_react49.default.createElement(import_markdown_to_jsx2.default,{...props,options:{forceBlock:!0,overrides:{code:CodeOrSourceMdx,a:AnchorMdx,...HeadersMdx,...props?.options?.overrides},...props?.options}})};var DescriptionType=(DescriptionType2=>(DescriptionType2.INFO="info",DescriptionType2.NOTES="notes",DescriptionType2.DOCGEN="docgen",DescriptionType2.AUTO="auto",DescriptionType2))(DescriptionType||{}),getDescriptionFromResolvedOf=resolvedOf=>{switch(resolvedOf.type){case"story":return resolvedOf.story.parameters.docs?.description?.story||null;case"meta":{let{parameters,component}=resolvedOf.preparedMeta,metaDescription=parameters.docs?.description?.component;return metaDescription||parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}case"component":{let{component,projectAnnotations:{parameters}}=resolvedOf;return parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}default:throw new Error(`Unrecognized module type resolved from 'useOf', got: ${resolvedOf.type}`)}},DescriptionContainer=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let resolvedOf=useOf(of||"meta"),markdown=getDescriptionFromResolvedOf(resolvedOf);return markdown?import_react50.default.createElement(Markdown2,null,markdown):null};var import_react61=__toESM(require("react"));var import_react52=__toESM(require("react")),import_global7=require("@storybook/global"),import_theming29=require("@storybook/theming");var import_react51=__toESM(require("react")),import_theming28=require("@storybook/theming"),tocbot=__toESM(require("tocbot")),Wrapper11=import_theming28.styled.div(({theme})=>({width:"10rem","@media (max-width: 768px)":{display:"none"}})),Content=import_theming28.styled.div(({theme})=>({position:"fixed",bottom:0,top:0,width:"10rem",paddingTop:"4rem",paddingBottom:"2rem",overflowY:"auto",fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s2,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch","& *":{boxSizing:"border-box"},"& > .toc-wrapper > .toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`}}},"& .toc-list-item":{position:"relative",listStyleType:"none",marginLeft:20,paddingTop:3,paddingBottom:3},"& .toc-list-item::before":{content:'""',position:"absolute",height:"100%",top:0,left:0,transform:"translateX(calc(-2px - 20px))",borderLeft:`solid 2px ${theme.color.mediumdark}`,opacity:0,transition:"opacity 0.2s"},"& .toc-list-item.is-active-li::before":{opacity:1},"& .toc-list-item > a":{color:theme.color.defaultText,textDecoration:"none"},"& .toc-list-item.is-active-li > a":{fontWeight:600,color:theme.color.secondary,textDecoration:"none"}})),Heading=import_theming28.styled.p(({theme})=>({fontWeight:600,fontSize:"0.875em",color:theme.textColor,textTransform:"uppercase",marginBottom:10})),OptionalTitle=({title})=>title===null?null:typeof title=="string"?import_react51.default.createElement(Heading,null,title):title,TableOfContents=({title,disable,headingSelector,contentsSelector,ignoreSelector,unsafeTocbotOptions})=>((0,import_react51.useEffect)(()=>{let configuration={tocSelector:".toc-wrapper",contentSelector:contentsSelector??".sbdocs-content",headingSelector:headingSelector??"h3",ignoreSelector:ignoreSelector??".docs-story *, .skip-toc",headingsOffset:40,scrollSmoothOffset:-40,orderedList:!1,onClick:()=>!1,...unsafeTocbotOptions},timeout=setTimeout(()=>tocbot.init(configuration),100);return()=>{clearTimeout(timeout),tocbot.destroy()}},[disable]),import_react51.default.createElement(import_react51.default.Fragment,null,import_react51.default.createElement(Wrapper11,null,disable?null:import_react51.default.createElement(Content,null,import_react51.default.createElement(OptionalTitle,{title:title||null}),import_react51.default.createElement("div",{className:"toc-wrapper"})))));var{document:document3,window:globalWindow3}=import_global7.global,DocsContainer=({context,theme,children})=>{let toc;try{toc=context.resolveOf("meta",["meta"]).preparedMeta.parameters?.docs?.toc}catch{toc=context?.projectAnnotations?.parameters?.docs?.toc}return(0,import_react52.useEffect)(()=>{let url;try{if(url=new URL(globalWindow3.parent.location.toString()),url.hash){let element=document3.getElementById(url.hash.substring(1));element&&setTimeout(()=>{scrollToElement(element)},200)}}catch{}}),import_react52.default.createElement(DocsContext.Provider,{value:context},import_react52.default.createElement(SourceContainer,{channel:context.channel},import_react52.default.createElement(import_theming29.ThemeProvider,{theme:(0,import_theming29.ensure)(theme)},import_react52.default.createElement(DocsPageWrapper,{toc:toc?import_react52.default.createElement(TableOfContents,{className:"sbdocs sbdocs-toc--custom",...toc}):null},children))))};var import_react60=__toESM(require("react"));var import_react53=__toESM(require("react"));var STORY_KIND_PATH_SEPARATOR=/\s*\/\s*/,extractTitle=title=>{let groups=title.trim().split(STORY_KIND_PATH_SEPARATOR);return groups?.[groups?.length-1]||title},Title2=props=>{let{children,of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let content=children||extractTitle(preparedMeta?.title);return content?import_react53.default.createElement(Title,{className:"sbdocs-title sb-unstyled"},content):null};var import_react54=__toESM(require("react")),import_client_logger5=require("@storybook/client-logger");var DEPRECATION_MIGRATION_LINK="https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#subtitle-block-and-parameterscomponentsubtitle",Subtitle2=props=>{let{of,children}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let{componentSubtitle,docs}=preparedMeta?.parameters||{};componentSubtitle&&(0,import_client_logger5.deprecate)(`Using 'parameters.componentSubtitle' property to subtitle stories is deprecated. See ${DEPRECATION_MIGRATION_LINK}`);let content=children||docs?.subtitle||componentSubtitle;return content?import_react54.default.createElement(Subtitle,{className:"sbdocs-subtitle sb-unstyled"},content):null};var import_react57=__toESM(require("react"));var import_react56=__toESM(require("react"));var import_react55=__toESM(require("react")),import_components33=require("@storybook/components");var Subheading=({children,disableAnchor})=>{if(disableAnchor||typeof children!="string")return import_react55.default.createElement(import_components33.H3,null,children);let tagID=globalThis.encodeURIComponent(children.toLowerCase());return import_react55.default.createElement(HeaderMdx,{as:"h3",id:tagID},children)};var DocsStory=({of,expanded=!0,withToolbar:withToolbarProp=!1,__forceInitialArgs=!1,__primary=!1})=>{let{story}=useOf(of||"story",["story"]),withToolbar=story.parameters.docs?.canvas?.withToolbar??withToolbarProp;return import_react56.default.createElement(Anchor,{storyId:story.id},expanded&&import_react56.default.createElement(import_react56.default.Fragment,null,import_react56.default.createElement(Subheading,null,story.name),import_react56.default.createElement(DescriptionContainer,{of})),import_react56.default.createElement(Canvas,{of,withToolbar,story:{__forceInitialArgs,__primary},source:{__forceInitialArgs}}))};var Primary=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let{csfFile}=useOf(of||"meta",["meta"]),primaryStory=(0,import_react57.useContext)(DocsContext).componentStoriesFromCSFFile(csfFile)[0];return primaryStory?import_react57.default.createElement(DocsStory,{of:primaryStory.moduleExport,expanded:!1,__primary:!0,withToolbar:!0}):null};var import_react59=__toESM(require("react")),import_theming30=require("@storybook/theming");var import_react58=__toESM(require("react")),import_components34=require("@storybook/components");var Heading2=({children,disableAnchor,...props})=>{if(disableAnchor||typeof children!="string")return import_react58.default.createElement(import_components34.H2,null,children);let tagID=children.toLowerCase().replace(/[^a-z0-9]/gi,"-");return import_react58.default.createElement(HeaderMdx,{as:"h2",id:tagID,...props},children)};var StyledHeading=(0,import_theming30.styled)(Heading2)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,fontWeight:theme.typography.weight.bold,lineHeight:"16px",letterSpacing:"0.35em",textTransform:"uppercase",color:theme.textMutedColor,border:0,marginBottom:"12px","&:first-of-type":{marginTop:"56px"}})),Stories=({title="Stories",includePrimary=!0})=>{let{componentStories,projectAnnotations,getStoryContext}=(0,import_react59.useContext)(DocsContext),stories=componentStories(),{stories:{filter}={filter:void 0}}=projectAnnotations.parameters?.docs||{};return filter&&(stories=stories.filter(story=>filter(story,getStoryContext(story)))),stories.some(story=>story.tags?.includes("autodocs"))&&(stories=stories.filter(story=>story.tags?.includes("autodocs"))),includePrimary||(stories=stories.slice(1)),!stories||stories.length===0?null:import_react59.default.createElement(import_react59.default.Fragment,null,import_react59.default.createElement(StyledHeading,null,title),stories.map(story=>story&&import_react59.default.createElement(DocsStory,{key:story.id,of:story.moduleExport,expanded:!0,__forceInitialArgs:!0})))};var DocsPage=()=>{let resolvedOf=useOf("meta",["meta"]),{stories}=resolvedOf.csfFile,isSingleStory=Object.keys(stories).length===1;return import_react60.default.createElement(import_react60.default.Fragment,null,import_react60.default.createElement(Title2,null),import_react60.default.createElement(Subtitle2,null),import_react60.default.createElement(DescriptionContainer,{of:"meta"}),isSingleStory?import_react60.default.createElement(DescriptionContainer,{of:"story"}):null,import_react60.default.createElement(Primary,null),import_react60.default.createElement(Controls3,null),isSingleStory?null:import_react60.default.createElement(Stories,null))};function Docs({context,docsParameter}){let Container=docsParameter.container||DocsContainer,Page=docsParameter.page||DocsPage;return import_react61.default.createElement(Container,{context,theme:docsParameter.theme},import_react61.default.createElement(Page,null))}var import_react62=__toESM(require("react")),import_preview_api5=require("@storybook/preview-api");var import_preview_api4=require("@storybook/preview-api"),import_channels=require("@storybook/channels");var import_preview_api3=require("@storybook/preview-api"),ExternalDocsContext=class extends import_preview_api3.DocsContext{constructor(channel,store,renderStoryToElement,processMetaExports){super(channel,store,renderStoryToElement,[]);this.channel=channel;this.store=store;this.renderStoryToElement=renderStoryToElement;this.processMetaExports=processMetaExports;this.referenceMeta=(metaExports,attach)=>{let csfFile=this.processMetaExports(metaExports);this.referenceCSFFile(csfFile),super.referenceMeta(metaExports,attach)}}};var ConstantMap=class{constructor(prefix){this.prefix=prefix;this.entries=new Map}get(key){return this.entries.has(key)||this.entries.set(key,`${this.prefix}${this.entries.size}`),this.entries.get(key)}},ExternalPreview=class extends import_preview_api4.Preview{constructor(projectAnnotations){super(path=>Promise.resolve(this.moduleExportsByImportPath[path]),()=>(0,import_preview_api4.composeConfigs)([{parameters:{docs:{story:{inline:!0}}}},this.projectAnnotations]),new import_channels.Channel({}));this.projectAnnotations=projectAnnotations;this.importPaths=new ConstantMap("./importPath/");this.titles=new ConstantMap("title-");this.storyIndex={v:5,entries:{}};this.moduleExportsByImportPath={};this.processMetaExports=metaExports=>{let importPath=this.importPaths.get(metaExports);this.moduleExportsByImportPath[importPath]=metaExports;let title=metaExports.default.title||this.titles.get(metaExports),csfFile=this.storyStoreValue.processCSFFileWithCache(metaExports,importPath,title);return Object.values(csfFile.stories).forEach(({id:id2,name})=>{this.storyIndex.entries[id2]={id:id2,importPath,title,name,type:"story"}}),this.onStoriesChanged({storyIndex:this.storyIndex}),csfFile};this.docsContext=()=>new ExternalDocsContext(this.channel,this.storyStoreValue,this.renderStoryToElement.bind(this),this.processMetaExports.bind(this))}async getStoryIndexFromServer(){return this.storyIndex}};function usePreview(projectAnnotations){let previewRef=(0,import_react62.useRef)();return previewRef.current||(previewRef.current=new ExternalPreview(projectAnnotations)),previewRef.current}function ExternalDocs({projectAnnotationsList,children}){let projectAnnotations=(0,import_preview_api5.composeConfigs)(projectAnnotationsList),preview2=usePreview(projectAnnotations),docsParameter={...projectAnnotations.parameters?.docs,page:()=>children};return import_react62.default.createElement(Docs,{docsParameter,context:preview2.docsContext()})}var import_react63=__toESM(require("react")),import_theming31=require("@storybook/theming");var preview,ExternalDocsContainer=({projectAnnotations,children})=>(preview||(preview=new ExternalPreview(projectAnnotations)),import_react63.default.createElement(DocsContext.Provider,{value:preview.docsContext()},import_react63.default.createElement(import_theming31.ThemeProvider,{theme:(0,import_theming31.ensure)(import_theming31.themes.light)},children)));var import_react64=__toESM(require("react"));var Meta=({of})=>{let context=(0,import_react64.useContext)(DocsContext);of&&context.referenceMeta(of,!0);try{let primary=context.storyById();return import_react64.default.createElement(Anchor,{storyId:primary.id})}catch{return null}};var import_react65=__toESM(require("react")),Unstyled=props=>import_react65.default.createElement("div",{...props,className:"sb-unstyled"});var import_react66=__toESM(require("react")),Wrapper12=({children})=>import_react66.default.createElement("div",{style:{fontFamily:"sans-serif"}},children);var PRIMARY_STORY="^";0&&(module.exports={AddContext,Anchor,AnchorMdx,ArgTypes,BooleanControl,Canvas,CodeOrSourceMdx,ColorControl,ColorItem,ColorPalette,Controls,DateControl,Description,DescriptionType,Docs,DocsContainer,DocsContext,DocsPage,DocsStory,ExternalDocs,ExternalDocsContainer,FilesControl,HeaderMdx,HeadersMdx,Heading,IconGallery,IconItem,Markdown,Meta,NumberControl,ObjectControl,OptionsControl,PRIMARY_STORY,Primary,PureArgsTable,RangeControl,Source,SourceContainer,SourceContext,Stories,Story,Subheading,Subtitle,TextControl,Title,Typeset,UNKNOWN_ARGS_HASH,Unstyled,Wrapper,anchorBlockIdFromId,argsHash,assertIsFn,extractTitle,format,formatDate,formatTime,getStoryId,getStoryProps,parse,parseDate,parseTime,useOf,useSourceProps}); -+ `);return import_react49.default.createElement(import_markdown_to_jsx2.default,{...props,options:{forceBlock:!0,overrides:{code:CodeOrSourceMdx,a:AnchorMdx,...HeadersMdx,...props?.options?.overrides},...props?.options}})};var DescriptionType=(DescriptionType2=>(DescriptionType2.INFO="info",DescriptionType2.NOTES="notes",DescriptionType2.DOCGEN="docgen",DescriptionType2.AUTO="auto",DescriptionType2))(DescriptionType||{}),getDescriptionFromResolvedOf=resolvedOf=>{switch(resolvedOf.type){case"story":return resolvedOf.story.parameters.docs?.description?.story||null;case"meta":{let{parameters,component}=resolvedOf.preparedMeta,metaDescription=parameters.docs?.description?.component;return metaDescription||parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}case"component":{let{component,projectAnnotations:{parameters}}=resolvedOf;return parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}default:throw new Error(`Unrecognized module type resolved from 'useOf', got: ${resolvedOf.type}`)}},DescriptionContainer=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let resolvedOf=useOf(of||"meta"),markdown=getDescriptionFromResolvedOf(resolvedOf);return markdown?import_react50.default.createElement(Markdown2,null,markdown):null};var import_react61=__toESM(require("react"));var import_react52=__toESM(require("react")),import_global7=require("@storybook/global"),import_theming29=require("@storybook/theming");var import_react51=__toESM(require("react")),import_theming28=require("@storybook/theming"),tocbot=__toESM(require("tocbot")),Wrapper11=import_theming28.styled.div(({theme})=>({width:"10rem","@media (max-width: 768px)":{display:"none"}})),Content=import_theming28.styled.div(({theme})=>({position:"fixed",bottom:0,top:0,width:"10rem",paddingTop:"4rem",paddingBottom:"2rem",overflowY:"auto",fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s2,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch","& *":{boxSizing:"border-box"},"& > .toc-wrapper > .toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`}}},"& .toc-list-item":{position:"relative",listStyleType:"none",marginLeft:20,paddingTop:3,paddingBottom:3},"& .toc-list-item::before":{content:'""',position:"absolute",height:"100%",top:0,left:0,transform:"translateX(calc(-2px - 20px))",borderLeft:`solid 2px ${theme.color.mediumdark}`,opacity:0,transition:"opacity 0.2s"},"& .toc-list-item.is-active-li::before":{opacity:1},"& .toc-list-item > a":{color:theme.color.defaultText,textDecoration:"none"},"& .toc-list-item.is-active-li > a":{fontWeight:600,color:theme.color.secondary,textDecoration:"none"}})),Heading=import_theming28.styled.p(({theme})=>({fontWeight:600,fontSize:"0.875em",color:theme.textColor,textTransform:"uppercase",marginBottom:10})),OptionalTitle=({title})=>title===null?null:typeof title=="string"?import_react51.default.createElement(Heading,null,title):title,TableOfContents=({title,disable,headingSelector,contentsSelector,ignoreSelector,unsafeTocbotOptions})=>((0,import_react51.useEffect)(()=>{let configuration={tocSelector:".toc-wrapper",contentSelector:contentsSelector??".sbdocs-content",headingSelector:headingSelector??"h3",ignoreSelector:ignoreSelector??".docs-story *, .skip-toc",headingsOffset:40,scrollSmoothOffset:-40,orderedList:!1,onClick:()=>!1,...unsafeTocbotOptions},timeout=setTimeout(()=>tocbot.init(configuration),100);return()=>{clearTimeout(timeout),tocbot.destroy()}},[disable]),import_react51.default.createElement(import_react51.default.Fragment,null,import_react51.default.createElement(Wrapper11,null,disable?null:import_react51.default.createElement(Content,null,import_react51.default.createElement(OptionalTitle,{title:title||null}),import_react51.default.createElement("div",{className:"toc-wrapper"})))));var{document:document3,window:globalWindow3}=import_global7.global,DocsContainer=({context,theme,children})=>{let toc;try{toc=context.resolveOf("meta",["meta"]).preparedMeta.parameters?.docs?.toc}catch{toc=context?.projectAnnotations?.parameters?.docs?.toc}return(0,import_react52.useEffect)(()=>{let url;try{if(url=new URL(globalWindow3.parent.location.toString()),url.hash){let element=document3.getElementById(url.hash.substring(1));element&&setTimeout(()=>{scrollToElement(element)},200)}}catch{}}),import_react52.default.createElement(DocsContext.Provider,{value:context},import_react52.default.createElement(SourceContainer,{channel:context.channel},import_react52.default.createElement(import_theming29.ThemeProvider,{theme:(0,import_theming29.ensure)(theme)},import_react52.default.createElement(DocsPageWrapper,{toc:toc?import_react52.default.createElement(TableOfContents,{className:"sbdocs sbdocs-toc--custom",...toc}):null},children))))};var import_react60=__toESM(require("react"));var import_react53=__toESM(require("react"));var STORY_KIND_PATH_SEPARATOR=/\s*\/\s*/,extractTitle=title=>{let groups=title.trim().split(STORY_KIND_PATH_SEPARATOR);return groups?.[groups?.length-1]||title},Title2=props=>{let{children,of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let content=children||extractTitle(preparedMeta?.title);return content?import_react53.default.createElement(Title,{className:"sbdocs-title sb-unstyled"},content):null};var import_react54=__toESM(require("react")),import_client_logger5=require("@storybook/client-logger");var DEPRECATION_MIGRATION_LINK="https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#subtitle-block-and-parameterscomponentsubtitle",Subtitle2=props=>{let{of,children}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let{componentSubtitle,docs}=preparedMeta?.parameters||{};componentSubtitle&&(0,import_client_logger5.deprecate)(`Using 'parameters.componentSubtitle' property to subtitle stories is deprecated. See ${DEPRECATION_MIGRATION_LINK}`);let content=children||docs?.subtitle||componentSubtitle;return content?import_react54.default.createElement(Subtitle,{className:"sbdocs-subtitle sb-unstyled"},content):null};var import_react57=__toESM(require("react"));var import_react56=__toESM(require("react"));var import_react55=__toESM(require("react")),import_components33=require("@storybook/components");var Subheading=({children,disableAnchor})=>{if(disableAnchor||typeof children!="string")return import_react55.default.createElement(import_components33.H3,null,children);let tagID=globalThis.encodeURIComponent(children.toLowerCase());return import_react55.default.createElement(HeaderMdx,{as:"h3",id:tagID},children)};var DocsStory=({of,expanded=!0,withToolbar:withToolbarProp=!1,__forceInitialArgs=!1,__primary=!1})=>{let{story}=useOf(of||"story",["story"]),withToolbar=story.parameters.docs?.canvas?.withToolbar??withToolbarProp;return import_react56.default.createElement(Anchor,{storyId:story.id},expanded&&import_react56.default.createElement(import_react56.default.Fragment,null,import_react56.default.createElement(Subheading,null,story.name),import_react56.default.createElement(DescriptionContainer,{of})),import_react56.default.createElement(Canvas,{of,withToolbar,story:{__forceInitialArgs,__primary},source:{__forceInitialArgs}}))};var Primary=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let{csfFile}=useOf(of||"meta",["meta"]),primaryStory=(0,import_react57.useContext)(DocsContext).componentStoriesFromCSFFile(csfFile)[0];return primaryStory?import_react57.default.createElement(DocsStory,{of:primaryStory.moduleExport,expanded:!1,__primary:!0,withToolbar:!0}):null};var import_react59=__toESM(require("react")),import_theming30=require("@storybook/theming");var import_react58=__toESM(require("react")),import_components34=require("@storybook/components");var Heading2=({children,disableAnchor,...props})=>{if(disableAnchor||typeof children!="string")return import_react58.default.createElement(import_components34.H2,null,children);let tagID=children.toLowerCase().replace(/[^a-z0-9]/gi,"-");return import_react58.default.createElement(HeaderMdx,{as:"h2",id:tagID,...props},children)};var StyledHeading=(0,import_theming30.styled)(Heading2)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,fontWeight:theme.typography.weight.bold,lineHeight:"16px",letterSpacing:"0.35em",textTransform:"uppercase",color:theme.textMutedColor,border:0,marginBottom:"12px","&:first-of-type":{marginTop:"56px"}})),Stories=({title="Stories",includePrimary=!0})=>{let{componentStories,projectAnnotations,getStoryContext}=(0,import_react59.useContext)(DocsContext),stories=componentStories(),{stories:{filter}={filter:void 0}}=projectAnnotations.parameters?.docs||{};return filter&&(stories=stories.filter(story=>filter(story,getStoryContext(story)))),stories.some(story=>story.tags?.includes("autodocs"))&&(stories=stories.filter(story=>story.tags?.includes("autodocs"))),includePrimary||(stories=stories.slice(1)),!stories||stories.length===0?null:import_react59.default.createElement(import_react59.default.Fragment,null,import_react59.default.createElement(StyledHeading,null,title),stories.map(story=>story&&import_react59.default.createElement(DocsStory,{key:story.id,of:story.moduleExport,expanded:!0,__forceInitialArgs:!0})))};var DocsPage=()=>{let resolvedOf=useOf("meta",["meta"]),{stories}=resolvedOf.csfFile,isSingleStory=Object.keys(stories).length===1;return import_react60.default.createElement(import_react60.default.Fragment,null,import_react60.default.createElement(Title2,null),import_react60.default.createElement(Subtitle2,null),import_react60.default.createElement(DescriptionContainer,{of:"meta"}),isSingleStory?import_react60.default.createElement(DescriptionContainer,{of:"story"}):null,import_react60.default.createElement(Primary,null),import_react60.default.createElement(Controls3,null),isSingleStory?null:import_react60.default.createElement(Stories,null))};function Docs({context,docsParameter}){let Container=docsParameter.container||DocsContainer,Page=docsParameter.page||DocsPage;return import_react61.default.createElement(Container,{context,theme:docsParameter.theme},import_react61.default.createElement(Page,null))}var import_react62=__toESM(require("react")),import_preview_api5=require("@storybook/preview-api");var import_preview_api4=require("@storybook/preview-api"),import_channels=require("@storybook/channels");var import_preview_api3=require("@storybook/preview-api"),ExternalDocsContext=class extends import_preview_api3.DocsContext{constructor(channel,store,renderStoryToElement,processMetaExports){super(channel,store,renderStoryToElement,[]);this.channel=channel;this.store=store;this.renderStoryToElement=renderStoryToElement;this.processMetaExports=processMetaExports;this.referenceMeta=(metaExports,attach)=>{let csfFile=this.processMetaExports(metaExports);this.referenceCSFFile(csfFile),super.referenceMeta(metaExports,attach)}}};var ConstantMap=class{constructor(prefix){this.prefix=prefix;this.entries=new Map}get(key){return this.entries.has(key)||this.entries.set(key,`${this.prefix}${this.entries.size}`),this.entries.get(key)}},ExternalPreview=class extends import_preview_api4.Preview{constructor(projectAnnotations){super(path=>Promise.resolve(this.moduleExportsByImportPath[path]),()=>(0,import_preview_api4.composeConfigs)([{parameters:{docs:{story:{inline:!0}}}},this.projectAnnotations]),new import_channels.Channel({}));this.projectAnnotations=projectAnnotations;this.importPaths=new ConstantMap("./importPath/");this.titles=new ConstantMap("title-");this.storyIndex={v:5,entries:{}};this.moduleExportsByImportPath={};this.processMetaExports=metaExports=>{let importPath=this.importPaths.get(metaExports);this.moduleExportsByImportPath[importPath]=metaExports;let title=metaExports.default.title||this.titles.get(metaExports),csfFile=this.storyStoreValue.processCSFFileWithCache(metaExports,importPath,title);return Object.values(csfFile.stories).forEach(({id:id2,name})=>{this.storyIndex.entries[id2]={id:id2,importPath,title,name,type:"story"}}),this.onStoriesChanged({storyIndex:this.storyIndex}),csfFile};this.docsContext=()=>new ExternalDocsContext(this.channel,this.storyStoreValue,this.renderStoryToElement.bind(this),this.processMetaExports.bind(this))}async getStoryIndexFromServer(){return this.storyIndex}};function usePreview(projectAnnotations){let previewRef=(0,import_react62.useRef)();return previewRef.current||(previewRef.current=new ExternalPreview(projectAnnotations)),previewRef.current}function ExternalDocs({projectAnnotationsList,children}){let projectAnnotations=(0,import_preview_api5.composeConfigs)(projectAnnotationsList),preview2=usePreview(projectAnnotations),docsParameter={...projectAnnotations.parameters?.docs,page:()=>children};return import_react62.default.createElement(Docs,{docsParameter,context:preview2.docsContext()})}var import_react63=__toESM(require("react")),import_theming31=require("@storybook/theming");var preview,ExternalDocsContainer=({projectAnnotations,children})=>(preview||(preview=new ExternalPreview(projectAnnotations)),import_react63.default.createElement(DocsContext.Provider,{value:preview.docsContext()},import_react63.default.createElement(import_theming31.ThemeProvider,{theme:(0,import_theming31.ensure)(import_theming31.themes.light)},children)));var import_react64=__toESM(require("react"));var Meta=({of})=>{let context=(0,import_react64.useContext)(DocsContext);of&&context.referenceMeta(of,!0);try{let primary=context.storyById();return import_react64.default.createElement(Anchor,{storyId:primary.id})}catch{return null}};var import_react65=__toESM(require("react")),Unstyled=props=>import_react65.default.createElement("div",{...props,className:"sb-unstyled"});var import_react66=__toESM(require("react")),Wrapper12=({children})=>import_react66.default.createElement("div",{style:{fontFamily:"sans-serif"}},children);var PRIMARY_STORY="^";0&&(module.exports={AddContext,Anchor,AnchorMdx,ArgTypes,BooleanControl,Canvas,CodeOrSourceMdx,ColorControl,ColorItem,ColorPalette,Controls,DateControl,Description,DescriptionType,Docs,DocsContainer,DocsContext,DocsPage,DocsStory,ExternalDocs,ExternalDocsContainer,FilesControl,HeaderMdx,HeadersMdx,Heading,IconGallery,IconItem,Markdown,Meta,NumberControl,ObjectControl,OptionsControl,PRIMARY_STORY,Preview,Primary,PureArgsTable,RangeControl,Source,SourceContainer,SourceContext,Stories,Story,Subheading,Subtitle,TextControl,Title,Typeset,UNKNOWN_ARGS_HASH,Unstyled,Wrapper,anchorBlockIdFromId,argsHash,assertIsFn,extractTitle,format,formatDate,formatTime,getStoryId,getStoryProps,parse,parseDate,parseTime,useOf,useSourceProps}); -diff --git a/dist/index.mjs b/dist/index.mjs -index a626b2de91e14417fad86dbea9cc5c4dac49a222..0cf497db98ebdf4961a06e25ca634f7081a68665 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -20,27 +20,27 @@ import dedent from 'ts-dedent'; - import * as tocbot from 'tocbot'; - import { Channel } from '@storybook/channels'; - --var Wrapper=styled.div(withReset,({theme})=>({backgroundColor:theme.base==="light"?"rgba(0,0,0,.01)":"rgba(255,255,255,.01)",borderRadius:theme.appBorderRadius,border:`1px dashed ${theme.appBorderColor}`,display:"flex",alignItems:"center",justifyContent:"center",padding:20,margin:"25px 0 40px",color:transparentize(.3,theme.color.defaultText),fontSize:theme.typography.size.s2})),EmptyBlock=props=>React17.createElement(Wrapper,{...props,className:"docblock-emptyblock sb-unstyled"});var StyledSyntaxHighlighter=styled(SyntaxHighlighter)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,lineHeight:"19px",margin:"25px 0 40px",borderRadius:theme.appBorderRadius,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0","pre.prismjs":{padding:20,background:"inherit"}}));var SourceSkeletonWrapper=styled.div(({theme})=>({background:theme.background.content,borderRadius:theme.appBorderRadius,border:`1px solid ${theme.appBorderColor}`,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",margin:"25px 0 40px",padding:"20px 20px 20px 22px"})),SourceSkeletonPlaceholder=styled.div(({theme})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,height:17,marginTop:1,width:"60%",[`&:first-child${ignoreSsrWarning}`]:{margin:0}})),SourceSkeleton=()=>React17.createElement(SourceSkeletonWrapper,null,React17.createElement(SourceSkeletonPlaceholder,null),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}}),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"30%"}}),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}})),Source=({isLoading,error,language,code,dark,format:format2=!1,...rest})=>{let{typography}=useTheme();if(isLoading)return React17.createElement(SourceSkeleton,null);if(error)return React17.createElement(EmptyBlock,null,error);let syntaxHighlighter=React17.createElement(StyledSyntaxHighlighter,{bordered:!0,copyable:!0,format:format2,language,className:"docblock-source sb-unstyled",...rest},code);if(typeof dark>"u")return syntaxHighlighter;let overrideTheme=dark?themes.dark:themes.light;return React17.createElement(ThemeProvider,{theme:convert({...overrideTheme,fontCode:typography.fonts.mono,fontBase:typography.fonts.base})},syntaxHighlighter)};var toGlobalSelector=element=>`& :where(${element}:not(.sb-anchor, .sb-unstyled, .sb-unstyled ${element}))`,breakpoint=600,Title=styled.h1(withReset,({theme})=>({color:theme.color.defaultText,fontSize:theme.typography.size.m3,fontWeight:theme.typography.weight.bold,lineHeight:"32px",[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.l1,lineHeight:"36px",marginBottom:"16px"}})),Subtitle=styled.h2(withReset,({theme})=>({fontWeight:theme.typography.weight.regular,fontSize:theme.typography.size.s3,lineHeight:"20px",borderBottom:"none",marginBottom:15,[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.m1,lineHeight:"28px",marginBottom:24},color:transparentize(.25,theme.color.defaultText)})),DocsContent=styled.div(({theme})=>{let reset={fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s3,margin:0,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch"},headers={margin:"20px 0 8px",padding:0,cursor:"text",position:"relative",color:theme.color.defaultText,"&:first-of-type":{marginTop:0,paddingTop:0},"&:hover a.anchor":{textDecoration:"none"},"& code":{fontSize:"inherit"}},code={lineHeight:1,margin:"0 2px",padding:"3px 5px",whiteSpace:"nowrap",borderRadius:3,fontSize:theme.typography.size.s2-1,border:theme.base==="light"?`1px solid ${theme.color.mediumlight}`:`1px solid ${theme.color.darker}`,color:theme.base==="light"?transparentize(.1,theme.color.defaultText):transparentize(.3,theme.color.defaultText),backgroundColor:theme.base==="light"?theme.color.lighter:theme.color.border};return {maxWidth:1e3,width:"100%",[toGlobalSelector("a")]:{...reset,fontSize:"inherit",lineHeight:"24px",color:theme.color.secondary,textDecoration:"none","&.absent":{color:"#cc0000"},"&.anchor":{display:"block",paddingLeft:30,marginLeft:-30,cursor:"pointer",position:"absolute",top:0,left:0,bottom:0}},[toGlobalSelector("blockquote")]:{...reset,margin:"16px 0",borderLeft:`4px solid ${theme.color.medium}`,padding:"0 15px",color:theme.color.dark,"& > :first-of-type":{marginTop:0},"& > :last-child":{marginBottom:0}},[toGlobalSelector("div")]:reset,[toGlobalSelector("dl")]:{...reset,margin:"16px 0",padding:0,"& dt":{fontSize:"14px",fontWeight:"bold",fontStyle:"italic",padding:0,margin:"16px 0 4px"},"& dt:first-of-type":{padding:0},"& dt > :first-of-type":{marginTop:0},"& dt > :last-child":{marginBottom:0},"& dd":{margin:"0 0 16px",padding:"0 15px"},"& dd > :first-of-type":{marginTop:0},"& dd > :last-child":{marginBottom:0}},[toGlobalSelector("h1")]:{...reset,...headers,fontSize:`${theme.typography.size.l1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h2")]:{...reset,...headers,fontSize:`${theme.typography.size.m2}px`,paddingBottom:4,borderBottom:`1px solid ${theme.appBorderColor}`},[toGlobalSelector("h3")]:{...reset,...headers,fontSize:`${theme.typography.size.m1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h4")]:{...reset,...headers,fontSize:`${theme.typography.size.s3}px`},[toGlobalSelector("h5")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`},[toGlobalSelector("h6")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`,color:theme.color.dark},[toGlobalSelector("hr")]:{border:"0 none",borderTop:`1px solid ${theme.appBorderColor}`,height:4,padding:0},[toGlobalSelector("img")]:{maxWidth:"100%"},[toGlobalSelector("li")]:{...reset,fontSize:theme.typography.size.s2,color:theme.color.defaultText,lineHeight:"24px","& + li":{marginTop:".25em"},"& ul, & ol":{marginTop:".25em",marginBottom:0},"& code":code},[toGlobalSelector("ol")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0}},[toGlobalSelector("p")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",color:theme.color.defaultText,"& code":code},[toGlobalSelector("pre")]:{...reset,fontFamily:theme.typography.fonts.mono,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",lineHeight:"18px",padding:"11px 1rem",whiteSpace:"pre-wrap",color:"inherit",borderRadius:3,margin:"1rem 0","&:not(.prismjs)":{background:"transparent",border:"none",borderRadius:0,padding:0,margin:0},"& pre, &.prismjs":{padding:15,margin:0,whiteSpace:"pre-wrap",color:"inherit",fontSize:"13px",lineHeight:"19px",code:{color:"inherit",fontSize:"inherit"}},"& code":{whiteSpace:"pre"},"& code, & tt":{border:"none"}},[toGlobalSelector("span")]:{...reset,"&.frame":{display:"block",overflow:"hidden","& > span":{border:`1px solid ${theme.color.medium}`,display:"block",float:"left",overflow:"hidden",margin:"13px 0 0",padding:7,width:"auto"},"& span img":{display:"block",float:"left"},"& span span":{clear:"both",color:theme.color.darkest,display:"block",padding:"5px 0 0"}},"&.align-center":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"center"},"& span img":{margin:"0 auto",textAlign:"center"}},"&.align-right":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px 0 0",textAlign:"right"},"& span img":{margin:0,textAlign:"right"}},"&.float-left":{display:"block",marginRight:13,overflow:"hidden",float:"left","& span":{margin:"13px 0 0"}},"&.float-right":{display:"block",marginLeft:13,overflow:"hidden",float:"right","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"right"}}},[toGlobalSelector("table")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",padding:0,borderCollapse:"collapse","& tr":{borderTop:`1px solid ${theme.appBorderColor}`,backgroundColor:theme.appContentBg,margin:0,padding:0},"& tr:nth-of-type(2n)":{backgroundColor:theme.base==="dark"?theme.color.darker:theme.color.lighter},"& tr th":{fontWeight:"bold",color:theme.color.defaultText,border:`1px solid ${theme.appBorderColor}`,margin:0,padding:"6px 13px"},"& tr td":{border:`1px solid ${theme.appBorderColor}`,color:theme.color.defaultText,margin:0,padding:"6px 13px"},"& tr th :first-of-type, & tr td :first-of-type":{marginTop:0},"& tr th :last-child, & tr td :last-child":{marginBottom:0}},[toGlobalSelector("ul")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0},listStyle:"disc"}}}),DocsWrapper=styled.div(({theme})=>({background:theme.background.content,display:"flex",justifyContent:"center",padding:"4rem 20px",minHeight:"100vh",boxSizing:"border-box",gap:"3rem",[`@media (min-width: ${breakpoint}px)`]:{}})),DocsPageWrapper=({children,toc})=>React17.createElement(DocsWrapper,{className:"sbdocs sbdocs-wrapper"},React17.createElement(DocsContent,{className:"sbdocs sbdocs-content"},children),toc);var getBlockBackgroundStyle=theme=>({borderRadius:theme.appBorderRadius,background:theme.background.content,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",border:`1px solid ${theme.appBorderColor}`});var Bar=styled(FlexBar)({position:"absolute",left:0,right:0,top:0,transition:"transform .2s linear"}),Wrapper2=styled.div({display:"flex",alignItems:"center",gap:4}),IconPlaceholder=styled.div(({theme})=>({width:14,height:14,borderRadius:2,margin:"0 7px",backgroundColor:theme.appBorderColor,animation:`${theme.animation.glow} 1.5s ease-in-out infinite`})),Toolbar=({isLoading,storyId,baseUrl,zoom,resetZoom,...rest})=>React17.createElement(Bar,{...rest},React17.createElement(Wrapper2,{key:"left"},isLoading?[1,2,3].map(key=>React17.createElement(IconPlaceholder,{key})):React17.createElement(React17.Fragment,null,React17.createElement(IconButton,{key:"zoomin",onClick:e=>{e.preventDefault(),zoom(.8);},title:"Zoom in"},React17.createElement(ZoomIcon,null)),React17.createElement(IconButton,{key:"zoomout",onClick:e=>{e.preventDefault(),zoom(1.25);},title:"Zoom out"},React17.createElement(ZoomOutIcon,null)),React17.createElement(IconButton,{key:"zoomreset",onClick:e=>{e.preventDefault(),resetZoom();},title:"Reset zoom"},React17.createElement(ZoomResetIcon,null)))));var ZoomContext=createContext({scale:1});var{window:globalWindow}=global,IFrame=class extends Component{constructor(){super(...arguments);this.iframe=null;}componentDidMount(){let{id}=this.props;this.iframe=globalWindow.document.getElementById(id);}shouldComponentUpdate(nextProps){let{scale}=nextProps;return scale!==this.props.scale&&this.setIframeBodyStyle({width:`${scale*100}%`,height:`${scale*100}%`,transform:`scale(${1/scale})`,transformOrigin:"top left"}),!1}setIframeBodyStyle(style){return Object.assign(this.iframe.contentDocument.body.style,style)}render(){let{id,title,src,allowFullScreen,scale,...rest}=this.props;return React17.createElement("iframe",{id,title,src,...allowFullScreen?{allow:"fullscreen"}:{},loading:"lazy",...rest})}};var{PREVIEW_URL}=global,BASE_URL=PREVIEW_URL||"iframe.html",storyBlockIdFromId=({story,primary})=>`story--${story.id}${primary?"--primary":""}`,InlineStory=props=>{let storyRef=useRef(),[showLoader,setShowLoader]=useState(!0),[error,setError]=useState(),{story,height,autoplay,forceInitialArgs,renderStoryToElement}=props;return useEffect(()=>{if(!(story&&storyRef.current))return ()=>{};let element=storyRef.current,cleanup=renderStoryToElement(story,element,{showMain:()=>{},showError:({title,description})=>setError(new Error(`${title} - ${description}`)),showException:err=>setError(err)},{autoplay,forceInitialArgs});return setShowLoader(!1),()=>{Promise.resolve().then(()=>cleanup());}},[autoplay,renderStoryToElement,story]),error?React17.createElement("pre",null,React17.createElement(ErrorFormatter,{error})):React17.createElement(React17.Fragment,null,height?React17.createElement("style",null,`#${storyBlockIdFromId(props)} { min-height: ${height}; transform: translateZ(0); overflow: auto }`):null,showLoader&&React17.createElement(StorySkeleton,null),React17.createElement("div",{ref:storyRef,id:`${storyBlockIdFromId(props)}-inner`,"data-name":story.name}))},IFrameStory=({story,height="500px"})=>React17.createElement("div",{style:{width:"100%",height}},React17.createElement(ZoomContext.Consumer,null,({scale})=>React17.createElement(IFrame,{key:"iframe",id:`iframe--${story.id}`,title:story.name,src:getStoryHref(BASE_URL,story.id,{viewMode:"story"}),allowFullScreen:!0,scale,style:{width:"100%",height:"100%",border:"0 none"}}))),Story=props=>{let{inline}=props;return React17.createElement("div",{id:storyBlockIdFromId(props),className:"sb-story sb-unstyled","data-story-block":"true"},inline?React17.createElement(InlineStory,{...props}):React17.createElement(IFrameStory,{...props}))},StorySkeleton=()=>React17.createElement(Loader,null);var ChildrenContainer=styled.div(({isColumn,columns,layout})=>({display:isColumn||!columns?"block":"flex",position:"relative",flexWrap:"wrap",overflow:"auto",flexDirection:isColumn?"column":"row","& .innerZoomElementWrapper > *":isColumn?{width:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"block"}:{maxWidth:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"inline-block"}}),({layout="padded"})=>layout==="centered"||layout==="padded"?{padding:"30px 20px","& .innerZoomElementWrapper > *":{width:"auto",border:"10px solid transparent!important"}}:{},({layout="padded"})=>layout==="centered"?{display:"flex",justifyContent:"center",justifyItems:"center",alignContent:"center",alignItems:"center"}:{},({columns})=>columns&&columns>1?{".innerZoomElementWrapper > *":{minWidth:`calc(100% / ${columns} - 20px)`}}:{}),StyledSource=styled(Source)(({theme})=>({margin:0,borderTopLeftRadius:0,borderTopRightRadius:0,borderBottomLeftRadius:theme.appBorderRadius,borderBottomRightRadius:theme.appBorderRadius,border:"none",background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":darken(.05,theme.background.content),color:theme.color.lightest,button:{background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":darken(.05,theme.background.content)}})),PreviewContainer=styled.div(({theme,withSource,isExpanded})=>({position:"relative",overflow:"hidden",margin:"25px 0 40px",...getBlockBackgroundStyle(theme),borderBottomLeftRadius:withSource&&isExpanded&&0,borderBottomRightRadius:withSource&&isExpanded&&0,borderBottomWidth:isExpanded&&0,"h3 + &":{marginTop:"16px"}}),({withToolbar})=>withToolbar&&{paddingTop:40}),getSource=(withSource,expanded,setExpanded)=>{switch(!0){case!!(withSource&&withSource.error):return {source:null,actionItem:{title:"No code available",className:"docblock-code-toggle docblock-code-toggle--disabled",disabled:!0,onClick:()=>setExpanded(!1)}};case expanded:return {source:React17.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Hide code",className:"docblock-code-toggle docblock-code-toggle--expanded",onClick:()=>setExpanded(!1)}};default:return {source:React17.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Show code",className:"docblock-code-toggle",onClick:()=>setExpanded(!0)}}}};function getStoryId(children){if(Children.count(children)===1){let elt=children;if(elt.props)return elt.props.id}return null}var PositionedToolbar=styled(Toolbar)({position:"absolute",top:0,left:0,right:0,height:40}),Relative=styled.div({overflow:"hidden",position:"relative"}),Preview=({isLoading,isColumn,columns,children,withSource,withToolbar=!1,isExpanded=!1,additionalActions,className,layout="padded",...props})=>{let[expanded,setExpanded]=useState(isExpanded),{source,actionItem}=getSource(withSource,expanded,setExpanded),[scale,setScale]=useState(1),previewClasses=[className].concat(["sbdocs","sbdocs-preview","sb-unstyled"]),defaultActionItems=withSource?[actionItem]:[],[additionalActionItems,setAdditionalActionItems]=useState(additionalActions?[...additionalActions]:[]),actionItems=[...defaultActionItems,...additionalActionItems],{window:globalWindow4}=global,copyToClipboard=useCallback(async text=>{let{createCopyToClipboardFunction}=await import('@storybook/components');createCopyToClipboardFunction();},[]),onCopyCapture=e=>{let selection=globalWindow4.getSelection();selection&&selection.type==="Range"||(e.preventDefault(),additionalActionItems.filter(item=>item.title==="Copied").length===0&©ToClipboard(source.props.code).then(()=>{setAdditionalActionItems([...additionalActionItems,{title:"Copied",onClick:()=>{}}]),globalWindow4.setTimeout(()=>setAdditionalActionItems(additionalActionItems.filter(item=>item.title!=="Copied")),1500);}));};return React17.createElement(PreviewContainer,{withSource,withToolbar,...props,className:previewClasses.join(" ")},withToolbar&&React17.createElement(PositionedToolbar,{isLoading,border:!0,zoom:z=>setScale(scale*z),resetZoom:()=>setScale(1),storyId:getStoryId(children),baseUrl:"./iframe.html"}),React17.createElement(ZoomContext.Provider,{value:{scale}},React17.createElement(Relative,{className:"docs-story",onCopyCapture:withSource&&onCopyCapture},React17.createElement(ChildrenContainer,{isColumn:isColumn||!Array.isArray(children),columns,layout},React17.createElement(Zoom.Element,{scale},Array.isArray(children)?children.map((child,i)=>React17.createElement("div",{key:i},child)):React17.createElement("div",null,children))),React17.createElement(ActionBar,{actionItems}))),withSource&&expanded&&source)};styled(Preview)(()=>({".docs-story":{paddingTop:32,paddingBottom:40}}));var Table=styled.table(({theme})=>({"&&":{borderCollapse:"collapse",borderSpacing:0,border:"none",tr:{border:"none !important",background:"none"},"td, th":{padding:0,border:"none",width:"auto!important"},marginTop:0,marginBottom:0,"th:first-of-type, td:first-of-type":{paddingLeft:0},"th:last-of-type, td:last-of-type":{paddingRight:0},td:{paddingTop:0,paddingBottom:4,"&:not(:first-of-type)":{paddingLeft:10,paddingRight:0}},tbody:{boxShadow:"none",border:"none"},code:codeCommon({theme}),div:{span:{fontWeight:"bold"}},"& code":{margin:0,display:"inline-block",fontSize:theme.typography.size.s1}}})),ArgJsDoc=({tags})=>{let params=(tags.params||[]).filter(x=>x.description),hasDisplayableParams=params.length!==0,hasDisplayableDeprecated=tags.deprecated!=null,hasDisplayableReturns=tags.returns!=null&&tags.returns.description!=null;return !hasDisplayableParams&&!hasDisplayableReturns&&!hasDisplayableDeprecated?null:React17.createElement(React17.Fragment,null,React17.createElement(Table,null,React17.createElement("tbody",null,hasDisplayableDeprecated&&React17.createElement("tr",{key:"deprecated"},React17.createElement("td",{colSpan:2},React17.createElement("strong",null,"Deprecated"),": ",tags.deprecated.toString())),hasDisplayableParams&¶ms.map(x=>React17.createElement("tr",{key:x.name},React17.createElement("td",null,React17.createElement("code",null,x.name)),React17.createElement("td",null,x.description))),hasDisplayableReturns&&React17.createElement("tr",{key:"returns"},React17.createElement("td",null,React17.createElement("code",null,"Returns")),React17.createElement("td",null,tags.returns.description)))))};var ITEMS_BEFORE_EXPANSION=8,Summary=styled.div(({isExpanded})=>({display:"flex",flexDirection:isExpanded?"column":"row",flexWrap:"wrap",alignItems:"flex-start",marginBottom:"-4px",minWidth:100})),Text=styled.span(codeCommon,({theme,simple=!1})=>({flex:"0 0 auto",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,wordBreak:"break-word",whiteSpace:"normal",maxWidth:"100%",margin:0,marginRight:"4px",marginBottom:"4px",paddingTop:"2px",paddingBottom:"2px",lineHeight:"13px",...simple&&{background:"transparent",border:"0 none",paddingLeft:0}})),ExpandButton=styled.button(({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,marginBottom:"4px",background:"none",border:"none"})),Expandable=styled.div(codeCommon,({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,fontSize:theme.typography.size.s1,margin:0,whiteSpace:"nowrap",display:"flex",alignItems:"center"})),Detail=styled.div(({theme,width})=>({width,minWidth:200,maxWidth:800,padding:15,fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,boxSizing:"content-box","& code":{padding:"0 !important"}})),ChevronUpIcon=styled(ChevronSmallUpIcon)({marginLeft:4}),ChevronDownIcon=styled(ChevronSmallDownIcon)({marginLeft:4}),EmptyArg=()=>React17.createElement("span",null,"-"),ArgText=({text,simple})=>React17.createElement(Text,{simple},text),calculateDetailWidth=memoize(1e3)(detail=>{let lines=detail.split(/\r?\n/);return `${Math.max(...lines.map(x=>x.length))}ch`}),getSummaryItems=summary=>{if(!summary)return [summary];let summaryItems=summary.split("|").map(value2=>value2.trim());return uniq(summaryItems)},renderSummaryItems=(summaryItems,isExpanded=!0)=>{let items=summaryItems;return isExpanded||(items=summaryItems.slice(0,ITEMS_BEFORE_EXPANSION)),items.map(item=>React17.createElement(ArgText,{key:item,text:item===""?'""':item}))},ArgSummary=({value:value2,initialExpandedArgs})=>{let{summary,detail}=value2,[isOpen,setIsOpen]=useState(!1),[isExpanded,setIsExpanded]=useState(initialExpandedArgs||!1);if(summary==null)return null;let summaryAsString=typeof summary.toString=="function"?summary.toString():summary;if(detail==null){if(/[(){}[\]<>]/.test(summaryAsString))return React17.createElement(ArgText,{text:summaryAsString});let summaryItems=getSummaryItems(summaryAsString),itemsCount=summaryItems.length;return itemsCount>ITEMS_BEFORE_EXPANSION?React17.createElement(Summary,{isExpanded},renderSummaryItems(summaryItems,isExpanded),React17.createElement(ExpandButton,{onClick:()=>setIsExpanded(!isExpanded)},isExpanded?"Show less...":`Show ${itemsCount-ITEMS_BEFORE_EXPANSION} more...`)):React17.createElement(Summary,null,renderSummaryItems(summaryItems))}return React17.createElement(WithTooltipPure,{closeOnOutsideClick:!0,placement:"bottom",visible:isOpen,onVisibleChange:isVisible=>{setIsOpen(isVisible);},tooltip:React17.createElement(Detail,{width:calculateDetailWidth(detail)},React17.createElement(SyntaxHighlighter,{language:"jsx",format:!1},detail))},React17.createElement(Expandable,{className:"sbdocs-expandable"},React17.createElement("span",null,summaryAsString),isOpen?React17.createElement(ChevronUpIcon,null):React17.createElement(ChevronDownIcon,null)))},ArgValue=({value:value2,initialExpandedArgs})=>value2==null?React17.createElement(EmptyArg,null):React17.createElement(ArgSummary,{value:value2,initialExpandedArgs});var Label=styled.label(({theme})=>({lineHeight:"18px",alignItems:"center",marginBottom:8,display:"inline-block",position:"relative",whiteSpace:"nowrap",background:theme.boolean.background,borderRadius:"3em",padding:1,'&[aria-disabled="true"]':{opacity:.5,input:{cursor:"not-allowed"}},input:{appearance:"none",width:"100%",height:"100%",position:"absolute",left:0,top:0,margin:0,padding:0,border:"none",background:"transparent",cursor:"pointer",borderRadius:"3em","&:focus":{outline:"none",boxShadow:`${theme.color.secondary} 0 0 0 1px inset !important`}},span:{textAlign:"center",fontSize:theme.typography.size.s1,fontWeight:theme.typography.weight.bold,lineHeight:"1",cursor:"pointer",display:"inline-block",padding:"7px 15px",transition:"all 100ms ease-out",userSelect:"none",borderRadius:"3em",color:transparentize(.5,theme.color.defaultText),background:"transparent","&:hover":{boxShadow:`${opacify(.3,theme.appBorderColor)} 0 0 0 1px inset`},"&:active":{boxShadow:`${opacify(.05,theme.appBorderColor)} 0 0 0 2px inset`,color:opacify(1,theme.appBorderColor)},"&:first-of-type":{paddingRight:8},"&:last-of-type":{paddingLeft:8}},"input:checked ~ span:last-of-type, input:not(:checked) ~ span:first-of-type":{background:theme.boolean.selectedBackground,boxShadow:theme.base==="light"?`${opacify(.1,theme.appBorderColor)} 0 0 2px`:`${theme.appBorderColor} 0 0 0 1px`,color:theme.color.defaultText,padding:"7px 15px"}})),parse=value2=>value2==="true",BooleanControl=({name,value:value2,onChange,onBlur,onFocus,argType})=>{let onSetFalse=useCallback(()=>onChange(!1),[onChange]),readonly=!!argType?.table?.readonly;if(value2===void 0)return React17.createElement(Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onSetFalse,disabled:readonly},"Set boolean");let controlId=getControlId(name),parsedValue=typeof value2=="string"?parse(value2):value2;return React17.createElement(Label,{"aria-disabled":readonly,htmlFor:controlId,"aria-label":name},React17.createElement("input",{id:controlId,type:"checkbox",onChange:e=>onChange(e.target.checked),checked:parsedValue,role:"switch",disabled:readonly,name,onBlur,onFocus}),React17.createElement("span",{"aria-hidden":"true"},"False"),React17.createElement("span",{"aria-hidden":"true"},"True"))};var parseDate=value2=>{let[year,month,day]=value2.split("-"),result=new Date;return result.setFullYear(parseInt(year,10),parseInt(month,10)-1,parseInt(day,10)),result},parseTime=value2=>{let[hours,minutes]=value2.split(":"),result=new Date;return result.setHours(parseInt(hours,10)),result.setMinutes(parseInt(minutes,10)),result},formatDate=value2=>{let date=new Date(value2),year=`000${date.getFullYear()}`.slice(-4),month=`0${date.getMonth()+1}`.slice(-2),day=`0${date.getDate()}`.slice(-2);return `${year}-${month}-${day}`},formatTime=value2=>{let date=new Date(value2),hours=`0${date.getHours()}`.slice(-2),minutes=`0${date.getMinutes()}`.slice(-2);return `${hours}:${minutes}`},FormInput=styled(Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),FlexSpaced=styled.div(({theme})=>({flex:1,display:"flex",input:{marginLeft:10,flex:1,height:32,"&::-webkit-calendar-picker-indicator":{opacity:.5,height:12,filter:theme.base==="light"?void 0:"invert(1)"}},"input:first-of-type":{marginLeft:0,flexGrow:4},"input:last-of-type":{flexGrow:3}})),DateControl=({name,value:value2,onChange,onFocus,onBlur,argType})=>{let[valid,setValid]=useState(!0),dateRef=useRef(),timeRef=useRef(),readonly=!!argType?.table?.readonly;useEffect(()=>{valid!==!1&&(dateRef&&dateRef.current&&(dateRef.current.value=formatDate(value2)),timeRef&&timeRef.current&&(timeRef.current.value=formatTime(value2)));},[value2]);let onDateChange=e=>{let parsed=parseDate(e.target.value),result=new Date(value2);result.setFullYear(parsed.getFullYear(),parsed.getMonth(),parsed.getDate());let time=result.getTime();time&&onChange(time),setValid(!!time);},onTimeChange=e=>{let parsed=parseTime(e.target.value),result=new Date(value2);result.setHours(parsed.getHours()),result.setMinutes(parsed.getMinutes());let time=result.getTime();time&&onChange(time),setValid(!!time);},controlId=getControlId(name);return React17.createElement(FlexSpaced,null,React17.createElement(FormInput,{type:"date",max:"9999-12-31",ref:dateRef,id:`${controlId}-date`,name:`${controlId}-date`,readOnly:readonly,onChange:onDateChange,onFocus,onBlur}),React17.createElement(FormInput,{type:"time",id:`${controlId}-time`,name:`${controlId}-time`,ref:timeRef,onChange:onTimeChange,readOnly:readonly,onFocus,onBlur}),valid?null:React17.createElement("div",null,"invalid"))};var Wrapper3=styled.label({display:"flex"}),parse2=value2=>{let result=parseFloat(value2);return Number.isNaN(result)?void 0:result},format=value2=>value2!=null?String(value2):"",FormInput2=styled(Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),NumberControl=({name,value:value2,onChange,min,max,step,onBlur,onFocus,argType})=>{let[inputValue,setInputValue]=useState(typeof value2=="number"?value2:""),[forceVisible,setForceVisible]=useState(!1),[parseError,setParseError]=useState(null),readonly=!!argType?.table?.readonly,handleChange=useCallback(event=>{setInputValue(event.target.value);let result=parseFloat(event.target.value);Number.isNaN(result)?setParseError(new Error(`'${event.target.value}' is not a number`)):(onChange(result),setParseError(null));},[onChange,setParseError]),onForceVisible=useCallback(()=>{setInputValue("0"),onChange(0),setForceVisible(!0);},[setForceVisible]),htmlElRef=useRef(null);return useEffect(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select();},[forceVisible]),useEffect(()=>{inputValue!==(typeof value2=="number"?value2:"")&&setInputValue(value2);},[value2]),value2===void 0?React17.createElement(Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onForceVisible,disabled:readonly},"Set number"):React17.createElement(Wrapper3,null,React17.createElement(FormInput2,{ref:htmlElRef,id:getControlId(name),type:"number",onChange:handleChange,size:"flex",placeholder:"Edit number...",value:inputValue,valid:parseError?"error":null,autoFocus:forceVisible,readOnly:readonly,name,min,max,step,onFocus,onBlur}))};var selectedKey=(value2,options)=>{let entry=options&&Object.entries(options).find(([_key,val])=>val===value2);return entry?entry[0]:void 0},selectedKeys=(value2,options)=>value2&&options?Object.entries(options).filter(entry=>value2.includes(entry[1])).map(entry=>entry[0]):[],selectedValues=(keys,options)=>keys&&options&&keys.map(key=>options[key]);var Wrapper4=styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return {input:{cursor:"not-allowed"}}}),Text2=styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label2=styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),CheckboxControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return logger.warn(`Checkbox with no options: ${name}`),React17.createElement(React17.Fragment,null,"-");let initial=selectedKeys(value2,options),[selected,setSelected]=useState(initial),readonly=!!argType?.table?.readonly,handleChange=e=>{let option=e.target.value,updated=[...selected];updated.includes(option)?updated.splice(updated.indexOf(option),1):updated.push(option),onChange(selectedValues(updated,options)),setSelected(updated);};useEffect(()=>{setSelected(selectedKeys(value2,options));},[value2]);let controlId=getControlId(name);return React17.createElement(Wrapper4,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id=`${controlId}-${index}`;return React17.createElement(Label2,{key:id,htmlFor:id},React17.createElement("input",{type:"checkbox",disabled:readonly,id,name:id,value:key,onChange:handleChange,checked:selected?.includes(key)}),React17.createElement(Text2,null,key))}))};var Wrapper5=styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return {input:{cursor:"not-allowed"}}}),Text3=styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label3=styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),RadioControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return logger.warn(`Radio with no options: ${name}`),React17.createElement(React17.Fragment,null,"-");let selection=selectedKey(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(Wrapper5,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id=`${controlId}-${index}`;return React17.createElement(Label3,{key:id,htmlFor:id},React17.createElement("input",{type:"radio",id,name:id,disabled:readonly,value:key,onChange:e=>onChange(options[e.currentTarget.value]),checked:key===selection}),React17.createElement(Text3,null,key))}))};var styleResets={appearance:"none",border:"0 none",boxSizing:"inherit",display:" block",margin:" 0",background:"transparent",padding:0,fontSize:"inherit",position:"relative"},OptionsSelect=styled.select(styleResets,({theme})=>({boxSizing:"border-box",position:"relative",padding:"6px 10px",width:"100%",color:theme.input.color||"inherit",background:theme.input.background,borderRadius:theme.input.borderRadius,boxShadow:`${theme.input.border} 0 0 0 1px inset`,fontSize:theme.typography.size.s2-1,lineHeight:"20px","&:focus":{boxShadow:`${theme.color.secondary} 0 0 0 1px inset`,outline:"none"},"&[disabled]":{cursor:"not-allowed",opacity:.5},"::placeholder":{color:theme.textMutedColor},"&[multiple]":{overflow:"auto",padding:0,option:{display:"block",padding:"6px 10px",marginLeft:1,marginRight:1}}})),SelectWrapper=styled.span(({theme})=>({display:"inline-block",lineHeight:"normal",overflow:"hidden",position:"relative",verticalAlign:"top",width:"100%",svg:{position:"absolute",zIndex:1,pointerEvents:"none",height:"12px",marginTop:"-6px",right:"12px",top:"50%",fill:theme.textMutedColor,path:{fill:theme.textMutedColor}}})),NO_SELECTION="Choose option...",SingleSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{onChange(options[e.currentTarget.value]);},selection=selectedKey(value2,options)||NO_SELECTION,controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(SelectWrapper,null,React17.createElement(ChevronSmallDownIcon,null),React17.createElement(OptionsSelect,{disabled:readonly,id:controlId,value:selection,onChange:handleChange},React17.createElement("option",{key:"no-selection",disabled:!0},NO_SELECTION),Object.keys(options).map(key=>React17.createElement("option",{key,value:key},key))))},MultiSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{let selection2=Array.from(e.currentTarget.options).filter(option=>option.selected).map(option=>option.value);onChange(selectedValues(selection2,options));},selection=selectedKeys(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(SelectWrapper,null,React17.createElement(OptionsSelect,{disabled:readonly,id:controlId,multiple:!0,value:selection,onChange:handleChange},Object.keys(options).map(key=>React17.createElement("option",{key,value:key},key))))},SelectControl=props=>{let{name,options}=props;return options?props.isMulti?React17.createElement(MultiSelect,{...props}):React17.createElement(SingleSelect,{...props}):(logger.warn(`Select with no options: ${name}`),React17.createElement(React17.Fragment,null,"-"))};var normalizeOptions=(options,labels)=>Array.isArray(options)?options.reduce((acc,item)=>(acc[labels?.[item]||String(item)]=item,acc),{}):options,Controls={check:CheckboxControl,"inline-check":CheckboxControl,radio:RadioControl,"inline-radio":RadioControl,select:SelectControl,"multi-select":SelectControl},OptionsControl=props=>{let{type="select",labels,argType}=props,normalized={...props,argType,options:argType?normalizeOptions(argType.options,labels):{},isInline:type.includes("inline"),isMulti:type.includes("multi")},Control=Controls[type];if(Control)return React17.createElement(Control,{...normalized});throw new Error(`Unknown options type: ${type}`)};var VALUE="value",KEY="key";var ERROR="Error",OBJECT="Object",ARRAY="Array",STRING="String",NUMBER="Number",BOOLEAN="Boolean",DATE="Date",NULL="Null",UNDEFINED="Undefined",FUNCTION="Function",SYMBOL="Symbol";var ADD_DELTA_TYPE="ADD_DELTA_TYPE",REMOVE_DELTA_TYPE="REMOVE_DELTA_TYPE",UPDATE_DELTA_TYPE="UPDATE_DELTA_TYPE";function getObjectType(obj){return obj!==null&&typeof obj=="object"&&!Array.isArray(obj)&&typeof obj[Symbol.iterator]=="function"?"Iterable":Object.prototype.toString.call(obj).slice(8,-1)}function isComponentWillChange(oldValue,newValue){let oldType=getObjectType(oldValue),newType=getObjectType(newValue);return (oldType==="Function"||newType==="Function")&&newType!==oldType}var JsonAddValue=class extends Component{constructor(props){super(props),this.state={inputRefKey:null,inputRefValue:null},this.refInputValue=this.refInputValue.bind(this),this.refInputKey=this.refInputKey.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onSubmit=this.onSubmit.bind(this);}componentDidMount(){let{inputRefKey,inputRefValue}=this.state,{onlyValue}=this.props;inputRefKey&&typeof inputRefKey.focus=="function"&&inputRefKey.focus(),onlyValue&&inputRefValue&&typeof inputRefValue.focus=="function"&&inputRefValue.focus(),document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.onSubmit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.props.handleCancel()));}onSubmit(){let{handleAdd,onlyValue,onSubmitValueParser,keyPath,deep}=this.props,{inputRefKey,inputRefValue}=this.state,result={};if(!onlyValue){if(!inputRefKey.value)return;result.key=inputRefKey.value;}result.newValue=onSubmitValueParser(!1,keyPath,deep,result.key,inputRefValue.value),handleAdd(result);}refInputKey(node){this.state.inputRefKey=node;}refInputValue(node){this.state.inputRefValue=node;}render(){let{handleCancel,onlyValue,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep}=this.props,addButtonElementLayout=cloneElement(addButtonElement,{onClick:this.onSubmit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:handleCancel}),inputElementValue=inputElementGenerator(VALUE,keyPath,deep),inputElementValueLayout=cloneElement(inputElementValue,{placeholder:"Value",ref:this.refInputValue}),inputElementKeyLayout=null;if(!onlyValue){let inputElementKey=inputElementGenerator(KEY,keyPath,deep);inputElementKeyLayout=cloneElement(inputElementKey,{placeholder:"Key",ref:this.refInputKey});}return React17.createElement("span",{className:"rejt-add-value-node"},inputElementKeyLayout,inputElementValueLayout,cancelButtonElementLayout,addButtonElementLayout)}};JsonAddValue.defaultProps={onlyValue:!1,addButtonElement:React17.createElement("button",null,"+"),cancelButtonElement:React17.createElement("button",null,"c")};var JsonArray=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={data:props.data,name:props.name,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveItem=this.handleRemoveItem.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data);}handleAddMode(){this.setState({addFormVisible:!0});}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}));}handleRemoveItem(index){return ()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[index];beforeRemoveAction(index,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key:index,oldValue,type:REMOVE_DELTA_TYPE};data.splice(index,1),this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult);}).catch(logger4.error);}}handleAddValueAdd({newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(data.length,keyPath,deep,newValue).then(()=>{let newData=[...data,newValue];this.setState({data:newData}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],newData),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key:newData.length-1,newValue});}).catch(logger4.error);}handleAddValueCancel(){this.setState({addFormVisible:!1});}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve(void 0);}).catch(reject);})}renderCollapsed(){let{name,data,keyPath,deep}=this.state,{handleRemove,readOnly,getStyle,dataType,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-collapsed"},React17.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"[...] ",data.length," ",data.length===1?"item":"items"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,addFormVisible,nextDeep}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,delimiter,ul,addForm}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=cloneElement(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-not-collapsed"},React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"["),!addFormVisible&&addItemButton,React17.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},data.map((item,index)=>React17.createElement(JsonNode,{key:index,name:index.toString(),data:item,keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveItem(index),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}))),!isReadOnly&&addFormVisible&&React17.createElement("div",{className:"rejt-add-form",style:addForm},React17.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,onlyValue:!0,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"]"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{dataType,getStyle}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return React17.createElement("div",{className:"rejt-array-node"},React17.createElement("span",{onClick:this.handleCollapseMode},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonArray.defaultProps={keyPath:[],deep:0,minusMenuElement:React17.createElement("span",null," - "),plusMenuElement:React17.createElement("span",null," + ")};var JsonFunctionValue=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this);}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,readOnlyResult=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!readOnlyResult&&typeof inputRef.focus=="function"&&inputRef.focus();}componentDidMount(){document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()));}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit();}).catch(logger4.error);}handleEditMode(){this.setState({editEnabled:!0});}refInput(node){this.state.inputRef=node;}handleCancelEdit(){this.setState({editEnabled:!1});}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,textareaElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),result=null,minusElement=null,resultOnlyResult=readOnly(name,originalValue,keyPath,deep,dataType);if(editEnabled&&!resultOnlyResult){let textareaElement=textareaElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=cloneElement(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:this.handleCancelEdit}),textareaElementLayout=cloneElement(textareaElement,{ref:this.refInput,defaultValue:originalValue});result=React17.createElement("span",{className:"rejt-edit-form",style:style.editForm},textareaElementLayout," ",cancelButtonElementLayout,editButtonElementLayout),minusElement=null;}else {result=React17.createElement("span",{className:"rejt-value",style:style.value,onClick:resultOnlyResult?null:this.handleEditMode},value2);let minusMenuLayout=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});minusElement=resultOnlyResult?null:minusMenuLayout;}return React17.createElement("li",{className:"rejt-function-value-node",style:style.li},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," "),result,minusElement)}};JsonFunctionValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>{},editButtonElement:React17.createElement("button",null,"e"),cancelButtonElement:React17.createElement("button",null,"c"),minusMenuElement:React17.createElement("span",null," - ")};var JsonNode=class extends Component{constructor(props){super(props),this.state={data:props.data,name:props.name,keyPath:props.keyPath,deep:props.deep};}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}render(){let{data,name,keyPath,deep}=this.state,{isCollapsed,handleRemove,handleUpdateValue,onUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,readOnlyTrue=()=>!0,dataType=getObjectType(data);switch(dataType){case ERROR:return React17.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly:readOnlyTrue,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case OBJECT:return React17.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case ARRAY:return React17.createElement(JsonArray,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case STRING:return React17.createElement(JsonValue,{name,value:`"${data}"`,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NUMBER:return React17.createElement(JsonValue,{name,value:data,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case BOOLEAN:return React17.createElement(JsonValue,{name,value:data?"true":"false",originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case DATE:return React17.createElement(JsonValue,{name,value:data.toISOString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NULL:return React17.createElement(JsonValue,{name,value:"null",originalValue:"null",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case UNDEFINED:return React17.createElement(JsonValue,{name,value:"undefined",originalValue:"undefined",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case FUNCTION:return React17.createElement(JsonFunctionValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,textareaElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case SYMBOL:return React17.createElement(JsonValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});default:return null}}};JsonNode.defaultProps={keyPath:[],deep:0};var JsonObject=class extends Component{constructor(props){super(props);let keyPath=props.deep===-1?[]:[...props.keyPath,props.name];this.state={name:props.name,data:props.data,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveValue=this.handleRemoveValue.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data);}handleAddMode(){this.setState({addFormVisible:!0});}handleAddValueCancel(){this.setState({addFormVisible:!1});}handleAddValueAdd({key,newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(key,keyPath,deep,newValue).then(()=>{data[key]=newValue,this.setState({data}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key,newValue});}).catch(logger4.error);}handleRemoveValue(key){return ()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeRemoveAction(key,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key,oldValue,type:REMOVE_DELTA_TYPE};delete data[key],this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult);}).catch(logger4.error);}}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}));}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve();}).catch(reject);})}renderCollapsed(){let{name,keyPath,deep,data}=this.state,{handleRemove,readOnly,dataType,getStyle,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-collapsed"},React17.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"{...}"," ",keyList.length," ",keyList.length===1?"key":"keys"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,nextDeep,addFormVisible}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,addForm,ul,delimiter}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=cloneElement(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus}),list=keyList.map(key=>React17.createElement(JsonNode,{key,name:key,data:data[key],keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveValue(key),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}));return React17.createElement("span",{className:"rejt-not-collapsed"},React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"{"),!isReadOnly&&addItemButton,React17.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},list),!isReadOnly&&addFormVisible&&React17.createElement("div",{className:"rejt-add-form",style:addForm},React17.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"}"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{getStyle,dataType}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return React17.createElement("div",{className:"rejt-object-node"},React17.createElement("span",{onClick:this.handleCollapseMode},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonObject.defaultProps={keyPath:[],deep:0,minusMenuElement:React17.createElement("span",null," - "),plusMenuElement:React17.createElement("span",null," + ")};var JsonValue=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this);}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,isReadOnly=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!isReadOnly&&typeof inputRef.focus=="function"&&inputRef.focus();}componentDidMount(){document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()));}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit();}).catch(logger4.error);}handleEditMode(){this.setState({editEnabled:!0});}refInput(node){this.state.inputRef=node;}handleCancelEdit(){this.setState({editEnabled:!1});}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,inputElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),isReadOnly=readOnly(name,originalValue,keyPath,deep,dataType),isEditing=editEnabled&&!isReadOnly,inputElement=inputElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=cloneElement(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:this.handleCancelEdit}),inputElementLayout=cloneElement(inputElement,{ref:this.refInput,defaultValue:JSON.stringify(originalValue)}),minusMenuLayout=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});return React17.createElement("li",{className:"rejt-value-node",style:style.li},React17.createElement("span",{className:"rejt-name",style:style.name},name," : "),isEditing?React17.createElement("span",{className:"rejt-edit-form",style:style.editForm},inputElementLayout," ",cancelButtonElementLayout,editButtonElementLayout):React17.createElement("span",{className:"rejt-value",style:style.value,onClick:isReadOnly?null:this.handleEditMode},String(value2)),!isReadOnly&&!isEditing&&minusMenuLayout)}};JsonValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>Promise.resolve(),editButtonElement:React17.createElement("button",null,"e"),cancelButtonElement:React17.createElement("button",null,"c"),minusMenuElement:React17.createElement("span",null," - ")};var object={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},array={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},value={minus:{color:"red"},editForm:{},value:{color:"#7bba3d"},li:{minHeight:"22px",lineHeight:"22px",outline:"0px"},name:{color:"#2287CD"}};function parse3(string){let result=string;if(result.indexOf("function")===0)return (0, eval)(`(${result})`);try{result=JSON.parse(string);}catch{}return result}var JsonTree=class extends Component{constructor(props){super(props),this.state={data:props.data,rootName:props.rootName},this.onUpdate=this.onUpdate.bind(this),this.removeRoot=this.removeRoot.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data||props.rootName!==state.rootName?{data:props.data,rootName:props.rootName}:null}onUpdate(key,data){this.setState({data}),this.props.onFullyUpdate(data);}removeRoot(){this.onUpdate(null,null);}render(){let{data,rootName}=this.state,{isCollapsed,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElement,textareaElement,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser,fallback=null}=this.props,dataType=getObjectType(data),readOnlyFunction=readOnly;getObjectType(readOnly)==="Boolean"&&(readOnlyFunction=()=>readOnly);let inputElementFunction=inputElement;inputElement&&getObjectType(inputElement)!=="Function"&&(inputElementFunction=()=>inputElement);let textareaElementFunction=textareaElement;return textareaElement&&getObjectType(textareaElement)!=="Function"&&(textareaElementFunction=()=>textareaElement),dataType==="Object"||dataType==="Array"?React17.createElement("div",{className:"rejt-tree"},React17.createElement(JsonNode,{data,name:rootName,deep:-1,isCollapsed,onUpdate:this.onUpdate,onDeltaUpdate,readOnly:readOnlyFunction,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator:inputElementFunction,textareaElementGenerator:textareaElementFunction,minusMenuElement,plusMenuElement,handleRemove:this.removeRoot,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser})):fallback}};JsonTree.defaultProps={rootName:"root",isCollapsed:(keyPath,deep)=>deep!==-1,getStyle:(keyName,data,keyPath,deep,dataType)=>{switch(dataType){case"Object":case"Error":return object;case"Array":return array;default:return value}},readOnly:()=>!1,onFullyUpdate:()=>{},onDeltaUpdate:()=>{},beforeRemoveAction:()=>Promise.resolve(),beforeAddAction:()=>Promise.resolve(),beforeUpdateAction:()=>Promise.resolve(),logger:{error:()=>{}},onSubmitValueParser:(isEditMode,keyPath,deep,name,rawValue)=>parse3(rawValue),inputElement:()=>React17.createElement("input",null),textareaElement:()=>React17.createElement("textarea",null),fallback:null};var {window:globalWindow2}=global,Wrapper6=styled.div(({theme})=>({position:"relative",display:"flex",'&[aria-readonly="true"]':{opacity:.5},".rejt-tree":{marginLeft:"1rem",fontSize:"13px"},".rejt-value-node, .rejt-object-node > .rejt-collapsed, .rejt-array-node > .rejt-collapsed, .rejt-object-node > .rejt-not-collapsed, .rejt-array-node > .rejt-not-collapsed":{"& > svg":{opacity:0,transition:"opacity 0.2s"}},".rejt-value-node:hover, .rejt-object-node:hover > .rejt-collapsed, .rejt-array-node:hover > .rejt-collapsed, .rejt-object-node:hover > .rejt-not-collapsed, .rejt-array-node:hover > .rejt-not-collapsed":{"& > svg":{opacity:1}},".rejt-edit-form button":{display:"none"},".rejt-add-form":{marginLeft:10},".rejt-add-value-node":{display:"inline-flex",alignItems:"center"},".rejt-name":{lineHeight:"22px"},".rejt-not-collapsed-delimiter":{lineHeight:"22px"},".rejt-plus-menu":{marginLeft:5},".rejt-object-node > span > *, .rejt-array-node > span > *":{position:"relative",zIndex:2},".rejt-object-node, .rejt-array-node":{position:"relative"},".rejt-object-node > span:first-of-type::after, .rejt-array-node > span:first-of-type::after, .rejt-collapsed::before, .rejt-not-collapsed::before":{content:'""',position:"absolute",top:0,display:"block",width:"100%",marginLeft:"-1rem",padding:"0 4px 0 1rem",height:22},".rejt-collapsed::before, .rejt-not-collapsed::before":{zIndex:1,background:"transparent",borderRadius:4,transition:"background 0.2s",pointerEvents:"none",opacity:.1},".rejt-object-node:hover, .rejt-array-node:hover":{"& > .rejt-collapsed::before, & > .rejt-not-collapsed::before":{background:theme.color.secondary}},".rejt-collapsed::after, .rejt-not-collapsed::after":{content:'""',position:"absolute",display:"inline-block",pointerEvents:"none",width:0,height:0},".rejt-collapsed::after":{left:-8,top:8,borderTop:"3px solid transparent",borderBottom:"3px solid transparent",borderLeft:"3px solid rgba(153,153,153,0.6)"},".rejt-not-collapsed::after":{left:-10,top:10,borderTop:"3px solid rgba(153,153,153,0.6)",borderLeft:"3px solid transparent",borderRight:"3px solid transparent"},".rejt-value":{display:"inline-block",border:"1px solid transparent",borderRadius:4,margin:"1px 0",padding:"0 4px",cursor:"text",color:theme.color.defaultText},".rejt-value-node:hover > .rejt-value":{background:theme.color.lighter,borderColor:theme.appBorderColor}})),ButtonInline=styled.button(({theme,primary})=>({border:0,height:20,margin:1,borderRadius:4,background:primary?theme.color.secondary:"transparent",color:primary?theme.color.lightest:theme.color.dark,fontWeight:primary?"bold":"normal",cursor:"pointer",order:primary?"initial":9})),ActionAddIcon=styled(AddIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.ancillary},"svg + &":{marginLeft:0}})),ActionSubstractIcon=styled(SubtractIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.negative},"svg + &":{marginLeft:0}})),Input=styled.input(({theme,placeholder})=>({outline:0,margin:placeholder?1:"1px 0",padding:"3px 4px",color:theme.color.defaultText,background:theme.background.app,border:`1px solid ${theme.appBorderColor}`,borderRadius:4,lineHeight:"14px",width:placeholder==="Key"?80:120,"&:focus":{border:`1px solid ${theme.color.secondary}`}})),RawButton=styled(IconButton)(({theme})=>({position:"absolute",zIndex:2,top:2,right:2,height:21,padding:"0 3px",background:theme.background.bar,border:`1px solid ${theme.appBorderColor}`,borderRadius:3,color:theme.textMutedColor,fontSize:"9px",fontWeight:"bold",textDecoration:"none",span:{marginLeft:3,marginTop:1}})),RawInput=styled(Form.Textarea)(({theme})=>({flex:1,padding:"7px 6px",fontFamily:theme.typography.fonts.mono,fontSize:"12px",lineHeight:"18px","&::placeholder":{fontFamily:theme.typography.fonts.base,fontSize:"13px"},"&:placeholder-shown":{padding:"7px 10px"}})),ENTER_EVENT={bubbles:!0,cancelable:!0,key:"Enter",code:"Enter",keyCode:13},dispatchEnterKey=event=>{event.currentTarget.dispatchEvent(new globalWindow2.KeyboardEvent("keydown",ENTER_EVENT));},selectValue=event=>{event.currentTarget.select();},getCustomStyleFunction=theme=>()=>({name:{color:theme.color.secondary},collapsed:{color:theme.color.dark},ul:{listStyle:"none",margin:"0 0 0 1rem",padding:0},li:{outline:0}}),ObjectControl=({name,value:value2,onChange,argType})=>{let theme=useTheme(),data=useMemo(()=>value2&&cloneDeep(value2),[value2]),hasData=data!=null,[showRaw,setShowRaw]=useState(!hasData),[parseError,setParseError]=useState(null),readonly=!!argType?.table?.readonly,updateRaw=useCallback(raw=>{try{raw&&onChange(JSON.parse(raw)),setParseError(void 0);}catch(e){setParseError(e);}},[onChange]),[forceVisible,setForceVisible]=useState(!1),onForceVisible=useCallback(()=>{onChange({}),setForceVisible(!0);},[setForceVisible]),htmlElRef=useRef(null);if(useEffect(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select();},[forceVisible]),!hasData)return React17.createElement(Button,{disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set object");let rawJSONForm=React17.createElement(RawInput,{ref:htmlElRef,id:getControlId(name),name,defaultValue:value2===null?"":JSON.stringify(value2,null,2),onBlur:event=>updateRaw(event.target.value),placeholder:"Edit JSON string...",autoFocus:forceVisible,valid:parseError?"error":null,readOnly:readonly}),isObjectOrArray=Array.isArray(value2)||typeof value2=="object"&&value2?.constructor===Object;return React17.createElement(Wrapper6,{"aria-readonly":readonly},isObjectOrArray&&React17.createElement(RawButton,{onClick:e=>{e.preventDefault(),setShowRaw(v=>!v);}},showRaw?React17.createElement(EyeCloseIcon,null):React17.createElement(EyeIcon,null),React17.createElement("span",null,"RAW")),showRaw?rawJSONForm:React17.createElement(JsonTree,{readOnly:readonly||!isObjectOrArray,isCollapsed:isObjectOrArray?void 0:()=>!0,data,rootName:name,onFullyUpdate:onChange,getStyle:getCustomStyleFunction(theme),cancelButtonElement:React17.createElement(ButtonInline,{type:"button"},"Cancel"),editButtonElement:React17.createElement(ButtonInline,{type:"submit"},"Save"),addButtonElement:React17.createElement(ButtonInline,{type:"submit",primary:!0},"Save"),plusMenuElement:React17.createElement(ActionAddIcon,null),minusMenuElement:React17.createElement(ActionSubstractIcon,null),inputElement:(_,__,___,key)=>key?React17.createElement(Input,{onFocus:selectValue,onBlur:dispatchEnterKey}):React17.createElement(Input,null),fallback:rawJSONForm}))};var RangeInput=styled.input(({theme,min,max,value:value2,disabled})=>({"&":{width:"100%",backgroundColor:"transparent",appearance:"none"},"&::-webkit-slider-runnable-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${lighten(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer"},"&::-webkit-slider-thumb":{marginTop:"-6px",width:16,height:16,border:`1px solid ${rgba(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${rgba(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grab",appearance:"none",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${darken(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:disabled?"not-allowed":"grab"}},"&:focus":{outline:"none","&::-webkit-slider-runnable-track":{borderColor:rgba(theme.color.secondary,.4)},"&::-webkit-slider-thumb":{borderColor:theme.color.secondary,boxShadow:`0 0px 5px 0px ${theme.color.secondary}`}},"&::-moz-range-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${lighten(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer",outline:"none"},"&::-moz-range-thumb":{width:16,height:16,border:`1px solid ${rgba(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${rgba(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grap",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${darken(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:"grabbing"}},"&::-ms-track":{background:theme.base==="light"?`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -- ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -- ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -- ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+var Wrapper=styled.div(withReset,({theme})=>({backgroundColor:theme.base==="light"?"rgba(0,0,0,.01)":"rgba(255,255,255,.01)",borderRadius:theme.appBorderRadius,border:`1px dashed ${theme.appBorderColor}`,display:"flex",alignItems:"center",justifyContent:"center",padding:20,margin:"25px 0 40px",color:transparentize(.3,theme.color.defaultText),fontSize:theme.typography.size.s2})),EmptyBlock=props=>React17.createElement(Wrapper,{...props,className:"docblock-emptyblock sb-unstyled"});var StyledSyntaxHighlighter=styled(SyntaxHighlighter)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,lineHeight:"19px",margin:"25px 0 40px",borderRadius:theme.appBorderRadius,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0","pre.prismjs":{padding:20,background:"inherit"}}));var SourceSkeletonWrapper=styled.div(({theme})=>({background:theme.background.content,borderRadius:theme.appBorderRadius,border:`1px solid ${theme.appBorderColor}`,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",margin:"25px 0 40px",padding:"20px 20px 20px 22px"})),SourceSkeletonPlaceholder=styled.div(({theme})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,height:17,marginTop:1,width:"60%",[`&:first-child${ignoreSsrWarning}`]:{margin:0}})),SourceSkeleton=()=>React17.createElement(SourceSkeletonWrapper,null,React17.createElement(SourceSkeletonPlaceholder,null),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}}),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"30%"}}),React17.createElement(SourceSkeletonPlaceholder,{style:{width:"80%"}})),Source=({isLoading,error,language,code,dark,format:format2=!1,...rest})=>{let{typography}=useTheme();if(isLoading)return React17.createElement(SourceSkeleton,null);if(error)return React17.createElement(EmptyBlock,null,error);let syntaxHighlighter=React17.createElement(StyledSyntaxHighlighter,{bordered:!0,copyable:!0,format:format2,language,className:"docblock-source sb-unstyled",...rest},code);if(typeof dark>"u")return syntaxHighlighter;let overrideTheme=dark?themes.dark:themes.light;return React17.createElement(ThemeProvider,{theme:convert({...overrideTheme,fontCode:typography.fonts.mono,fontBase:typography.fonts.base})},syntaxHighlighter)};var toGlobalSelector=element=>`& :where(${element}:not(.sb-anchor, .sb-unstyled, .sb-unstyled ${element}))`,breakpoint=600,Title=styled.h1(withReset,({theme})=>({color:theme.color.defaultText,fontSize:theme.typography.size.m3,fontWeight:theme.typography.weight.bold,lineHeight:"32px",[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.l1,lineHeight:"36px",marginBottom:"16px"}})),Subtitle=styled.h2(withReset,({theme})=>({fontWeight:theme.typography.weight.regular,fontSize:theme.typography.size.s3,lineHeight:"20px",borderBottom:"none",marginBottom:15,[`@media (min-width: ${breakpoint}px)`]:{fontSize:theme.typography.size.m1,lineHeight:"28px",marginBottom:24},color:transparentize(.25,theme.color.defaultText)})),DocsContent=styled.div(({theme})=>{let reset={fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s3,margin:0,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch"},headers={margin:"20px 0 8px",padding:0,cursor:"text",position:"relative",color:theme.color.defaultText,"&:first-of-type":{marginTop:0,paddingTop:0},"&:hover a.anchor":{textDecoration:"none"},"& code":{fontSize:"inherit"}},code={lineHeight:1,margin:"0 2px",padding:"3px 5px",whiteSpace:"nowrap",borderRadius:3,fontSize:theme.typography.size.s2-1,border:theme.base==="light"?`1px solid ${theme.color.mediumlight}`:`1px solid ${theme.color.darker}`,color:theme.base==="light"?transparentize(.1,theme.color.defaultText):transparentize(.3,theme.color.defaultText),backgroundColor:theme.base==="light"?theme.color.lighter:theme.color.border};return {maxWidth:1e3,width:"100%",[toGlobalSelector("a")]:{...reset,fontSize:"inherit",lineHeight:"24px",color:theme.color.secondary,textDecoration:"none","&.absent":{color:"#cc0000"},"&.anchor":{display:"block",paddingLeft:30,marginLeft:-30,cursor:"pointer",position:"absolute",top:0,left:0,bottom:0}},[toGlobalSelector("blockquote")]:{...reset,margin:"16px 0",borderLeft:`4px solid ${theme.color.medium}`,padding:"0 15px",color:theme.color.dark,"& > :first-of-type":{marginTop:0},"& > :last-child":{marginBottom:0}},[toGlobalSelector("div")]:reset,[toGlobalSelector("dl")]:{...reset,margin:"16px 0",padding:0,"& dt":{fontSize:"14px",fontWeight:"bold",fontStyle:"italic",padding:0,margin:"16px 0 4px"},"& dt:first-of-type":{padding:0},"& dt > :first-of-type":{marginTop:0},"& dt > :last-child":{marginBottom:0},"& dd":{margin:"0 0 16px",padding:"0 15px"},"& dd > :first-of-type":{marginTop:0},"& dd > :last-child":{marginBottom:0}},[toGlobalSelector("h1")]:{...reset,...headers,fontSize:`${theme.typography.size.l1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h2")]:{...reset,...headers,fontSize:`${theme.typography.size.m2}px`,paddingBottom:4,borderBottom:`1px solid ${theme.appBorderColor}`},[toGlobalSelector("h3")]:{...reset,...headers,fontSize:`${theme.typography.size.m1}px`,fontWeight:theme.typography.weight.bold},[toGlobalSelector("h4")]:{...reset,...headers,fontSize:`${theme.typography.size.s3}px`},[toGlobalSelector("h5")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`},[toGlobalSelector("h6")]:{...reset,...headers,fontSize:`${theme.typography.size.s2}px`,color:theme.color.dark},[toGlobalSelector("hr")]:{border:"0 none",borderTop:`1px solid ${theme.appBorderColor}`,height:4,padding:0},[toGlobalSelector("img")]:{maxWidth:"100%"},[toGlobalSelector("li")]:{...reset,fontSize:theme.typography.size.s2,color:theme.color.defaultText,lineHeight:"24px","& + li":{marginTop:".25em"},"& ul, & ol":{marginTop:".25em",marginBottom:0},"& code":code},[toGlobalSelector("ol")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0}},[toGlobalSelector("p")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",color:theme.color.defaultText,"& code":code},[toGlobalSelector("pre")]:{...reset,fontFamily:theme.typography.fonts.mono,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",lineHeight:"18px",padding:"11px 1rem",whiteSpace:"pre-wrap",color:"inherit",borderRadius:3,margin:"1rem 0","&:not(.prismjs)":{background:"transparent",border:"none",borderRadius:0,padding:0,margin:0},"& pre, &.prismjs":{padding:15,margin:0,whiteSpace:"pre-wrap",color:"inherit",fontSize:"13px",lineHeight:"19px",code:{color:"inherit",fontSize:"inherit"}},"& code":{whiteSpace:"pre"},"& code, & tt":{border:"none"}},[toGlobalSelector("span")]:{...reset,"&.frame":{display:"block",overflow:"hidden","& > span":{border:`1px solid ${theme.color.medium}`,display:"block",float:"left",overflow:"hidden",margin:"13px 0 0",padding:7,width:"auto"},"& span img":{display:"block",float:"left"},"& span span":{clear:"both",color:theme.color.darkest,display:"block",padding:"5px 0 0"}},"&.align-center":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"center"},"& span img":{margin:"0 auto",textAlign:"center"}},"&.align-right":{display:"block",overflow:"hidden",clear:"both","& > span":{display:"block",overflow:"hidden",margin:"13px 0 0",textAlign:"right"},"& span img":{margin:0,textAlign:"right"}},"&.float-left":{display:"block",marginRight:13,overflow:"hidden",float:"left","& span":{margin:"13px 0 0"}},"&.float-right":{display:"block",marginLeft:13,overflow:"hidden",float:"right","& > span":{display:"block",overflow:"hidden",margin:"13px auto 0",textAlign:"right"}}},[toGlobalSelector("table")]:{...reset,margin:"16px 0",fontSize:theme.typography.size.s2,lineHeight:"24px",padding:0,borderCollapse:"collapse","& tr":{borderTop:`1px solid ${theme.appBorderColor}`,backgroundColor:theme.appContentBg,margin:0,padding:0},"& tr:nth-of-type(2n)":{backgroundColor:theme.base==="dark"?theme.color.darker:theme.color.lighter},"& tr th":{fontWeight:"bold",color:theme.color.defaultText,border:`1px solid ${theme.appBorderColor}`,margin:0,padding:"6px 13px"},"& tr td":{border:`1px solid ${theme.appBorderColor}`,color:theme.color.defaultText,margin:0,padding:"6px 13px"},"& tr th :first-of-type, & tr td :first-of-type":{marginTop:0},"& tr th :last-child, & tr td :last-child":{marginBottom:0}},[toGlobalSelector("ul")]:{...reset,margin:"16px 0",paddingLeft:30,"& :first-of-type":{marginTop:0},"& :last-child":{marginBottom:0},listStyle:"disc"}}}),DocsWrapper=styled.div(({theme})=>({background:theme.background.content,display:"flex",justifyContent:"center",padding:"4rem 20px",minHeight:"100vh",boxSizing:"border-box",gap:"3rem",[`@media (min-width: ${breakpoint}px)`]:{}})),DocsPageWrapper=({children,toc})=>React17.createElement(DocsWrapper,{className:"sbdocs sbdocs-wrapper"},React17.createElement(DocsContent,{className:"sbdocs sbdocs-content"},children),toc);var getBlockBackgroundStyle=theme=>({borderRadius:theme.appBorderRadius,background:theme.background.content,boxShadow:theme.base==="light"?"rgba(0, 0, 0, 0.10) 0 1px 3px 0":"rgba(0, 0, 0, 0.20) 0 2px 5px 0",border:`1px solid ${theme.appBorderColor}`});var Bar=styled(FlexBar)({position:"absolute",left:0,right:0,top:0,transition:"transform .2s linear"}),Wrapper2=styled.div({display:"flex",alignItems:"center",gap:4}),IconPlaceholder=styled.div(({theme})=>({width:14,height:14,borderRadius:2,margin:"0 7px",backgroundColor:theme.appBorderColor,animation:`${theme.animation.glow} 1.5s ease-in-out infinite`})),Toolbar=({isLoading,storyId,baseUrl,zoom,resetZoom,...rest})=>React17.createElement(Bar,{...rest},React17.createElement(Wrapper2,{key:"left"},isLoading?[1,2,3].map(key=>React17.createElement(IconPlaceholder,{key})):React17.createElement(React17.Fragment,null,React17.createElement(IconButton,{key:"zoomin",onClick:e=>{e.preventDefault(),zoom(.8);},title:"Zoom in"},React17.createElement(ZoomIcon,null)),React17.createElement(IconButton,{key:"zoomout",onClick:e=>{e.preventDefault(),zoom(1.25);},title:"Zoom out"},React17.createElement(ZoomOutIcon,null)),React17.createElement(IconButton,{key:"zoomreset",onClick:e=>{e.preventDefault(),resetZoom();},title:"Reset zoom"},React17.createElement(ZoomResetIcon,null)))));var ZoomContext=createContext({scale:1});var{window:globalWindow}=global,IFrame=class extends Component{constructor(){super(...arguments);this.iframe=null;}componentDidMount(){let{id}=this.props;this.iframe=globalWindow.document.getElementById(id);}shouldComponentUpdate(nextProps){let{scale}=nextProps;return scale!==this.props.scale&&this.setIframeBodyStyle({width:`${scale*100}%`,height:`${scale*100}%`,transform:`scale(${1/scale})`,transformOrigin:"top left"}),!1}setIframeBodyStyle(style){return Object.assign(this.iframe.contentDocument.body.style,style)}render(){let{id,title,src,allowFullScreen,scale,...rest}=this.props;return React17.createElement("iframe",{id,title,src,...allowFullScreen?{allow:"fullscreen"}:{},loading:"lazy",...rest})}};var{PREVIEW_URL}=global,BASE_URL=PREVIEW_URL||"iframe.html",storyBlockIdFromId=({story,primary})=>`story--${story.id}${primary?"--primary":""}`,InlineStory=props=>{let storyRef=useRef(),[showLoader,setShowLoader]=useState(!0),[error,setError]=useState(),{story,height,autoplay,forceInitialArgs,renderStoryToElement}=props;return useEffect(()=>{if(!(story&&storyRef.current))return ()=>{};let element=storyRef.current,cleanup=renderStoryToElement(story,element,{showMain:()=>{},showError:({title,description})=>setError(new Error(`${title} - ${description}`)),showException:err=>setError(err)},{autoplay,forceInitialArgs});return setShowLoader(!1),()=>{Promise.resolve().then(()=>cleanup());}},[autoplay,renderStoryToElement,story]),error?React17.createElement("pre",null,React17.createElement(ErrorFormatter,{error})):React17.createElement(React17.Fragment,null,height?React17.createElement("style",null,`#${storyBlockIdFromId(props)} { min-height: ${height}; transform: translateZ(0); overflow: auto }`):null,showLoader&&React17.createElement(StorySkeleton,null),React17.createElement("div",{ref:storyRef,id:`${storyBlockIdFromId(props)}-inner`,"data-name":story.name}))},IFrameStory=({story,height="500px"})=>React17.createElement("div",{style:{width:"100%",height}},React17.createElement(ZoomContext.Consumer,null,({scale})=>React17.createElement(IFrame,{key:"iframe",id:`iframe--${story.id}`,title:story.name,src:getStoryHref(BASE_URL,story.id,{viewMode:"story"}),allowFullScreen:!0,scale,style:{width:"100%",height:"100%",border:"0 none"}}))),Story=props=>{let{inline}=props;return React17.createElement("div",{id:storyBlockIdFromId(props),className:"sb-story sb-unstyled","data-story-block":"true"},inline?React17.createElement(InlineStory,{...props}):React17.createElement(IFrameStory,{...props}))},StorySkeleton=()=>React17.createElement(Loader,null);var ChildrenContainer=styled.div(({isColumn,columns,layout})=>({display:isColumn||!columns?"block":"flex",position:"relative",flexWrap:"wrap",overflow:"auto",flexDirection:isColumn?"column":"row","& .innerZoomElementWrapper > *":isColumn?{width:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"block"}:{maxWidth:layout!=="fullscreen"?"calc(100% - 20px)":"100%",display:"inline-block"}}),({layout="padded"})=>layout==="centered"||layout==="padded"?{padding:"30px 20px","& .innerZoomElementWrapper > *":{width:"auto",border:"10px solid transparent!important"}}:{},({layout="padded"})=>layout==="centered"?{display:"flex",justifyContent:"center",justifyItems:"center",alignContent:"center",alignItems:"center"}:{},({columns})=>columns&&columns>1?{".innerZoomElementWrapper > *":{minWidth:`calc(100% / ${columns} - 20px)`}}:{}),StyledSource=styled(Source)(({theme})=>({margin:0,borderTopLeftRadius:0,borderTopRightRadius:0,borderBottomLeftRadius:theme.appBorderRadius,borderBottomRightRadius:theme.appBorderRadius,border:"none",background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":darken(.05,theme.background.content),color:theme.color.lightest,button:{background:theme.base==="light"?"rgba(0, 0, 0, 0.85)":darken(.05,theme.background.content)}})),PreviewContainer=styled.div(({theme,withSource,isExpanded})=>({position:"relative",overflow:"hidden",margin:"25px 0 40px",...getBlockBackgroundStyle(theme),borderBottomLeftRadius:withSource&&isExpanded&&0,borderBottomRightRadius:withSource&&isExpanded&&0,borderBottomWidth:isExpanded&&0,"h3 + &":{marginTop:"16px"}}),({withToolbar})=>withToolbar&&{paddingTop:40}),getSource=(withSource,expanded,setExpanded)=>{switch(!0){case!!(withSource&&withSource.error):return {source:null,actionItem:{title:"No code available",className:"docblock-code-toggle docblock-code-toggle--disabled",disabled:!0,onClick:()=>setExpanded(!1)}};case expanded:return {source:React17.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Hide code",className:"docblock-code-toggle docblock-code-toggle--expanded",onClick:()=>setExpanded(!1)}};default:return {source:React17.createElement(StyledSource,{...withSource,dark:!0}),actionItem:{title:"Show code",className:"docblock-code-toggle",onClick:()=>setExpanded(!0)}}}};function getStoryId(children){if(Children.count(children)===1){let elt=children;if(elt.props)return elt.props.id}return null}var PositionedToolbar=styled(Toolbar)({position:"absolute",top:0,left:0,right:0,height:40}),Relative=styled.div({overflow:"hidden",position:"relative"}),Preview=({isLoading,isColumn,columns,children,withSource,withToolbar=!1,isExpanded=!1,additionalActions,className,layout="padded",...props})=>{let[expanded,setExpanded]=useState(isExpanded),{source,actionItem}=getSource(withSource,expanded,setExpanded),[scale,setScale]=useState(1),previewClasses=[className].concat(["sbdocs","sbdocs-preview","sb-unstyled"]),defaultActionItems=withSource?[actionItem]:[],[additionalActionItems,setAdditionalActionItems]=useState(additionalActions?[...additionalActions]:[]),actionItems=[...defaultActionItems,...additionalActionItems],{window:globalWindow4}=global,copyToClipboard=useCallback(async text=>{let{createCopyToClipboardFunction}=await import('@storybook/components');createCopyToClipboardFunction();},[]),onCopyCapture=e=>{let selection=globalWindow4.getSelection();selection&&selection.type==="Range"||(e.preventDefault(),additionalActionItems.filter(item=>item.title==="Copied").length===0&©ToClipboard(source.props.code).then(()=>{setAdditionalActionItems([...additionalActionItems,{title:"Copied",onClick:()=>{}}]),globalWindow4.setTimeout(()=>setAdditionalActionItems(additionalActionItems.filter(item=>item.title!=="Copied")),1500);}));};return React17.createElement(PreviewContainer,{withSource,withToolbar,...props,className:previewClasses.join(" ")},withToolbar&&React17.createElement(PositionedToolbar,{isLoading,border:!0,zoom:z=>setScale(scale*z),resetZoom:()=>setScale(1),storyId:getStoryId(children),baseUrl:"./iframe.html"}),React17.createElement(ZoomContext.Provider,{value:{scale}},React17.createElement(Relative,{className:"docs-story",onCopyCapture:withSource&&onCopyCapture},React17.createElement(ChildrenContainer,{isColumn:isColumn||!Array.isArray(children),columns,layout},React17.createElement(Zoom.Element,{scale},Array.isArray(children)?children.map((child,i)=>React17.createElement("div",{key:i},child)):React17.createElement("div",null,children))),React17.createElement(ActionBar,{actionItems}))),withSource&&expanded&&source)};styled(Preview)(()=>({".docs-story":{paddingTop:32,paddingBottom:40}}));var Table=styled.table(({theme})=>({"&&":{borderCollapse:"collapse",borderSpacing:0,border:"none",tr:{border:"none !important",background:"none"},"td, th":{padding:0,border:"none",width:"auto!important"},marginTop:0,marginBottom:0,"th:first-of-type, td:first-of-type":{paddingLeft:0},"th:last-of-type, td:last-of-type":{paddingRight:0},td:{paddingTop:0,paddingBottom:4,"&:not(:first-of-type)":{paddingLeft:10,paddingRight:0}},tbody:{boxShadow:"none",border:"none"},code:codeCommon({theme}),div:{span:{fontWeight:"bold"}},"& code":{margin:0,display:"inline-block",fontSize:theme.typography.size.s1}}})),ArgJsDoc=({tags})=>{let params=(tags.params||[]).filter(x=>x.description),hasDisplayableParams=params.length!==0,hasDisplayableDeprecated=tags.deprecated!=null,hasDisplayableReturns=tags.returns!=null&&tags.returns.description!=null;return !hasDisplayableParams&&!hasDisplayableReturns&&!hasDisplayableDeprecated?null:React17.createElement(React17.Fragment,null,React17.createElement(Table,null,React17.createElement("tbody",null,hasDisplayableDeprecated&&React17.createElement("tr",{key:"deprecated"},React17.createElement("td",{colSpan:2},React17.createElement("strong",null,"Deprecated"),": ",tags.deprecated.toString())),hasDisplayableParams&¶ms.map(x=>React17.createElement("tr",{key:x.name},React17.createElement("td",null,React17.createElement("code",null,x.name)),React17.createElement("td",null,x.description))),hasDisplayableReturns&&React17.createElement("tr",{key:"returns"},React17.createElement("td",null,React17.createElement("code",null,"Returns")),React17.createElement("td",null,tags.returns.description)))))};var ITEMS_BEFORE_EXPANSION=8,Summary=styled.div(({isExpanded})=>({display:"flex",flexDirection:isExpanded?"column":"row",flexWrap:"wrap",alignItems:"flex-start",marginBottom:"-4px",minWidth:100})),Text=styled.span(codeCommon,({theme,simple=!1})=>({flex:"0 0 auto",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,wordBreak:"break-word",whiteSpace:"normal",maxWidth:"100%",margin:0,marginRight:"4px",marginBottom:"4px",paddingTop:"2px",paddingBottom:"2px",lineHeight:"13px",...simple&&{background:"transparent",border:"0 none",paddingLeft:0}})),ExpandButton=styled.button(({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,marginBottom:"4px",background:"none",border:"none"})),Expandable=styled.div(codeCommon,({theme})=>({fontFamily:theme.typography.fonts.mono,color:theme.color.secondary,fontSize:theme.typography.size.s1,margin:0,whiteSpace:"nowrap",display:"flex",alignItems:"center"})),Detail=styled.div(({theme,width})=>({width,minWidth:200,maxWidth:800,padding:15,fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,boxSizing:"content-box","& code":{padding:"0 !important"}})),ChevronUpIcon=styled(ChevronSmallUpIcon)({marginLeft:4}),ChevronDownIcon=styled(ChevronSmallDownIcon)({marginLeft:4}),EmptyArg=()=>React17.createElement("span",null,"-"),ArgText=({text,simple})=>React17.createElement(Text,{simple},text),calculateDetailWidth=memoize(1e3)(detail=>{let lines=detail.split(/\r?\n/);return `${Math.max(...lines.map(x=>x.length))}ch`}),getSummaryItems=summary=>{if(!summary)return [summary];let summaryItems=summary.split("|").map(value2=>value2.trim());return uniq(summaryItems)},renderSummaryItems=(summaryItems,isExpanded=!0)=>{let items=summaryItems;return isExpanded||(items=summaryItems.slice(0,ITEMS_BEFORE_EXPANSION)),items.map(item=>React17.createElement(ArgText,{key:item,text:item===""?'""':item}))},ArgSummary=({value:value2,initialExpandedArgs})=>{let{summary,detail}=value2,[isOpen,setIsOpen]=useState(!1),[isExpanded,setIsExpanded]=useState(initialExpandedArgs||!1);if(summary==null)return null;let summaryAsString=typeof summary.toString=="function"?summary.toString():summary;if(detail==null){if(/[(){}[\]<>]/.test(summaryAsString))return React17.createElement(ArgText,{text:summaryAsString});let summaryItems=getSummaryItems(summaryAsString),itemsCount=summaryItems.length;return itemsCount>ITEMS_BEFORE_EXPANSION?React17.createElement(Summary,{isExpanded},renderSummaryItems(summaryItems,isExpanded),React17.createElement(ExpandButton,{onClick:()=>setIsExpanded(!isExpanded)},isExpanded?"Show less...":`Show ${itemsCount-ITEMS_BEFORE_EXPANSION} more...`)):React17.createElement(Summary,null,renderSummaryItems(summaryItems))}return React17.createElement(WithTooltipPure,{closeOnOutsideClick:!0,placement:"bottom",visible:isOpen,onVisibleChange:isVisible=>{setIsOpen(isVisible);},tooltip:React17.createElement(Detail,{width:calculateDetailWidth(detail)},React17.createElement(SyntaxHighlighter,{language:"jsx",format:!1},detail))},React17.createElement(Expandable,{className:"sbdocs-expandable"},React17.createElement("span",null,summaryAsString),isOpen?React17.createElement(ChevronUpIcon,null):React17.createElement(ChevronDownIcon,null)))},ArgValue=({value:value2,initialExpandedArgs})=>value2==null?React17.createElement(EmptyArg,null):React17.createElement(ArgSummary,{value:value2,initialExpandedArgs});var Label=styled.label(({theme})=>({lineHeight:"18px",alignItems:"center",marginBottom:8,display:"inline-block",position:"relative",whiteSpace:"nowrap",background:theme.boolean.background,borderRadius:"3em",padding:1,'&[aria-disabled="true"]':{opacity:.5,input:{cursor:"not-allowed"}},input:{appearance:"none",width:"100%",height:"100%",position:"absolute",left:0,top:0,margin:0,padding:0,border:"none",background:"transparent",cursor:"pointer",borderRadius:"3em","&:focus":{outline:"none",boxShadow:`${theme.color.secondary} 0 0 0 1px inset !important`}},span:{textAlign:"center",fontSize:theme.typography.size.s1,fontWeight:theme.typography.weight.bold,lineHeight:"1",cursor:"pointer",display:"inline-block",padding:"7px 15px",transition:"all 100ms ease-out",userSelect:"none",borderRadius:"3em",color:transparentize(.5,theme.color.defaultText),background:"transparent","&:hover":{boxShadow:`${opacify(.3,theme.appBorderColor)} 0 0 0 1px inset`},"&:active":{boxShadow:`${opacify(.05,theme.appBorderColor)} 0 0 0 2px inset`,color:opacify(1,theme.appBorderColor)},"&:first-of-type":{paddingRight:8},"&:last-of-type":{paddingLeft:8}},"input:checked ~ span:last-of-type, input:not(:checked) ~ span:first-of-type":{background:theme.boolean.selectedBackground,boxShadow:theme.base==="light"?`${opacify(.1,theme.appBorderColor)} 0 0 2px`:`${theme.appBorderColor} 0 0 0 1px`,color:theme.color.defaultText,padding:"7px 15px"}})),parse=value2=>value2==="true",BooleanControl=({name,value:value2,onChange,onBlur,onFocus,argType})=>{let onSetFalse=useCallback(()=>onChange(!1),[onChange]),readonly=!!argType?.table?.readonly;if(value2===void 0)return React17.createElement(Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onSetFalse,disabled:readonly},"Set boolean");let controlId=getControlId(name),parsedValue=typeof value2=="string"?parse(value2):value2;return React17.createElement(Label,{"aria-disabled":readonly,htmlFor:controlId,"aria-label":name},React17.createElement("input",{id:controlId,type:"checkbox",onChange:e=>onChange(e.target.checked),checked:parsedValue,role:"switch",disabled:readonly,name,onBlur,onFocus}),React17.createElement("span",{"aria-hidden":"true"},"False"),React17.createElement("span",{"aria-hidden":"true"},"True"))};var parseDate=value2=>{let[year,month,day]=value2.split("-"),result=new Date;return result.setFullYear(parseInt(year,10),parseInt(month,10)-1,parseInt(day,10)),result},parseTime=value2=>{let[hours,minutes]=value2.split(":"),result=new Date;return result.setHours(parseInt(hours,10)),result.setMinutes(parseInt(minutes,10)),result},formatDate=value2=>{let date=new Date(value2),year=`000${date.getFullYear()}`.slice(-4),month=`0${date.getMonth()+1}`.slice(-2),day=`0${date.getDate()}`.slice(-2);return `${year}-${month}-${day}`},formatTime=value2=>{let date=new Date(value2),hours=`0${date.getHours()}`.slice(-2),minutes=`0${date.getMinutes()}`.slice(-2);return `${hours}:${minutes}`},FormInput=styled(Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),FlexSpaced=styled.div(({theme})=>({flex:1,display:"flex",input:{marginLeft:10,flex:1,height:32,"&::-webkit-calendar-picker-indicator":{opacity:.5,height:12,filter:theme.base==="light"?void 0:"invert(1)"}},"input:first-of-type":{marginLeft:0,flexGrow:4},"input:last-of-type":{flexGrow:3}})),DateControl=({name,value:value2,onChange,onFocus,onBlur,argType})=>{let[valid,setValid]=useState(!0),dateRef=useRef(),timeRef=useRef(),readonly=!!argType?.table?.readonly;useEffect(()=>{valid!==!1&&(dateRef&&dateRef.current&&(dateRef.current.value=formatDate(value2)),timeRef&&timeRef.current&&(timeRef.current.value=formatTime(value2)));},[value2]);let onDateChange=e=>{let parsed=parseDate(e.target.value),result=new Date(value2);result.setFullYear(parsed.getFullYear(),parsed.getMonth(),parsed.getDate());let time=result.getTime();time&&onChange(time),setValid(!!time);},onTimeChange=e=>{let parsed=parseTime(e.target.value),result=new Date(value2);result.setHours(parsed.getHours()),result.setMinutes(parsed.getMinutes());let time=result.getTime();time&&onChange(time),setValid(!!time);},controlId=getControlId(name);return React17.createElement(FlexSpaced,null,React17.createElement(FormInput,{type:"date",max:"9999-12-31",ref:dateRef,id:`${controlId}-date`,name:`${controlId}-date`,readOnly:readonly,onChange:onDateChange,onFocus,onBlur}),React17.createElement(FormInput,{type:"time",id:`${controlId}-time`,name:`${controlId}-time`,ref:timeRef,onChange:onTimeChange,readOnly:readonly,onFocus,onBlur}),valid?null:React17.createElement("div",null,"invalid"))};var Wrapper3=styled.label({display:"flex"}),parse2=value2=>{let result=parseFloat(value2);return Number.isNaN(result)?void 0:result},format=value2=>value2!=null?String(value2):"",FormInput2=styled(Form.Input)(({readOnly})=>({opacity:readOnly?.5:1})),NumberControl=({name,value:value2,onChange,min,max,step,onBlur,onFocus,argType})=>{let[inputValue,setInputValue]=useState(typeof value2=="number"?value2:""),[forceVisible,setForceVisible]=useState(!1),[parseError,setParseError]=useState(null),readonly=!!argType?.table?.readonly,handleChange=useCallback(event=>{setInputValue(event.target.value);let result=parseFloat(event.target.value);Number.isNaN(result)?setParseError(new Error(`'${event.target.value}' is not a number`)):(onChange(result),setParseError(null));},[onChange,setParseError]),onForceVisible=useCallback(()=>{setInputValue("0"),onChange(0),setForceVisible(!0);},[setForceVisible]),htmlElRef=useRef(null);return useEffect(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select();},[forceVisible]),useEffect(()=>{inputValue!==(typeof value2=="number"?value2:"")&&setInputValue(value2);},[value2]),value2===void 0?React17.createElement(Button,{variant:"outline",size:"medium",id:getControlSetterButtonId(name),onClick:onForceVisible,disabled:readonly},"Set number"):React17.createElement(Wrapper3,null,React17.createElement(FormInput2,{ref:htmlElRef,id:getControlId(name),type:"number",onChange:handleChange,size:"flex",placeholder:"Edit number...",value:inputValue,valid:parseError?"error":null,autoFocus:forceVisible,readOnly:readonly,name,min,max,step,onFocus,onBlur}))};var selectedKey=(value2,options)=>{let entry=options&&Object.entries(options).find(([_key,val])=>val===value2);return entry?entry[0]:void 0},selectedKeys=(value2,options)=>value2&&options?Object.entries(options).filter(entry=>value2.includes(entry[1])).map(entry=>entry[0]):[],selectedValues=(keys,options)=>keys&&options&&keys.map(key=>options[key]);var Wrapper4=styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return {input:{cursor:"not-allowed"}}}),Text2=styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label2=styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),CheckboxControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return logger.warn(`Checkbox with no options: ${name}`),React17.createElement(React17.Fragment,null,"-");let initial=selectedKeys(value2,options),[selected,setSelected]=useState(initial),readonly=!!argType?.table?.readonly,handleChange=e=>{let option=e.target.value,updated=[...selected];updated.includes(option)?updated.splice(updated.indexOf(option),1):updated.push(option),onChange(selectedValues(updated,options)),setSelected(updated);};useEffect(()=>{setSelected(selectedKeys(value2,options));},[value2]);let controlId=getControlId(name);return React17.createElement(Wrapper4,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id=`${controlId}-${index}`;return React17.createElement(Label2,{key:id,htmlFor:id},React17.createElement("input",{type:"checkbox",disabled:readonly,id,name:id,value:key,onChange:handleChange,checked:selected?.includes(key)}),React17.createElement(Text2,null,key))}))};var Wrapper5=styled.div(({isInline})=>isInline?{display:"flex",flexWrap:"wrap",alignItems:"flex-start",label:{display:"inline-flex",marginRight:15}}:{label:{display:"flex"}},props=>{if(props["aria-readonly"]==="true")return {input:{cursor:"not-allowed"}}}),Text3=styled.span({"[aria-readonly=true] &":{opacity:.5}}),Label3=styled.label({lineHeight:"20px",alignItems:"center",marginBottom:8,"&:last-child":{marginBottom:0},input:{margin:0,marginRight:6}}),RadioControl=({name,options,value:value2,onChange,isInline,argType})=>{if(!options)return logger.warn(`Radio with no options: ${name}`),React17.createElement(React17.Fragment,null,"-");let selection=selectedKey(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(Wrapper5,{"aria-readonly":readonly,isInline},Object.keys(options).map((key,index)=>{let id=`${controlId}-${index}`;return React17.createElement(Label3,{key:id,htmlFor:id},React17.createElement("input",{type:"radio",id,name:id,disabled:readonly,value:key,onChange:e=>onChange(options[e.currentTarget.value]),checked:key===selection}),React17.createElement(Text3,null,key))}))};var styleResets={appearance:"none",border:"0 none",boxSizing:"inherit",display:" block",margin:" 0",background:"transparent",padding:0,fontSize:"inherit",position:"relative"},OptionsSelect=styled.select(styleResets,({theme})=>({boxSizing:"border-box",position:"relative",padding:"6px 10px",width:"100%",color:theme.input.color||"inherit",background:theme.input.background,borderRadius:theme.input.borderRadius,boxShadow:`${theme.input.border} 0 0 0 1px inset`,fontSize:theme.typography.size.s2-1,lineHeight:"20px","&:focus":{boxShadow:`${theme.color.secondary} 0 0 0 1px inset`,outline:"none"},"&[disabled]":{cursor:"not-allowed",opacity:.5},"::placeholder":{color:theme.textMutedColor},"&[multiple]":{overflow:"auto",padding:0,option:{display:"block",padding:"6px 10px",marginLeft:1,marginRight:1}}})),SelectWrapper=styled.span(({theme})=>({display:"inline-block",lineHeight:"normal",overflow:"hidden",position:"relative",verticalAlign:"top",width:"100%",svg:{position:"absolute",zIndex:1,pointerEvents:"none",height:"12px",marginTop:"-6px",right:"12px",top:"50%",fill:theme.textMutedColor,path:{fill:theme.textMutedColor}}})),NO_SELECTION="Choose option...",SingleSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{onChange(options[e.currentTarget.value]);},selection=selectedKey(value2,options)||NO_SELECTION,controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(SelectWrapper,null,React17.createElement(ChevronSmallDownIcon,null),React17.createElement(OptionsSelect,{disabled:readonly,id:controlId,value:selection,onChange:handleChange},React17.createElement("option",{key:"no-selection",disabled:!0},NO_SELECTION),Object.keys(options).map(key=>React17.createElement("option",{key,value:key},key))))},MultiSelect=({name,value:value2,options,onChange,argType})=>{let handleChange=e=>{let selection2=Array.from(e.currentTarget.options).filter(option=>option.selected).map(option=>option.value);onChange(selectedValues(selection2,options));},selection=selectedKeys(value2,options),controlId=getControlId(name),readonly=!!argType?.table?.readonly;return React17.createElement(SelectWrapper,null,React17.createElement(OptionsSelect,{disabled:readonly,id:controlId,multiple:!0,value:selection,onChange:handleChange},Object.keys(options).map(key=>React17.createElement("option",{key,value:key},key))))},SelectControl=props=>{let{name,options}=props;return options?props.isMulti?React17.createElement(MultiSelect,{...props}):React17.createElement(SingleSelect,{...props}):(logger.warn(`Select with no options: ${name}`),React17.createElement(React17.Fragment,null,"-"))};var normalizeOptions=(options,labels)=>Array.isArray(options)?options.reduce((acc,item)=>(acc[labels?.[item]||String(item)]=item,acc),{}):options,Controls={check:CheckboxControl,"inline-check":CheckboxControl,radio:RadioControl,"inline-radio":RadioControl,select:SelectControl,"multi-select":SelectControl},OptionsControl=props=>{let{type="select",labels,argType}=props,normalized={...props,argType,options:argType?normalizeOptions(argType.options,labels):{},isInline:type.includes("inline"),isMulti:type.includes("multi")},Control=Controls[type];if(Control)return React17.createElement(Control,{...normalized});throw new Error(`Unknown options type: ${type}`)};var VALUE="value",KEY="key";var ERROR="Error",OBJECT="Object",ARRAY="Array",STRING="String",NUMBER="Number",BOOLEAN="Boolean",DATE="Date",NULL="Null",UNDEFINED="Undefined",FUNCTION="Function",SYMBOL="Symbol";var ADD_DELTA_TYPE="ADD_DELTA_TYPE",REMOVE_DELTA_TYPE="REMOVE_DELTA_TYPE",UPDATE_DELTA_TYPE="UPDATE_DELTA_TYPE";function getObjectType(obj){return obj!==null&&typeof obj=="object"&&!Array.isArray(obj)&&typeof obj[Symbol.iterator]=="function"?"Iterable":Object.prototype.toString.call(obj).slice(8,-1)}function isComponentWillChange(oldValue,newValue){let oldType=getObjectType(oldValue),newType=getObjectType(newValue);return (oldType==="Function"||newType==="Function")&&newType!==oldType}var JsonAddValue=class extends Component{constructor(props){super(props),this.state={inputRefKey:null,inputRefValue:null},this.refInputValue=this.refInputValue.bind(this),this.refInputKey=this.refInputKey.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onSubmit=this.onSubmit.bind(this);}componentDidMount(){let{inputRefKey,inputRefValue}=this.state,{onlyValue}=this.props;inputRefKey&&typeof inputRefKey.focus=="function"&&inputRefKey.focus(),onlyValue&&inputRefValue&&typeof inputRefValue.focus=="function"&&inputRefValue.focus(),document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.onSubmit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.props.handleCancel()));}onSubmit(){let{handleAdd,onlyValue,onSubmitValueParser,keyPath,deep}=this.props,{inputRefKey,inputRefValue}=this.state,result={};if(!onlyValue){if(!inputRefKey.value)return;result.key=inputRefKey.value;}result.newValue=onSubmitValueParser(!1,keyPath,deep,result.key,inputRefValue.value),handleAdd(result);}refInputKey(node){this.state.inputRefKey=node;}refInputValue(node){this.state.inputRefValue=node;}render(){let{handleCancel,onlyValue,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep}=this.props,addButtonElementLayout=cloneElement(addButtonElement,{onClick:this.onSubmit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:handleCancel}),inputElementValue=inputElementGenerator(VALUE,keyPath,deep),inputElementValueLayout=cloneElement(inputElementValue,{placeholder:"Value",ref:this.refInputValue}),inputElementKeyLayout=null;if(!onlyValue){let inputElementKey=inputElementGenerator(KEY,keyPath,deep);inputElementKeyLayout=cloneElement(inputElementKey,{placeholder:"Key",ref:this.refInputKey});}return React17.createElement("span",{className:"rejt-add-value-node"},inputElementKeyLayout,inputElementValueLayout,cancelButtonElementLayout,addButtonElementLayout)}};JsonAddValue.defaultProps={onlyValue:!1,addButtonElement:React17.createElement("button",null,"+"),cancelButtonElement:React17.createElement("button",null,"c")};var JsonArray=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={data:props.data,name:props.name,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveItem=this.handleRemoveItem.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data);}handleAddMode(){this.setState({addFormVisible:!0});}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}));}handleRemoveItem(index){return ()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[index];beforeRemoveAction(index,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key:index,oldValue,type:REMOVE_DELTA_TYPE};data.splice(index,1),this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult);}).catch(logger4.error);}}handleAddValueAdd({newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(data.length,keyPath,deep,newValue).then(()=>{let newData=[...data,newValue];this.setState({data:newData}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],newData),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key:newData.length-1,newValue});}).catch(logger4.error);}handleAddValueCancel(){this.setState({addFormVisible:!1});}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve(void 0);}).catch(reject);})}renderCollapsed(){let{name,data,keyPath,deep}=this.state,{handleRemove,readOnly,getStyle,dataType,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-collapsed"},React17.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"[...] ",data.length," ",data.length===1?"item":"items"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,addFormVisible,nextDeep}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,delimiter,ul,addForm}=getStyle(name,data,keyPath,deep,dataType),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=cloneElement(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-not-collapsed"},React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"["),!addFormVisible&&addItemButton,React17.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},data.map((item,index)=>React17.createElement(JsonNode,{key:index,name:index.toString(),data:item,keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveItem(index),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}))),!isReadOnly&&addFormVisible&&React17.createElement("div",{className:"rejt-add-form",style:addForm},React17.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,onlyValue:!0,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"]"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{dataType,getStyle}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return React17.createElement("div",{className:"rejt-array-node"},React17.createElement("span",{onClick:this.handleCollapseMode},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonArray.defaultProps={keyPath:[],deep:0,minusMenuElement:React17.createElement("span",null," - "),plusMenuElement:React17.createElement("span",null," + ")};var JsonFunctionValue=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this);}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,readOnlyResult=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!readOnlyResult&&typeof inputRef.focus=="function"&&inputRef.focus();}componentDidMount(){document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()));}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit();}).catch(logger4.error);}handleEditMode(){this.setState({editEnabled:!0});}refInput(node){this.state.inputRef=node;}handleCancelEdit(){this.setState({editEnabled:!1});}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,textareaElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),result=null,minusElement=null,resultOnlyResult=readOnly(name,originalValue,keyPath,deep,dataType);if(editEnabled&&!resultOnlyResult){let textareaElement=textareaElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=cloneElement(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:this.handleCancelEdit}),textareaElementLayout=cloneElement(textareaElement,{ref:this.refInput,defaultValue:originalValue});result=React17.createElement("span",{className:"rejt-edit-form",style:style.editForm},textareaElementLayout," ",cancelButtonElementLayout,editButtonElementLayout),minusElement=null;}else {result=React17.createElement("span",{className:"rejt-value",style:style.value,onClick:resultOnlyResult?null:this.handleEditMode},value2);let minusMenuLayout=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});minusElement=resultOnlyResult?null:minusMenuLayout;}return React17.createElement("li",{className:"rejt-function-value-node",style:style.li},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," "),result,minusElement)}};JsonFunctionValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>{},editButtonElement:React17.createElement("button",null,"e"),cancelButtonElement:React17.createElement("button",null,"c"),minusMenuElement:React17.createElement("span",null," - ")};var JsonNode=class extends Component{constructor(props){super(props),this.state={data:props.data,name:props.name,keyPath:props.keyPath,deep:props.deep};}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}render(){let{data,name,keyPath,deep}=this.state,{isCollapsed,handleRemove,handleUpdateValue,onUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,readOnlyTrue=()=>!0,dataType=getObjectType(data);switch(dataType){case ERROR:return React17.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly:readOnlyTrue,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case OBJECT:return React17.createElement(JsonObject,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case ARRAY:return React17.createElement(JsonArray,{data,name,isCollapsed,keyPath,deep,handleRemove,onUpdate,onDeltaUpdate,readOnly,dataType,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser});case STRING:return React17.createElement(JsonValue,{name,value:`"${data}"`,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NUMBER:return React17.createElement(JsonValue,{name,value:data,originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case BOOLEAN:return React17.createElement(JsonValue,{name,value:data?"true":"false",originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case DATE:return React17.createElement(JsonValue,{name,value:data.toISOString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case NULL:return React17.createElement(JsonValue,{name,value:"null",originalValue:"null",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case UNDEFINED:return React17.createElement(JsonValue,{name,value:"undefined",originalValue:"undefined",keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case FUNCTION:return React17.createElement(JsonFunctionValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly,dataType,getStyle,cancelButtonElement,editButtonElement,textareaElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});case SYMBOL:return React17.createElement(JsonValue,{name,value:data.toString(),originalValue:data,keyPath,deep,handleRemove,handleUpdateValue,readOnly:readOnlyTrue,dataType,getStyle,cancelButtonElement,editButtonElement,inputElementGenerator,minusMenuElement,logger:logger4,onSubmitValueParser});default:return null}}};JsonNode.defaultProps={keyPath:[],deep:0};var JsonObject=class extends Component{constructor(props){super(props);let keyPath=props.deep===-1?[]:[...props.keyPath,props.name];this.state={name:props.name,data:props.data,keyPath,deep:props.deep,nextDeep:props.deep+1,collapsed:props.isCollapsed(keyPath,props.deep,props.data),addFormVisible:!1},this.handleCollapseMode=this.handleCollapseMode.bind(this),this.handleRemoveValue=this.handleRemoveValue.bind(this),this.handleAddMode=this.handleAddMode.bind(this),this.handleAddValueAdd=this.handleAddValueAdd.bind(this),this.handleAddValueCancel=this.handleAddValueCancel.bind(this),this.handleEditValue=this.handleEditValue.bind(this),this.onChildUpdate=this.onChildUpdate.bind(this),this.renderCollapsed=this.renderCollapsed.bind(this),this.renderNotCollapsed=this.renderNotCollapsed.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data?{data:props.data}:null}onChildUpdate(childKey,childData){let{data,keyPath}=this.state;data[childKey]=childData,this.setState({data});let{onUpdate}=this.props,size=keyPath.length;onUpdate(keyPath[size-1],data);}handleAddMode(){this.setState({addFormVisible:!0});}handleAddValueCancel(){this.setState({addFormVisible:!1});}handleAddValueAdd({key,newValue}){let{data,keyPath,nextDeep:deep}=this.state,{beforeAddAction,logger:logger4}=this.props;beforeAddAction(key,keyPath,deep,newValue).then(()=>{data[key]=newValue,this.setState({data}),this.handleAddValueCancel();let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:ADD_DELTA_TYPE,keyPath,deep,key,newValue});}).catch(logger4.error);}handleRemoveValue(key){return ()=>{let{beforeRemoveAction,logger:logger4}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeRemoveAction(key,keyPath,deep,oldValue).then(()=>{let deltaUpdateResult={keyPath,deep,key,oldValue,type:REMOVE_DELTA_TYPE};delete data[key],this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate(deltaUpdateResult);}).catch(logger4.error);}}handleCollapseMode(){this.setState(state=>({collapsed:!state.collapsed}));}handleEditValue({key,value:value2}){return new Promise((resolve,reject)=>{let{beforeUpdateAction}=this.props,{data,keyPath,nextDeep:deep}=this.state,oldValue=data[key];beforeUpdateAction(key,keyPath,deep,oldValue,value2).then(()=>{data[key]=value2,this.setState({data});let{onUpdate,onDeltaUpdate}=this.props;onUpdate(keyPath[keyPath.length-1],data),onDeltaUpdate({type:UPDATE_DELTA_TYPE,keyPath,deep,key,newValue:value2,oldValue}),resolve();}).catch(reject);})}renderCollapsed(){let{name,keyPath,deep,data}=this.state,{handleRemove,readOnly,dataType,getStyle,minusMenuElement}=this.props,{minus,collapsed}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus});return React17.createElement("span",{className:"rejt-collapsed"},React17.createElement("span",{className:"rejt-collapsed-text",style:collapsed,onClick:this.handleCollapseMode},"{...}"," ",keyList.length," ",keyList.length===1?"key":"keys"),!isReadOnly&&removeItemButton)}renderNotCollapsed(){let{name,data,keyPath,deep,nextDeep,addFormVisible}=this.state,{isCollapsed,handleRemove,onDeltaUpdate,readOnly,getStyle,dataType,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}=this.props,{minus,plus,addForm,ul,delimiter}=getStyle(name,data,keyPath,deep,dataType),keyList=Object.getOwnPropertyNames(data),isReadOnly=readOnly(name,data,keyPath,deep,dataType),addItemButton=cloneElement(plusMenuElement,{onClick:this.handleAddMode,className:"rejt-plus-menu",style:plus}),removeItemButton=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:minus}),list=keyList.map(key=>React17.createElement(JsonNode,{key,name:key,data:data[key],keyPath,deep:nextDeep,isCollapsed,handleRemove:this.handleRemoveValue(key),handleUpdateValue:this.handleEditValue,onUpdate:this.onChildUpdate,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator,textareaElementGenerator,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser}));return React17.createElement("span",{className:"rejt-not-collapsed"},React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"{"),!isReadOnly&&addItemButton,React17.createElement("ul",{className:"rejt-not-collapsed-list",style:ul},list),!isReadOnly&&addFormVisible&&React17.createElement("div",{className:"rejt-add-form",style:addForm},React17.createElement(JsonAddValue,{handleAdd:this.handleAddValueAdd,handleCancel:this.handleAddValueCancel,addButtonElement,cancelButtonElement,inputElementGenerator,keyPath,deep,onSubmitValueParser})),React17.createElement("span",{className:"rejt-not-collapsed-delimiter",style:delimiter},"}"),!isReadOnly&&removeItemButton)}render(){let{name,collapsed,data,keyPath,deep}=this.state,{getStyle,dataType}=this.props,value2=collapsed?this.renderCollapsed():this.renderNotCollapsed(),style=getStyle(name,data,keyPath,deep,dataType);return React17.createElement("div",{className:"rejt-object-node"},React17.createElement("span",{onClick:this.handleCollapseMode},React17.createElement("span",{className:"rejt-name",style:style.name},name," :"," ")),value2)}};JsonObject.defaultProps={keyPath:[],deep:0,minusMenuElement:React17.createElement("span",null," - "),plusMenuElement:React17.createElement("span",null," + ")};var JsonValue=class extends Component{constructor(props){super(props);let keyPath=[...props.keyPath,props.name];this.state={value:props.value,name:props.name,keyPath,deep:props.deep,editEnabled:!1,inputRef:null},this.handleEditMode=this.handleEditMode.bind(this),this.refInput=this.refInput.bind(this),this.handleCancelEdit=this.handleCancelEdit.bind(this),this.handleEdit=this.handleEdit.bind(this),this.onKeydown=this.onKeydown.bind(this);}static getDerivedStateFromProps(props,state){return props.value!==state.value?{value:props.value}:null}componentDidUpdate(){let{editEnabled,inputRef,name,value:value2,keyPath,deep}=this.state,{readOnly,dataType}=this.props,isReadOnly=readOnly(name,value2,keyPath,deep,dataType);editEnabled&&!isReadOnly&&typeof inputRef.focus=="function"&&inputRef.focus();}componentDidMount(){document.addEventListener("keydown",this.onKeydown);}componentWillUnmount(){document.removeEventListener("keydown",this.onKeydown);}onKeydown(event){event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||event.repeat||((event.code==="Enter"||event.key==="Enter")&&(event.preventDefault(),this.handleEdit()),(event.code==="Escape"||event.key==="Escape")&&(event.preventDefault(),this.handleCancelEdit()));}handleEdit(){let{handleUpdateValue,originalValue,logger:logger4,onSubmitValueParser,keyPath}=this.props,{inputRef,name,deep}=this.state;if(!inputRef)return;let newValue=onSubmitValueParser(!0,keyPath,deep,name,inputRef.value);handleUpdateValue({value:newValue,key:name}).then(()=>{isComponentWillChange(originalValue,newValue)||this.handleCancelEdit();}).catch(logger4.error);}handleEditMode(){this.setState({editEnabled:!0});}refInput(node){this.state.inputRef=node;}handleCancelEdit(){this.setState({editEnabled:!1});}render(){let{name,value:value2,editEnabled,keyPath,deep}=this.state,{handleRemove,originalValue,readOnly,dataType,getStyle,editButtonElement,cancelButtonElement,inputElementGenerator,minusMenuElement,keyPath:comeFromKeyPath}=this.props,style=getStyle(name,originalValue,keyPath,deep,dataType),isReadOnly=readOnly(name,originalValue,keyPath,deep,dataType),isEditing=editEnabled&&!isReadOnly,inputElement=inputElementGenerator(VALUE,comeFromKeyPath,deep,name,originalValue,dataType),editButtonElementLayout=cloneElement(editButtonElement,{onClick:this.handleEdit}),cancelButtonElementLayout=cloneElement(cancelButtonElement,{onClick:this.handleCancelEdit}),inputElementLayout=cloneElement(inputElement,{ref:this.refInput,defaultValue:JSON.stringify(originalValue)}),minusMenuLayout=cloneElement(minusMenuElement,{onClick:handleRemove,className:"rejt-minus-menu",style:style.minus});return React17.createElement("li",{className:"rejt-value-node",style:style.li},React17.createElement("span",{className:"rejt-name",style:style.name},name," : "),isEditing?React17.createElement("span",{className:"rejt-edit-form",style:style.editForm},inputElementLayout," ",cancelButtonElementLayout,editButtonElementLayout):React17.createElement("span",{className:"rejt-value",style:style.value,onClick:isReadOnly?null:this.handleEditMode},String(value2)),!isReadOnly&&!isEditing&&minusMenuLayout)}};JsonValue.defaultProps={keyPath:[],deep:0,handleUpdateValue:()=>Promise.resolve(),editButtonElement:React17.createElement("button",null,"e"),cancelButtonElement:React17.createElement("button",null,"c"),minusMenuElement:React17.createElement("span",null," - ")};var object={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},array={minus:{color:"red"},plus:{color:"green"},collapsed:{color:"grey"},delimiter:{},ul:{padding:"0px",margin:"0 0 0 25px",listStyle:"none"},name:{color:"#2287CD"},addForm:{}},value={minus:{color:"red"},editForm:{},value:{color:"#7bba3d"},li:{minHeight:"22px",lineHeight:"22px",outline:"0px"},name:{color:"#2287CD"}};function parse3(string){let result=string;if(result.indexOf("function")===0)return (0, eval)(`(${result})`);try{result=JSON.parse(string);}catch{}return result}var JsonTree=class extends Component{constructor(props){super(props),this.state={data:props.data,rootName:props.rootName},this.onUpdate=this.onUpdate.bind(this),this.removeRoot=this.removeRoot.bind(this);}static getDerivedStateFromProps(props,state){return props.data!==state.data||props.rootName!==state.rootName?{data:props.data,rootName:props.rootName}:null}onUpdate(key,data){this.setState({data}),this.props.onFullyUpdate(data);}removeRoot(){this.onUpdate(null,null);}render(){let{data,rootName}=this.state,{isCollapsed,onDeltaUpdate,readOnly,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElement,textareaElement,minusMenuElement,plusMenuElement,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser,fallback=null}=this.props,dataType=getObjectType(data),readOnlyFunction=readOnly;getObjectType(readOnly)==="Boolean"&&(readOnlyFunction=()=>readOnly);let inputElementFunction=inputElement;inputElement&&getObjectType(inputElement)!=="Function"&&(inputElementFunction=()=>inputElement);let textareaElementFunction=textareaElement;return textareaElement&&getObjectType(textareaElement)!=="Function"&&(textareaElementFunction=()=>textareaElement),dataType==="Object"||dataType==="Array"?React17.createElement("div",{className:"rejt-tree"},React17.createElement(JsonNode,{data,name:rootName,deep:-1,isCollapsed,onUpdate:this.onUpdate,onDeltaUpdate,readOnly:readOnlyFunction,getStyle,addButtonElement,cancelButtonElement,editButtonElement,inputElementGenerator:inputElementFunction,textareaElementGenerator:textareaElementFunction,minusMenuElement,plusMenuElement,handleRemove:this.removeRoot,beforeRemoveAction,beforeAddAction,beforeUpdateAction,logger:logger4,onSubmitValueParser})):fallback}};JsonTree.defaultProps={rootName:"root",isCollapsed:(keyPath,deep)=>deep!==-1,getStyle:(keyName,data,keyPath,deep,dataType)=>{switch(dataType){case"Object":case"Error":return object;case"Array":return array;default:return value}},readOnly:()=>!1,onFullyUpdate:()=>{},onDeltaUpdate:()=>{},beforeRemoveAction:()=>Promise.resolve(),beforeAddAction:()=>Promise.resolve(),beforeUpdateAction:()=>Promise.resolve(),logger:{error:()=>{}},onSubmitValueParser:(isEditMode,keyPath,deep,name,rawValue)=>parse3(rawValue),inputElement:()=>React17.createElement("input",null),textareaElement:()=>React17.createElement("textarea",null),fallback:null};var {window:globalWindow2}=global,Wrapper6=styled.div(({theme})=>({position:"relative",display:"flex",'&[aria-readonly="true"]':{opacity:.5},".rejt-tree":{marginLeft:"1rem",fontSize:"13px"},".rejt-value-node, .rejt-object-node > .rejt-collapsed, .rejt-array-node > .rejt-collapsed, .rejt-object-node > .rejt-not-collapsed, .rejt-array-node > .rejt-not-collapsed":{"& > svg":{opacity:0,transition:"opacity 0.2s"}},".rejt-value-node:hover, .rejt-object-node:hover > .rejt-collapsed, .rejt-array-node:hover > .rejt-collapsed, .rejt-object-node:hover > .rejt-not-collapsed, .rejt-array-node:hover > .rejt-not-collapsed":{"& > svg":{opacity:1}},".rejt-edit-form button":{display:"none"},".rejt-add-form":{marginLeft:10},".rejt-add-value-node":{display:"inline-flex",alignItems:"center"},".rejt-name":{lineHeight:"22px"},".rejt-not-collapsed-delimiter":{lineHeight:"22px"},".rejt-plus-menu":{marginLeft:5},".rejt-object-node > span > *, .rejt-array-node > span > *":{position:"relative",zIndex:2},".rejt-object-node, .rejt-array-node":{position:"relative"},".rejt-object-node > span:first-of-type::after, .rejt-array-node > span:first-of-type::after, .rejt-collapsed::before, .rejt-not-collapsed::before":{content:'""',position:"absolute",top:0,display:"block",width:"100%",marginLeft:"-1rem",padding:"0 4px 0 1rem",height:22},".rejt-collapsed::before, .rejt-not-collapsed::before":{zIndex:1,background:"transparent",borderRadius:4,transition:"background 0.2s",pointerEvents:"none",opacity:.1},".rejt-object-node:hover, .rejt-array-node:hover":{"& > .rejt-collapsed::before, & > .rejt-not-collapsed::before":{background:theme.color.secondary}},".rejt-collapsed::after, .rejt-not-collapsed::after":{content:'""',position:"absolute",display:"inline-block",pointerEvents:"none",width:0,height:0},".rejt-collapsed::after":{left:-8,top:8,borderTop:"3px solid transparent",borderBottom:"3px solid transparent",borderLeft:"3px solid rgba(153,153,153,0.6)"},".rejt-not-collapsed::after":{left:-10,top:10,borderTop:"3px solid rgba(153,153,153,0.6)",borderLeft:"3px solid transparent",borderRight:"3px solid transparent"},".rejt-value":{display:"inline-block",border:"1px solid transparent",borderRadius:4,margin:"1px 0",padding:"0 4px",cursor:"text",color:theme.color.defaultText},".rejt-value-node:hover > .rejt-value":{background:theme.color.lighter,borderColor:theme.appBorderColor}})),ButtonInline=styled.button(({theme,primary})=>({border:0,height:20,margin:1,borderRadius:4,background:primary?theme.color.secondary:"transparent",color:primary?theme.color.lightest:theme.color.dark,fontWeight:primary?"bold":"normal",cursor:"pointer",order:primary?"initial":9})),ActionAddIcon=styled(AddIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.ancillary},"svg + &":{marginLeft:0}})),ActionSubstractIcon=styled(SubtractIcon)(({theme,disabled})=>({display:"inline-block",verticalAlign:"middle",width:15,height:15,padding:3,marginLeft:5,cursor:disabled?"not-allowed":"pointer",color:theme.textMutedColor,"&:hover":disabled?{}:{color:theme.color.negative},"svg + &":{marginLeft:0}})),Input=styled.input(({theme,placeholder})=>({outline:0,margin:placeholder?1:"1px 0",padding:"3px 4px",color:theme.color.defaultText,background:theme.background.app,border:`1px solid ${theme.appBorderColor}`,borderRadius:4,lineHeight:"14px",width:placeholder==="Key"?80:120,"&:focus":{border:`1px solid ${theme.color.secondary}`}})),RawButton=styled(IconButton)(({theme})=>({position:"absolute",zIndex:2,top:2,right:2,height:21,padding:"0 3px",background:theme.background.bar,border:`1px solid ${theme.appBorderColor}`,borderRadius:3,color:theme.textMutedColor,fontSize:"9px",fontWeight:"bold",textDecoration:"none",span:{marginLeft:3,marginTop:1}})),RawInput=styled(Form.Textarea)(({theme})=>({flex:1,padding:"7px 6px",fontFamily:theme.typography.fonts.mono,fontSize:"12px",lineHeight:"18px","&::placeholder":{fontFamily:theme.typography.fonts.base,fontSize:"13px"},"&:placeholder-shown":{padding:"7px 10px"}})),ENTER_EVENT={bubbles:!0,cancelable:!0,key:"Enter",code:"Enter",keyCode:13},dispatchEnterKey=event=>{event.currentTarget.dispatchEvent(new globalWindow2.KeyboardEvent("keydown",ENTER_EVENT));},selectValue=event=>{event.currentTarget.select();},getCustomStyleFunction=theme=>()=>({name:{color:theme.color.secondary},collapsed:{color:theme.color.dark},ul:{listStyle:"none",margin:"0 0 0 1rem",padding:0},li:{outline:0}}),ObjectControl=({name,value:value2,onChange,argType})=>{let theme=useTheme(),data=useMemo(()=>value2&&cloneDeep(value2),[value2]),hasData=data!=null,[showRaw,setShowRaw]=useState(!hasData),[parseError,setParseError]=useState(null),readonly=!!argType?.table?.readonly,updateRaw=useCallback(raw=>{try{raw&&onChange(JSON.parse(raw)),setParseError(void 0);}catch(e){setParseError(e);}},[onChange]),[forceVisible,setForceVisible]=useState(!1),onForceVisible=useCallback(()=>{onChange({}),setForceVisible(!0);},[setForceVisible]),htmlElRef=useRef(null);if(useEffect(()=>{forceVisible&&htmlElRef.current&&htmlElRef.current.select();},[forceVisible]),!hasData)return React17.createElement(Button,{disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set object");let rawJSONForm=React17.createElement(RawInput,{ref:htmlElRef,id:getControlId(name),name,defaultValue:value2===null?"":JSON.stringify(value2,null,2),onBlur:event=>updateRaw(event.target.value),placeholder:"Edit JSON string...",autoFocus:forceVisible,valid:parseError?"error":null,readOnly:readonly}),isObjectOrArray=Array.isArray(value2)||typeof value2=="object"&&value2?.constructor===Object;return React17.createElement(Wrapper6,{"aria-readonly":readonly},isObjectOrArray&&React17.createElement(RawButton,{onClick:e=>{e.preventDefault(),setShowRaw(v=>!v);}},showRaw?React17.createElement(EyeCloseIcon,null):React17.createElement(EyeIcon,null),React17.createElement("span",null,"RAW")),showRaw?rawJSONForm:React17.createElement(JsonTree,{readOnly:readonly||!isObjectOrArray,isCollapsed:isObjectOrArray?void 0:()=>!0,data,rootName:name,onFullyUpdate:onChange,getStyle:getCustomStyleFunction(theme),cancelButtonElement:React17.createElement(ButtonInline,{type:"button"},"Cancel"),editButtonElement:React17.createElement(ButtonInline,{type:"submit"},"Save"),addButtonElement:React17.createElement(ButtonInline,{type:"submit",primary:!0},"Save"),plusMenuElement:React17.createElement(ActionAddIcon,null),minusMenuElement:React17.createElement(ActionSubstractIcon,null),inputElement:(_,__,___,key)=>key?React17.createElement(Input,{onFocus:selectValue,onBlur:dispatchEnterKey}):React17.createElement(Input,null),fallback:rawJSONForm}))};var RangeInput=styled.input(({theme,min,max,value:value2,disabled})=>({"&":{width:"100%",backgroundColor:"transparent",appearance:"none"},"&::-webkit-slider-runnable-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${lighten(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer"},"&::-webkit-slider-thumb":{marginTop:"-6px",width:16,height:16,border:`1px solid ${rgba(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${rgba(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grab",appearance:"none",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${darken(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:disabled?"not-allowed":"grab"}},"&:focus":{outline:"none","&::-webkit-slider-runnable-track":{borderColor:rgba(theme.color.secondary,.4)},"&::-webkit-slider-thumb":{borderColor:theme.color.secondary,boxShadow:`0 0px 5px 0px ${theme.color.secondary}`}},"&::-moz-range-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${lighten(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,borderRadius:6,width:"100%",height:6,cursor:disabled?"not-allowed":"pointer",outline:"none"},"&::-moz-range-thumb":{width:16,height:16,border:`1px solid ${rgba(theme.appBorderColor,.2)}`,borderRadius:"50px",boxShadow:`0 1px 3px 0px ${rgba(theme.appBorderColor,.2)}`,cursor:disabled?"not-allowed":"grap",background:`${theme.input.background}`,transition:"all 150ms ease-out","&:hover":{background:`${darken(.05,theme.input.background)}`,transform:"scale3d(1.1, 1.1, 1.1) translateY(-1px)",transition:"all 50ms ease-out"},"&:active":{background:`${theme.input.background}`,transform:"scale3d(1, 1, 1) translateY(0px)",cursor:"grabbing"}},"&::-ms-track":{background:theme.base==="light"?`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, -+ ${darken(.02,theme.input.background)} 100%)`:`linear-gradient(to right, -+ ${theme.color.green} 0%, ${theme.color.green} ${(value2-min)/(max-min)*100}%, -+ ${lighten(.02,theme.input.background)} ${(value2-min)/(max-min)*100}%, - ${lighten(.02,theme.input.background)} 100%)`,boxShadow:`${theme.appBorderColor} 0 0 0 1px inset`,color:"transparent",width:"100%",height:"6px",cursor:"pointer"},"&::-ms-fill-lower":{borderRadius:6},"&::-ms-fill-upper":{borderRadius:6},"&::-ms-thumb":{width:16,height:16,background:`${theme.input.background}`,border:`1px solid ${rgba(theme.appBorderColor,.2)}`,borderRadius:50,cursor:"grab",marginTop:0},"@supports (-ms-ime-align:auto)":{"input[type=range]":{margin:"0"}}})),RangeLabel=styled.span({paddingLeft:5,paddingRight:5,fontSize:12,whiteSpace:"nowrap",fontFeatureSettings:"tnum",fontVariantNumeric:"tabular-nums","[aria-readonly=true] &":{opacity:.5}}),RangeCurrentAndMaxLabel=styled(RangeLabel)(({numberOFDecimalsPlaces,max})=>({width:`${numberOFDecimalsPlaces+max.toString().length*2+3}ch`,textAlign:"right",flexShrink:0})),RangeWrapper=styled.div({display:"flex",alignItems:"center",width:"100%"});function getNumberOfDecimalPlaces(number){let match=number.toString().match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return match?Math.max(0,(match[1]?match[1].length:0)-(match[2]?+match[2]:0)):0}var RangeControl=({name,value:value2,onChange,min=0,max=100,step=1,onBlur,onFocus,argType})=>{let handleChange=event=>{onChange(parse2(event.target.value));},hasValue=value2!==void 0,numberOFDecimalsPlaces=useMemo(()=>getNumberOfDecimalPlaces(step),[step]),readonly=!!argType?.table?.readonly;return React17.createElement(RangeWrapper,{"aria-readonly":readonly},React17.createElement(RangeLabel,null,min),React17.createElement(RangeInput,{id:getControlId(name),type:"range",disabled:readonly,onChange:handleChange,name,value:value2,min,max,step,onFocus,onBlur}),React17.createElement(RangeCurrentAndMaxLabel,{numberOFDecimalsPlaces,max},hasValue?value2.toFixed(numberOFDecimalsPlaces):"--"," / ",max))};var Wrapper7=styled.label({display:"flex"}),MaxLength=styled.div(({isMaxed})=>({marginLeft:"0.75rem",paddingTop:"0.35rem",color:isMaxed?"red":void 0})),TextControl=({name,value:value2,onChange,onFocus,onBlur,maxLength,argType})=>{let handleChange=event=>{onChange(event.target.value);},readonly=!!argType?.table?.readonly,[forceVisible,setForceVisible]=useState(!1),onForceVisible=useCallback(()=>{onChange(""),setForceVisible(!0);},[setForceVisible]);if(value2===void 0)return React17.createElement(Button,{variant:"outline",size:"medium",disabled:readonly,id:getControlSetterButtonId(name),onClick:onForceVisible},"Set string");let isValid=typeof value2=="string";return React17.createElement(Wrapper7,null,React17.createElement(Form.Textarea,{id:getControlId(name),maxLength,onChange:handleChange,disabled:readonly,size:"flex",placeholder:"Edit string...",autoFocus:forceVisible,valid:isValid?null:"error",name,value:isValid?value2:"",onFocus,onBlur}),maxLength&&React17.createElement(MaxLength,{isMaxed:value2?.length===maxLength},value2?.length??0," / ",maxLength))};var FileInput=styled(Form.Input)({padding:10});function revokeOldUrls(urls){urls.forEach(url=>{url.startsWith("blob:")&&URL.revokeObjectURL(url);});}var FilesControl=({onChange,name,accept="image/*",value:value2,argType})=>{let inputElement=useRef(null),readonly=argType?.control?.readOnly;function handleFileChange(e){if(!e.target.files)return;let fileUrls=Array.from(e.target.files).map(file=>URL.createObjectURL(file));onChange(fileUrls),revokeOldUrls(value2);}return useEffect(()=>{value2==null&&inputElement.current&&(inputElement.current.value=null);},[value2,name]),React17.createElement(FileInput,{ref:inputElement,id:getControlId(name),type:"file",name,multiple:!0,disabled:readonly,onChange:handleFileChange,accept,size:"flex"})};var LazyColorControl=lazy(()=>import('./Color-PRSJMWNM.mjs')),ColorControl=props=>React17.createElement(Suspense,{fallback:React17.createElement("div",null)},React17.createElement(LazyColorControl,{...props}));var Controls2={array:ObjectControl,object:ObjectControl,boolean:BooleanControl,color:ColorControl,date:DateControl,number:NumberControl,check:OptionsControl,"inline-check":OptionsControl,radio:OptionsControl,"inline-radio":OptionsControl,select:OptionsControl,"multi-select":OptionsControl,range:RangeControl,text:TextControl,file:FilesControl},NoControl=()=>React17.createElement(React17.Fragment,null,"-"),ArgControl=({row,arg,updateArgs,isHovered})=>{let{key,control}=row,[isFocused,setFocused]=useState(!1),[boxedValue,setBoxedValue]=useState({value:arg});useEffect(()=>{isFocused||setBoxedValue({value:arg});},[isFocused,arg]);let onChange=useCallback(argVal=>(setBoxedValue({value:argVal}),updateArgs({[key]:argVal}),argVal),[updateArgs,key]),onBlur=useCallback(()=>setFocused(!1),[]),onFocus=useCallback(()=>setFocused(!0),[]);if(!control||control.disable){let canBeSetup=control?.disable!==!0&&row?.type?.name!=="function";return isHovered&&canBeSetup?React17.createElement(Link,{href:"https://storybook.js.org/docs/react/essentials/controls",target:"_blank",withArrow:!0},"Setup controls"):React17.createElement(NoControl,null)}let props={name:key,argType:row,value:boxedValue.value,onChange,onBlur,onFocus},Control=Controls2[control.type]||NoControl;return React17.createElement(Control,{...props,...control,controlType:control.type})};var Name=styled.span({fontWeight:"bold"}),Required=styled.span(({theme})=>({color:theme.color.negative,fontFamily:theme.typography.fonts.mono,cursor:"help"})),Description=styled.div(({theme})=>({"&&":{p:{margin:"0 0 10px 0"},a:{color:theme.color.secondary}},code:{...codeCommon({theme}),fontSize:12,fontFamily:theme.typography.fonts.mono},"& code":{margin:0,display:"inline-block"},"& pre > code":{whiteSpace:"pre-wrap"}})),Type=styled.div(({theme,hasDescription})=>({color:theme.base==="light"?transparentize(.1,theme.color.defaultText):transparentize(.2,theme.color.defaultText),marginTop:hasDescription?4:0})),TypeWithJsDoc=styled.div(({theme,hasDescription})=>({color:theme.base==="light"?transparentize(.1,theme.color.defaultText):transparentize(.2,theme.color.defaultText),marginTop:hasDescription?12:0,marginBottom:12})),StyledTd=styled.td(({theme,expandable})=>({paddingLeft:expandable?"40px !important":"20px !important"})),toSummary=value2=>value2&&{summary:typeof value2=="string"?value2:value2.name},ArgRow=props=>{let[isHovered,setIsHovered]=useState(!1),{row,updateArgs,compact,expandable,initialExpandedArgs}=props,{name,description}=row,table=row.table||{},type=table.type||toSummary(row.type),defaultValue=table.defaultValue||row.defaultValue,required=row.type?.required,hasDescription=description!=null&&description!=="";return React17.createElement("tr",{onMouseEnter:()=>setIsHovered(!0),onMouseLeave:()=>setIsHovered(!1)},React17.createElement(StyledTd,{expandable},React17.createElement(Name,null,name),required?React17.createElement(Required,{title:"Required"},"*"):null),compact?null:React17.createElement("td",null,hasDescription&&React17.createElement(Description,null,React17.createElement(Markdown,null,description)),table.jsDocTags!=null?React17.createElement(React17.Fragment,null,React17.createElement(TypeWithJsDoc,{hasDescription},React17.createElement(ArgValue,{value:type,initialExpandedArgs})),React17.createElement(ArgJsDoc,{tags:table.jsDocTags})):React17.createElement(Type,{hasDescription},React17.createElement(ArgValue,{value:type,initialExpandedArgs}))),compact?null:React17.createElement("td",null,React17.createElement(ArgValue,{value:defaultValue,initialExpandedArgs})),updateArgs?React17.createElement("td",null,React17.createElement(ArgControl,{...props,isHovered})):null)};var ExpanderIconDown=styled(ChevronDownIcon$1)(({theme})=>({marginRight:8,marginLeft:-10,marginTop:-2,height:12,width:12,color:theme.base==="light"?transparentize(.25,theme.color.defaultText):transparentize(.3,theme.color.defaultText),border:"none",display:"inline-block"})),ExpanderIconRight=styled(ChevronRightIcon)(({theme})=>({marginRight:8,marginLeft:-10,marginTop:-2,height:12,width:12,color:theme.base==="light"?transparentize(.25,theme.color.defaultText):transparentize(.3,theme.color.defaultText),border:"none",display:"inline-block"})),FlexWrapper=styled.span(({theme})=>({display:"flex",lineHeight:"20px",alignItems:"center"})),Section=styled.td(({theme})=>({position:"relative",letterSpacing:"0.35em",textTransform:"uppercase",fontWeight:theme.typography.weight.bold,fontSize:theme.typography.size.s1-1,color:theme.base==="light"?transparentize(.4,theme.color.defaultText):transparentize(.6,theme.color.defaultText),background:`${theme.background.app} !important`,"& ~ td":{background:`${theme.background.app} !important`}})),Subsection=styled.td(({theme})=>({position:"relative",fontWeight:theme.typography.weight.bold,fontSize:theme.typography.size.s2-1,background:theme.background.app})),StyledTd2=styled.td(()=>({position:"relative"})),StyledTr=styled.tr(({theme})=>({"&:hover > td":{backgroundColor:`${lighten(.005,theme.background.app)} !important`,boxShadow:`${theme.color.mediumlight} 0 - 1px 0 0 inset`,cursor:"row-resize"}})),ClickIntercept=styled.button(()=>({background:"none",border:"none",padding:"0",font:"inherit",position:"absolute",top:0,bottom:0,left:0,right:0,height:"100%",width:"100%",color:"transparent",cursor:"row-resize !important"})),SectionRow=({level="section",label,children,initialExpanded=!0,colSpan=3})=>{let[expanded,setExpanded]=useState(initialExpanded),Level=level==="subsection"?Subsection:Section,itemCount=children?.length||0,caption=level==="subsection"?`${itemCount} item${itemCount!==1?"s":""}`:"",helperText=`${expanded?"Hide":"Show"} ${level==="subsection"?itemCount:label} item${itemCount!==1?"s":""}`;return React17.createElement(React17.Fragment,null,React17.createElement(StyledTr,{title:helperText},React17.createElement(Level,{colSpan:1},React17.createElement(ClickIntercept,{onClick:e=>setExpanded(!expanded),tabIndex:0},helperText),React17.createElement(FlexWrapper,null,expanded?React17.createElement(ExpanderIconDown,null):React17.createElement(ExpanderIconRight,null),label)),React17.createElement(StyledTd2,{colSpan:colSpan-1},React17.createElement(ClickIntercept,{onClick:e=>setExpanded(!expanded),tabIndex:-1,style:{outline:"none"}},helperText),expanded?null:caption)),expanded?children:null)};var Row=styled.div(({theme})=>({display:"flex",gap:16,borderBottom:`1px solid ${theme.appBorderColor}`,"&:last-child":{borderBottom:0}})),Column=styled.div(({numColumn})=>({display:"flex",flexDirection:"column",flex:numColumn||1,gap:5,padding:"12px 20px"})),SkeletonText=styled.div(({theme,width,height})=>({animation:`${theme.animation.glow} 1.5s ease-in-out infinite`,background:theme.appBorderColor,width:width||"100%",height:height||16,borderRadius:3})),columnWidth=[2,4,2,2],Skeleton=()=>React17.createElement(React17.Fragment,null,React17.createElement(Row,null,React17.createElement(Column,{numColumn:columnWidth[0]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[1]},React17.createElement(SkeletonText,{width:"30%"})),React17.createElement(Column,{numColumn:columnWidth[2]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[3]},React17.createElement(SkeletonText,{width:"60%"}))),React17.createElement(Row,null,React17.createElement(Column,{numColumn:columnWidth[0]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[1]},React17.createElement(SkeletonText,{width:"80%"}),React17.createElement(SkeletonText,{width:"30%"})),React17.createElement(Column,{numColumn:columnWidth[2]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[3]},React17.createElement(SkeletonText,{width:"60%"}))),React17.createElement(Row,null,React17.createElement(Column,{numColumn:columnWidth[0]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[1]},React17.createElement(SkeletonText,{width:"80%"}),React17.createElement(SkeletonText,{width:"30%"})),React17.createElement(Column,{numColumn:columnWidth[2]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[3]},React17.createElement(SkeletonText,{width:"60%"}))),React17.createElement(Row,null,React17.createElement(Column,{numColumn:columnWidth[0]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[1]},React17.createElement(SkeletonText,{width:"80%"}),React17.createElement(SkeletonText,{width:"30%"})),React17.createElement(Column,{numColumn:columnWidth[2]},React17.createElement(SkeletonText,{width:"60%"})),React17.createElement(Column,{numColumn:columnWidth[3]},React17.createElement(SkeletonText,{width:"60%"}))));var Wrapper8=styled.div(({inAddonPanel,theme})=>({height:inAddonPanel?"100%":"auto",display:"flex",border:inAddonPanel?"none":`1px solid ${theme.appBorderColor}`,borderRadius:inAddonPanel?0:theme.appBorderRadius,padding:inAddonPanel?0:40,alignItems:"center",justifyContent:"center",flexDirection:"column",gap:15,background:theme.background.content,boxShadow:"rgba(0, 0, 0, 0.10) 0 1px 3px 0"})),Links=styled.div(({theme})=>({display:"flex",fontSize:theme.typography.size.s2-1,gap:25})),Divider=styled.div(({theme})=>({width:1,height:16,backgroundColor:theme.appBorderColor})),Empty=({inAddonPanel})=>{let[isLoading,setIsLoading]=useState(!0);return useEffect(()=>{let load=setTimeout(()=>{setIsLoading(!1);},100);return ()=>clearTimeout(load)},[]),isLoading?null:React17.createElement(Wrapper8,{inAddonPanel},React17.createElement(EmptyTabContent,{title:inAddonPanel?"Interactive story playground":"Args table with interactive controls couldn't be auto-generated",description:React17.createElement(React17.Fragment,null,"Controls give you an easy to use interface to test your components. Set your story args and you'll see controls appearing here automatically."),footer:React17.createElement(Links,null,inAddonPanel&&React17.createElement(React17.Fragment,null,React17.createElement(Link,{href:"https://youtu.be/0gOfS6K0x0E",target:"_blank",withArrow:!0},React17.createElement(VideoIcon,null)," Watch 5m video"),React17.createElement(Divider,null),React17.createElement(Link,{href:"https://storybook.js.org/docs/essentials/controls",target:"_blank",withArrow:!0},React17.createElement(DocumentIcon,null)," Read docs")),!inAddonPanel&&React17.createElement(Link,{href:"https://storybook.js.org/docs/essentials/controls",target:"_blank",withArrow:!0},React17.createElement(DocumentIcon,null)," Learn how to set that up"))}))};var TableWrapper=styled.table(({theme,compact,inAddonPanel})=>({"&&":{borderSpacing:0,color:theme.color.defaultText,"td, th":{padding:0,border:"none",verticalAlign:"top",textOverflow:"ellipsis"},fontSize:theme.typography.size.s2-1,lineHeight:"20px",textAlign:"left",width:"100%",marginTop:inAddonPanel?0:25,marginBottom:inAddonPanel?0:40,"thead th:first-of-type, td:first-of-type":{width:"25%"},"th:first-of-type, td:first-of-type":{paddingLeft:20},"th:nth-of-type(2), td:nth-of-type(2)":{...compact?null:{width:"35%"}},"td:nth-of-type(3)":{...compact?null:{width:"15%"}},"th:last-of-type, td:last-of-type":{paddingRight:20,...compact?null:{width:"25%"}},th:{color:theme.base==="light"?transparentize(.25,theme.color.defaultText):transparentize(.45,theme.color.defaultText),paddingTop:10,paddingBottom:10,paddingLeft:15,paddingRight:15},td:{paddingTop:"10px",paddingBottom:"10px","&:not(:first-of-type)":{paddingLeft:15,paddingRight:15},"&:last-of-type":{paddingRight:20}},marginLeft:inAddonPanel?0:1,marginRight:inAddonPanel?0:1,tbody:{...inAddonPanel?null:{filter:theme.base==="light"?"drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.10))":"drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.20))"},"> tr > *":{background:theme.background.content,borderTop:`1px solid ${theme.appBorderColor}`},...inAddonPanel?null:{"> tr:first-of-type > *":{borderBlockStart:`1px solid ${theme.appBorderColor}`},"> tr:last-of-type > *":{borderBlockEnd:`1px solid ${theme.appBorderColor}`},"> tr > *:first-of-type":{borderInlineStart:`1px solid ${theme.appBorderColor}`},"> tr > *:last-of-type":{borderInlineEnd:`1px solid ${theme.appBorderColor}`},"> tr:first-of-type > td:first-of-type":{borderTopLeftRadius:theme.appBorderRadius},"> tr:first-of-type > td:last-of-type":{borderTopRightRadius:theme.appBorderRadius},"> tr:last-of-type > td:first-of-type":{borderBottomLeftRadius:theme.appBorderRadius},"> tr:last-of-type > td:last-of-type":{borderBottomRightRadius:theme.appBorderRadius}}}}})),StyledIconButton=styled(IconButton)(({theme})=>({margin:"-4px -12px -4px 0"})),ControlHeadingWrapper=styled.span({display:"flex",justifyContent:"space-between"});var sortFns={alpha:(a,b)=>a.name.localeCompare(b.name),requiredFirst:(a,b)=>+!!b.type?.required-+!!a.type?.required||a.name.localeCompare(b.name),none:void 0},groupRows=(rows,sort)=>{let sections={ungrouped:[],ungroupedSubsections:{},sections:{}};if(!rows)return sections;Object.entries(rows).forEach(([key,row])=>{let{category,subcategory}=row?.table||{};if(category){let section=sections.sections[category]||{ungrouped:[],subsections:{}};if(!subcategory)section.ungrouped.push({key,...row});else {let subsection=section.subsections[subcategory]||[];subsection.push({key,...row}),section.subsections[subcategory]=subsection;}sections.sections[category]=section;}else if(subcategory){let subsection=sections.ungroupedSubsections[subcategory]||[];subsection.push({key,...row}),sections.ungroupedSubsections[subcategory]=subsection;}else sections.ungrouped.push({key,...row});});let sortFn=sortFns[sort],sortSubsection=record=>sortFn?Object.keys(record).reduce((acc,cur)=>({...acc,[cur]:record[cur].sort(sortFn)}),{}):record;return {ungrouped:sections.ungrouped.sort(sortFn),ungroupedSubsections:sortSubsection(sections.ungroupedSubsections),sections:Object.keys(sections.sections).reduce((acc,cur)=>({...acc,[cur]:{ungrouped:sections.sections[cur].ungrouped.sort(sortFn),subsections:sortSubsection(sections.sections[cur].subsections)}}),{})}},safeIncludeConditionalArg=(row,args,globals)=>{try{return includeConditionalArg(row,args,globals)}catch(err){return once.warn(err.message),!1}},ArgsTable=props=>{let{updateArgs,resetArgs,compact,inAddonPanel,initialExpandedArgs,sort="none",isLoading}=props;if("error"in props){let{error}=props;return React17.createElement(EmptyBlock,null,error,"\xA0",React17.createElement(Link,{href:"http://storybook.js.org/docs/",target:"_blank",withArrow:!0},React17.createElement(DocumentIcon,null)," Read the docs"))}if(isLoading)return React17.createElement(Skeleton,null);let{rows,args,globals}="rows"in props&&props,groups=groupRows(pickBy(rows,row=>!row?.table?.disable&&safeIncludeConditionalArg(row,args||{},globals||{})),sort),hasNoUngrouped=groups.ungrouped.length===0,hasNoSections=Object.entries(groups.sections).length===0,hasNoUngroupedSubsections=Object.entries(groups.ungroupedSubsections).length===0;if(hasNoUngrouped&&hasNoSections&&hasNoUngroupedSubsections)return React17.createElement(Empty,{inAddonPanel});let colSpan=1;updateArgs&&(colSpan+=1),compact||(colSpan+=2);let expandable=Object.keys(groups.sections).length>0,common={updateArgs,compact,inAddonPanel,initialExpandedArgs};return React17.createElement(ResetWrapper,null,React17.createElement(TableWrapper,{compact,inAddonPanel,className:"docblock-argstable sb-unstyled"},React17.createElement("thead",{className:"docblock-argstable-head"},React17.createElement("tr",null,React17.createElement("th",null,React17.createElement("span",null,"Name")),compact?null:React17.createElement("th",null,React17.createElement("span",null,"Description")),compact?null:React17.createElement("th",null,React17.createElement("span",null,"Default")),updateArgs?React17.createElement("th",null,React17.createElement(ControlHeadingWrapper,null,"Control"," ",!isLoading&&resetArgs&&React17.createElement(StyledIconButton,{onClick:()=>resetArgs(),title:"Reset controls"},React17.createElement(UndoIcon,{"aria-hidden":!0})))):null)),React17.createElement("tbody",{className:"docblock-argstable-body"},groups.ungrouped.map(row=>React17.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],...common})),Object.entries(groups.ungroupedSubsections).map(([subcategory,subsection])=>React17.createElement(SectionRow,{key:subcategory,label:subcategory,level:"subsection",colSpan},subsection.map(row=>React17.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],expandable,...common})))),Object.entries(groups.sections).map(([category,section])=>React17.createElement(SectionRow,{key:category,label:category,level:"section",colSpan},section.ungrouped.map(row=>React17.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],...common})),Object.entries(section.subsections).map(([subcategory,subsection])=>React17.createElement(SectionRow,{key:subcategory,label:subcategory,level:"subsection",colSpan},subsection.map(row=>React17.createElement(ArgRow,{key:row.key,row,arg:args&&args[row.key],expandable,...common})))))))))};var TabbedArgsTable=({tabs,...props})=>{let entries=Object.entries(tabs);return entries.length===1?React17.createElement(ArgsTable,{...entries[0][1],...props}):React17.createElement(TabsState,null,entries.map((entry,index)=>{let[label,table]=entry,id=`prop_table_div_${label}`,Component4="div",argsTableProps=index===0?props:{sort:props.sort};return React17.createElement(Component4,{key:id,id,title:label},({active})=>active?React17.createElement(ArgsTable,{key:`prop_table_${label}`,...table,...argsTableProps}):null)}))};var Label4=styled.div(({theme})=>({marginRight:30,fontSize:`${theme.typography.size.s1}px`,color:theme.base==="light"?transparentize(.4,theme.color.defaultText):transparentize(.6,theme.color.defaultText)})),Sample=styled.div({overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"}),TypeSpecimen=styled.div({display:"flex",flexDirection:"row",alignItems:"baseline","&:not(:last-child)":{marginBottom:"1rem"}}),Wrapper9=styled.div(withReset,({theme})=>({...getBlockBackgroundStyle(theme),margin:"25px 0 40px",padding:"30px 20px"})),Typeset=({fontFamily,fontSizes,fontWeight,sampleText,...props})=>React17.createElement(Wrapper9,{...props,className:"docblock-typeset sb-unstyled"},fontSizes.map(size=>React17.createElement(TypeSpecimen,{key:size},React17.createElement(Label4,null,size),React17.createElement(Sample,{style:{fontFamily,fontSize:size,fontWeight,lineHeight:1.2}},sampleText||"Was he a beast if music could move him so?"))));var ItemTitle=styled.div(({theme})=>({fontWeight:theme.typography.weight.bold,color:theme.color.defaultText})),ItemSubtitle=styled.div(({theme})=>({color:theme.base==="light"?transparentize(.2,theme.color.defaultText):transparentize(.6,theme.color.defaultText)})),ItemDescription=styled.div({flex:"0 0 30%",lineHeight:"20px",marginTop:5}),SwatchLabel=styled.div(({theme})=>({flex:1,textAlign:"center",fontFamily:theme.typography.fonts.mono,fontSize:theme.typography.size.s1,lineHeight:1,overflow:"hidden",color:theme.base==="light"?transparentize(.4,theme.color.defaultText):transparentize(.6,theme.color.defaultText),"> div":{display:"inline-block",overflow:"hidden",maxWidth:"100%",textOverflow:"ellipsis"},span:{display:"block",marginTop:2}})),SwatchLabels=styled.div({display:"flex",flexDirection:"row"}),Swatch=styled.div(({background})=>({position:"relative",flex:1,"&::before":{position:"absolute",top:0,left:0,width:"100%",height:"100%",background,content:'""'}})),SwatchColors=styled.div(({theme})=>({...getBlockBackgroundStyle(theme),display:"flex",flexDirection:"row",height:50,marginBottom:5,overflow:"hidden",backgroundColor:"white",backgroundImage:"repeating-linear-gradient(-45deg, #ccc, #ccc 1px, #fff 1px, #fff 16px)",backgroundClip:"padding-box"})),SwatchSpecimen=styled.div({display:"flex",flexDirection:"column",flex:1,position:"relative",marginBottom:30}),Swatches=styled.div({flex:1,display:"flex",flexDirection:"row"}),Item=styled.div({display:"flex",alignItems:"flex-start"}),ListName=styled.div({flex:"0 0 30%"}),ListSwatches=styled.div({flex:1}),ListHeading=styled.div(({theme})=>({display:"flex",flexDirection:"row",alignItems:"center",paddingBottom:20,fontWeight:theme.typography.weight.bold,color:theme.base==="light"?transparentize(.4,theme.color.defaultText):transparentize(.6,theme.color.defaultText)})),List=styled.div(({theme})=>({fontSize:theme.typography.size.s2,lineHeight:"20px",display:"flex",flexDirection:"column"}));function renderSwatch(color,index){return React17.createElement(Swatch,{key:`${color}-${index}`,title:color,background:color})}function renderSwatchLabel(color,index,colorDescription){return React17.createElement(SwatchLabel,{key:`${color}-${index}`,title:color},React17.createElement("div",null,color,colorDescription&&React17.createElement("span",null,colorDescription)))}function renderSwatchSpecimen(colors){if(Array.isArray(colors))return React17.createElement(SwatchSpecimen,null,React17.createElement(SwatchColors,null,colors.map((color,index)=>renderSwatch(color,index))),React17.createElement(SwatchLabels,null,colors.map((color,index)=>renderSwatchLabel(color,index))));let swatchElements=[],labelElements=[];for(let colorKey in colors){let colorValue=colors[colorKey];swatchElements.push(renderSwatch(colorValue,swatchElements.length)),labelElements.push(renderSwatchLabel(colorKey,labelElements.length,colorValue));}return React17.createElement(SwatchSpecimen,null,React17.createElement(SwatchColors,null,swatchElements),React17.createElement(SwatchLabels,null,labelElements))}var ColorItem=({title,subtitle,colors})=>React17.createElement(Item,null,React17.createElement(ItemDescription,null,React17.createElement(ItemTitle,null,title),React17.createElement(ItemSubtitle,null,subtitle)),React17.createElement(Swatches,null,renderSwatchSpecimen(colors))),ColorPalette=({children,...props})=>React17.createElement(ResetWrapper,null,React17.createElement(List,{...props,className:"docblock-colorpalette sb-unstyled"},React17.createElement(ListHeading,null,React17.createElement(ListName,null,"Name"),React17.createElement(ListSwatches,null,"Swatches")),children));var ItemLabel=styled.div(({theme})=>({fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s2,color:theme.color.defaultText,marginLeft:10,lineHeight:1.2})),ItemSpecimen=styled.div(({theme})=>({...getBlockBackgroundStyle(theme),overflow:"hidden",height:40,width:40,display:"flex",alignItems:"center",justifyContent:"center",flex:"none","> img, > svg":{width:20,height:20}})),Item2=styled.div({display:"inline-flex",flexDirection:"row",alignItems:"center",flex:"0 1 calc(20% - 10px)",minWidth:120,margin:"0px 10px 30px 0"}),List2=styled.div({display:"flex",flexFlow:"row wrap"}),IconItem=({name,children})=>React17.createElement(Item2,null,React17.createElement(ItemSpecimen,null,children),React17.createElement(ItemLabel,null,name)),IconGallery=({children,...props})=>React17.createElement(ResetWrapper,null,React17.createElement(List2,{...props,className:"docblock-icongallery sb-unstyled"},children));var anchorBlockIdFromId=storyId=>`anchor--${storyId}`,Anchor=({storyId,children})=>React17.createElement("div",{id:anchorBlockIdFromId(storyId),className:"sb-anchor"},children);global&&global.__DOCS_CONTEXT__===void 0&&(global.__DOCS_CONTEXT__=createContext(null),global.__DOCS_CONTEXT__.displayName="DocsContext");var DocsContext=global?global.__DOCS_CONTEXT__:createContext(null);var useOf=(moduleExportOrType,validTypes)=>useContext(DocsContext).resolveOf(moduleExportOrType,validTypes);var titleCase=str=>str.split("-").map(part=>part.charAt(0).toUpperCase()+part.slice(1)).join(""),getComponentName=component=>{if(component)return typeof component=="string"?component.includes("-")?titleCase(component):component:component.__docgenInfo&&component.__docgenInfo.displayName?component.__docgenInfo.displayName:component.name};function scrollToElement(element,block="start"){element.scrollIntoView({behavior:"smooth",block,inline:"nearest"});}function extractComponentArgTypes(component,parameters){let{extractArgTypes}=parameters.docs||{};if(!extractArgTypes)throw new Error("Args unsupported. See Args documentation for your framework.");return extractArgTypes(component)}function getArgTypesFromResolved(resolved){if(resolved.type==="component"){let{component:component2,projectAnnotations:{parameters:parameters2}}=resolved;return {argTypes:extractComponentArgTypes(component2,parameters2),parameters:parameters2,component:component2}}if(resolved.type==="meta"){let{preparedMeta:{argTypes:argTypes2,parameters:parameters2,component:component2,subcomponents:subcomponents2}}=resolved;return {argTypes:argTypes2,parameters:parameters2,component:component2,subcomponents:subcomponents2}}let{story:{argTypes,parameters,component,subcomponents}}=resolved;return {argTypes,parameters,component,subcomponents}}var ArgTypes=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let resolved=useOf(of||"meta"),{argTypes,parameters,component,subcomponents}=getArgTypesFromResolved(resolved),argTypesParameters=parameters.docs?.argTypes||{},include=props.include??argTypesParameters.include,exclude=props.exclude??argTypesParameters.exclude,sort=props.sort??argTypesParameters.sort,filteredArgTypes=filterArgTypes(argTypes,include,exclude);if(!(!!subcomponents&&Object.keys(subcomponents).length>0))return React17.createElement(ArgsTable,{rows:filteredArgTypes,sort});let mainComponentName=getComponentName(component),subcomponentTabs=Object.fromEntries(Object.entries(subcomponents).map(([key,comp])=>[key,{rows:filterArgTypes(extractComponentArgTypes(comp,parameters),include,exclude),sort}])),tabs={[mainComponentName]:{rows:filteredArgTypes,sort},...subcomponentTabs};return React17.createElement(TabbedArgsTable,{tabs,sort})};function argsHash(args){return stringify(args,{allowFunction:!1})}var SourceContext=createContext({sources:{}}),UNKNOWN_ARGS_HASH="--unknown--",SourceContainer=({children,channel})=>{let[sources,setSources]=useState({});return useEffect(()=>{let handleSnippetRendered=(idOrEvent,inputSource=null,inputFormat=!1)=>{let{id,args=void 0,source,format:format2}=typeof idOrEvent=="string"?{id:idOrEvent,source:inputSource,format:inputFormat}:idOrEvent,hash=args?argsHash(args):UNKNOWN_ARGS_HASH;setSources(current=>({...current,[id]:{...current[id],[hash]:{code:source,format:format2}}}));};return channel.on(SNIPPET_RENDERED,handleSnippetRendered),()=>channel.off(SNIPPET_RENDERED,handleSnippetRendered)},[]),React17.createElement(SourceContext.Provider,{value:{sources}},children)};var getStorySource=(storyId,args,sourceContext)=>{let{sources}=sourceContext,sourceMap=sources?.[storyId];return sourceMap?.[argsHash(args)]||sourceMap?.[UNKNOWN_ARGS_HASH]||{code:""}},getSnippet=({snippet,storyContext,typeFromProps,transformFromProps})=>{let{__isArgsStory:isArgsStory}=storyContext.parameters,sourceParameters=storyContext.parameters.docs?.source||{},type=typeFromProps||sourceParameters.type||SourceType.AUTO;if(sourceParameters.code!==void 0)return sourceParameters.code;let code=type===SourceType.DYNAMIC||type===SourceType.AUTO&&snippet&&isArgsStory?snippet:sourceParameters.originalSource||"";return (transformFromProps??sourceParameters.transform)?.(code,storyContext)||code},useSourceProps=(props,docsContext,sourceContext)=>{let story,{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");if(of)story=docsContext.resolveOf(of,["story"]).story;else try{story=docsContext.storyById();}catch{}let sourceParameters=story?.parameters?.docs?.source||{},{code}=props,format2=props.format??sourceParameters.format,language=props.language??sourceParameters.language??"jsx",dark=props.dark??sourceParameters.dark??!1;if(!code&&!story)return {error:"Oh no! The source is not available."};if(code)return {code,format:format2,language,dark};let storyContext=docsContext.getStoryContext(story),argsForSource=props.__forceInitialArgs?storyContext.initialArgs:storyContext.unmappedArgs,source=getStorySource(story.id,argsForSource,sourceContext);return format2=source.format??story.parameters.docs?.source?.format??!1,{code:getSnippet({snippet:source.code,storyContext:{...storyContext,args:argsForSource},typeFromProps:props.type,transformFromProps:props.transform}),format:format2,language,dark}},Source2=props=>{let sourceContext=useContext(SourceContext),docsContext=useContext(DocsContext),sourceProps=useSourceProps(props,docsContext,sourceContext);return React17.createElement(Source,{...sourceProps})};function useStory(storyId,context){let stories=useStories([storyId],context);return stories&&stories[0]}function useStories(storyIds,context){let[storiesById,setStories]=useState({});return useEffect(()=>{Promise.all(storyIds.map(async storyId=>{let story=await context.loadStory(storyId);setStories(current=>current[storyId]===story?current:{...current,[storyId]:story});}));}),storyIds.map(storyId=>{if(storiesById[storyId])return storiesById[storyId];try{return context.storyById(storyId)}catch{return null}})}var getStoryId2=(props,context)=>{let{of,meta}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");return meta&&context.referenceMeta(meta,!1),context.resolveOf(of||"story",["story"]).story.id},getStoryProps=(props,story,context)=>{let{parameters={}}=story||{},{docs={}}=parameters,storyParameters=docs.story||{};if(docs.disable)return null;if(props.inline??storyParameters.inline??!1){let height2=props.height??storyParameters.height,autoplay=props.autoplay??storyParameters.autoplay??!1;return {story,inline:!0,height:height2,autoplay,forceInitialArgs:!!props.__forceInitialArgs,primary:!!props.__primary,renderStoryToElement:context.renderStoryToElement}}let height=props.height??storyParameters.height??storyParameters.iframeHeight??"100px";return {story,inline:!1,height,primary:!!props.__primary}},Story2=(props={__forceInitialArgs:!1,__primary:!1})=>{let context=useContext(DocsContext),storyId=getStoryId2(props,context),story=useStory(storyId,context);if(!story)return React17.createElement(StorySkeleton,null);let storyProps=getStoryProps(props,story,context);return storyProps?React17.createElement(Story,{...storyProps}):null};var Canvas=props=>{let docsContext=useContext(DocsContext),sourceContext=useContext(SourceContext),{of,source}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let{story}=useOf(of||"story",["story"]),sourceProps=useSourceProps({...source,...of&&{of}},docsContext,sourceContext),layout=props.layout??story.parameters.layout??story.parameters.docs?.canvas?.layout??"padded",withToolbar=props.withToolbar??story.parameters.docs?.canvas?.withToolbar??!1,additionalActions=props.additionalActions??story.parameters.docs?.canvas?.additionalActions,sourceState=props.sourceState??story.parameters.docs?.canvas?.sourceState??"hidden",className=props.className??story.parameters.docs?.canvas?.className;return React17.createElement(Preview,{withSource:sourceState==="none"?void 0:sourceProps,isExpanded:sourceState==="shown",withToolbar,additionalActions,className,layout},React17.createElement(Story2,{of:of||story.moduleExport,meta:props.meta,...props.story}))};var useGlobals=(story,context)=>{let storyContext=context.getStoryContext(story),[globals,setGlobals]=useState(storyContext.globals);return useEffect(()=>{let onGlobalsUpdated=changed=>{setGlobals(changed.globals);};return context.channel.on(GLOBALS_UPDATED,onGlobalsUpdated),()=>context.channel.off(GLOBALS_UPDATED,onGlobalsUpdated)},[context.channel]),[globals]};var useArgs=(story,context)=>{let result=useArgsIfDefined(story,context);if(!result)throw new Error("No result when story was defined");return result},useArgsIfDefined=(story,context)=>{let storyContext=story?context.getStoryContext(story):{args:{}},{id:storyId}=story||{id:"none"},[args,setArgs]=useState(storyContext.args);useEffect(()=>{let onArgsUpdated=changed=>{changed.storyId===storyId&&setArgs(changed.args);};return context.channel.on(STORY_ARGS_UPDATED,onArgsUpdated),()=>context.channel.off(STORY_ARGS_UPDATED,onArgsUpdated)},[storyId,context.channel]);let updateArgs=useCallback(updatedArgs=>context.channel.emit(UPDATE_STORY_ARGS,{storyId,updatedArgs}),[storyId,context.channel]),resetArgs=useCallback(argNames=>context.channel.emit(RESET_STORY_ARGS,{storyId,argNames}),[storyId,context.channel]);return story&&[args,updateArgs,resetArgs]};function extractComponentArgTypes2(component,parameters){let{extractArgTypes}=parameters.docs||{};if(!extractArgTypes)throw new Error("Args unsupported. See Args documentation for your framework.");return extractArgTypes(component)}var Controls3=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let context=useContext(DocsContext),{story}=context.resolveOf(of||"story",["story"]),{parameters,argTypes,component,subcomponents}=story,controlsParameters=parameters.docs?.controls||{},include=props.include??controlsParameters.include,exclude=props.exclude??controlsParameters.exclude,sort=props.sort??controlsParameters.sort,[args,updateArgs,resetArgs]=useArgs(story,context),[globals]=useGlobals(story,context),filteredArgTypes=filterArgTypes(argTypes,include,exclude);if(!(!!subcomponents&&Object.keys(subcomponents).length>0))return Object.keys(filteredArgTypes).length>0||Object.keys(args).length>0?React17.createElement(ArgsTable,{rows:filteredArgTypes,sort,args,globals,updateArgs,resetArgs}):null;let mainComponentName=getComponentName(component),subcomponentTabs=Object.fromEntries(Object.entries(subcomponents).map(([key,comp])=>[key,{rows:filterArgTypes(extractComponentArgTypes2(comp,parameters),include,exclude),sort}])),tabs={[mainComponentName]:{rows:filteredArgTypes,sort},...subcomponentTabs};return React17.createElement(TabbedArgsTable,{tabs,sort,args,globals,updateArgs,resetArgs})};var {document:document2}=global,assertIsFn=val=>{if(typeof val!="function")throw new Error(`Expected story function, got: ${val}`);return val},AddContext=props=>{let{children,...rest}=props,parentContext=React17.useContext(DocsContext);return React17.createElement(DocsContext.Provider,{value:{...parentContext,...rest}},children)},CodeOrSourceMdx=({className,children,...rest})=>{if(typeof className!="string"&&(typeof children!="string"||!children.match(/[\n\r]/g)))return React17.createElement(Code,null,children);let language=className&&className.split("-");return React17.createElement(Source,{language:language&&language[1]||"text",format:!1,code:children,...rest})};function navigate(context,url){context.channel.emit(NAVIGATE_URL,url);}var A=components.a,AnchorInPage=({hash,children})=>{let context=useContext(DocsContext);return React17.createElement(A,{href:hash,target:"_self",onClick:event=>{let id=hash.substring(1);document2.getElementById(id)&&navigate(context,hash);}},children)},AnchorMdx=props=>{let{href,target,children,...rest}=props,context=useContext(DocsContext);return !href||target==="_blank"||/^https?:\/\//.test(href)?React17.createElement(A,{...props}):href.startsWith("#")?React17.createElement(AnchorInPage,{hash:href},children):React17.createElement(A,{href,onClick:event=>{event.button===0&&!event.altKey&&!event.ctrlKey&&!event.metaKey&&!event.shiftKey&&(event.preventDefault(),navigate(context,event.currentTarget.getAttribute("href")));},target,...rest},children)},SUPPORTED_MDX_HEADERS=["h1","h2","h3","h4","h5","h6"],OcticonHeaders=SUPPORTED_MDX_HEADERS.reduce((acc,headerType)=>({...acc,[headerType]:styled(headerType)({"& svg":{position:"relative",top:"-0.1em",visibility:"hidden"},"&:hover svg":{visibility:"visible"}})}),{}),OcticonAnchor=styled.a(()=>({float:"left",lineHeight:"inherit",paddingRight:"10px",marginLeft:"-24px",color:"inherit"})),HeaderWithOcticonAnchor=({as,id,children,...rest})=>{let context=useContext(DocsContext),OcticonHeader=OcticonHeaders[as],hash=`#${id}`;return React17.createElement(OcticonHeader,{id,...rest},React17.createElement(OcticonAnchor,{"aria-hidden":"true",href:hash,tabIndex:-1,target:"_self",onClick:event=>{document2.getElementById(id)&&navigate(context,hash);}},React17.createElement(LinkIcon,null)),children)},HeaderMdx=props=>{let{as,id,children,...rest}=props;if(id)return React17.createElement(HeaderWithOcticonAnchor,{as,id,...rest},children);let Component4=as,{as:omittedAs,...withoutAs}=props;return React17.createElement(Component4,{...nameSpaceClassNames(withoutAs,as)})},HeadersMdx=SUPPORTED_MDX_HEADERS.reduce((acc,headerType)=>({...acc,[headerType]:props=>React17.createElement(HeaderMdx,{as:headerType,...props})}),{});var Markdown2=props=>{if(!props.children)return null;if(typeof props.children!="string")throw new Error(dedent`The Markdown block only accepts children as a single string, but children were of type: '${typeof props.children}' - This is often caused by not wrapping the child in a template string. -- -+ - This is invalid: - - # Some heading -@@ -56,4 +56,4 @@ var Wrapper=styled.div(withReset,({theme})=>({backgroundColor:theme.base==="ligh - - `);return React17.createElement(Markdown,{...props,options:{forceBlock:!0,overrides:{code:CodeOrSourceMdx,a:AnchorMdx,...HeadersMdx,...props?.options?.overrides},...props?.options}})};var DescriptionType=(DescriptionType2=>(DescriptionType2.INFO="info",DescriptionType2.NOTES="notes",DescriptionType2.DOCGEN="docgen",DescriptionType2.AUTO="auto",DescriptionType2))(DescriptionType||{}),getDescriptionFromResolvedOf=resolvedOf=>{switch(resolvedOf.type){case"story":return resolvedOf.story.parameters.docs?.description?.story||null;case"meta":{let{parameters,component}=resolvedOf.preparedMeta,metaDescription=parameters.docs?.description?.component;return metaDescription||parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}case"component":{let{component,projectAnnotations:{parameters}}=resolvedOf;return parameters.docs?.extractComponentDescription?.(component,{component,parameters})||null}default:throw new Error(`Unrecognized module type resolved from 'useOf', got: ${resolvedOf.type}`)}},DescriptionContainer=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let resolvedOf=useOf(of||"meta"),markdown=getDescriptionFromResolvedOf(resolvedOf);return markdown?React17.createElement(Markdown2,null,markdown):null};var Wrapper10=styled.div(({theme})=>({width:"10rem","@media (max-width: 768px)":{display:"none"}})),Content=styled.div(({theme})=>({position:"fixed",bottom:0,top:0,width:"10rem",paddingTop:"4rem",paddingBottom:"2rem",overflowY:"auto",fontFamily:theme.typography.fonts.base,fontSize:theme.typography.size.s2,WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",WebkitOverflowScrolling:"touch","& *":{boxSizing:"border-box"},"& > .toc-wrapper > .toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`,".toc-list":{paddingLeft:0,borderLeft:`solid 2px ${theme.color.mediumlight}`}}},"& .toc-list-item":{position:"relative",listStyleType:"none",marginLeft:20,paddingTop:3,paddingBottom:3},"& .toc-list-item::before":{content:'""',position:"absolute",height:"100%",top:0,left:0,transform:"translateX(calc(-2px - 20px))",borderLeft:`solid 2px ${theme.color.mediumdark}`,opacity:0,transition:"opacity 0.2s"},"& .toc-list-item.is-active-li::before":{opacity:1},"& .toc-list-item > a":{color:theme.color.defaultText,textDecoration:"none"},"& .toc-list-item.is-active-li > a":{fontWeight:600,color:theme.color.secondary,textDecoration:"none"}})),Heading=styled.p(({theme})=>({fontWeight:600,fontSize:"0.875em",color:theme.textColor,textTransform:"uppercase",marginBottom:10})),OptionalTitle=({title})=>title===null?null:typeof title=="string"?React17.createElement(Heading,null,title):title,TableOfContents=({title,disable,headingSelector,contentsSelector,ignoreSelector,unsafeTocbotOptions})=>(useEffect(()=>{let configuration={tocSelector:".toc-wrapper",contentSelector:contentsSelector??".sbdocs-content",headingSelector:headingSelector??"h3",ignoreSelector:ignoreSelector??".docs-story *, .skip-toc",headingsOffset:40,scrollSmoothOffset:-40,orderedList:!1,onClick:()=>!1,...unsafeTocbotOptions},timeout=setTimeout(()=>tocbot.init(configuration),100);return ()=>{clearTimeout(timeout),tocbot.destroy();}},[disable]),React17.createElement(React17.Fragment,null,React17.createElement(Wrapper10,null,disable?null:React17.createElement(Content,null,React17.createElement(OptionalTitle,{title:title||null}),React17.createElement("div",{className:"toc-wrapper"})))));var {document:document3,window:globalWindow3}=global,DocsContainer=({context,theme,children})=>{let toc;try{toc=context.resolveOf("meta",["meta"]).preparedMeta.parameters?.docs?.toc;}catch{toc=context?.projectAnnotations?.parameters?.docs?.toc;}return useEffect(()=>{let url;try{if(url=new URL(globalWindow3.parent.location.toString()),url.hash){let element=document3.getElementById(url.hash.substring(1));element&&setTimeout(()=>{scrollToElement(element);},200);}}catch{}}),React17.createElement(DocsContext.Provider,{value:context},React17.createElement(SourceContainer,{channel:context.channel},React17.createElement(ThemeProvider,{theme:ensure(theme)},React17.createElement(DocsPageWrapper,{toc:toc?React17.createElement(TableOfContents,{className:"sbdocs sbdocs-toc--custom",...toc}):null},children))))};var STORY_KIND_PATH_SEPARATOR=/\s*\/\s*/,extractTitle=title=>{let groups=title.trim().split(STORY_KIND_PATH_SEPARATOR);return groups?.[groups?.length-1]||title},Title2=props=>{let{children,of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta;}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let content=children||extractTitle(preparedMeta?.title);return content?React17.createElement(Title,{className:"sbdocs-title sb-unstyled"},content):null};var DEPRECATION_MIGRATION_LINK="https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#subtitle-block-and-parameterscomponentsubtitle",Subtitle2=props=>{let{of,children}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let preparedMeta;try{preparedMeta=useOf(of||"meta",["meta"]).preparedMeta;}catch(error){if(children&&!error.message.includes("did you forget to use ?"))throw error}let{componentSubtitle,docs}=preparedMeta?.parameters||{};componentSubtitle&&deprecate(`Using 'parameters.componentSubtitle' property to subtitle stories is deprecated. See ${DEPRECATION_MIGRATION_LINK}`);let content=children||docs?.subtitle||componentSubtitle;return content?React17.createElement(Subtitle,{className:"sbdocs-subtitle sb-unstyled"},content):null};var Subheading=({children,disableAnchor})=>{if(disableAnchor||typeof children!="string")return React17.createElement(H3,null,children);let tagID=globalThis.encodeURIComponent(children.toLowerCase());return React17.createElement(HeaderMdx,{as:"h3",id:tagID},children)};var DocsStory=({of,expanded=!0,withToolbar:withToolbarProp=!1,__forceInitialArgs=!1,__primary=!1})=>{let{story}=useOf(of||"story",["story"]),withToolbar=story.parameters.docs?.canvas?.withToolbar??withToolbarProp;return React17.createElement(Anchor,{storyId:story.id},expanded&&React17.createElement(React17.Fragment,null,React17.createElement(Subheading,null,story.name),React17.createElement(DescriptionContainer,{of})),React17.createElement(Canvas,{of,withToolbar,story:{__forceInitialArgs,__primary},source:{__forceInitialArgs}}))};var Primary=props=>{let{of}=props;if("of"in props&&of===void 0)throw new Error("Unexpected `of={undefined}`, did you mistype a CSF file reference?");let{csfFile}=useOf(of||"meta",["meta"]),primaryStory=useContext(DocsContext).componentStoriesFromCSFFile(csfFile)[0];return primaryStory?React17.createElement(DocsStory,{of:primaryStory.moduleExport,expanded:!1,__primary:!0,withToolbar:!0}):null};var Heading2=({children,disableAnchor,...props})=>{if(disableAnchor||typeof children!="string")return React17.createElement(H2,null,children);let tagID=children.toLowerCase().replace(/[^a-z0-9]/gi,"-");return React17.createElement(HeaderMdx,{as:"h2",id:tagID,...props},children)};var StyledHeading=styled(Heading2)(({theme})=>({fontSize:`${theme.typography.size.s2-1}px`,fontWeight:theme.typography.weight.bold,lineHeight:"16px",letterSpacing:"0.35em",textTransform:"uppercase",color:theme.textMutedColor,border:0,marginBottom:"12px","&:first-of-type":{marginTop:"56px"}})),Stories=({title="Stories",includePrimary=!0})=>{let{componentStories,projectAnnotations,getStoryContext}=useContext(DocsContext),stories=componentStories(),{stories:{filter}={filter:void 0}}=projectAnnotations.parameters?.docs||{};return filter&&(stories=stories.filter(story=>filter(story,getStoryContext(story)))),stories.some(story=>story.tags?.includes("autodocs"))&&(stories=stories.filter(story=>story.tags?.includes("autodocs"))),includePrimary||(stories=stories.slice(1)),!stories||stories.length===0?null:React17.createElement(React17.Fragment,null,React17.createElement(StyledHeading,null,title),stories.map(story=>story&&React17.createElement(DocsStory,{key:story.id,of:story.moduleExport,expanded:!0,__forceInitialArgs:!0})))};var DocsPage=()=>{let resolvedOf=useOf("meta",["meta"]),{stories}=resolvedOf.csfFile,isSingleStory=Object.keys(stories).length===1;return React17.createElement(React17.Fragment,null,React17.createElement(Title2,null),React17.createElement(Subtitle2,null),React17.createElement(DescriptionContainer,{of:"meta"}),isSingleStory?React17.createElement(DescriptionContainer,{of:"story"}):null,React17.createElement(Primary,null),React17.createElement(Controls3,null),isSingleStory?null:React17.createElement(Stories,null))};function Docs({context,docsParameter}){let Container=docsParameter.container||DocsContainer,Page=docsParameter.page||DocsPage;return React17.createElement(Container,{context,theme:docsParameter.theme},React17.createElement(Page,null))}var ExternalDocsContext=class extends DocsContext$1{constructor(channel,store,renderStoryToElement,processMetaExports){super(channel,store,renderStoryToElement,[]);this.channel=channel;this.store=store;this.renderStoryToElement=renderStoryToElement;this.processMetaExports=processMetaExports;this.referenceMeta=(metaExports,attach)=>{let csfFile=this.processMetaExports(metaExports);this.referenceCSFFile(csfFile),super.referenceMeta(metaExports,attach);};}};var ConstantMap=class{constructor(prefix){this.prefix=prefix;this.entries=new Map;}get(key){return this.entries.has(key)||this.entries.set(key,`${this.prefix}${this.entries.size}`),this.entries.get(key)}},ExternalPreview=class extends Preview$1{constructor(projectAnnotations){super(path=>Promise.resolve(this.moduleExportsByImportPath[path]),()=>composeConfigs([{parameters:{docs:{story:{inline:!0}}}},this.projectAnnotations]),new Channel({}));this.projectAnnotations=projectAnnotations;this.importPaths=new ConstantMap("./importPath/");this.titles=new ConstantMap("title-");this.storyIndex={v:5,entries:{}};this.moduleExportsByImportPath={};this.processMetaExports=metaExports=>{let importPath=this.importPaths.get(metaExports);this.moduleExportsByImportPath[importPath]=metaExports;let title=metaExports.default.title||this.titles.get(metaExports),csfFile=this.storyStoreValue.processCSFFileWithCache(metaExports,importPath,title);return Object.values(csfFile.stories).forEach(({id,name})=>{this.storyIndex.entries[id]={id,importPath,title,name,type:"story"};}),this.onStoriesChanged({storyIndex:this.storyIndex}),csfFile};this.docsContext=()=>new ExternalDocsContext(this.channel,this.storyStoreValue,this.renderStoryToElement.bind(this),this.processMetaExports.bind(this));}async getStoryIndexFromServer(){return this.storyIndex}};function usePreview(projectAnnotations){let previewRef=useRef();return previewRef.current||(previewRef.current=new ExternalPreview(projectAnnotations)),previewRef.current}function ExternalDocs({projectAnnotationsList,children}){let projectAnnotations=composeConfigs(projectAnnotationsList),preview2=usePreview(projectAnnotations),docsParameter={...projectAnnotations.parameters?.docs,page:()=>children};return React17.createElement(Docs,{docsParameter,context:preview2.docsContext()})}var preview,ExternalDocsContainer=({projectAnnotations,children})=>(preview||(preview=new ExternalPreview(projectAnnotations)),React17.createElement(DocsContext.Provider,{value:preview.docsContext()},React17.createElement(ThemeProvider,{theme:ensure(themes.light)},children)));var Meta=({of})=>{let context=useContext(DocsContext);of&&context.referenceMeta(of,!0);try{let primary=context.storyById();return React17.createElement(Anchor,{storyId:primary.id})}catch{return null}};var Unstyled=props=>React17.createElement("div",{...props,className:"sb-unstyled"});var Wrapper11=({children})=>React17.createElement("div",{style:{fontFamily:"sans-serif"}},children);var PRIMARY_STORY="^"; - --export { AddContext, Anchor, AnchorMdx, ArgTypes, BooleanControl, Canvas, CodeOrSourceMdx, ColorControl, ColorItem, ColorPalette, Controls3 as Controls, DateControl, DescriptionContainer as Description, DescriptionType, Docs, DocsContainer, DocsContext, DocsPage, DocsStory, ExternalDocs, ExternalDocsContainer, FilesControl, HeaderMdx, HeadersMdx, Heading2 as Heading, IconGallery, IconItem, Markdown2 as Markdown, Meta, NumberControl, ObjectControl, OptionsControl, PRIMARY_STORY, Primary, ArgsTable as PureArgsTable, RangeControl, Source2 as Source, SourceContainer, SourceContext, Stories, Story2 as Story, Subheading, Subtitle2 as Subtitle, TextControl, Title2 as Title, Typeset, UNKNOWN_ARGS_HASH, Unstyled, Wrapper11 as Wrapper, anchorBlockIdFromId, argsHash, assertIsFn, extractTitle, format, formatDate, formatTime, getStoryId2 as getStoryId, getStoryProps, parse2 as parse, parseDate, parseTime, useOf, useSourceProps }; -+export { AddContext, Anchor, AnchorMdx, ArgTypes, BooleanControl, Canvas, CodeOrSourceMdx, ColorControl, ColorItem, ColorPalette, Controls3 as Controls, DateControl, DescriptionContainer as Description, DescriptionType, Docs, DocsContainer, DocsContext, DocsPage, DocsStory, ExternalDocs, ExternalDocsContainer, FilesControl, HeaderMdx, HeadersMdx, Heading2 as Heading, IconGallery, IconItem, Markdown2 as Markdown, Meta, NumberControl, ObjectControl, OptionsControl, PRIMARY_STORY, Preview, Primary, ArgsTable as PureArgsTable, RangeControl, Source2 as Source, SourceContainer, SourceContext, Stories, Story2 as Story, Subheading, Subtitle2 as Subtitle, TextControl, Title2 as Title, Typeset, UNKNOWN_ARGS_HASH, Unstyled, Wrapper11 as Wrapper, anchorBlockIdFromId, argsHash, assertIsFn, extractTitle, format, formatDate, formatTime, getStoryId2 as getStoryId, getStoryProps, parse2 as parse, parseDate, parseTime, useOf, useSourceProps }; diff --git a/package.json b/package.json index 9f3aed5c838..d4dfc47342b 100644 --- a/package.json +++ b/package.json @@ -429,7 +429,6 @@ "react-split-pane@0.1.92": "patch:react-split-pane@npm:0.1.92#.yarn/patches/react-split-pane-npm-0.1.92-93dbf51dff.patch", "history@4.10.1": "patch:history@npm%3A4.10.1#./.yarn/patches/history-npm-4.10.1-ee217563ae.patch", "redux": "^5.0.0", - "@storybook/blocks@npm:8.1.6": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch", "react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch", "@grafana/plugin-e2e/@grafana/e2e-selectors": "workspace:*", "@grafana/scenes/@grafana/e2e-selectors": "workspace:*", diff --git a/packages/grafana-ui/.storybook/copyAssets.ts b/packages/grafana-ui/.storybook/copyAssets.ts new file mode 100644 index 00000000000..ecd7c2663f6 --- /dev/null +++ b/packages/grafana-ui/.storybook/copyAssets.ts @@ -0,0 +1,62 @@ +// This script is used to copy assets from the public folder to a temporary static folder within +// the .storybook directory. +// We selectively limit assets that are uploaded to the Storybook bucket to prevent rate limiting +// when publishing new storybook. +// Note: Storybook has a static copying feature but it copies entire directories which can contain thousands of icons. + +import { copySync, emptyDirSync, lstatSync } from 'fs-extra'; +import { resolve } from 'node:path'; + +// avoid importing from @grafana/data to prevent error: window is not defined +import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon'; +import { getIconSubDir } from '../src/components/Icon/utils'; + +// doesn't require uploading 1000s of unused assets. +const iconPaths = Object.keys(availableIconsIndex) + .filter((iconName) => !iconName.startsWith('fa ')) + .map((iconName) => { + const subDir = getIconSubDir(iconName as IconName, 'default'); + return { + from: `../../../public/img/icons/${subDir}/${iconName}.svg`, + to: `./static/public/img/icons/${subDir}/${iconName}.svg`, + }; + }); + +export function copyAssetsSync() { + const assets = [ + { + from: '../../../public/fonts', + to: './static/public/fonts', + }, + { + from: '../../../public/img/grafana_text_logo-dark.svg', + to: './static/public/img/grafana_text_logo-dark.svg', + }, + { + from: '../../../public/img/grafana_text_logo-light.svg', + to: './static/public/img/grafana_text_logo-light.svg', + }, + { + from: '../../../public/img/fav32.png', + to: './static/public/img/fav32.png', + }, + { + from: '../../../public/lib', + to: './static/public/lib', + }, + ...iconPaths, + ]; + + const staticDir = resolve(__dirname, 'static', 'public'); + + emptyDirSync(staticDir); + + for (const asset of assets) { + const fromPath = resolve(__dirname, asset.from); + const toPath = resolve(__dirname, asset.to); + + copySync(fromPath, toPath, { + filter: (src) => !lstatSync(src).isSymbolicLink(), + }); + } +} diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index f8bf57783db..6314014c9f9 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -1,8 +1,6 @@ -import path, { dirname, join } from 'path'; +import path, { dirname, join } from 'node:path'; import type { StorybookConfig } from '@storybook/react-webpack5'; -// avoid importing from @grafana/data to prevent node error: ERR_REQUIRE_ESM -import { availableIconsIndex, IconName } from '../../grafana-data/src/types/icon'; -import { getIconSubDir } from '../src/components/Icon/utils'; +import { copyAssetsSync } from './copyAssets'; // Internal stories should only be visible during development const storyGlob = @@ -12,17 +10,8 @@ const storyGlob = const stories = ['../src/Intro.mdx', storyGlob]; -// We limit icon paths to only the available icons so publishing -// doesn't require uploading 1000s of unused assets. -const iconPaths = Object.keys(availableIconsIndex) - .filter((iconName) => !iconName.startsWith('fa ')) - .map((iconName) => { - const subDir = getIconSubDir(iconName as IconName, 'default'); - return { - from: `../../../public/img/icons/${subDir}/${iconName}.svg`, - to: `/public/img/icons/${subDir}/${iconName}.svg`, - }; - }); +// Copy the assets required by storybook before starting the storybook server. +copyAssetsSync(); const mainConfig: StorybookConfig = { stories, @@ -45,17 +34,18 @@ const mainConfig: StorybookConfig = { url: false, importLoaders: 2, }, + sassLoaderOptions: { + sassOptions: { + // silencing these warnings since we're planning to remove sass when angular is gone + silenceDeprecations: ['import', 'global-builtin'], + }, + }, }, }, getAbsolutePath('@storybook/addon-storysource'), getAbsolutePath('storybook-dark-mode'), - getAbsolutePath('@storybook/addon-mdx-gfm'), getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), ], - core: {}, - docs: { - autodocs: true, - }, framework: { name: getAbsolutePath('@storybook/react-webpack5'), options: { @@ -66,29 +56,7 @@ const mainConfig: StorybookConfig = { }, }, logLevel: 'debug', - staticDirs: [ - { - from: '../../../public/fonts', - to: '/public/fonts', - }, - { - from: '../../../public/img/grafana_text_logo-dark.svg', - to: '/public/img/grafana_text_logo-dark.svg', - }, - { - from: '../../../public/img/grafana_text_logo-light.svg', - to: '/public/img/grafana_text_logo-light.svg', - }, - { - from: '../../../public/img/fav32.png', - to: '/public/img/fav32.png', - }, - { - from: '../../../public/lib', - to: '/public/lib', - }, - ...iconPaths, - ], + staticDirs: ['static'], typescript: { check: true, reactDocgen: 'react-docgen-typescript', diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index 28110965ade..cc4be9bfbe8 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -46,7 +46,6 @@ const preview: Preview = { knobs: { disable: true, }, - layout: 'fullscreen', options: { // Sort stories first by Docs Overview, then alphabetically // We should be able to use the builtin alphabetical sort, but is broken in SB 7.0 @@ -83,6 +82,7 @@ const preview: Preview = { }, }, }, + tags: ['autodocs'], }; export default preview; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 9b3f5d41a0e..d9282c115fb 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -118,22 +118,22 @@ "@faker-js/faker": "^9.0.0", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.3.0", - "@storybook/addon-a11y": "^8.1.6", - "@storybook/addon-actions": "^8.1.6", - "@storybook/addon-docs": "^8.1.6", - "@storybook/addon-essentials": "^8.1.6", - "@storybook/addon-mdx-gfm": "^8.1.6", - "@storybook/addon-storysource": "^8.1.6", - "@storybook/addon-webpack5-compiler-swc": "^1.0.2", - "@storybook/blocks": "patch:@storybook/blocks@npm%3A8.1.6#~/.yarn/patches/@storybook-blocks-npm-8.1.6-892f57a6d7.patch", - "@storybook/components": "^8.1.6", - "@storybook/core-events": "^8.1.6", + "@storybook/addon-a11y": "^8.4.2", + "@storybook/addon-actions": "^8.4.2", + "@storybook/addon-docs": "^8.4.2", + "@storybook/addon-essentials": "^8.4.2", + "@storybook/addon-storysource": "^8.4.2", + "@storybook/addon-webpack5-compiler-swc": "^1.0.5", + "@storybook/blocks": "^8.4.2", + "@storybook/components": "^8.4.2", + "@storybook/core-events": "^8.4.2", + "@storybook/manager-api": "^8.4.2", "@storybook/mdx2-csf": "1.1.0", "@storybook/preset-scss": "1.0.3", - "@storybook/preview-api": "^8.1.6", - "@storybook/react": "^8.1.6", - "@storybook/react-webpack5": "^8.1.6", - "@storybook/theming": "^8.1.6", + "@storybook/preview-api": "^8.4.2", + "@storybook/react": "^8.4.2", + "@storybook/react-webpack5": "^8.4.2", + "@storybook/theming": "^8.4.2", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.0.1", @@ -166,6 +166,7 @@ "csstype": "3.1.3", "esbuild": "0.24.0", "expose-loader": "5.0.0", + "fs-extra": "^11.2.0", "mock-raf": "1.0.1", "process": "^0.11.10", "react": "18.2.0", @@ -180,8 +181,8 @@ "rollup-plugin-node-externals": "^7.1.3", "rollup-plugin-svg-import": "3.0.0", "sass-loader": "16.0.3", - "storybook": "^8.1.6", - "storybook-dark-mode": "^4.0.1", + "storybook": "^8.4.2", + "storybook-dark-mode": "4.0.1", "style-loader": "4.0.0", "typescript": "5.5.4", "webpack": "5.95.0" diff --git a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.mdx b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.mdx index 56c887844ec..2d6433d00c4 100644 --- a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.mdx +++ b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.mdx @@ -1,6 +1,6 @@ -import { Preview, ArgTypes } from '@storybook/blocks'; +import { Canvas, ArgTypes } from '@storybook/blocks'; import { AutoSaveField } from './AutoSaveField'; -import { Basic } from './AutoSaveField.story.tsx'; +import * as AutoSaveFieldStories from './AutoSaveField.story'; # AutoSaveField @@ -217,9 +217,7 @@ The `AutoSaveField`component is comprised of: {' '} - - - +
### Placement diff --git a/packages/grafana-ui/src/components/Button/Button.mdx b/packages/grafana-ui/src/components/Button/Button.mdx index cce79cbf3bd..48dfcad9143 100644 --- a/packages/grafana-ui/src/components/Button/Button.mdx +++ b/packages/grafana-ui/src/components/Button/Button.mdx @@ -1,15 +1,12 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes, Unstyled } from '@storybook/blocks'; import { Button, LinkButton } from './Button'; import { Alert } from '../Alert/Alert'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; # Button - - After reviewing this component we are asking you to use the IconButton when you require a button with only an icon. - - When using a button please always follow a11y rules (e.g. W3C Recommendation [3.3.2 Labels or instructions](https://www.w3.org/TR/WCAG21/#labels-or-instructions)) and make sure the context in which the button is located is also communicated by screen readers. ## Primary @@ -18,19 +15,17 @@ Used for "call to action", i.e. triggering the main action. There should never b If there is no primary action, all `Button` components should be secondary. - -
- - - -
-
+ + + + + ## Secondary @@ -39,70 +34,64 @@ The secondary `Button` is the default button style and can trigger various actio 1. When next to the primary `Button`, the Secondary style can for example be used for "Cancel" or "Abort" actions. 2. When there is no main important action on a given page, all `Button` components should use the secondary style. - -
- - - -
-
+ + + + + ## Destructive Used for triggering a removing or deleting action. Because of its dominant coloring, it should be used sparingly. If you need multiple Destructive `Button` components in one view, we recommend using a secondary `Button` or Link variant instead and only use the Destructive variant to double confirm. - -
- - - -
-
+ + + + + ## Text - -
- - - -
-
- - + + + + + ## Links To add an anchor that looks like a button use the `` component and pass a href prop. - -
- - {'Small'} - - - {'Medium'} - - - {'Large'} - -
-
+ + + {'Small'} + + + {'Medium'} + + + {'Large'} + + + +## Props + + diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.internal.story.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.internal.story.tsx index 0259c72d2ba..d2a5c36eefe 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.internal.story.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.internal.story.tsx @@ -1,16 +1,10 @@ import type { Meta, StoryObj } from '@storybook/react'; import { MultiCombobox } from './MultiCombobox'; -import mdx from './MultiCombobox.mdx'; const meta: Meta = { title: 'Forms/MultiCombobox', component: MultiCombobox, - parameters: { - docs: { - page: mdx, - }, - }, }; export default meta; diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.mdx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.mdx deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.mdx b/packages/grafana-ui/src/components/Input/AutoSizeInput.mdx index f0084baf586..cd19b078b30 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.mdx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.mdx @@ -1,7 +1,8 @@ -import { ArgTypes, Preview } from '@storybook/blocks'; +import { ArgTypes } from '@storybook/blocks'; import { AutoSizeInput } from './AutoSizeInput'; import { Field } from '../Forms/Field'; import { Icon } from '../Icon/Icon'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; # AutoSizeInput @@ -15,9 +16,9 @@ To add more context to the input, you can add either text or an icon before or a } /> ``` - + } /> - + ## Usage in forms with Field @@ -29,9 +30,12 @@ Use `AutoSizeInput`with the`Field`component to get labels and descriptions. Also ``` - + - + + +## Props + diff --git a/packages/grafana-ui/src/components/Input/Input.mdx b/packages/grafana-ui/src/components/Input/Input.mdx index fe027a2787f..a47f84b055d 100644 --- a/packages/grafana-ui/src/components/Input/Input.mdx +++ b/packages/grafana-ui/src/components/Input/Input.mdx @@ -1,12 +1,16 @@ -import { ArgTypes, Preview } from '@storybook/blocks'; +import { ArgTypes, Canvas } from '@storybook/blocks'; import { Input } from './Input'; import { Field } from '../Forms/Field'; import { Icon } from '../Icon/Icon'; +import * as InputStories from './Input.story'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; # Input Used for regular text input. For an array of data or tree-structured data, consider using `Select` or `Cascader` respectively. + + ## Prefix and suffix To add more context to the input you can add either text or an icon before or after the input. You can use the `prefix` and `suffix` props for this. Try some examples in the Preview! @@ -15,9 +19,9 @@ To add more context to the input you can add either text or an icon before or af } /> ``` - + } /> - + ## Usage in forms with Field @@ -29,12 +33,15 @@ To add more context to the input you can add either text or an icon before or af ``` - + - + + +## Props + diff --git a/packages/grafana-ui/src/components/Layout/Layout.mdx b/packages/grafana-ui/src/components/Layout/Layout.mdx index 08de385acca..854cbcc9e2f 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.mdx +++ b/packages/grafana-ui/src/components/Layout/Layout.mdx @@ -1,7 +1,9 @@ -import { Preview, ArgTypes } from '@storybook/blocks'; +import { Canvas, ArgTypes } from '@storybook/blocks'; import { Layout, HorizontalGroup, VerticalGroup } from './Layout'; import { Button } from '../Button'; import { Select } from '../index'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; +import * as LayoutStories from './Layout.story'; # Layout @@ -19,78 +21,10 @@ Expects multiple elements as `children` prop. This is the base for more speciali Used for horizontally aligning several elements (e.g. Button, Select) with a predefined spacing between them. - - - - - - - - - - {}} - options={[ - { value: 1, label: 'Option 1' }, - { value: 2, label: 'Option 2' }, - ]} - /> - {}} - options={[ - { value: 1, label: 'Option 1' }, - { value: 2, label: 'Option 2' }, - ]} - /> - {}} - options={[ - { value: 1, label: 'Option 1' }, - { value: 2, label: 'Option 2' }, - ]} - /> - - + diff --git a/packages/grafana-ui/src/components/Layout/Layout.story.tsx b/packages/grafana-ui/src/components/Layout/Layout.story.tsx index ab890009c80..5e447599f40 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.story.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.story.tsx @@ -62,8 +62,9 @@ export default meta; export const Horizontal: StoryFn = (args) => { return ( + + - ); }; @@ -71,8 +72,9 @@ export const Horizontal: StoryFn = (args) => { export const Vertical: StoryFn = (args) => { return ( + + - ); }; diff --git a/packages/grafana-ui/src/components/Link/TextLink.mdx b/packages/grafana-ui/src/components/Link/TextLink.mdx index 414204fd720..5303d4f3986 100644 --- a/packages/grafana-ui/src/components/Link/TextLink.mdx +++ b/packages/grafana-ui/src/components/Link/TextLink.mdx @@ -1,6 +1,7 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { TextLink } from './TextLink'; -import { Basic } from './TextLink.story.tsx'; +import * as TextLinkStories from './TextLink.story.tsx'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; @@ -81,30 +82,26 @@ The following is the default behaviour and so, it will be applied according to i ### **Inline**:
- - - {'This an inline link example'} - - -- _Initial appearence_: Blue and underlined at first. - _On hover_: blue and not underlined +- _Initial appearence_: Blue and underlined at first. +- _On hover_: blue and not underlined + + ###
**Standalone**: - - - {'This an standalone link example'} - - -- _Initial appearence_: Blue and not underlined at first. - _On hover_: blue and underlined. +- _Initial appearence_: Blue and not underlined at first. +- _On hover_: blue and underlined. + + _Example_: We should take into account that through the color prop the user will be able to change this value, but the rest will be kept. So, in this example, if the user renders an external and standalone _text link_ setting the color value to 'primary' the result will be a text link 'white' in dark theme or 'black' in light theme, as set by the user, and not underlined at first while blue and underlined when onhover, as it inherits from the standalone default behaviour. - + {'This an external standalone link example'} - + ```jsx @@ -116,11 +113,11 @@ but the rest will be kept. So, in this example, if the user renders an external By default, **external _text links_** will show an `external-alt-icon`. If by design reasons, the user needs to set another icon it can be done through the correspondent prop. - + {'This an external with the default icon'} - + ```jsx @@ -130,11 +127,11 @@ By default, **external _text links_** will show an `external-alt-icon`. If by de Both, **external** and **internal** links can show a specific icon using the `icon` prop. - + {'This an external with a specific icon'} - + ```jsx This an external with a specific icon diff --git a/packages/grafana-ui/src/components/Link/TextLink.story.tsx b/packages/grafana-ui/src/components/Link/TextLink.story.tsx index 2126daf6d50..6ecc925796c 100644 --- a/packages/grafana-ui/src/components/Link/TextLink.story.tsx +++ b/packages/grafana-ui/src/components/Link/TextLink.story.tsx @@ -32,7 +32,7 @@ const meta: Meta = { inline: { control: 'boolean' }, }, args: { - href: 'https://www.google.com', + href: 'https://www.grafana.com', external: true, icon: 'external-link-alt', }, @@ -67,14 +67,33 @@ Example.parameters = { controls: { exclude: ['href', 'external', 'variant', 'weight', 'color', 'inline', 'icon'] }, }; -export const Basic: StoryFn = (args) => { +export const Inline: StoryFn = (args) => { return (
+ For more information{' '} - Go to Google + see Grafana.com
); }; +Inline.args = { + inline: true, +}; + +export const Standalone: StoryFn = (args) => { + return ( +
+ + Go to Grafana.com + +
+ ); +}; + +Standalone.args = { + inline: false, +}; + export default meta; diff --git a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.mdx b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.mdx index cf0b0e26f65..3a30012c763 100644 --- a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.mdx +++ b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.mdx @@ -1,5 +1,6 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { LoadingBar } from './LoadingBar'; +import * as LoadingBarStories from './LoadingBar.story'; @@ -7,16 +8,6 @@ import { LoadingBar } from './LoadingBar'; The LoadingBar is used as a simple loading slider animation in the top of its container. - -
- -
-
- -```jsx -
- -
-``` + diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.mdx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.mdx index 492ed2eca98..c892110fbab 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.mdx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.mdx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { Meta, Preview } from '@storybook/blocks'; +import { Meta, Canvas } from '@storybook/blocks'; import { PanelChrome } from './PanelChrome'; import { action } from '@storybook/addon-actions'; @@ -8,6 +8,8 @@ import { Button } from '../Button'; import { Menu } from '../Menu/Menu'; import { Stack } from '../Layout/Stack/Stack'; import { IconButton } from '../IconButton/IconButton'; +import * as PanelChromeStories from './PanelChrome.story'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; @@ -17,97 +19,11 @@ Component used for rendering content wrapped in the same style as grafana panels ### Basic Usage: Title, Description and Content -```tsx - - {(innerwidth, innerheight) => { - return ( -
- New panel with old API -
- ); - }} -
-``` - - - - {(innerwidth, innerheight) => { - return ( -
- Content -
- ); - }} -
-
+ ### Menu: Standard & Hover -```tsx -} - menu={() => ( - - - - - - - - )} - description="Here I will put a description that explains a bit more this panel" - width={400} - height={200} -> - {(innerwidth, innerheight) => { - return ( -
- New panel with old API -
- ); - }} -
-``` - - + - + ### States: Loading , Streaming, Error @@ -215,7 +131,7 @@ Component used for rendering content wrapped in the same style as grafana panels ``` - + - + ### Extra options? Title items and actions @@ -345,7 +261,7 @@ Component used for rendering content wrapped in the same style as grafana panels ``` - + - + ### Migration from old PanelChrome (before v9.4.0) @@ -418,7 +334,7 @@ Component used for rendering content wrapped in the same style as grafana panels ``` - + - + #### After ```tsx @@ -478,7 +394,7 @@ Component used for rendering content wrapped in the same style as grafana panels ``` - + - + ### Collapsible @@ -545,7 +461,7 @@ function Container() { } ``` - + {(innerwidth, innerheight) => { return ( @@ -564,4 +480,4 @@ function Container() { ); }} - + diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.mdx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.mdx index 8c5317a610d..4cd0fcc5c66 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.mdx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.mdx @@ -1,5 +1,6 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { PluginSignatureBadge } from './PluginSignatureBadge'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; @@ -12,32 +13,32 @@ This is a security measure to make sure plugins haven't been tampered with. Upon 1.- Core: this badge indicates that the plugin has been built into Grafana. - + - + 2.- Signed: the plugin's signature was successfully verified. - + - + 3.- Invalid signature: this plugin has a invalid signature. - + - + 4.- Modified signature: this plugin has changed since it was signed. This may indicate malicious intent. - + - + 5.- Unsigned: the plugin is not signed. In this case, as upon loading Grafana checks the plugins signature, Grafana will not load or start it. Furthermore, it will write an error message to the server log. - + - + diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.mdx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.mdx index 0c65652d1c0..b04d667842d 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.mdx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.mdx @@ -1,5 +1,6 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { RefreshPicker } from './RefreshPicker'; +import * as RefreshPickerStories from './RefreshPicker.story'; @@ -11,10 +12,6 @@ This component is used on dashboards to refresh visualizations. Grafana does not **The down arrow:** will display a list of refresh intervals. If one of them is selected the dashboard will regularly refresh according to that schedule. - - - - - + diff --git a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.mdx b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.mdx index 81e63aa4dbe..24220cbb5c9 100644 --- a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.mdx +++ b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.mdx @@ -1,4 +1,4 @@ -import { ArgTypes } from '@storybook/blocks'; +import { Meta, ArgTypes } from '@storybook/blocks'; import { RenderUserContentAsHTML } from './RenderUserContentAsHTML'; diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.mdx b/packages/grafana-ui/src/components/Spinner/Spinner.mdx index 34148ae430b..3a85656393e 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.mdx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.mdx @@ -1,5 +1,6 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { Spinner } from './Spinner'; +import * as SpinnerStories from './Spinner.story'; @@ -7,11 +8,6 @@ import { Spinner } from './Spinner'; Spinner is `fa-spinner` icon animated. It is used to alert a user to wait for an activity to complete. - -
- - -
-
+ diff --git a/packages/grafana-ui/src/components/Tags/Tag.mdx b/packages/grafana-ui/src/components/Tags/Tag.mdx index 41cb0c6ad13..f159e1f6715 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.mdx +++ b/packages/grafana-ui/src/components/Tags/Tag.mdx @@ -1,5 +1,6 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { Tag } from './Tag'; +import * as TagStories from './Tag.story'; @@ -7,19 +8,7 @@ import { Tag } from './Tag'; Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name. - -
- console.log(name)} /> -
-
- -### Usage - -```jsx -import { Tag } from '@grafana/ui'; - - console.log(name)} />; -``` + ### Props diff --git a/packages/grafana-ui/src/components/Tags/TagList.mdx b/packages/grafana-ui/src/components/Tags/TagList.mdx index d56aab9dabc..829451d6b08 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.mdx +++ b/packages/grafana-ui/src/components/Tags/TagList.mdx @@ -1,22 +1,12 @@ -import { Preview, ArgTypes } from '@storybook/blocks'; +import { Canvas, ArgTypes } from '@storybook/blocks'; import { TagList } from './TagList'; +import * as TagListStories from './TagList.story'; # TagList List of tags with predefined margins and positioning. - - - - -### Usage - -```jsx -import { TagList } from '@grafana/ui'; -const tags = ['datasource-test', 'gdev', 'mysql', 'mssql']; - - console.log(tag)} />; -``` + ### Props diff --git a/packages/grafana-ui/src/components/Text/Text.mdx b/packages/grafana-ui/src/components/Text/Text.mdx index d0402a866af..9fb69f4a73d 100644 --- a/packages/grafana-ui/src/components/Text/Text.mdx +++ b/packages/grafana-ui/src/components/Text/Text.mdx @@ -1,8 +1,9 @@ -import { Meta, Preview, ArgTypes } from '@storybook/blocks'; +import { Meta, Canvas, ArgTypes } from '@storybook/blocks'; import { Text } from './Text'; import { TextLink } from '../Link/TextLink.tsx'; import { Basic } from './Text.story.tsx'; import { Tooltip } from '../Tooltip'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; @@ -65,14 +66,14 @@ The following is the default behaviour and so, it will be applied according to i The Text component is mainly comprised by itself. In occasions, the Text component can have another Text or TextLink component as a child. - + {'If you need more help of how to write in Grafana you can go to our '} {'Writer’s Toolkit'} - + ```jsx @@ -83,12 +84,12 @@ The Text component is mainly comprised by itself. In occasions, the Text compone ``` - + {'And Forrest Gump said: '} {"Life is like a box of chocolates. You never know what you're gonna get."} - + ```jsx @@ -104,23 +105,23 @@ The Text component can be truncated. However, the Text component element rendere 1. The parent element is a Text component: the user just has to set the element prop to another value and the truncate prop to true. As a result, the Text will be truncated but when the user hovers over it the full text will be seen on a tooltip. - + {'And Forrest Gump said: '} {'Life is like a box of chocolates. You never know what you are gonna get.'} - + ```jsx And Forrest Gump said: - {'Life is like a box of chocolates. You never know what you are gonna get.'} + Life is like a box of chocolates. You never know what you are gonna get. ``` 2. The parent element is not a Text component: the user has to add `overflow: hidden`, `text-overflow: ellipsis` and `whiteSpace: 'nowrap'` to it. In this case, the user should wrap up this container with a Tooltip, so when the text is truncated its content can still be seen hovering on the text. - +
@@ -128,7 +129,7 @@ The Text component can be truncated. However, the Text component element rendere
-
+ ```jsx
diff --git a/packages/grafana-ui/src/components/TextArea/TextArea.mdx b/packages/grafana-ui/src/components/TextArea/TextArea.mdx index c7cd22e72a8..878f41dd36b 100644 --- a/packages/grafana-ui/src/components/TextArea/TextArea.mdx +++ b/packages/grafana-ui/src/components/TextArea/TextArea.mdx @@ -1,6 +1,8 @@ -import { ArgTypes, Preview } from '@storybook/blocks'; +import { ArgTypes, Canvas } from '@storybook/blocks'; import { Field } from '../Forms/Field'; import { TextArea } from './TextArea'; +import * as TextAreaStories from './TextArea.story'; +import { ExampleFrame } from '../../utils/storybook/ExampleFrame'; # TextArea @@ -8,27 +10,19 @@ Use for multi line inputs like descriptions. ### Usage -```jsx -