diff --git a/docusaurus/docs/scenes-ml/advanced-callbacks.md b/docusaurus/docs/scenes-ml/advanced-callbacks.md new file mode 100644 index 000000000..3c1dc67b6 --- /dev/null +++ b/docusaurus/docs/scenes-ml/advanced-callbacks.md @@ -0,0 +1,16 @@ +--- +id: advanced-callbacks +title: Using callbacks +--- + +Many of the components in `scenes-ml` allow a callback to be passed in the constructor. For example, in `SceneOutlierDetector`: + +```ts +const outlierDetector = new SceneOutlierDetector({ + onOutlierDetected: (outlier: Outlier) => {}, +}); +``` + +This callback can be used to create more customised experiences specific to your Scene. + +For example, you may have a custom scene showing all pods for a given Kubernetes deployment. By enabling the outlier detector, you could use the callback to store all pods and timestamps which appear to be behaving differently, and render a second panel to show the logs for those pods and timestamps. diff --git a/docusaurus/docs/scenes-ml/baselines-and-forecasts.md b/docusaurus/docs/scenes-ml/baselines-and-forecasts.md new file mode 100644 index 000000000..fd5a6b7d5 --- /dev/null +++ b/docusaurus/docs/scenes-ml/baselines-and-forecasts.md @@ -0,0 +1,55 @@ +--- +id: baselines-and-forecasts +title: Baselines and forecasts +--- + +import DiscoverSeasonalitiesSvg from '/static/img/discover-seasonalities.svg'; +import PinnedSvg from '/static/img/pinned.svg'; + +**Baselines** provide smoothed estimates of time series data along with lower and upper bounds for the data over time. They can also be used as **forecasts**, using historical data to predict the behaviour of future data. + +This functionality can also be used for **anomaly detection** by highlighting timestamps where the true value strayed outside of the lower and upper predicted bounds. + +Baselines can be added to panels by using the `SceneBaseliner` component of `scenes-ml`, which will add a control to enable/disable the calculation, adjust the prediction intervals, discover seasonalities, and pin the results. + +![Panel with baselines added](/img/baseliner.png) + +## Usage + +The code example below demonstrates how to add baselines to a time series panel. + +```ts +import { SceneBaseliner } from '@grafana/scenes-ml'; + +// Default values are shown here, all are optional. +const baseliner = new SceneBaseliner({ + interval: 0.95, + discoverSeasonalities: false, + pinned: false, +}); +const panel = PanelBuilders.timeseries() + .setHeaderActions([baseliner]) + .build(); +``` + +:::note +Make sure you only add baselines to **time series** panels, as they rarely make sense for other panel types. +::: + +### Pinning results + +By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range. + +Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed. + +## Technical details + +`scenes-ml` uses the [MSTL][mstl] algorithm to produce in-sample and out-of-sample forecasts. This algorithm decomposes the data into **trend**, **seasonality** and **residuals**, then uses an an [ETS][ets] algorithm to model the trend series. + +By default, the algorithm assumes **hourly**, **daily**, **weekly** and **yearly** seasonality (if the data spans at least two of the given season length, i.e. at least two hours for hourly or at least two days for daily). + +If the **discover seasonalities ** setting is enabled, the baseliner will first attempt to detect any non-standard seasonality in the data using a [periodogram] and account for these seasonalities when modeling the data. + +[mstl]: https://arxiv.org/abs/2107.13462 +[ets]: https://otexts.com/fpp3/ets-forecasting.html +[periodogram]: https://www.sktime.net/en/latest/api_reference/auto_generated/sktime.param_est.seasonality.SeasonalityPeriodogram.html diff --git a/docusaurus/docs/scenes-ml/changepoint-detection.md b/docusaurus/docs/scenes-ml/changepoint-detection.md new file mode 100644 index 000000000..536307f69 --- /dev/null +++ b/docusaurus/docs/scenes-ml/changepoint-detection.md @@ -0,0 +1,50 @@ +--- +id: changepoint-detection +title: Changepoint detection +--- + +import PinnedSvg from '/static/img/pinned.svg'; + +**Changepoint detection** attempts to identify timestamps where a time series has changed behaviour. For example, it could be used to identify sudden changes in the **magnitude** or the **variance** of a time series. + +The `SceneChangepointDetector` component from `scenes-ml` can be used to add this functionality to all series in a panel. This component will add an annotation at every detected changepoint. + +![Panel with changepoints added](/img/changepoints.png) + +:::warning +Changepoint detection is currently a beta feature. The underlying algorithm may perform slowly for certain panels, so be sure to test it thoroughly before using it. +::: + +## Usage + +The code example below demonstrates how to add changepoint detection to a time series panel. + +```ts +import { SceneChangepointDetector } from '@grafana/scenes-ml'; + +// Default values are shown here, all are optional. +const changepointDetector = new SceneChangepointDetector({ + enabled: false, + pinned: false, + onChangepointDetected: (changepoint: Changepoint) => {}, +}); +const panel = PanelBuilders.timeseries() + .setHeaderActions([outlierDetector]) + .build(); +``` + +:::note +Make sure you only add changepoint detection to **time series** panels, as it rarely makes sense for other panel types. +::: + +### Pinning results + +By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range. + +Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed. + +## Technical details + +`scenes-ml` currently uses the [AutoRegressive Gaussian Process Change Point detection][argpcp] (ARGPCP) algorithm, which can be slow in some cases. Alternative algorithms may be added in future. + +[argpcp]: https://redpoll.ai/blog/changepoint/#autoregressive-gaussian-process-change-point-detector-argpcp diff --git a/docusaurus/docs/scenes-ml/getting-started.md b/docusaurus/docs/scenes-ml/getting-started.md new file mode 100644 index 000000000..f8e529d2a --- /dev/null +++ b/docusaurus/docs/scenes-ml/getting-started.md @@ -0,0 +1,85 @@ +--- +id: getting-started +title: Getting started +--- + +`@grafana/scenes-ml` is a separate npm package which lets you add Machine Learning powered functionality to your scenes. + +This topic explains hows to install Scenes ML and use it within a Grafana App plugin. + +## Installation + +If you're adding Scenes ML to an existing Scenes app plugin, first make sure your plugin config is up-to-date by running: + +```bash +npx @grafana/create-plugin@latest --update +``` + +Then add `@grafana/scenes-ml` to your plugin by running the following commands in your project: + +```bash +yarn add @grafana/scenes-ml +``` + +## Add ML features to a scene + +### 1. Create a scene + +Create a scene using the snippet below. This will add a time series panel to the scene with built-in controls to add trend, lower and upper bounds to all series in the panel. + +```ts +// helloMLScene.ts + +import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes'; +import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml'; + +// Register the demo datasource from `scenes-ml`. +// This isn't required for normal usage, it just gives us some sensible demo data. +sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') }) + +function getForecastQueryRunner() { + return new SceneQueryRunner({ + queries: [ + { refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' }, + ], + }); +} + +export function getScene() { + return new EmbeddedScene({ + body: new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + width: '50%', + height: 300, + body: PanelBuilders.timeseries() + .setTitle('Forecast demo') + .setData(getForecastQueryRunner()) + // Add the `SceneBaseliner` to the panel. + .setHeaderActions([new SceneBaseliner({ interval: 0.95 })]) + .build() + }), + ], + }), + }); +} +``` + +### 2. Render the scene + +Use the following code in your Grafana app plugin page to render the "Hello ML" scene: + +```tsx +import React from 'react'; +import { getScene } from './helloMLScene'; + +export const HelloMLPluginPage = () => { + const scene = getScene(); + + return ; +}; +``` + +## Source code + +[View the example source code](https://github.com/grafana/scenes/tree/main/docusaurus/docs/scenes-ml/getting-started.tsx) diff --git a/docusaurus/docs/scenes-ml/getting-started.tsx b/docusaurus/docs/scenes-ml/getting-started.tsx new file mode 100644 index 000000000..c84d9fa62 --- /dev/null +++ b/docusaurus/docs/scenes-ml/getting-started.tsx @@ -0,0 +1,34 @@ +import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneQueryRunner, PanelBuilders, sceneUtils } from '@grafana/scenes'; +import { SceneBaseliner, MLDemoDS } from '@grafana/scenes-ml'; + +// Register the demo datasource from `scenes-ml`. +// This isn't required for normal usage, it just gives us some sensible demo data. +sceneUtils.registerRuntimeDataSource({ dataSource: new MLDemoDS('ml-test', 'ml-test') }) + +function getForecastQueryRunner() { + return new SceneQueryRunner({ + queries: [ + { refId: 'A', datasource: { uid: 'ml-test', type: 'ml-test', }, type: 'forecasts' }, + ], + }); +} + +export function getScene() { + return new EmbeddedScene({ + body: new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + width: '50%', + height: 300, + body: PanelBuilders.timeseries() + .setTitle('Forecast demo') + .setData(getForecastQueryRunner()) + // Add the `SceneBaseliner` to the panel. + .setHeaderActions([new SceneBaseliner({ interval: 0.95 })]) + .build() + }), + ], + }), + }); +} + diff --git a/docusaurus/docs/scenes-ml/outlier-detection.md b/docusaurus/docs/scenes-ml/outlier-detection.md new file mode 100644 index 000000000..d062161c1 --- /dev/null +++ b/docusaurus/docs/scenes-ml/outlier-detection.md @@ -0,0 +1,47 @@ +--- +id: outlier-detection +title: Outlier detection +--- + +import PinnedSvg from '/static/img/pinned.svg'; + +**Outlier detection** is the problem of identifying when one or more series within a group is behaving differently to the rest. + +`scenes-ml` provides a `SceneOutlierDetector` component which will perform outlier detection and highlight any misbehaving series. It will also add a grey band indicating the 'cluster range' (the range of data that can be considered non-outlying), and (optionally) add annotations at time ranges where an outlier was detected. + +![Panel with outliers added](/img/outliers.png) + +## Usage + +The code example below demonstrates how to add outlier detection to a time series panel. + +```ts +import { SceneOutlierDetector } from '@grafana/scenes-ml'; + +// Default values are shown here, all are optional. +const outlierDetector = new SceneOutlierDetector({ + sensitivity: 0.5, + addAnnotations: false, + pinned: false, + onOutlierDetected: (outlier: Outlier) => {}, +}); +const panel = PanelBuilders.timeseries() + .setHeaderActions([outlierDetector]) + .build(); +``` + +:::note +Make sure you only add outlier detection to **time series** panels, as it rarely makes sense for other panel types. +::: + +### Pinning results + +By default, baselines are recalculated on every state change, i.e. whenever the time range, query or interval changes. This isn't always desirable: for example, the user may want to zoom out and view the current forecasts in a future time range. + +Enabling the **pinned ** setting will freeze the current results, so they won't be recalculated as the time range or other settings are changed. + +## Technical details + +`scenes-ml` currently uses a variant of the [DBSCAN][dbscan] algorithm to detect outliers. Additional algorithms may be added in future. + +[dbscan]: https://en.wikipedia.org/wiki/DBSCAN diff --git a/docusaurus/website/sidebars.js b/docusaurus/website/sidebars.js index e8bd22abb..23399f365 100644 --- a/docusaurus/website/sidebars.js +++ b/docusaurus/website/sidebars.js @@ -60,6 +60,18 @@ const sidebars = { 'advanced-time-range-comparison', ], }, + { + type: 'category', + label: '@grafana/scenes-ml', + collapsible: true, + collapsed: false, + items: [ + 'getting-started', + 'baselines-and-forecasts', + 'outlier-detection', + 'changepoint-detection', + ].map(id => `scenes-ml/${id}`), + } ], }; module.exports = sidebars; diff --git a/docusaurus/website/src/css/custom.css b/docusaurus/website/src/css/custom.css index e000825ba..ab345527f 100644 --- a/docusaurus/website/src/css/custom.css +++ b/docusaurus/website/src/css/custom.css @@ -219,9 +219,7 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before color: #63676d; } -.algolia-autocomplete.algolia-autocomplete - .algolia-docsearch-suggestion--text - .algolia-docsearch-suggestion--highlight { +.algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { box-shadow: inset 0 -2px 0 0 var(--primary-border); } @@ -237,3 +235,9 @@ html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before .algolia-autocomplete.algolia-autocomplete .algolia-docsearch-suggestion--content:before { background: var(--ifm-toc-border-color); } + +/* icons */ +.ml-icon { + height: 16px; + fill: #FFFFFF; +} diff --git a/docusaurus/website/static/img/baseliner.png b/docusaurus/website/static/img/baseliner.png new file mode 100644 index 000000000..3decb058b Binary files /dev/null and b/docusaurus/website/static/img/baseliner.png differ diff --git a/docusaurus/website/static/img/changepoints.png b/docusaurus/website/static/img/changepoints.png new file mode 100644 index 000000000..e516854a0 Binary files /dev/null and b/docusaurus/website/static/img/changepoints.png differ diff --git a/docusaurus/website/static/img/discover-seasonalities.svg b/docusaurus/website/static/img/discover-seasonalities.svg new file mode 100644 index 000000000..6674465f9 --- /dev/null +++ b/docusaurus/website/static/img/discover-seasonalities.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docusaurus/website/static/img/outliers.png b/docusaurus/website/static/img/outliers.png new file mode 100644 index 000000000..73546c193 Binary files /dev/null and b/docusaurus/website/static/img/outliers.png differ diff --git a/docusaurus/website/static/img/pinned.svg b/docusaurus/website/static/img/pinned.svg new file mode 100644 index 000000000..70c45e48f --- /dev/null +++ b/docusaurus/website/static/img/pinned.svg @@ -0,0 +1,4 @@ + + + diff --git a/packages/scenes-app/package.json b/packages/scenes-app/package.json index 3b805771f..3f591482e 100644 --- a/packages/scenes-app/package.json +++ b/packages/scenes-app/package.json @@ -6,8 +6,8 @@ "license": "AGPL-3.0-only", "description": "A basic grafana app plugin", "scripts": { - "build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./.config/webpack/webpack.config.ts --env production", - "dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./.config/webpack/webpack.config.ts --env development", + "build": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -c ./webpack.config.ts --env production", + "dev": "TS_NODE_PROJECT=\"./.config/webpack/tsconfig.webpack.json\" webpack -w -c ./webpack.config.ts --env development", "e2e": "yarn cypress install && TZ=UTC yarn grafana-e2e run", "test": "jest --passWithNoTests", "test:ci": "jest --maxWorkers 4", @@ -63,6 +63,7 @@ "@grafana/data": "^11.0.0", "@grafana/runtime": "^11.0.0", "@grafana/scenes": "workspace:*", + "@grafana/scenes-ml": "^0.2.0", "@grafana/scenes-react": "workspace:*", "@grafana/schema": "^11.0.0", "@grafana/ui": "^11.0.0", diff --git a/packages/scenes-app/src/demos/index.ts b/packages/scenes-app/src/demos/index.ts index afb1e7178..e3ec6c7f0 100644 --- a/packages/scenes-app/src/demos/index.ts +++ b/packages/scenes-app/src/demos/index.ts @@ -37,6 +37,7 @@ import { getQueryControllerDemo } from './queryController'; import { getDynamicDataLayersDemo } from './dynamicDataLayers'; import { getInteropDemo } from './interopDemo'; import { getUrlSyncTest } from './urlSyncTest'; +import { getMlDemo } from './ml'; export interface DemoDescriptor { title: string; @@ -83,5 +84,6 @@ export function getDemos(): DemoDescriptor[] { { title: 'Query controller demo', getPage: getQueryControllerDemo }, { title: 'Interop with hooks and context', getPage: getInteropDemo }, { title: 'Url sync test', getPage: getUrlSyncTest }, + { title: 'Machine Learning', getPage: getMlDemo }, ].sort((a, b) => a.title.localeCompare(b.title)); } diff --git a/packages/scenes-app/src/demos/ml.tsx b/packages/scenes-app/src/demos/ml.tsx new file mode 100644 index 000000000..63a7b1dc9 --- /dev/null +++ b/packages/scenes-app/src/demos/ml.tsx @@ -0,0 +1,212 @@ +import { + EmbeddedScene, + PanelBuilders, + QueryRunnerState, + SceneAppPage, + SceneAppPageState, + SceneControlsSpacer, + SceneFlexItem, + SceneFlexLayout, + SceneQueryRunner, + SceneTimePicker, + SceneTimeRange, + SceneTimeRangeCompare, +} from '@grafana/scenes'; +import { SceneBaseliner, SceneChangepointDetector, SceneOutlierDetector } from '@grafana/scenes-ml'; +import { DataQuery } from '@grafana/schema'; +import { DATASOURCE_REF } from '../constants'; + +interface PredictableCSVWaveQuery extends DataQuery { + timeStep?: number; + valuesCSV?: string; + name?: string; +} + +// Data from https://www.kaggle.com/datasets/rakannimer/air-passengers. +const AIR_PASSENGERS = [ + 112, 118, 132, 129, 121, 135, 148, 148, 136, 119, 104, 118, 115, 126, 141, 135, 125, 149, 170, 170, 158, 133, 114, + 140, 145, 150, 178, 163, 172, 178, 199, 199, 184, 162, 146, 166, 171, 180, 193, 181, 183, 218, 230, 242, 209, 191, + 172, 194, 196, 196, 236, 235, 229, 243, 264, 272, 237, 211, 180, 201, 204, 188, 235, 227, 234, 264, 302, 293, 259, + 229, 203, 229, 242, 233, 267, 269, 270, 315, 364, 347, 312, 274, 237, 278, 284, 277, 317, 313, 318, 374, 413, 405, + 355, 306, 271, 306, 315, 301, 356, 348, 355, 422, 465, 467, 404, 347, 305, 336, 340, 318, 362, 348, 363, 435, 491, + 505, 404, 359, 310, 337, 360, 342, 406, 396, 420, 472, 548, 559, 463, 407, 362, 405, 417, 391, 419, 461, 472, 535, + 622, 606, 508, 461, 390, 432, + // Add in a few changepoints to make things more interesting. + 10000, 10001, 10000.0, 10000.2, +].join(','); + +// Data from https://www.abs.gov.au/statistics/people/population/national-state-and-territory-population/sep-2020. +const AUSTRALIAN_RESIDENTS = [ + 13067.3, 13130.5, 13198.4, 13254.2, 13303.7, 13353.9, 13409.3, 13459.2, 13504.5, 13552.6, 13614.3, 13669.5, 13722.6, + 13772.1, 13832.0, 13862.6, 13893.0, 13926.8, 13968.9, 14004.7, 14033.1, 14066.0, 14110.1, 14155.6, 14192.2, 14231.7, + 14281.5, 14330.3, 14359.3, 14396.6, 14430.8, 14478.4, 14515.7, 14554.9, 14602.5, 14646.4, 14695.4, 14746.6, 14807.4, + 14874.4, 14923.3, 14988.7, 15054.1, 15121.7, 15184.2, 15239.3, 15288.9, 15346.2, 15393.5, 15439.0, 15483.5, 15531.5, + 15579.4, 15628.5, 15677.3, 15736.7, 15788.3, 15839.7, 15900.6, 15961.5, 16018.3, 16076.9, 16139.0, 16203.0, 16263.3, + 16327.9, 16398.9, 16478.3, 16538.2, 16621.6, 16697.0, 16777.2, 16833.1, 16891.6, 16956.8, 17026.3, 17085.4, 17106.9, + 17169.4, 17239.4, 17292.0, 17354.2, 17414.2, 17447.3, 17482.6, 17526.0, 17568.7, 17627.1, 17661.5, +].join(','); + +const INDIA_TEMPERATURES = [ + 10.0, 7.4, 7.166666666666667, 8.666666666666666, 6.0, 7.0, 7.0, 8.857142857142858, 14.0, 11.0, 15.714285714285714, + 14.0, 15.833333333333334, 12.833333333333334, 14.714285714285714, 13.833333333333334, 16.5, 13.833333333333334, 12.5, + 11.285714285714286, 11.2, 9.5, 14.0, 13.833333333333334, 12.25, 12.666666666666666, 12.857142857142858, + 14.833333333333334, 14.125, 14.714285714285714, 16.2, 16.0, 16.285714285714285, 18.0, 17.428571428571427, 16.625, + 16.666666666666668, 15.6, 14.0, 15.428571428571429, 15.25, 15.875, 15.333333333333334, 16.285714285714285, + 17.333333333333332, 19.166666666666668, 14.428571428571429, 13.666666666666666, 15.6, 15.857142857142858, + 17.714285714285715, 20.0, 20.5, 17.428571428571427, 16.857142857142858, 16.875, 17.857142857142858, 20.8, + 19.428571428571427, +].join(','); + +const OUTLIER_DATA = [ + Array.from({ length: 100 }, () => Math.random() * 100), + Array.from({ length: 100 }, () => Math.random() * 100), + [ + ...Array.from({ length: 49 }, () => Math.random() * 100), + ...Array.from({ length: 2 }, () => Math.random() * 1000), + ...Array.from({ length: 49 }, () => Math.random() * 100), + ], +]; + +function getOutlierQueryRunner() { + return new SceneQueryRunner({ + queries: OUTLIER_DATA.map((values, i) => ({ + refId: String.fromCharCode(65 + i), + datasource: DATASOURCE_REF, + scenarioId: 'predictable_csv_wave', + csvWave: [ + { + timeStep: 600, + valuesCSV: values.join(','), + }, + ], + })), + maxDataPointsFromWidth: true, + }); +} + +export function getQueryRunnerWithCSVWaveQuery( + overrides?: Partial, + queryRunnerOverrides?: Partial +) { + return new SceneQueryRunner({ + queries: [ + { + refId: 'A', + datasource: DATASOURCE_REF, + scenarioId: 'predictable_csv_wave', + csvWave: [ + { + timeStep: 600, + valuesCSV: '0,0,0.5,1,2,2,1,1,0.5,0.3', + ...overrides, + }, + ], + }, + ], + ...queryRunnerOverrides, + }); +} + +export function getMlDemo(defaults: SceneAppPageState) { + return new SceneAppPage({ + ...defaults, + subTitle: 'Time series demos', + getScene: () => { + return new EmbeddedScene({ + $data: getQueryRunnerWithCSVWaveQuery({}, { maxDataPointsFromWidth: false }), + $timeRange: new SceneTimeRange({ from: 'now-48h', to: 'now' }), + + controls: [new SceneControlsSpacer(), new SceneTimePicker({}), new SceneTimeRangeCompare({ key: 'top' })], + key: 'Flex layout embedded scene', + body: new SceneFlexLayout({ + direction: 'column', + children: [ + new SceneFlexLayout({ + direction: 'row', + wrap: 'wrap', + children: [ + new SceneFlexItem({ + minWidth: '100%', + minHeight: 300, + body: PanelBuilders.timeseries() + .setTitle('Outlier data') + .setData(getOutlierQueryRunner()) + .setHeaderActions([ + new SceneOutlierDetector({ + sensitivity: 0.5, + // onOutlierDetected: console.log, + }), + ]) + .build(), + }), + new SceneFlexItem({ + minWidth: '100%', + minHeight: 300, + body: PanelBuilders.timeseries() + .setTitle('Simple wave') + .setData(getQueryRunnerWithCSVWaveQuery({}, { maxDataPointsFromWidth: true })) + .setHeaderActions([new SceneBaseliner({ interval: 0.95 })]) + .build(), + }), + + new SceneFlexItem({ + minWidth: '100%', + minHeight: 300, + body: PanelBuilders.timeseries() + .setTitle('Spikey data with changepoints') + .setData( + getQueryRunnerWithCSVWaveQuery({ valuesCSV: AIR_PASSENGERS }, { maxDataPointsFromWidth: true }) + ) + .setHeaderActions([ + new SceneBaseliner({}), + new SceneChangepointDetector({ + enabled: true, + // onChangepointDetected: console.log, + }), + ]) + .build(), + }), + new SceneFlexItem({ + minWidth: '100%', + minHeight: 300, + body: PanelBuilders.timeseries() + .setTitle('Realistic repeated series') + .setData( + getQueryRunnerWithCSVWaveQuery( + { valuesCSV: INDIA_TEMPERATURES }, + { maxDataPointsFromWidth: true } + ) + ) + .setHeaderActions([ + new SceneBaseliner({ + interval: 0.95, + }), + ]) + .build(), + }), + new SceneFlexItem({ + minWidth: '100%', + minHeight: 300, + body: PanelBuilders.timeseries() + .setTitle('Sawtooth data') + .setData( + getQueryRunnerWithCSVWaveQuery( + { valuesCSV: AUSTRALIAN_RESIDENTS }, + { maxDataPointsFromWidth: true } + ) + ) + .setHeaderActions([ + new SceneBaseliner({ + interval: 0.95, + }), + ]) + .build(), + }), + ], + }), + ], + }), + }); + }, + }); +} diff --git a/packages/scenes-app/webpack.config.ts b/packages/scenes-app/webpack.config.ts new file mode 100644 index 000000000..8f6ea6ef2 --- /dev/null +++ b/packages/scenes-app/webpack.config.ts @@ -0,0 +1,21 @@ +import type { Configuration } from 'webpack'; +import { merge } from 'webpack-merge'; +import grafanaConfig from './.config/webpack/webpack.config'; +import { getPluginId } from './.config/webpack/utils'; + +const config = (env: any): Configuration => { + const pluginId = getPluginId(); + const baseConfig = grafanaConfig(env); + return merge(baseConfig, { + experiments: { + asyncWebAssembly: true, + }, + output: { + publicPath: `public/plugins/${pluginId}/`, + uniqueName: pluginId, + }, + }); +}; + +export default config; + diff --git a/yarn.lock b/yarn.lock index c042be53d..11557490a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2210,6 +2210,13 @@ __metadata: languageName: node linkType: hard +"@bsull/augurs@npm:0.2.0": + version: 0.2.0 + resolution: "@bsull/augurs@npm:0.2.0" + checksum: 10/ed0dfd0d9feeeac4b252d0dc25b87471998b8fb6b657aca1d11f771872e26b33b0ba8b61e2f78c5a049a6f6c701f043ace050fcdea866e8299b011826396dc29 + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -3462,6 +3469,24 @@ __metadata: languageName: node linkType: hard +"@grafana/scenes-ml@npm:^0.2.0": + version: 0.2.0 + resolution: "@grafana/scenes-ml@npm:0.2.0" + dependencies: + "@bsull/augurs": "npm:0.2.0" + date-fns: "npm:^3.6.0" + peerDependencies: + "@grafana/data": ^10.0.3 + "@grafana/runtime": ^10.0.3 + "@grafana/scenes": ">=4.26.1" + "@grafana/schema": ^10.0.3 + "@grafana/ui": ^10.0.3 + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/44c667f8eed7777af0851f6ea3ef1d34c0c1fb8e98311cdcebdc232e8aae17085c922988928a3bfb533e10f2df590411b6126afee97eee42cd906ab46d94c400 + languageName: node + linkType: hard + "@grafana/scenes-react@workspace:*, @grafana/scenes-react@workspace:packages/scenes-react": version: 0.0.0-use.local resolution: "@grafana/scenes-react@workspace:packages/scenes-react" @@ -11193,7 +11218,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:3.6.0": +"date-fns@npm:3.6.0, date-fns@npm:^3.6.0": version: 3.6.0 resolution: "date-fns@npm:3.6.0" checksum: 10/cac35c58926a3b5d577082ff2b253612ec1c79eb6754fddef46b6a8e826501ea2cb346ecbd211205f1ba382ddd1f9d8c3f00bf433ad63cc3063454d294e3a6b8 @@ -23284,6 +23309,7 @@ __metadata: "@grafana/eslint-config": "npm:5.0.0" "@grafana/runtime": "npm:^11.0.0" "@grafana/scenes": "workspace:*" + "@grafana/scenes-ml": "npm:^0.2.0" "@grafana/scenes-react": "workspace:*" "@grafana/schema": "npm:^11.0.0" "@grafana/tsconfig": "npm:1.3.0-rc1"