From bf9a34f2ca81fdec3c4be74f5e63a9fe6bbf5102 Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:45:24 +0000 Subject: [PATCH 1/8] TimePicker: Ability to manually specify quick ranges (#101465) * add quickRanges to scene model transforms * add test for quickRanges transformations * fix lint --- .../src/schema/dashboard/v2alpha0/examples.ts | 12 + .../transformSceneToSaveModel.test.ts.snap | 375 ++++++++++++++++++ .../transformSaveModelSchemaV2ToScene.test.ts | 4 +- .../transformSaveModelSchemaV2ToScene.ts | 4 +- .../transformSaveModelToScene.test.ts | 30 ++ .../transformSaveModelToScene.ts | 4 +- .../transformSceneToSaveModel.test.ts | 27 ++ .../transformSceneToSaveModel.ts | 1 + .../transformSceneToSaveModelSchemaV2.test.ts | 12 + .../transformSceneToSaveModelSchemaV2.ts | 1 + 10 files changed, 465 insertions(+), 5 deletions(-) diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index c12f1cc73f7..fe92defe024 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -18,6 +18,18 @@ export const handyTestingSchema: DashboardV2Spec = { timezone: 'UTC', to: 'now', weekStart: 'monday', + quickRanges: [ + { + display: 'Last 6 hours', + from: 'now-6h', + to: 'now', + }, + { + display: 'Last 3 days', + from: 'now-3d', + to: 'now', + }, + ], }, annotations: [ { diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap index 585b51daf4e..eaf887a176b 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap @@ -1,5 +1,380 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Given a scene with custom quick ranges should save quick ranges to save model 1`] = ` +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana", + }, + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard", + }, + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata", + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Enabled", + "target": { + "lines": 4, + "refId": "Anno", + "scenarioId": "annotations", + }, + }, + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata", + }, + "enable": false, + "hide": false, + "iconColor": "yellow", + "name": "Disabled", + "target": { + "lines": 5, + "refId": "Anno", + "scenarioId": "annotations", + }, + }, + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata", + }, + "enable": true, + "hide": true, + "iconColor": "dark-purple", + "name": "Hidden", + "target": { + "lines": 6, + "refId": "Anno", + "scenarioId": "annotations", + }, + }, + ], + }, + "editable": true, + "fiscalYearStartMonth": 1, + "graphTooltip": 1, + "id": 1351, + "links": [], + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "description": "This is a simple time series graph", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + }, + "custom": { + "fillOpacity": 0, + "gradientMode": "none", + "lineWidth": 2, + }, + }, + "overrides": [], + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0, + }, + "id": 28, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + }, + "tooltip": { + "mode": "single", + "sort": "none", + }, + }, + "targets": [ + { + "alias": "series", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1, + }, + ], + "title": "Simple time series graph ", + "type": "timeseries", + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8, + }, + "id": 5, + "panels": [], + "title": "Row title", + "type": "row", + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "fieldConfig": { + "defaults": {}, + "overrides": [], + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 9, + }, + "id": 29, + "options": {}, + "targets": [ + { + "alias": "series", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1, + }, + ], + "title": "panel inside row", + "type": "timeseries", + }, + { + "fieldConfig": { + "defaults": {}, + "overrides": [], + }, + "gridPos": { + "h": 10, + "w": 11, + "x": 12, + "y": 9, + }, + "id": 25, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false, + }, + "content": "content", + "mode": "markdown", + }, + "pluginVersion": "10.2.0-pre", + "title": "Transparent text panel", + "transparent": true, + "type": "text", + }, + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [ + "gdev", + "graph-ng", + "demo", + ], + "templating": { + "list": [ + { + "baseFilters": [], + "datasource": { + "type": "prometheus", + "uid": "wc2AL7L7k", + }, + "filters": [], + "name": "Filters", + "type": "adhoc", + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "text": "1m", + "value": "1m", + }, + "name": "intervalVar", + "options": [ + { + "selected": true, + "text": "1m", + "value": "1m", + }, + { + "selected": false, + "text": "10m", + "value": "10m", + }, + { + "selected": false, + "text": "30m", + "value": "30m", + }, + { + "selected": false, + "text": "1h", + "value": "1h", + }, + { + "selected": false, + "text": "6h", + "value": "6h", + }, + { + "selected": false, + "text": "12h", + "value": "12h", + }, + { + "selected": false, + "text": "1d", + "value": "1d", + }, + { + "selected": false, + "text": "7d", + "value": "7d", + }, + { + "selected": false, + "text": "14d", + "value": "14d", + }, + { + "selected": false, + "text": "30d", + "value": "30d", + }, + ], + "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "refresh": 2, + "type": "interval", + }, + { + "current": { + "text": [ + "a", + ], + "value": [ + "a", + ], + }, + "includeAll": true, + "multi": true, + "name": "customVar", + "options": [], + "query": "a, b, c", + "type": "custom", + }, + { + "current": { + "text": "gdev-testdata", + "value": "PD8C576611E62080A", + }, + "includeAll": false, + "name": "dsVar", + "options": [], + "query": "grafana-testdata-datasource", + "refresh": 1, + "regex": "", + "type": "datasource", + }, + { + "current": { + "text": "A", + "value": "A", + }, + "definition": "*", + "includeAll": false, + "name": "query0", + "options": [], + "query": { + "query": "*", + "refId": "StandardVariableQuery", + }, + "refresh": 1, + "regex": "", + "type": "query", + }, + { + "current": { + "text": "test", + "value": "test", + }, + "hide": 2, + "name": "constant", + "query": "test", + "skipUrlSync": true, + "type": "constant", + }, + ], + }, + "time": { + "from": "now-5m", + "to": "now", + }, + "timepicker": { + "quick_ranges": [ + { + "display": "Last 6 hours", + "from": "now-6h", + "to": "now", + }, + { + "display": "Last 3 days", + "from": "now-3d", + "to": "now", + }, + ], + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d", + ], + }, + "timezone": "America/New_York", + "title": "Dashboard to load1", + "uid": "nP8rcffGkasd", + "version": 2, + "weekStart": "saturday", +} +`; + exports[`transformSceneToSaveModel Annotations should transform annotations to save model 1`] = ` [ { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index cf8308c90ca..e1bd8b6fe0f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -131,6 +131,7 @@ describe('transformSaveModelSchemaV2ToScene', () => { expect(dashboardControls).toBeDefined(); expect(dashboardControls.state.refreshPicker.state.intervals).toEqual(time.autoRefreshIntervals); expect(dashboardControls.state.hideTimeControls).toBe(time.hideTimepicker); + expect(dashboardControls.state.timePicker.state.quickRanges).toEqual(dash.timeSettings.quickRanges); // Variables const variables = scene.state?.$variables; @@ -223,9 +224,6 @@ describe('transformSaveModelSchemaV2ToScene', () => { expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable); expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide); - // To be implemented - // expect(timePicker.state.ranges).toEqual(dash.timeSettings.quickRanges); - // VizPanel const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); expect(vizPanels).toHaveLength(3); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 40b5b01fbf9..43219038d22 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -203,7 +203,9 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo { }); }); +describe('When creating a snapshot dashboard scene', () => { + it('should initialize a dashboard scene with SnapshotVariables', () => { + const dashboard = { + ...defaultDashboard, + title: 'With custom quick ranges', + uid: 'test-uid', + timepicker: { + ...defaultTimePickerConfig, + quick_ranges: [ + { + display: 'Last 6 hours', + from: 'now-6h', + to: 'now', + }, + { + display: 'Last 3 days', + from: 'now-3d', + to: 'now', + }, + ], + }, + }; + + const oldModel = new DashboardModel(dashboard); + const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard); + + expect(scene.state.controls?.state.timePicker.state.quickRanges).toBe(dashboard.timepicker.quick_ranges); + }); +}); + function buildGridItemForTest(saveModel: Partial): { gridItem: DashboardGridItem; vizPanel: VizPanel } { const gridItem = buildGridItemForPanel(new PanelModel(saveModel)); if (gridItem instanceof DashboardGridItem) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index b271697a87e..cd65a502b7c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -283,7 +283,9 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, $data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }), controls: new DashboardControls({ variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], - timePicker: new SceneTimePicker({}), + timePicker: new SceneTimePicker({ + quickRanges: oldModel.timepicker.quick_ranges, + }), refreshPicker: new SceneRefreshPicker({ refresh: oldModel.refresh, intervals: oldModel.timepicker.refresh_intervals, diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index d9576c47fd0..9d6061e3c6f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -1057,6 +1057,33 @@ describe('transformSceneToSaveModel', () => { }); }); +describe('Given a scene with custom quick ranges', () => { + it('should save quick ranges to save model', () => { + const dashboardWithCustomSettings = { + ...dashboard_to_load1, + timepicker: { + ...dashboard_to_load1.timepicker, + quick_ranges: [ + { + display: 'Last 6 hours', + from: 'now-6h', + to: 'now', + }, + { + display: 'Last 3 days', + from: 'now-3d', + to: 'now', + }, + ], + }, + }; + const scene = transformSaveModelToScene({ dashboard: dashboardWithCustomSettings as DashboardDataDTO, meta: {} }); + const saveModel = transformSceneToSaveModel(scene); + + expect(saveModel).toMatchSnapshot(); + }); +}); + export function buildGridItemFromPanelSchema(panel: Partial) { return buildGridItemForPanel(new PanelModel(panel)); } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 89710e4c7f1..b9f280b4a42 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -100,6 +100,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa refresh_intervals: refreshPicker?.state.intervals, hidden: controlsState?.hideTimeControls, nowDelay: timeRange.UNSAFE_nowDelay, + quick_ranges: controlsState?.timePicker.state.quickRanges, }, defaultTimePickerConfig ); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 050565bc711..7e438b72b56 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -404,6 +404,18 @@ function getMinimalSceneState(body: DashboardLayoutManager): Partial Date: Tue, 4 Mar 2025 11:56:35 +0100 Subject: [PATCH 2/8] Feature: Introduce subresource integrity checks (SRI) for frontend assets (#100983) * feat(featuremgmt): introduce feature toggle for enabling sri checks * feat(frontend): use assetSriChecks feature toggle to inject integrity hash into script tags * chore(webpack): align sri algorithms across dev and prod builds * docs(featuremgmt): update assetSriChecks to pass CI * docs(featuremgmt): fix more spelling complaints with assetSriChecks * Add crossorigin attribute * chore(webpack): add subresource-integrity plugin * build(webpack): wrap webpack jsonp loader integrity checks in feature flag checks * revert(index.html): remove crossorigin attribute if assertSriChecks is disabled --------- Co-authored-by: Kristian Bremberg --- .../feature-toggles/index.md | 1 + package.json | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/codeowners.go | 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 +++++ public/views/index.html | 27 ++++++-- .../plugins/FeatureFlaggedSriPlugin.js | 66 +++++++++++++++++++ scripts/webpack/webpack.dev.js | 1 + scripts/webpack/webpack.prod.js | 8 +++ yarn.lock | 14 ++++ 13 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 scripts/webpack/plugins/FeatureFlaggedSriPlugin.js 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 f40ad6a1766..7ceb0dc3460 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -226,6 +226,7 @@ Experimental features might be changed or removed without prior notice. | `datasourceConnectionsTab` | Shows defined connections for a data source in the plugins detail page | | `newLogsPanel` | Enables the new logs panel in Explore | | `pluginsCDNSyncLoader` | Load plugins from CDN synchronously | +| `assetSriChecks` | Enables SRI checks for Grafana JavaScript assets | ## Development feature toggles diff --git a/package.json b/package.json index 1c9393c4bd9..3708b2b49ac 100644 --- a/package.json +++ b/package.json @@ -248,6 +248,7 @@ "webpack-livereload-plugin": "3.0.2", "webpack-manifest-plugin": "5.0.0", "webpack-merge": "6.0.1", + "webpack-subresource-integrity": "^5.2.0-rc.1", "webpackbar": "^7.0.0", "yaml": "^2.0.0", "yargs": "^17.5.1" diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 87c5ff7c0d1..7e3ac0d6dba 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -254,4 +254,5 @@ export interface FeatureToggles { alertingRuleVersionHistoryRestore?: boolean; newShareReportDrawer?: boolean; rendererDisableAppPluginsPreload?: boolean; + assetSriChecks?: boolean; } diff --git a/pkg/services/featuremgmt/codeowners.go b/pkg/services/featuremgmt/codeowners.go index 13520e2a02b..a201b5e4b23 100644 --- a/pkg/services/featuremgmt/codeowners.go +++ b/pkg/services/featuremgmt/codeowners.go @@ -13,6 +13,7 @@ const ( grafanaBackendServicesSquad codeowner = "@grafana/grafana-backend-services-squad" grafanaSearchAndStorageSquad codeowner = "@grafana/search-and-storage" grafanaPluginsPlatformSquad codeowner = "@grafana/plugins-platform-backend" + grafanaFrontendOpsWG codeowner = "@grafana/frontend-ops" grafanaAsCodeSquad codeowner = "@grafana/grafana-as-code" identityAccessTeam codeowner = "@grafana/identity-access-team" grafanaObservabilityLogsSquad codeowner = "@grafana/observability-logs" diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f3b1da31b5c..27da9533003 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1775,6 +1775,13 @@ var ( HideFromDocs: true, FrontendOnly: true, }, + { + Name: "assetSriChecks", + Description: "Enables SRI checks for Grafana JavaScript assets", + Stage: FeatureStageExperimental, + Owner: grafanaFrontendOpsWG, + FrontendOnly: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fc0b0c8dfb7..ace09d69f56 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -235,3 +235,4 @@ alertingJiraIntegration,experimental,@grafana/alerting-squad,false,false,true alertingRuleVersionHistoryRestore,GA,@grafana/alerting-squad,false,false,true newShareReportDrawer,experimental,@grafana/sharing-squad,false,false,false rendererDisableAppPluginsPreload,experimental,@grafana/sharing-squad,false,false,true +assetSriChecks,experimental,@grafana/frontend-ops,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 56c3a7d53da..d651c7290f6 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -950,4 +950,8 @@ const ( // FlagRendererDisableAppPluginsPreload // Disable pre-loading app plugins when the request is coming from the renderer FlagRendererDisableAppPluginsPreload = "rendererDisableAppPluginsPreload" + + // FlagAssetSriChecks + // Enables SRI checks for Grafana JavaScript assets + FlagAssetSriChecks = "assetSriChecks" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 3f5045123d9..b093cb5dd21 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -598,6 +598,22 @@ "codeowner": "@grafana/grafana-frontend-platform" } }, + { + "metadata": { + "name": "assetSriChecks", + "resourceVersion": "1739984409734", + "creationTimestamp": "2025-02-19T15:56:59Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-02-19 17:00:09.734088 +0000 UTC" + } + }, + "spec": { + "description": "Enables SRI checks for Grafana JavaScript assets", + "stage": "experimental", + "codeowner": "@grafana/frontend-ops", + "frontend": true + } + }, { "metadata": { "name": "authAPIAccessTokenAuth", diff --git a/public/views/index.html b/public/views/index.html index cd539b05c9e..d7c54f4967b 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -355,13 +355,26 @@ [[end]] - [[range $asset := .Assets.JSFiles]] - + [[if .Settings.FeatureToggles.assetSriChecks ]] + [[range $asset := .Assets.JSFiles]] + + [[end]] + [[else]] + [[range $asset := .Assets.JSFiles]] + + [[end]] [[end]]