@grafana/create-plugin 1.3.1 → 1.4.0-canary.246.66c1931.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.
- package/CHANGELOG.md +0 -18
- package/dist/constants.js +1 -1
- package/package.json +2 -2
- package/src/constants.ts +1 -1
- package/templates/app/src/components/App/App.test.tsx +7 -2
- package/templates/app/src/components/App/App.tsx +18 -5
- package/templates/app/src/components/AppConfig/AppConfig.test.tsx +6 -19
- package/templates/app/src/components/AppConfig/AppConfig.tsx +95 -51
- package/templates/app/src/components/testIds.ts +22 -0
- package/templates/app/src/constants.ts +36 -0
- package/templates/app/src/pages/PageFour.tsx +42 -0
- package/templates/app/src/pages/PageOne.tsx +31 -0
- package/templates/app/src/pages/PageThree.tsx +47 -0
- package/templates/app/src/pages/PageTwo.tsx +13 -0
- package/templates/app/src/pages/index.tsx +4 -0
- package/templates/app/src/utils/utils.routing.ts +6 -0
- package/templates/common/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
# v1.3.1 (Mon May 15 2023)
|
|
2
|
-
|
|
3
|
-
#### 🐛 Bug Fix
|
|
4
|
-
|
|
5
|
-
- Create Plugin: Fix react types errors [#238](https://github.com/grafana/plugin-tools/pull/238) ([@jackw](https://github.com/jackw))
|
|
6
|
-
- chore: update grafanaDependency for datasources [#247](https://github.com/grafana/plugin-tools/pull/247) ([@sympatheticmoose](https://github.com/sympatheticmoose))
|
|
7
|
-
|
|
8
|
-
#### 📝 Documentation
|
|
9
|
-
|
|
10
|
-
- docs: readme update for windows [#240](https://github.com/grafana/plugin-tools/pull/240) ([@sympatheticmoose](https://github.com/sympatheticmoose))
|
|
11
|
-
|
|
12
|
-
#### Authors: 2
|
|
13
|
-
|
|
14
|
-
- David Harris ([@sympatheticmoose](https://github.com/sympatheticmoose))
|
|
15
|
-
- Jack Westbrook ([@jackw](https://github.com/jackw))
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
1
|
# v1.3.0 (Fri Apr 28 2023)
|
|
20
2
|
|
|
21
3
|
#### 🚀 Enhancement
|
package/dist/constants.js
CHANGED
|
@@ -30,7 +30,7 @@ var PLUGIN_TYPES;
|
|
|
30
30
|
PLUGIN_TYPES["secretsmanager"] = "secretsmanager";
|
|
31
31
|
})(PLUGIN_TYPES = exports.PLUGIN_TYPES || (exports.PLUGIN_TYPES = {}));
|
|
32
32
|
exports.EXTRA_TEMPLATE_VARIABLES = {
|
|
33
|
-
grafanaVersion: '9.
|
|
33
|
+
grafanaVersion: '9.5.0'
|
|
34
34
|
};
|
|
35
35
|
exports.GRAFANA_FE_PACKAGES = [
|
|
36
36
|
'@grafana/data',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-canary.246.66c1931.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/create-plugin",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": ">=16"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "66c1931b45e2c4efcf098ea3a87db432e1a26202"
|
|
68
68
|
}
|
package/src/constants.ts
CHANGED
|
@@ -38,7 +38,7 @@ export enum PLUGIN_TYPES {
|
|
|
38
38
|
// and will be available to use in the templates.
|
|
39
39
|
// Example: "@grafana/ui": "{{ grafanaVersion }}"
|
|
40
40
|
export const EXTRA_TEMPLATE_VARIABLES = {
|
|
41
|
-
grafanaVersion: '9.
|
|
41
|
+
grafanaVersion: '9.5.0',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export const GRAFANA_FE_PACKAGES = [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
2
3
|
import { AppRootProps, PluginType } from '@grafana/data';
|
|
3
4
|
import { render, screen } from '@testing-library/react';
|
|
4
5
|
import { App } from './App';
|
|
@@ -25,8 +26,12 @@ describe('Components/App', () => {
|
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
test('renders without an error"', () => {
|
|
28
|
-
render(
|
|
29
|
+
render(
|
|
30
|
+
<BrowserRouter>
|
|
31
|
+
<App {...props} />
|
|
32
|
+
</BrowserRouter>
|
|
33
|
+
);
|
|
29
34
|
|
|
30
|
-
expect(screen.queryByText(/
|
|
35
|
+
expect(screen.queryByText(/this is page one./i)).toBeInTheDocument();
|
|
31
36
|
});
|
|
32
37
|
});
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Route, Switch } from 'react-router-dom';
|
|
2
3
|
import { AppRootProps } from '@grafana/data';
|
|
4
|
+
import { ROUTES } from '../../constants';
|
|
5
|
+
import { PageFour, PageOne, PageThree, PageTwo } from '../../pages';
|
|
6
|
+
import { prefixRoute } from '../../utils/utils.routing';
|
|
3
7
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
export function App(props: AppRootProps) {
|
|
9
|
+
return (
|
|
10
|
+
<Switch>
|
|
11
|
+
<Route exact path={prefixRoute(ROUTES.Two)} component={PageTwo} />
|
|
12
|
+
<Route exact path={prefixRoute(`${ROUTES.Three}/:id?`)} component={PageThree} />
|
|
13
|
+
|
|
14
|
+
{/* Full-width page (this page will have no side navigation) */}
|
|
15
|
+
<Route exact path={prefixRoute(ROUTES.Four)} component={PageFour} />
|
|
16
|
+
|
|
17
|
+
{/* Default page */}
|
|
18
|
+
<Route component={PageOne} />
|
|
19
|
+
</Switch>
|
|
20
|
+
);
|
|
8
21
|
}
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { PluginType } from '@grafana/data';
|
|
4
4
|
import { AppConfig, AppConfigProps } from './AppConfig';
|
|
5
|
+
import { testIds } from 'components/testIds';
|
|
5
6
|
|
|
6
7
|
describe('Components/AppConfig', () => {
|
|
7
8
|
let props: AppConfigProps;
|
|
@@ -23,29 +24,15 @@ describe('Components/AppConfig', () => {
|
|
|
23
24
|
} as unknown as AppConfigProps;
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
test('renders
|
|
27
|
-
render(<AppConfig plugin={props.plugin} query={props.query} />);
|
|
28
|
-
|
|
29
|
-
expect(screen.queryByText(/Enable \/ Disable/i)).toBeInTheDocument();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('renders an "Enable" button if the plugin is disabled', () => {
|
|
27
|
+
test('renders the "API Settings" fieldset with API key, API url inputs and button', () => {
|
|
33
28
|
const plugin = { meta: { ...props.plugin.meta, enabled: false } };
|
|
34
29
|
|
|
35
30
|
// @ts-ignore - We don't need to provide `addConfigPage()` and `setChannelSupport()` for these tests
|
|
36
31
|
render(<AppConfig plugin={plugin} query={props.query} />);
|
|
37
32
|
|
|
38
|
-
expect(screen.
|
|
39
|
-
expect(screen.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
test('renders a "Disable" button if the plugin is enabled', () => {
|
|
43
|
-
const plugin = { meta: { ...props.plugin.meta, enabled: true } };
|
|
44
|
-
|
|
45
|
-
// @ts-ignore - We don't need to provide `addConfigPage()` and `setChannelSupport()` for these tests
|
|
46
|
-
render(<AppConfig plugin={plugin} query={props.query} />);
|
|
47
|
-
|
|
48
|
-
expect(screen.queryByText(/The plugin is currently enabled./i)).toBeInTheDocument();
|
|
49
|
-
expect(screen.queryByText(/The plugin is currently not enabled./i)).not.toBeInTheDocument();
|
|
33
|
+
expect(screen.queryByRole('group', { name: /api settings/i })).toBeInTheDocument();
|
|
34
|
+
expect(screen.queryByTestId(testIds.appConfig.apiKey)).toBeInTheDocument();
|
|
35
|
+
expect(screen.queryByTestId(testIds.appConfig.apiUrl)).toBeInTheDocument();
|
|
36
|
+
expect(screen.queryByRole('button', { name: /save api settings/i })).toBeInTheDocument();
|
|
50
37
|
});
|
|
51
38
|
});
|
|
@@ -1,62 +1,102 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Button, Legend, useStyles2 } from '@grafana/ui';
|
|
3
|
-
import { PluginConfigPageProps, AppPluginMeta, PluginMeta, GrafanaTheme2 } from '@grafana/data';
|
|
4
|
-
import { getBackendSrv } from '@grafana/runtime';
|
|
5
|
-
import { css } from '@emotion/css';
|
|
1
|
+
import React, { ChangeEvent, useState } from 'react';
|
|
6
2
|
import { lastValueFrom } from 'rxjs';
|
|
3
|
+
import { css } from '@emotion/css';
|
|
4
|
+
import { AppPluginMeta, GrafanaTheme2, PluginConfigPageProps, PluginMeta } from '@grafana/data';
|
|
5
|
+
import { getBackendSrv } from '@grafana/runtime';
|
|
6
|
+
import { Button, Field, FieldSet, Input, SecretInput, useStyles2 } from '@grafana/ui';
|
|
7
|
+
import { testIds } from '../testIds';
|
|
7
8
|
|
|
8
|
-
export type AppPluginSettings = {
|
|
9
|
+
export type AppPluginSettings = {
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type State = {
|
|
14
|
+
// The URL to reach our custom API.
|
|
15
|
+
apiUrl: string;
|
|
16
|
+
// Tells us if the API key secret is set.
|
|
17
|
+
isApiKeySet: boolean;
|
|
18
|
+
// An secret key for our custom API.
|
|
19
|
+
apiKey: string;
|
|
20
|
+
};
|
|
9
21
|
|
|
10
22
|
export interface AppConfigProps extends PluginConfigPageProps<AppPluginMeta<AppPluginSettings>> {}
|
|
11
23
|
|
|
12
24
|
export const AppConfig = ({ plugin }: AppConfigProps) => {
|
|
13
25
|
const s = useStyles2(getStyles);
|
|
14
|
-
const { enabled, jsonData } = plugin.meta;
|
|
26
|
+
const { enabled, pinned, jsonData, secureJsonFields } = plugin.meta;
|
|
27
|
+
const [state, setState] = useState<State>({
|
|
28
|
+
apiUrl: jsonData?.apiUrl || '',
|
|
29
|
+
apiKey: '',
|
|
30
|
+
isApiKeySet: Boolean(secureJsonFields?.apiKey),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const onResetApiKey = () =>
|
|
34
|
+
setState({
|
|
35
|
+
...state,
|
|
36
|
+
apiKey: '',
|
|
37
|
+
isApiKeySet: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
41
|
+
setState({
|
|
42
|
+
...state,
|
|
43
|
+
[event.target.name]: event.target.value.trim(),
|
|
44
|
+
});
|
|
45
|
+
};
|
|
15
46
|
|
|
16
47
|
return (
|
|
17
|
-
<div
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
jsonData,
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
>
|
|
35
|
-
Enable plugin
|
|
36
|
-
</Button>
|
|
37
|
-
</>
|
|
38
|
-
)}
|
|
48
|
+
<div data-testid={testIds.appConfig.container}>
|
|
49
|
+
<FieldSet label="API Settings">
|
|
50
|
+
<Field label="API Key" description="A secret key for authenticating to our custom API">
|
|
51
|
+
<SecretInput
|
|
52
|
+
width={60}
|
|
53
|
+
data-testid={testIds.appConfig.apiKey}
|
|
54
|
+
name="apiKey"
|
|
55
|
+
value={state.apiKey}
|
|
56
|
+
isConfigured={state.isApiKeySet}
|
|
57
|
+
placeholder={'Your secret API key'}
|
|
58
|
+
onChange={onChange}
|
|
59
|
+
onReset={onResetApiKey}
|
|
60
|
+
/>
|
|
61
|
+
</Field>
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
<Field label="API Url" description="" className={s.marginTop}>
|
|
64
|
+
<Input
|
|
65
|
+
width={60}
|
|
66
|
+
name="apiUrl"
|
|
67
|
+
data-testid={testIds.appConfig.apiUrl}
|
|
68
|
+
value={state.apiUrl}
|
|
69
|
+
placeholder={`E.g.: http://mywebsite.com/api/v1`}
|
|
70
|
+
onChange={onChange}
|
|
71
|
+
/>
|
|
72
|
+
</Field>
|
|
73
|
+
|
|
74
|
+
<div className={s.marginTop}>
|
|
75
|
+
<Button
|
|
76
|
+
type="submit"
|
|
77
|
+
data-testid={testIds.appConfig.submit}
|
|
78
|
+
onClick={() =>
|
|
79
|
+
updatePluginAndReload(plugin.meta.id, {
|
|
80
|
+
enabled,
|
|
81
|
+
pinned,
|
|
82
|
+
jsonData: {
|
|
83
|
+
apiUrl: state.apiUrl,
|
|
84
|
+
},
|
|
85
|
+
// This cannot be queried later by the frontend.
|
|
86
|
+
// We don't want to override it in case it was set previously and left untouched now.
|
|
87
|
+
secureJsonData: state.isApiKeySet
|
|
88
|
+
? undefined
|
|
89
|
+
: {
|
|
90
|
+
apiKey: state.apiKey,
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
disabled={Boolean(!state.apiUrl || (!state.isApiKeySet && !state.apiKey))}
|
|
95
|
+
>
|
|
96
|
+
Save API settings
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
</FieldSet>
|
|
60
100
|
</div>
|
|
61
101
|
);
|
|
62
102
|
};
|
|
@@ -68,9 +108,12 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|
|
68
108
|
marginTop: css`
|
|
69
109
|
margin-top: ${theme.spacing(3)};
|
|
70
110
|
`,
|
|
111
|
+
marginTopXl: css`
|
|
112
|
+
margin-top: ${theme.spacing(6)};
|
|
113
|
+
`,
|
|
71
114
|
});
|
|
72
115
|
|
|
73
|
-
const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta
|
|
116
|
+
const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta<AppPluginSettings>>) => {
|
|
74
117
|
try {
|
|
75
118
|
await updatePlugin(pluginId, data);
|
|
76
119
|
|
|
@@ -83,10 +126,11 @@ const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta>
|
|
|
83
126
|
};
|
|
84
127
|
|
|
85
128
|
export const updatePlugin = async (pluginId: string, data: Partial<PluginMeta>) => {
|
|
86
|
-
const response = getBackendSrv().fetch({
|
|
129
|
+
const response = await getBackendSrv().fetch({
|
|
87
130
|
url: `/api/plugins/${pluginId}/settings`,
|
|
88
131
|
method: 'POST',
|
|
89
132
|
data,
|
|
90
133
|
});
|
|
134
|
+
|
|
91
135
|
return lastValueFrom(response);
|
|
92
136
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const testIds = {
|
|
2
|
+
appConfig: {
|
|
3
|
+
container: 'data-testid ac-container',
|
|
4
|
+
apiKey: 'data-testid ac-api-key',
|
|
5
|
+
apiUrl: 'data-testid ac-api-url',
|
|
6
|
+
submit: 'data-testid ac-submit-form',
|
|
7
|
+
},
|
|
8
|
+
pageOne: {
|
|
9
|
+
container: 'data-testid pg-one-container',
|
|
10
|
+
navigateToFour: 'data-testid navigate-to-four',
|
|
11
|
+
},
|
|
12
|
+
pageTwo: {
|
|
13
|
+
container: 'data-testid pg-two-container',
|
|
14
|
+
},
|
|
15
|
+
pageThree: {
|
|
16
|
+
container: 'data-testid pg-three-container',
|
|
17
|
+
},
|
|
18
|
+
pageFour: {
|
|
19
|
+
container: 'data-testid pg-four-container',
|
|
20
|
+
navigateBack: 'data-testid navigate-back',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { NavModelItem } from '@grafana/data';
|
|
2
|
+
import pluginJson from './plugin.json';
|
|
3
|
+
|
|
4
|
+
export const PLUGIN_BASE_URL = `/a/${pluginJson.id}`;
|
|
5
|
+
|
|
6
|
+
export enum ROUTES {
|
|
7
|
+
One = 'one',
|
|
8
|
+
Two = 'two',
|
|
9
|
+
Three = 'three',
|
|
10
|
+
Four = 'four',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const NAVIGATION_TITLE = 'Basic App Plugin';
|
|
14
|
+
export const NAVIGATION_SUBTITLE = 'Some extra description...';
|
|
15
|
+
|
|
16
|
+
// Add a navigation item for each route you would like to display in the navigation bar
|
|
17
|
+
export const NAVIGATION: Record<string, NavModelItem> = {
|
|
18
|
+
[ROUTES.One]: {
|
|
19
|
+
id: ROUTES.One,
|
|
20
|
+
text: 'Page One',
|
|
21
|
+
icon: 'database',
|
|
22
|
+
url: `${PLUGIN_BASE_URL}/one`,
|
|
23
|
+
},
|
|
24
|
+
[ROUTES.Two]: {
|
|
25
|
+
id: ROUTES.Two,
|
|
26
|
+
text: 'Page Two',
|
|
27
|
+
icon: 'key-skeleton-alt',
|
|
28
|
+
url: `${PLUGIN_BASE_URL}/two`,
|
|
29
|
+
},
|
|
30
|
+
[ROUTES.Three]: {
|
|
31
|
+
id: ROUTES.Three,
|
|
32
|
+
text: 'Page Three',
|
|
33
|
+
icon: 'chart-line',
|
|
34
|
+
url: `${PLUGIN_BASE_URL}/three`,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { css } from '@emotion/css';
|
|
3
|
+
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
|
4
|
+
import { LinkButton, useStyles2 } from '@grafana/ui';
|
|
5
|
+
import { ROUTES } from '../constants';
|
|
6
|
+
import { prefixRoute } from '../utils/utils.routing';
|
|
7
|
+
import { testIds } from '../components/testIds';
|
|
8
|
+
import { PluginPage } from '@grafana/runtime';
|
|
9
|
+
|
|
10
|
+
export function PageFour() {
|
|
11
|
+
const s = useStyles2(getStyles);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<PluginPage layout={PageLayoutType.Canvas}>
|
|
15
|
+
<div className={s.page} data-testid={testIds.pageFour.container}>
|
|
16
|
+
<div className={s.container}>
|
|
17
|
+
<LinkButton data-testid={testIds.pageFour.navigateBack} icon="arrow-left" href={prefixRoute(ROUTES.One)}>
|
|
18
|
+
Back
|
|
19
|
+
</LinkButton>
|
|
20
|
+
<div className={s.content}>This is a full-width page without a navigation bar.</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</PluginPage>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getStyles = (theme: GrafanaTheme2) => ({
|
|
28
|
+
page: css`
|
|
29
|
+
padding: ${theme.spacing(3)};
|
|
30
|
+
background-color: ${theme.colors.background.secondary};
|
|
31
|
+
display: flex;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
`,
|
|
34
|
+
container: css`
|
|
35
|
+
width: 900px;
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
min-height: 500px;
|
|
38
|
+
`,
|
|
39
|
+
content: css`
|
|
40
|
+
margin-top: ${theme.spacing(6)};
|
|
41
|
+
`,
|
|
42
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { css } from '@emotion/css';
|
|
3
|
+
import { GrafanaTheme2 } from '@grafana/data';
|
|
4
|
+
import { LinkButton, useStyles2 } from '@grafana/ui';
|
|
5
|
+
import { prefixRoute } from '../utils/utils.routing';
|
|
6
|
+
import { ROUTES } from '../constants';
|
|
7
|
+
import { testIds } from '../components/testIds';
|
|
8
|
+
import { PluginPage } from '@grafana/runtime';
|
|
9
|
+
|
|
10
|
+
export function PageOne() {
|
|
11
|
+
const s = useStyles2(getStyles);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<PluginPage>
|
|
15
|
+
<div data-testid={testIds.pageOne.container}>
|
|
16
|
+
This is page one.
|
|
17
|
+
<div className={s.marginTop}>
|
|
18
|
+
<LinkButton data-testid={testIds.pageOne.navigateToFour} href={prefixRoute(ROUTES.Four)}>
|
|
19
|
+
Full-width page example
|
|
20
|
+
</LinkButton>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</PluginPage>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getStyles = (theme: GrafanaTheme2) => ({
|
|
28
|
+
marginTop: css`
|
|
29
|
+
margin-top: ${theme.spacing(2)};
|
|
30
|
+
`,
|
|
31
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { css } from '@emotion/css';
|
|
3
|
+
import { useParams, Link } from 'react-router-dom';
|
|
4
|
+
import { GrafanaTheme2 } from '@grafana/data';
|
|
5
|
+
import { useStyles2 } from '@grafana/ui';
|
|
6
|
+
import { prefixRoute } from '../utils/utils.routing';
|
|
7
|
+
import { ROUTES } from '../constants';
|
|
8
|
+
import { testIds } from '../components/testIds';
|
|
9
|
+
import { PluginPage } from '@grafana/runtime';
|
|
10
|
+
|
|
11
|
+
export function PageThree() {
|
|
12
|
+
const s = useStyles2(getStyles);
|
|
13
|
+
const { id } = useParams<{ id: string }>();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<PluginPage>
|
|
17
|
+
<div data-testid={testIds.pageThree.container}>
|
|
18
|
+
This is page three.
|
|
19
|
+
<br />
|
|
20
|
+
<br />
|
|
21
|
+
{/* The ID parameter is set */}
|
|
22
|
+
{id && (
|
|
23
|
+
<>
|
|
24
|
+
<strong>ID:</strong> {id}
|
|
25
|
+
</>
|
|
26
|
+
)}
|
|
27
|
+
{/* No ID parameter */}
|
|
28
|
+
{!id && (
|
|
29
|
+
<>
|
|
30
|
+
<strong>No id parameter is set in the URL.</strong> <br />
|
|
31
|
+
Try the following link: <br />
|
|
32
|
+
<Link className={s.link} to={prefixRoute(`${ROUTES.Three}/123456789`)}>
|
|
33
|
+
{prefixRoute(`${ROUTES.Three}/123456789`)}
|
|
34
|
+
</Link>
|
|
35
|
+
</>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
</PluginPage>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getStyles = (theme: GrafanaTheme2) => ({
|
|
43
|
+
link: css`
|
|
44
|
+
color: ${theme.colors.text.link};
|
|
45
|
+
text-decoration: underline;
|
|
46
|
+
`,
|
|
47
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { testIds } from '../components/testIds';
|
|
3
|
+
import { PluginPage } from '@grafana/runtime';
|
|
4
|
+
|
|
5
|
+
export function PageTwo() {
|
|
6
|
+
return (
|
|
7
|
+
<PluginPage>
|
|
8
|
+
<div data-testid={testIds.pageTwo.container}>
|
|
9
|
+
<p>This is page two.</p>
|
|
10
|
+
</div>
|
|
11
|
+
</PluginPage>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"react": "17.0.2",
|
|
65
65
|
"react-dom": "17.0.2",{{#if_eq pluginType "app"}}
|
|
66
66
|
"react-router-dom": "^5.2.0",
|
|
67
|
-
"rxjs": "7.
|
|
67
|
+
"rxjs": "7.8.0",{{/if_eq}}
|
|
68
68
|
"tslib": "2.5.0"
|
|
69
69
|
},
|
|
70
70
|
"packageManager": "{{ packageManagerName }}@{{ packageManagerVersion }}"
|