@grafana/create-plugin 1.4.0 → 1.4.1-canary.250.42054cd.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/commands/generate.plopfile.js +7 -6
  2. package/dist/constants.js +2 -0
  3. package/package.json +4 -2
  4. package/scripts/generate-scenes-app.js +24 -0
  5. package/src/commands/generate.plopfile.ts +10 -8
  6. package/src/commands/types.ts +8 -0
  7. package/src/constants.ts +2 -0
  8. package/templates/common/package.json +6 -5
  9. package/templates/scenes-app/README.md +23 -0
  10. package/templates/scenes-app/provisioning/datasources/default.yaml +6 -0
  11. package/templates/scenes-app/src/components/App/App.tsx +14 -0
  12. package/templates/scenes-app/src/components/App/index.tsx +1 -0
  13. package/templates/scenes-app/src/components/AppConfig/AppConfig.tsx +193 -0
  14. package/templates/scenes-app/src/components/AppConfig/index.tsx +1 -0
  15. package/templates/scenes-app/src/components/Routes/Routes.tsx +18 -0
  16. package/templates/scenes-app/src/components/Routes/index.tsx +1 -0
  17. package/templates/scenes-app/src/components/SecretInput/SecretInput.tsx +25 -0
  18. package/templates/scenes-app/src/components/SecretInput/index.tsx +1 -0
  19. package/templates/scenes-app/src/components/testIds.ts +22 -0
  20. package/templates/scenes-app/src/constants.ts +14 -0
  21. package/templates/scenes-app/src/img/logo.svg +1 -0
  22. package/templates/scenes-app/src/module.ts +10 -0
  23. package/templates/scenes-app/src/pages/Home/CustomSceneObject.tsx +31 -0
  24. package/templates/scenes-app/src/pages/Home/Home.tsx +49 -0
  25. package/templates/scenes-app/src/pages/Home/index.tsx +1 -0
  26. package/templates/scenes-app/src/pages/Home/scenes.tsx +100 -0
  27. package/templates/scenes-app/src/pages/WithDrilldown/WithDrilldown.tsx +97 -0
  28. package/templates/scenes-app/src/pages/WithDrilldown/index.tsx +1 -0
  29. package/templates/scenes-app/src/pages/WithDrilldown/panels.tsx +188 -0
  30. package/templates/scenes-app/src/pages/WithDrilldown/scenes.tsx +122 -0
  31. package/templates/scenes-app/src/pages/WithTabs/WithTabs.tsx +45 -0
  32. package/templates/scenes-app/src/pages/WithTabs/index.tsx +1 -0
  33. package/templates/scenes-app/src/plugin.json +54 -0
  34. package/templates/scenes-app/src/utils/utils.plugin.ts +17 -0
  35. package/templates/scenes-app/src/utils/utils.routing.ts +6 -0
@@ -0,0 +1,49 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { SceneApp, SceneAppPage } from '@grafana/scenes';
4
+ import { getBasicScene } from './scenes';
5
+ import { prefixRoute } from '../../utils/utils.routing';
6
+ import { DATASOURCE_REF, ROUTES } from '../../constants';
7
+ import { config } from '@grafana/runtime';
8
+ import { Alert } from '@grafana/ui';
9
+
10
+ const getScene = () => {
11
+ return new SceneApp({
12
+ pages: [
13
+ new SceneAppPage({
14
+ title: 'Home page',
15
+ subTitle:
16
+ 'This scene showcases a basic scene functionality, including query runner, variable and a custom scene object.',
17
+ url: prefixRoute(ROUTES.Home),
18
+ getScene: () => {
19
+ return getBasicScene();
20
+ },
21
+ }),
22
+ ],
23
+ });
24
+ };
25
+ export const HomePage = () => {
26
+ const scene = useMemo(() => getScene(), []);
27
+
28
+ return (
29
+ <>
30
+ {!config.featureToggles.topnav && (
31
+ <Alert title="Missing topnav feature toggle">
32
+ Scenes are designed to work with the new navigation wrapper that will be standard in Grafana 10
33
+ </Alert>
34
+ )}
35
+
36
+ {!config.datasources[DATASOURCE_REF.uid] && (
37
+ <Alert title={`Missing ${DATASOURCE_REF.uid} datasource`}>
38
+ These demos depend on <b>testdata</b> datasource: <code>{JSON.stringify(DATASOURCE_REF)}</code>. See{' '}
39
+ <a href="https://github.com/grafana/grafana/tree/main/devenv#set-up-your-development-environment">
40
+ https://github.com/grafana/grafana/tree/main/devenv#set-up-your-development-environment
41
+ </a>{' '}
42
+ for more details.
43
+ </Alert>
44
+ )}
45
+
46
+ <scene.Component model={scene} />
47
+ </>
48
+ );
49
+ };
@@ -0,0 +1 @@
1
+ export * from './Home';
@@ -0,0 +1,100 @@
1
+ import {
2
+ CustomVariable,
3
+ EmbeddedScene,
4
+ SceneControlsSpacer,
5
+ SceneFlexItem,
6
+ SceneFlexLayout,
7
+ SceneQueryRunner,
8
+ SceneRefreshPicker,
9
+ SceneTimePicker,
10
+ SceneTimeRange,
11
+ SceneVariableSet,
12
+ VariableValueSelectors,
13
+ VizPanel,
14
+ } from '@grafana/scenes';
15
+ import { DATASOURCE_REF } from '../../constants';
16
+ import { CustomSceneObject } from './CustomSceneObject';
17
+
18
+ export function getBasicScene(templatised = true, seriesToShow = '__server_names') {
19
+ const timeRange = new SceneTimeRange({
20
+ from: 'now-6h',
21
+ to: 'now',
22
+ });
23
+
24
+ // Variable definition, using Grafana built-in TestData datasource
25
+ const customVariable = new CustomVariable({
26
+ name: 'seriesToShow',
27
+ label: 'Series to show',
28
+ value: '__server_names',
29
+ query: 'Server Names : __server_names, House locations : __house_locations',
30
+ });
31
+
32
+ // Query runner definition, using Grafana built-in TestData datasource
33
+ const queryRunner = new SceneQueryRunner({
34
+ datasource: DATASOURCE_REF,
35
+ queries: [
36
+ {
37
+ refId: 'A',
38
+ datasource: DATASOURCE_REF,
39
+ scenarioId: 'random_walk',
40
+ seriesCount: 5,
41
+ // Query is using variable value
42
+ alias: templatised ? '${seriesToShow}' : seriesToShow,
43
+ min: 30,
44
+ max: 60,
45
+ },
46
+ ],
47
+ maxDataPoints: 100,
48
+ });
49
+
50
+ // Custom object definition
51
+ const customObject = new CustomSceneObject({
52
+ counter: 5,
53
+ });
54
+
55
+ // Query runner activation handler that will update query runner state when custom object state changes
56
+ queryRunner.addActivationHandler(() => {
57
+ const sub = customObject.subscribeToState((newState) => {
58
+ queryRunner.setState({
59
+ queries: [
60
+ {
61
+ ...queryRunner.state.queries[0],
62
+ seriesCount: newState.counter,
63
+ },
64
+ ],
65
+ });
66
+ queryRunner.runQueries();
67
+ });
68
+
69
+ return () => {
70
+ sub.unsubscribe();
71
+ };
72
+ });
73
+
74
+ return new EmbeddedScene({
75
+ $timeRange: timeRange,
76
+ $variables: new SceneVariableSet({ variables: templatised ? [customVariable] : [] }),
77
+ $data: queryRunner,
78
+ body: new SceneFlexLayout({
79
+ children: [
80
+ new SceneFlexItem({
81
+ body: new VizPanel({
82
+ pluginId: 'timeseries',
83
+ // Title is using variable value
84
+ title: templatised ? '${seriesToShow}' : seriesToShow,
85
+ }),
86
+ }),
87
+ ],
88
+ }),
89
+ controls: [
90
+ new VariableValueSelectors({}),
91
+ new SceneControlsSpacer(),
92
+ customObject,
93
+ new SceneTimePicker({ isOnCanvas: true }),
94
+ new SceneRefreshPicker({
95
+ intervals: ['5s', '1m', '1h'],
96
+ isOnCanvas: true,
97
+ }),
98
+ ],
99
+ });
100
+ }
@@ -0,0 +1,97 @@
1
+ import React, { useMemo } from 'react';
2
+ import { prefixRoute } from '../../utils/utils.routing';
3
+ import { DATASOURCE_REF, ROUTES } from '../../constants';
4
+ import {
5
+ EmbeddedScene,
6
+ SceneApp,
7
+ SceneAppPage,
8
+ SceneFlexItem,
9
+ SceneFlexLayout,
10
+ SceneQueryRunner,
11
+ } from '@grafana/scenes';
12
+ import { getHumidityOverviewScene, getTemperatureOverviewScene } from './scenes';
13
+ import { getRoomsTemperatureStats, getRoomsTemperatureTable } from './panels';
14
+
15
+ const roomsTemperatureQuery = {
16
+ refId: 'Rooms temperature',
17
+ datasource: DATASOURCE_REF,
18
+ scenarioId: 'random_walk',
19
+ seriesCount: 8,
20
+ alias: '__house_locations',
21
+ min: 10,
22
+ max: 27,
23
+ };
24
+
25
+ const getScene = () =>
26
+ new EmbeddedScene({
27
+ $data: new SceneQueryRunner({
28
+ datasource: DATASOURCE_REF,
29
+ queries: [roomsTemperatureQuery],
30
+ maxDataPoints: 100,
31
+ }),
32
+
33
+ body: new SceneFlexLayout({
34
+ direction: 'column',
35
+ children: [
36
+ new SceneFlexItem({
37
+ height: 300,
38
+ body: getRoomsTemperatureTable(),
39
+ }),
40
+ new SceneFlexItem({
41
+ ySizing: 'fill',
42
+ body: getRoomsTemperatureStats(),
43
+ }),
44
+ ],
45
+ }),
46
+ });
47
+
48
+ const getDrilldownsAppScene = () => {
49
+ return new SceneApp({
50
+ pages: [
51
+ new SceneAppPage({
52
+ title: 'Page with drilldown',
53
+ subTitle: 'This scene showcases a basic drilldown functionality. Interact with room to see room details scene.',
54
+
55
+ url: prefixRoute(`${ROUTES.WithDrilldown}`),
56
+ hideFromBreadcrumbs: true,
57
+ getScene,
58
+ drilldowns: [
59
+ {
60
+ routePath: prefixRoute(`${ROUTES.WithDrilldown}`) + '/room/:roomName',
61
+ getPage(routeMatch, parent) {
62
+ const roomName = routeMatch.params.roomName;
63
+
64
+ return new SceneAppPage({
65
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + `/room/${roomName}/temperature`,
66
+ title: `${roomName} overview`,
67
+ subTitle: 'This scene is a particular room drilldown. It implements two tabs to organise the data.',
68
+ getParentPage: () => parent,
69
+ getScene: () => {
70
+ return new EmbeddedScene({ body: new SceneFlexLayout({ children: [] }) });
71
+ },
72
+ tabs: [
73
+ new SceneAppPage({
74
+ title: 'Temperature',
75
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + `/room/${roomName}/temperature`,
76
+ getScene: () => getTemperatureOverviewScene(roomName),
77
+ }),
78
+ new SceneAppPage({
79
+ title: 'Humidity',
80
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + `/room/${roomName}/humidity`,
81
+ getScene: () => getHumidityOverviewScene(roomName),
82
+ }),
83
+ ],
84
+ });
85
+ },
86
+ },
87
+ ],
88
+ }),
89
+ ],
90
+ });
91
+ };
92
+
93
+ export const WithDrilldown = () => {
94
+ const scene = useMemo(() => getDrilldownsAppScene(), []);
95
+
96
+ return <scene.Component model={scene} />;
97
+ };
@@ -0,0 +1 @@
1
+ export * from './WithDrilldown';
@@ -0,0 +1,188 @@
1
+ import { ReducerID, ThresholdsMode } from '@grafana/data';
2
+ import {
3
+ SceneByFrameRepeater,
4
+ SceneDataNode,
5
+ SceneDataTransformer,
6
+ SceneFlexItem,
7
+ SceneFlexLayout,
8
+ VizPanel,
9
+ } from '@grafana/scenes';
10
+ import { ROUTES } from '../../constants';
11
+ import { prefixRoute } from '../../utils/utils.routing';
12
+
13
+ export function getRoomsTemperatureTable() {
14
+ return new VizPanel({
15
+ pluginId: 'table',
16
+ $data: new SceneDataTransformer({
17
+ transformations: [
18
+ {
19
+ id: 'reduce',
20
+ options: {
21
+ reducers: ['mean'],
22
+ },
23
+ },
24
+ {
25
+ id: 'organize',
26
+ options: {
27
+ excludeByName: {},
28
+ indexByName: {},
29
+ renameByName: {
30
+ Field: 'Room',
31
+ Mean: 'Average temperature',
32
+ },
33
+ },
34
+ },
35
+ ],
36
+ }),
37
+ displayMode: 'transparent',
38
+ title: 'Room temperature overview',
39
+ hoverHeader: true,
40
+ options: {
41
+ sortBy: ['Average temperature'],
42
+ },
43
+ fieldConfig: {
44
+ defaults: {
45
+ custom: {
46
+ align: 'auto',
47
+ cellOptions: {
48
+ type: 'auto',
49
+ },
50
+ inspect: false,
51
+ },
52
+ mappings: [],
53
+ thresholds: {
54
+ mode: ThresholdsMode.Absolute,
55
+ steps: [
56
+ {
57
+ color: 'light-blue',
58
+ value: 0,
59
+ },
60
+ {
61
+ color: 'orange',
62
+ value: 19,
63
+ },
64
+ {
65
+ color: 'dark-red',
66
+ value: 26,
67
+ },
68
+ ],
69
+ },
70
+ color: {
71
+ mode: 'thresholds',
72
+ },
73
+ },
74
+ overrides: [
75
+ {
76
+ matcher: {
77
+ id: 'byName',
78
+ options: 'Average temperature',
79
+ },
80
+ properties: [
81
+ {
82
+ id: 'unit',
83
+ value: 'celsius',
84
+ },
85
+ {
86
+ id: 'custom.cellOptions',
87
+ value: {
88
+ type: 'color-background',
89
+ mode: 'basic',
90
+ },
91
+ },
92
+ {
93
+ id: 'custom.width',
94
+ value: 200,
95
+ },
96
+ {
97
+ id: 'custom.align',
98
+ value: 'center',
99
+ },
100
+ ],
101
+ },
102
+ {
103
+ matcher: {
104
+ id: 'byName',
105
+ options: 'Room',
106
+ },
107
+ properties: [
108
+ {
109
+ id: 'links',
110
+ value: [
111
+ {
112
+ title: 'Go to room overview',
113
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + '/room/${__value.text}/temperature',
114
+ },
115
+ ],
116
+ },
117
+ ],
118
+ },
119
+ ],
120
+ },
121
+ });
122
+ }
123
+
124
+ export function getRoomsTemperatureStats() {
125
+ return new SceneByFrameRepeater({
126
+ body: new SceneFlexLayout({
127
+ direction: 'row',
128
+ wrap: 'wrap',
129
+ children: [],
130
+ }),
131
+ getLayoutChild: (data, frame) => {
132
+ return new SceneFlexItem({
133
+ height: '50%',
134
+ minWidth: '20%',
135
+ body: new VizPanel({
136
+ title: frame.name,
137
+ pluginId: 'stat',
138
+
139
+ $data: new SceneDataNode({
140
+ data: {
141
+ ...data,
142
+ series: [frame],
143
+ },
144
+ }),
145
+ fieldConfig: {
146
+ defaults: {
147
+ unit: 'celsius',
148
+ links: [
149
+ {
150
+ title: 'Go to room temperature overview',
151
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + '/room/${__field.name}/temperature',
152
+ },
153
+ {
154
+ title: 'Go to room humidity overview',
155
+ url: prefixRoute(`${ROUTES.WithDrilldown}`) + '/room/${__field.name}/humidity',
156
+ },
157
+ ],
158
+ },
159
+ overrides: [],
160
+ },
161
+ }),
162
+ });
163
+ },
164
+ });
165
+ }
166
+
167
+ export function getRoomTemperatureStatPanel(reducers: ReducerID[]) {
168
+ return new VizPanel({
169
+ pluginId: 'stat',
170
+ title: '',
171
+ $data: new SceneDataTransformer({
172
+ transformations: [
173
+ {
174
+ id: 'reduce',
175
+ options: {
176
+ reducers,
177
+ },
178
+ },
179
+ ],
180
+ }),
181
+ fieldConfig: {
182
+ defaults: {
183
+ unit: 'celsius',
184
+ },
185
+ overrides: [],
186
+ },
187
+ });
188
+ }
@@ -0,0 +1,122 @@
1
+ import { ReducerID } from '@grafana/data';
2
+ import {
3
+ EmbeddedScene,
4
+ SceneFlexLayout,
5
+ SceneQueryRunner,
6
+ VizPanel,
7
+ SceneTimeRange,
8
+ SceneTimePicker,
9
+ SceneControlsSpacer,
10
+ SceneRefreshPicker,
11
+ SceneFlexItem,
12
+ } from '@grafana/scenes';
13
+ import { DATASOURCE_REF } from '../../constants';
14
+ import { getRoomTemperatureStatPanel } from './panels';
15
+
16
+ export function getTemperatureOverviewScene(roomName: string) {
17
+ return new EmbeddedScene({
18
+ $timeRange: new SceneTimeRange({
19
+ from: 'now-12h',
20
+ to: 'now',
21
+ }),
22
+ $data: new SceneQueryRunner({
23
+ datasource: DATASOURCE_REF,
24
+ queries: [getRoomTemperatureQuery(roomName)],
25
+ maxDataPoints: 100,
26
+ }),
27
+ body: new SceneFlexLayout({
28
+ direction: 'column',
29
+ children: [
30
+ new SceneFlexItem({
31
+ height: 500,
32
+ body: new VizPanel({
33
+ title: 'Temperature over time',
34
+ pluginId: 'timeseries',
35
+
36
+ fieldConfig: {
37
+ defaults: {
38
+ unit: 'celsius',
39
+ },
40
+ overrides: [],
41
+ },
42
+ }),
43
+ }),
44
+ new SceneFlexItem({
45
+ body: new SceneFlexLayout({
46
+ direction: 'row',
47
+ children: [
48
+ new SceneFlexItem({
49
+ body: getRoomTemperatureStatPanel([ReducerID.min]),
50
+ }),
51
+ new SceneFlexItem({
52
+ body: getRoomTemperatureStatPanel([ReducerID.max]),
53
+ }),
54
+ new SceneFlexItem({
55
+ body: getRoomTemperatureStatPanel([ReducerID.mean]),
56
+ }),
57
+ ],
58
+ }),
59
+ }),
60
+ ],
61
+ }),
62
+ controls: [new SceneControlsSpacer(), new SceneTimePicker({ isOnCanvas: true }), new SceneRefreshPicker({})],
63
+ });
64
+ }
65
+
66
+ export function getHumidityOverviewScene(roomName: string) {
67
+ return new EmbeddedScene({
68
+ $timeRange: new SceneTimeRange({
69
+ from: 'now-12h',
70
+ to: 'now',
71
+ }),
72
+ $data: new SceneQueryRunner({
73
+ datasource: DATASOURCE_REF,
74
+ queries: [getRoomHumidityQuery(roomName)],
75
+ maxDataPoints: 100,
76
+ }),
77
+ body: new SceneFlexLayout({
78
+ direction: 'column',
79
+ children: [
80
+ new SceneFlexItem({
81
+ height: 500,
82
+ body: new VizPanel({
83
+ title: 'Humidity readings over time',
84
+ pluginId: 'timeseries',
85
+
86
+ fieldConfig: {
87
+ defaults: {
88
+ unit: 'humidity',
89
+ },
90
+ overrides: [],
91
+ },
92
+ }),
93
+ }),
94
+ ],
95
+ }),
96
+ controls: [new SceneControlsSpacer(), new SceneTimePicker({ isOnCanvas: true }), new SceneRefreshPicker({})],
97
+ });
98
+ }
99
+
100
+ function getRoomTemperatureQuery(roomName: string) {
101
+ return {
102
+ refId: 'Temp',
103
+ datasource: DATASOURCE_REF,
104
+ scenarioId: 'random_walk',
105
+ seriesCount: 1,
106
+ alias: roomName,
107
+ min: 10,
108
+ max: 30,
109
+ };
110
+ }
111
+
112
+ function getRoomHumidityQuery(roomName: string) {
113
+ return {
114
+ refId: 'Humidity',
115
+ datasource: DATASOURCE_REF,
116
+ scenarioId: 'random_walk',
117
+ seriesCount: 1,
118
+ alias: roomName,
119
+ min: 30,
120
+ max: 60,
121
+ };
122
+ }
@@ -0,0 +1,45 @@
1
+ import React, { useMemo } from 'react';
2
+ import { SceneApp, SceneAppPage } from '@grafana/scenes';
3
+ import { ROUTES } from '../../constants';
4
+ import { prefixRoute } from '../../utils/utils.routing';
5
+ import { getBasicScene } from '../Home/scenes';
6
+
7
+ const getTab1Scene = () => {
8
+ return getBasicScene(false, '__server_names');
9
+ };
10
+
11
+ const getTab2Scene = () => {
12
+ return getBasicScene(false, '__house_locations');
13
+ };
14
+
15
+ const getScene = () =>
16
+ new SceneApp({
17
+ pages: [
18
+ new SceneAppPage({
19
+ title: 'Page with tabs',
20
+ subTitle: 'This scene showcases a basic tabs functionality.',
21
+ // Important: Mind the page route is ambiguous for the tabs to work properly
22
+ url: prefixRoute(`${ROUTES.WithTabs}`),
23
+ hideFromBreadcrumbs: true,
24
+ getScene: getTab1Scene,
25
+ tabs: [
26
+ new SceneAppPage({
27
+ title: 'Server names',
28
+ url: prefixRoute(`${ROUTES.WithTabs}`),
29
+ getScene: getTab1Scene,
30
+ }),
31
+ new SceneAppPage({
32
+ title: 'House locations',
33
+ url: prefixRoute(`${ROUTES.WithTabs}/tab-two`),
34
+ getScene: getTab2Scene,
35
+ }),
36
+ ],
37
+ }),
38
+ ],
39
+ });
40
+
41
+ export const PageWithTabs = () => {
42
+ const scene = useMemo(() => getScene(), []);
43
+
44
+ return <scene.Component model={scene} />;
45
+ };
@@ -0,0 +1 @@
1
+ export * from './WithTabs';
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/grafana/grafana/master/docs/sources/developers/plugins/plugin.schema.json",
3
+ "type": "app",
4
+ "name": "{{ titleCase pluginName }}",
5
+ "id": "{{ normalize_id pluginName orgName 'app' }}",{{#if hasBackend}}
6
+ "backend": true,
7
+ "executable": "gpx_{{ snakeCase pluginName }}",{{/if}}
8
+ "info": {
9
+ "keywords": [
10
+ "app"
11
+ ],
12
+ "description": "{{ sentenceCase pluginDescription }}",
13
+ "author": {
14
+ "name": "{{ sentenceCase orgName }}"
15
+ },
16
+ "logos": {
17
+ "small": "img/logo.svg",
18
+ "large": "img/logo.svg"
19
+ },
20
+ "screenshots": [],
21
+ "version": "%VERSION%",
22
+ "updated": "%TODAY%"
23
+ },
24
+ "includes": [
25
+ {
26
+ "type": "page",
27
+ "name": "Home",
28
+ "path": "/a/%PLUGIN_ID%/home",
29
+ "role": "Admin",
30
+ "addToNav": true,
31
+ "defaultNav": true
32
+ },
33
+ {
34
+ "type": "page",
35
+ "name": "Page with tabs",
36
+ "path": "/a/%PLUGIN_ID%/page-with-tabs",
37
+ "role": "Admin",
38
+ "addToNav": true,
39
+ "defaultNav": false
40
+ },
41
+ {
42
+ "type": "page",
43
+ "name": "Page with drilldown",
44
+ "path": "/a/%PLUGIN_ID%/page-with-drilldown",
45
+ "role": "Admin",
46
+ "addToNav": true,
47
+ "defaultNav": false
48
+ }
49
+ ],
50
+ "dependencies": {
51
+ "grafanaDependency": ">={{ grafanaVersion }}",
52
+ "plugins": []
53
+ }
54
+ }
@@ -0,0 +1,17 @@
1
+ import React, { useContext } from 'react';
2
+ import { AppRootProps } from '@grafana/data';
3
+
4
+ // This is used to be able to retrieve the root plugin props anywhere inside the app.
5
+ export const PluginPropsContext = React.createContext<AppRootProps | null>(null);
6
+
7
+ export const usePluginProps = () => {
8
+ const pluginProps = useContext(PluginPropsContext);
9
+
10
+ return pluginProps;
11
+ };
12
+
13
+ export const usePluginMeta = () => {
14
+ const pluginProps = usePluginProps();
15
+
16
+ return pluginProps?.meta;
17
+ };
@@ -0,0 +1,6 @@
1
+ import { PLUGIN_BASE_URL } from '../constants';
2
+
3
+ // Prefixes the route with the base URL of the plugin
4
+ export function prefixRoute(route: string): string {
5
+ return `${PLUGIN_BASE_URL}/${route}`;
6
+ }