From 22b2d3c38a5745352aa064f01e1c1f8158de64ef Mon Sep 17 00:00:00 2001 From: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com> Date: Tue, 4 May 2021 15:03:42 +0800 Subject: [PATCH] frontend for trim/apply defaults and some bug fixing (#33561) * remove empty object and workaround on list * frontend * add toggle on frontend --- cue/data/gen.cue | 4 +- packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/plugins.go | 9 +++ pkg/schema/load/applydefault_test.go | 1 - pkg/schema/load/generic.go | 35 +++++++-- .../artifacts/dashboards/trimdefault/test4 | 73 +++++++++++++++++++ .../components/ShareModal/ShareExport.tsx | 58 ++++++++++++++- 8 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test4 diff --git a/cue/data/gen.cue b/cue/data/gen.cue index 823a700326d..c824da7294b 100644 --- a/cue/data/gen.cue +++ b/cue/data/gen.cue @@ -10,7 +10,7 @@ Family: scuemata.#Family & { // TODO must isolate or remove identifiers local to a Grafana instance...? id?: number // Unique dashboard identifier that can be generated by anyone. string (8-40) - uid: string + uid?: string // Title of dashboard. title?: string // Description of dashboard. @@ -181,7 +181,7 @@ Family: scuemata.#Family & { // nullValueMode?: NullValueMode; // // The behavior when clicking on a result - // links?: DataLink[]; + links?: [...] // Alternative to empty string noValue?: string diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 870128e9745..99c645d6d2d 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -46,6 +46,7 @@ export interface FeatureToggles { live: boolean; ngalert: boolean; + trimDefaults: boolean; panelLibrary: boolean; accesscontrol: boolean; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index af410e6c234..cc43e8af5bc 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -59,6 +59,7 @@ export class GrafanaBootConfig implements GrafanaConfig { panelLibrary: false, reportVariables: false, accesscontrol: false, + trimDefaults: false, }; licenseInfo: LicenseInfo = {} as LicenseInfo; rendererAvailable = false; diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index e5b48f2c268..caf8e14306f 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -206,10 +206,19 @@ func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response } func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDashboardCommand) response.Response { + var err error if apiCmd.PluginId == "" && apiCmd.Dashboard == nil { return response.Error(422, "Dashboard must be set", nil) } + trimDefaults := c.QueryBoolWithDefault("trimdefaults", true) + if trimDefaults && !hs.LoadSchemaService.IsDisabled() { + apiCmd.Dashboard, err = hs.LoadSchemaService.DashboardApplyDefaults(apiCmd.Dashboard) + if err != nil { + return response.Error(500, "Error while applying default value to the dashboard json", err) + } + } + dashInfo, err := hs.PluginManager.ImportDashboard(apiCmd.PluginId, apiCmd.Path, c.OrgId, apiCmd.FolderId, apiCmd.Dashboard, apiCmd.Overwrite, apiCmd.Inputs, c.SignedInUser, hs.DataService) if err != nil { diff --git a/pkg/schema/load/applydefault_test.go b/pkg/schema/load/applydefault_test.go index 67e43493180..e429843b4a6 100644 --- a/pkg/schema/load/applydefault_test.go +++ b/pkg/schema/load/applydefault_test.go @@ -51,7 +51,6 @@ func TestGenerate(t *testing.T) { }) } - t.Skip() for _, c := range cases { t.Run(c.Name+" trim default value", func(t *testing.T) { var r cue.Runtime diff --git a/pkg/schema/load/generic.go b/pkg/schema/load/generic.go index d7f0d0f8d92..1cb0019141e 100644 --- a/pkg/schema/load/generic.go +++ b/pkg/schema/load/generic.go @@ -3,6 +3,7 @@ package load import ( "bytes" "fmt" + "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/load" @@ -149,6 +150,7 @@ func (gvs *genericVersionedSchema) TrimDefaults(r schema.Resource) (schema.Resou return r, err } re, err := convertCUEValueToString(rv) + fmt.Println("the trimed fields would be: ", re) if err != nil { return r, err } @@ -171,9 +173,10 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, if err != nil { return rv, false, err } + keySet := make(map[string]bool) for iter.Next() { lable, _ := iter.Value().Label() - + keySet[lable] = true lv := input.LookupPath(cue.MakePath(cue.Str(lable))) if err != nil { continue @@ -185,6 +188,17 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, } } } + // Get all the fields that are not defined in schema yet for panel + iter, err = input.Fields() + if err != nil { + return rv, false, err + } + for iter.Next() { + lable, _ := iter.Value().Label() + if exists := keySet[lable]; !exists { + rv = rv.FillPath(cue.MakePath(cue.Str(lable)), iter.Value()) + } + } return rv, false, nil case cue.ListKind: val, _ := inputdef.Default() @@ -194,7 +208,6 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, return rv, true, nil } ele := inputdef.LookupPath(cue.MakePath(cue.AnyIndex)) - fmt.Println("xxxxxxxxxxxxxxxxxxxxx ", ele.IncompleteKind()) if ele.IncompleteKind() == cue.BottomKind { return rv, true, nil } @@ -203,17 +216,23 @@ func removeDefaultHelper(inputdef cue.Value, input cue.Value) (cue.Value, bool, if err != nil { return rv, true, nil } - index := 0 + var iterlist []string for iter.Next() { re, isEqual, err := removeDefaultHelper(ele, iter.Value()) if err == nil && !isEqual { - rv = rv.FillPath(cue.MakePath(cue.Index(index)), re) - index++ + reString, err := convertCUEValueToString(re) + if err != nil { + return rv, true, nil + } + iterlist = append(iterlist, reString) } } - - // rv = rv.FillPath(cue.MakePath(cue.Str(lable)), rv) - return rv, false, nil + iterlistContent := fmt.Sprintf("[%s]", strings.Join(iterlist, ",")) + liInstance, err := rt.Compile("resource", []byte(iterlistContent)) + if err != nil { + return rv, false, err + } + return liInstance.Value(), false, nil default: val, _ := inputdef.Default() err1 := input.Subsume(val) diff --git a/pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test4 b/pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test4 new file mode 100644 index 00000000000..a759b156b10 --- /dev/null +++ b/pkg/schema/load/testdata/artifacts/dashboards/trimdefault/test4 @@ -0,0 +1,73 @@ +Verifies common usecases for trimdefault/applydefault functions: +* open structure should be kept when fields not present + +-- CUE -- +{ + templating?: list: [...{...}] +} + +-- Full -- +{ + "templating": { + "list": [ + { + "allValue": null, + "current": { + "text": "America", + "value": "America" + }, + "datasource": "gdev-postgres", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Datacenter", + "multi": false, + "name": "datacenter", + "options": [], + "query": "SELECT DISTINCT datacenter FROM grafana_metric", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + } +} + +-- Trimed -- +{ + "templating": { + "list": [ + { + "allValue": null, + "current": { + "text": "America", + "value": "America" + }, + "datasource": "gdev-postgres", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Datacenter", + "multi": false, + "name": "datacenter", + "options": [], + "query": "SELECT DISTINCT datacenter FROM grafana_metric", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + } +} \ No newline at end of file diff --git a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx index f055af6a7d9..4feeec45b6c 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react'; import { saveAs } from 'file-saver'; +import { getBackendSrv } from 'app/core/services/backend_srv'; import { Button, Field, Modal, Switch } from '@grafana/ui'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; import { appEvents } from 'app/core/core'; import { ShowModalReactEvent } from 'app/types/events'; import { ViewJsonModal } from './ViewJsonModal'; +import { config } from '@grafana/runtime'; interface Props { dashboard: DashboardModel; @@ -15,6 +17,7 @@ interface Props { interface State { shareExternally: boolean; + trimDefaults: boolean; } export class ShareExport extends PureComponent { @@ -24,6 +27,7 @@ export class ShareExport extends PureComponent { super(props); this.state = { shareExternally: false, + trimDefaults: false, }; this.exporter = new DashboardExporter(); @@ -35,29 +39,69 @@ export class ShareExport extends PureComponent { }); }; + onTrimDefaultsChange = () => { + this.setState({ + trimDefaults: !this.state.trimDefaults, + }); + }; + onSaveAsFile = () => { const { dashboard } = this.props; const { shareExternally } = this.state; + const { trimDefaults } = this.state; if (shareExternally) { this.exporter.makeExportable(dashboard).then((dashboardJson: any) => { - this.openSaveAsDialog(dashboardJson); + if (trimDefaults) { + getBackendSrv() + .post('/api/dashboards/trim', { dashboard: dashboardJson }) + .then((resp: any) => { + this.openSaveAsDialog(resp.dashboard); + }); + } else { + this.openSaveAsDialog(dashboardJson); + } }); } else { - this.openSaveAsDialog(dashboard.getSaveModelClone()); + if (trimDefaults) { + getBackendSrv() + .post('/api/dashboards/trim', { dashboard: dashboard.getSaveModelClone() }) + .then((resp: any) => { + this.openSaveAsDialog(resp.dashboard); + }); + } else { + this.openSaveAsDialog(dashboard.getSaveModelClone()); + } } }; onViewJson = () => { const { dashboard } = this.props; const { shareExternally } = this.state; + const { trimDefaults } = this.state; if (shareExternally) { this.exporter.makeExportable(dashboard).then((dashboardJson: any) => { - this.openJsonModal(dashboardJson); + if (trimDefaults) { + getBackendSrv() + .post('/api/dashboards/trim', { dashboard: dashboardJson }) + .then((resp: any) => { + this.openJsonModal(resp.dashboard); + }); + } else { + this.openJsonModal(dashboardJson); + } }); } else { - this.openJsonModal(dashboard.getSaveModelClone()); + if (trimDefaults) { + getBackendSrv() + .post('/api/dashboards/trim', { dashboard: dashboard.getSaveModelClone() }) + .then((resp: any) => { + this.openJsonModal(resp.dashboard); + }); + } else { + this.openJsonModal(dashboard.getSaveModelClone()); + } } }; @@ -86,6 +130,7 @@ export class ShareExport extends PureComponent { render() { const { onDismiss } = this.props; const { shareExternally } = this.state; + const { trimDefaults } = this.state; return ( <> @@ -93,6 +138,11 @@ export class ShareExport extends PureComponent { + {config.featureToggles.trimDefaults && ( + + + + )}