From 0be20b5b62491ce708c077585a8f47bbf28cf877 Mon Sep 17 00:00:00 2001 From: Chris Bedwell Date: Tue, 17 Oct 2023 11:16:43 +0100 Subject: [PATCH] Fix: Routing bug on uninitialised plugin (#628) * feat: add Router to test suite customRender * feat: add featureToggles option to customRender * fix: import Router from react-router-dom * fix: tidy up router and add routing tests * feat: add useLayout effect to route back home * feat: add 404 tests to routing --------- Co-authored-by: Russ <8377044+rdubrock@users.noreply.github.com> --- __mocks__/@grafana/runtime.ts | 1 + src/components/ConfigActions.tsx | 2 +- src/components/InstanceProvider.tsx | 4 +- src/components/Routing.test.tsx | 84 +++++++++++++ src/components/Routing.tsx | 115 ++++++++---------- src/contexts/InstanceContext.ts | 2 - src/hooks/useNavigation.ts | 32 +++-- .../AlertingPage.test.tsx} | 4 +- .../Alerting.tsx => page/AlertingPage.tsx} | 26 ++-- src/page/ConfigPage.tsx | 108 ++++++++-------- src/page/DashboardPage.tsx | 16 ++- src/page/HomePage.test.tsx | 2 +- src/page/HomePage.tsx | 4 +- .../UnprovisionedSetup.tsx | 5 +- src/page/WelcomePage.tsx | 16 +-- src/test/render.tsx | 63 ++++++++-- src/test/setupTests.ts | 0 src/types.ts | 2 - 18 files changed, 311 insertions(+), 175 deletions(-) create mode 100644 src/components/Routing.test.tsx rename src/{components/Alerting.test.tsx => page/AlertingPage.test.tsx} (98%) rename src/{components/Alerting.tsx => page/AlertingPage.tsx} (94%) rename src/{components => page}/UnprovisionedSetup.tsx (99%) delete mode 100644 src/test/setupTests.ts diff --git a/__mocks__/@grafana/runtime.ts b/__mocks__/@grafana/runtime.ts index 4ef6bcbf5..14ffce054 100644 --- a/__mocks__/@grafana/runtime.ts +++ b/__mocks__/@grafana/runtime.ts @@ -4,6 +4,7 @@ export const getBackendSrv = () => ({ fetch: { toPromise: () => jest.fn().mockResolvedValue({ ok: true }), }, + request: () => jest.fn().mockResolvedValue({ ok: true }), }); export const getLocationSrv = () => ({ diff --git a/src/components/ConfigActions.tsx b/src/components/ConfigActions.tsx index 351d6df2c..1fc3ce7a2 100644 --- a/src/components/ConfigActions.tsx +++ b/src/components/ConfigActions.tsx @@ -31,7 +31,7 @@ export const ConfigActions = ({ enabled, pluginId }: Props) => { }; const handleSetup = () => { - navigate(ROUTES.Setup); + navigate(ROUTES.Home); }; const getAction = () => { diff --git a/src/components/InstanceProvider.tsx b/src/components/InstanceProvider.tsx index 786ce55bc..ef87e5701 100644 --- a/src/components/InstanceProvider.tsx +++ b/src/components/InstanceProvider.tsx @@ -151,10 +151,8 @@ export const InstanceProvider = ({ throw new Error('There was an error finding datasources required for Synthetic Monitoring'); } - const provisioned = Boolean(meta.jsonData?.metrics?.grafanaName); - return ( - + {children} ); diff --git a/src/components/Routing.test.tsx b/src/components/Routing.test.tsx new file mode 100644 index 000000000..002f743a9 --- /dev/null +++ b/src/components/Routing.test.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; + +import { type CustomRenderOptions, createInstance, render } from 'test/render'; +import { ROUTES } from 'types'; +import { PLUGIN_URL_PATH } from './constants'; +import { getRoute, Routing } from './Routing'; + +function renderRouting(options?: CustomRenderOptions) { + return render(, options); +} + +const notaRoute = `${PLUGIN_URL_PATH}/404`; + +describe('Only renders the unprovisioned setup page regardless of route when app is not provisioned', () => { + Object.entries(ROUTES).map(([key, route]) => { + test(`Route ${key}`, () => { + renderRouting({ path: getRoute(route), meta: { jsonData: undefined } }); + screen.getByText('Provisioning is required for Synthetic Monitoring.'); + }); + }); + + test('Non-existent route (404)', () => { + renderRouting({ path: notaRoute, meta: { jsonData: undefined } }); + screen.getByText('Provisioning is required for Synthetic Monitoring.'); + }); +}); + +describe('Only renders the welcome page regardless of route when app is not initializd', () => { + Object.entries(ROUTES).map(([key, route]) => { + test(`Route ${key}`, () => { + const instance = createInstance({ api: undefined }); + renderRouting({ instance, path: getRoute(route) }); + screen.getByText('Ready to start using synthetic monitoring?'); + }); + }); + + test('Non-existent route (404)', () => { + const instance = createInstance({ api: undefined }); + renderRouting({ instance, path: notaRoute }); + screen.getByText('Ready to start using synthetic monitoring?'); + }); +}); + +// Would like to have asserted on the h1s but rendering the Grafana pluginpage is tricky +describe('Routes to pages correctly', () => { + test('Home page renders', async () => { + renderRouting({ path: getRoute(ROUTES.Home) }); + const homePageText = await screen.findByText('What you can do', { selector: 'h2' }); + expect(homePageText).toBeInTheDocument(); + }); + + test('Checks page renders', async () => { + renderRouting({ path: getRoute(ROUTES.Checks) }); + const checksButton = await screen.findByText('Add new check'); + expect(checksButton).toBeInTheDocument(); + }); + + test('Probes page renders', async () => { + renderRouting({ path: getRoute(ROUTES.Probes) }); + const probeReachabilityTexts = await screen.findAllByText('Reachability'); + expect(probeReachabilityTexts.length).toBeGreaterThan(0); + }); + + test('Alert page renders', async () => { + renderRouting({ path: getRoute(ROUTES.Alerts) }); + const alertsText = await screen.findByText('Learn more about alerting for Synthetic Monitoring.'); + expect(alertsText).toBeInTheDocument(); + }); + + test('Config page renders', async () => { + renderRouting({ path: getRoute(ROUTES.Config) }); + const configText = await screen.findByText( + /Synthetic Monitoring is a blackbox monitoring solution provided as part of/i + ); + expect(configText).toBeInTheDocument(); + }); + + test('Non-existent route redirects to homepage', async () => { + renderRouting({ path: notaRoute }); + const homePageText = await screen.findByText('What you can do', { selector: 'h2' }); + expect(homePageText).toBeInTheDocument(); + }); +}); diff --git a/src/components/Routing.tsx b/src/components/Routing.tsx index cde20cc47..9bf5f116f 100644 --- a/src/components/Routing.tsx +++ b/src/components/Routing.tsx @@ -1,58 +1,39 @@ -import { CheckRouter } from 'page/CheckRouter'; -import HomePage from 'page/HomePage'; -import { ProbeRouter } from 'page/ProbeRouter'; -import React, { useEffect, useContext } from 'react'; +import React, { useLayoutEffect, useEffect, useContext } from 'react'; import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; -import { Alerting } from 'components/Alerting'; import { AppRootProps } from '@grafana/data'; -import { getNavModel } from 'page/pageDefinitions'; +import { config } from '@grafana/runtime'; + import { PLUGIN_URL_PATH } from './constants'; -import { InstanceContext } from 'contexts/InstanceContext'; -import { WelcomePage } from 'page/WelcomePage'; -import { UnprovisionedSetup } from './UnprovisionedSetup'; +import { ROUTES } from 'types'; import { QueryParamMap, useNavigation } from 'hooks/useNavigation'; import { useQuery } from 'hooks/useQuery'; -import { DashboardRedirecter } from './DashboardRedirecter'; -import { ROUTES } from 'types'; -import { config } from '@grafana/runtime'; -import { PluginPage } from 'components/PluginPage'; +import { AlertingPage } from 'page/AlertingPage'; +import { CheckRouter } from 'page/CheckRouter'; import { ConfigPage } from 'page/ConfigPage'; import { DashboardPage } from 'page/DashboardPage'; -import { ChecksContextProvider } from './ChecksContextProvider'; +import { getNavModel } from 'page/pageDefinitions'; +import { InstanceContext } from 'contexts/InstanceContext'; +import { ProbeRouter } from 'page/ProbeRouter'; +import { UnprovisionedSetup } from 'page/UnprovisionedSetup'; +import { WelcomePage } from 'page/WelcomePage'; +import { HomePage } from 'page/HomePage'; +import { DashboardRedirecter } from './DashboardRedirecter'; -export const Routing = ({ onNavChanged, meta, ...rest }: AppRootProps) => { +export const Routing = ({ onNavChanged }: Pick) => { const queryParams = useQuery(); const navigate = useNavigation(); const location = useLocation(); - const { instance, provisioned } = useContext(InstanceContext); - const initialized = meta.enabled && instance.api; + const { instance, meta } = useContext(InstanceContext); + const provisioned = Boolean(meta?.jsonData?.metrics?.grafanaName); + const initialized = meta?.enabled && instance.api; + const logo = meta?.info.logos.large || ``; useEffect(() => { - const navModel = getNavModel(meta.info.logos.large, location.pathname); + const navModel = getNavModel(logo, location.pathname); if (!config.featureToggles.topnav) { onNavChanged(navModel); } - }, [meta.info.logos.large, onNavChanged, location.pathname]); - - useEffect(() => { - // not provisioned and not initialized - if ( - meta.enabled && - (!instance.metrics || !instance.logs) && - !provisioned && - !location.pathname.includes('unprovisioned') - ) { - navigate(ROUTES.Unprovisioned); - } - // not provisioned and just initialized - if (meta.enabled && instance.metrics && instance.logs && location.pathname.includes('unprovisioned')) { - navigate(ROUTES.Home); - } - // Provisioned but not initialized - if (meta.enabled && !instance.api && provisioned && !location.pathname.includes('setup')) { - navigate(ROUTES.Setup); - } - }, [meta.enabled, instance.metrics, instance.logs, location.pathname, navigate, instance.api, provisioned]); + }, [logo, onNavChanged, location.pathname]); const page = queryParams.get('page'); useEffect(() => { @@ -66,46 +47,52 @@ export const Routing = ({ onNavChanged, meta, ...rest }: AppRootProps) => { } }, [page, navigate, queryParams]); + useLayoutEffect(() => { + if (!provisioned || (!initialized && location.pathname !== getRoute(ROUTES.Home))) { + navigate(ROUTES.Home); + } + }, [provisioned, initialized, location.pathname, navigate]); + + if (!provisioned) { + return ; + } + + if (!initialized) { + return ; + } + return ( - + - - {initialized ? : } - - - - - + - - - - - - - + + - + - - - - + + + + + - - - - + + {/* Default route (only redirect if the path matches the plugin's URL) */} - + ); }; + +export function getRoute(route: ROUTES) { + return `${PLUGIN_URL_PATH}${route}`; +} diff --git a/src/contexts/InstanceContext.ts b/src/contexts/InstanceContext.ts index 09defa1ae..e116d4adb 100644 --- a/src/contexts/InstanceContext.ts +++ b/src/contexts/InstanceContext.ts @@ -6,12 +6,10 @@ export interface InstanceContextValue { loading: boolean; instance: GrafanaInstances; meta: AppPluginMeta | undefined; - provisioned?: boolean; } export const InstanceContext = createContext({ instance: {}, loading: true, meta: undefined, - provisioned: false, }); diff --git a/src/hooks/useNavigation.ts b/src/hooks/useNavigation.ts index 782c4bce7..82c8c2507 100644 --- a/src/hooks/useNavigation.ts +++ b/src/hooks/useNavigation.ts @@ -1,6 +1,8 @@ +import { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; import { getLocationSrv } from '@grafana/runtime'; + import { PLUGIN_URL_PATH } from 'components/constants'; -import { useHistory } from 'react-router-dom'; export type QueryParamMap = { [key: string]: string; @@ -8,17 +10,21 @@ export type QueryParamMap = { export function useNavigation() { const history = useHistory(); - const navigate = (url: string, queryParams?: QueryParamMap, external?: boolean, additionalState?: any) => { - const normalized = url.startsWith('/') ? url.slice(1) : url; - if (external) { - getLocationSrv().update({ partial: false, path: `/${normalized}`, query: queryParams }); - } else { - const paramString = Object.entries(queryParams ?? {}).reduce( - (acc, [key, val]) => acc.concat(`&${key}=${val}`), - '' - ); - history.push(`${PLUGIN_URL_PATH}${normalized}${paramString ? '?' : ''}${paramString}`, additionalState); - } - }; + const navigate = useCallback( + (url: string, queryParams?: QueryParamMap, external?: boolean, additionalState?: any) => { + const normalized = url.startsWith('/') ? url.slice(1) : url; + if (external) { + getLocationSrv().update({ partial: false, path: `/${normalized}`, query: queryParams }); + } else { + const paramString = Object.entries(queryParams ?? {}).reduce( + (acc, [key, val]) => acc.concat(`&${key}=${val}`), + '' + ); + history.push(`${PLUGIN_URL_PATH}${normalized}${paramString ? '?' : ''}${paramString}`, additionalState); + } + }, + [history] + ); + return navigate; } diff --git a/src/components/Alerting.test.tsx b/src/page/AlertingPage.test.tsx similarity index 98% rename from src/components/Alerting.test.tsx rename to src/page/AlertingPage.test.tsx index 5554cb898..e32723cb5 100644 --- a/src/components/Alerting.test.tsx +++ b/src/page/AlertingPage.test.tsx @@ -4,7 +4,7 @@ import { screen, waitFor, within } from '@testing-library/react'; import { type UserEvent } from '@testing-library/user-event'; import { render } from 'test/render'; -import { Alerting } from 'components/Alerting'; +import { AlertingPage } from 'page/AlertingPage'; import { ALERT_PROBE_SUCCESS_RECORDING_EXPR, DEFAULT_ALERT_NAMES_BY_FAMILY_AND_SENSITIVITY, @@ -38,7 +38,7 @@ const setDefaultRules = jest.fn(); const setRules = jest.fn().mockImplementation(() => Promise.resolve({ ok: true })); const renderAlerting = ({ withAlerting = true } = {}) => { - return render(, { + return render(, { instance: { alertRuler: withAlerting ? ({ url: 'alertUrl' } as unknown as DataSourceSettings) : undefined, }, diff --git a/src/components/Alerting.tsx b/src/page/AlertingPage.tsx similarity index 94% rename from src/components/Alerting.tsx rename to src/page/AlertingPage.tsx index e3693d1ec..0b855e531 100644 --- a/src/components/Alerting.tsx +++ b/src/page/AlertingPage.tsx @@ -1,14 +1,16 @@ -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, HorizontalGroup, Icon, Modal, Spinner, useStyles2, Alert } from '@grafana/ui'; import React, { FC, useState, useContext } from 'react'; import { css } from '@emotion/css'; -import { useAlerts } from 'hooks/useAlerts'; -import { AlertRuleForm } from './AlertRuleForm'; +import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Button, HorizontalGroup, Icon, Modal, Spinner, useStyles2, Alert } from '@grafana/ui'; + import { AlertFormValues, AlertRule } from 'types'; -import { InstanceContext } from 'contexts/InstanceContext'; -import { transformAlertFormValues } from './alertingTransformations'; +import { useAlerts } from 'hooks/useAlerts'; import useUnifiedAlertsEnabled from 'hooks/useUnifiedAlertsEnabled'; -import { config } from '@grafana/runtime'; +import { AlertRuleForm } from 'components/AlertRuleForm'; +import { InstanceContext } from 'contexts/InstanceContext'; +import { transformAlertFormValues } from 'components/alertingTransformations'; +import { PluginPage } from 'components/PluginPage'; type SplitAlertRules = { recordingRules: AlertRule[]; @@ -36,7 +38,15 @@ const getStyles = (theme: GrafanaTheme2) => ({ `, }); -export const Alerting: FC = () => { +export const AlertingPage = () => { + return ( + + + + ); +}; + +const Alerting: FC = () => { const styles = useStyles2(getStyles); const { alertRules, setDefaultRules, setRules, alertError } = useAlerts(); const [updatingDefaultRules, setUpdatingDefaultRules] = useState(false); diff --git a/src/page/ConfigPage.tsx b/src/page/ConfigPage.tsx index 19c420d7c..59a6b9d0e 100644 --- a/src/page/ConfigPage.tsx +++ b/src/page/ConfigPage.tsx @@ -1,12 +1,14 @@ +import React, { useContext } from 'react'; import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Container, Spinner, useStyles2 } from '@grafana/ui'; + +import { InstanceContext } from 'contexts/InstanceContext'; import { ConfigActions } from 'components/ConfigActions'; import { DashboardList } from 'components/DashboardList'; +import { PluginPage } from 'components/PluginPage'; import { ProgrammaticManagement } from 'components/ProgrammaticManagement'; import LinkedDatasourceView from 'components/LinkedDatasourceView'; -import { InstanceContext } from 'contexts/InstanceContext'; -import React, { useContext } from 'react'; function getStyles(theme: GrafanaTheme2) { return { @@ -36,62 +38,64 @@ function getStyles(theme: GrafanaTheme2) { export function ConfigPage() { const { meta, loading, instance } = useContext(InstanceContext); - const styles = useStyles2(getStyles); - if (loading) { - return ; - } return ( -
-
-

- Synthetic Monitoring is a blackbox monitoring solution provided as part of{' '} - - Grafana Cloud - - . If you don't already have a Grafana Cloud service,{' '} - - sign up now{' '} - -

-
- {instance.api && ( -
- -
-

Linked Data Sources

- - - - + + {loading ? ( + + ) : ( +
+
+

+ Synthetic Monitoring is a blackbox monitoring solution provided as part of{' '} + + Grafana Cloud + + . If you don't already have a Grafana Cloud service,{' '} + + sign up now{' '} + +

-
-

Backend address

-
{instance.api.instanceSettings.jsonData.apiHost}
+ {instance.api && ( +
+ +
+

Linked Data Sources

+ + + + +
+
+

Backend address

+
{instance.api.instanceSettings.jsonData.apiHost}
+
+
+ )} +
{meta?.enabled && }
+
+
+
+
Plugin version: {meta?.info.version}
)} -
{meta?.enabled && }
-
-
- -
-
Plugin version: {meta?.info.version}
-
+
); } diff --git a/src/page/DashboardPage.tsx b/src/page/DashboardPage.tsx index ddfd55644..e524af34f 100644 --- a/src/page/DashboardPage.tsx +++ b/src/page/DashboardPage.tsx @@ -1,13 +1,15 @@ +import React, { useContext, useMemo } from 'react'; import { Spinner } from '@grafana/ui'; + +import { FeatureName } from 'types'; import { ChecksContext } from 'contexts/ChecksContext'; import { InstanceContext } from 'contexts/InstanceContext'; +import { ChecksContextProvider } from 'components/ChecksContextProvider'; import { useFeatureFlag } from 'hooks/useFeatureFlag'; import { useNavigation } from 'hooks/useNavigation'; -import React, { useContext, useMemo } from 'react'; import { getDashboardSceneApp } from 'scenes/dashboardSceneApp'; -import { FeatureName } from 'types'; -export function DashboardPage() { +function DashboardPageContent() { const { instance } = useContext(InstanceContext); const { isEnabled } = useFeatureFlag(FeatureName.Scenes); const { isEnabled: multiHttpEnabled } = useFeatureFlag(FeatureName.MultiHttp); @@ -44,3 +46,11 @@ export function DashboardPage() { return ; } + +export function DashboardPage() { + return ( + + + + ); +} diff --git a/src/page/HomePage.test.tsx b/src/page/HomePage.test.tsx index 1e1bbcb3b..78a4a73c5 100644 --- a/src/page/HomePage.test.tsx +++ b/src/page/HomePage.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { screen, within } from '@testing-library/react'; import { render } from 'test/render'; -import HomePage from './HomePage'; +import { HomePage } from './HomePage'; import { CheckInfoContextProvider } from 'components/CheckInfoContextProvider'; const renderHomePage = () => { diff --git a/src/page/HomePage.tsx b/src/page/HomePage.tsx index 84e9d5da4..562d968bb 100644 --- a/src/page/HomePage.tsx +++ b/src/page/HomePage.tsx @@ -126,7 +126,7 @@ const sortSummaryToTop = (dashboardA: DashboardInfo, dashboardB: DashboardInfo) return 0; }; -const HomePage = () => { +export const HomePage = () => { const styles = useStyles2(getStyles); const { instance } = useContext(InstanceContext); const [checks, setChecks] = useState([]); @@ -343,5 +343,3 @@ const HomePage = () => { ); }; - -export default HomePage; diff --git a/src/components/UnprovisionedSetup.tsx b/src/page/UnprovisionedSetup.tsx similarity index 99% rename from src/components/UnprovisionedSetup.tsx rename to src/page/UnprovisionedSetup.tsx index 4df5b0e0b..25386a103 100644 --- a/src/components/UnprovisionedSetup.tsx +++ b/src/page/UnprovisionedSetup.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { PluginPage } from 'components/PluginPage'; +import { css } from '@emotion/css'; import { Alert, useStyles2 } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; + +import { PluginPage } from 'components/PluginPage'; const getStyles = (theme: GrafanaTheme2) => ({ container: css` diff --git a/src/page/WelcomePage.tsx b/src/page/WelcomePage.tsx index 80d1137a1..21c91cd03 100644 --- a/src/page/WelcomePage.tsx +++ b/src/page/WelcomePage.tsx @@ -1,19 +1,21 @@ import React, { FC, useState, useContext } from 'react'; import { Button, Alert, useStyles2, Spinner, Modal } from '@grafana/ui'; import { getBackendSrv, config } from '@grafana/runtime'; +import { DataSourceInstanceSettings, GrafanaTheme2, OrgRole, DataSourceJsonData } from '@grafana/data'; +import { FaroEvent, reportEvent, reportError } from 'faro'; +import { css } from '@emotion/css'; +import { isNumber } from 'lodash'; + +import { ROUTES, SubmissionErrorWrapper } from 'types'; import { findSMDataSources, hasRole, initializeDatasource } from 'utils'; +import { dashboardScreenshot, dashboardScreenshotLight } from 'img'; import { importAllDashboards } from 'dashboards/loader'; import { InstanceContext } from 'contexts/InstanceContext'; -import { DataSourceInstanceSettings, GrafanaTheme2, OrgRole, DataSourceJsonData } from '@grafana/data'; -import { css } from '@emotion/css'; +import { getRoute } from 'components/Routing'; import { colors, LEGACY_LOGS_DS_NAME, LEGACY_METRICS_DS_NAME } from 'components/constants'; -import { dashboardScreenshot, dashboardScreenshotLight } from 'img'; -import { isNumber } from 'lodash'; -import { SubmissionErrorWrapper } from 'types'; import { DisplayCard } from 'components/DisplayCard'; import FeaturesBanner from 'components/FeaturesBanner'; import { PluginPage } from 'components/PluginPage'; -import { FaroEvent, reportEvent, reportError } from 'faro'; const getStyles = (theme: GrafanaTheme2) => { const textColor = theme.isDark ? colors.darkText : colors.lightText; @@ -274,7 +276,7 @@ export const WelcomePage: FC = () => { await initializeDatasource(datasourcePayload, dashboards); // force reload so that GrafanaBootConfig is updated. - window.location.reload(); + window.location.href = `${window.location.origin}${getRoute(ROUTES.Home)}`; } catch (e) { const err = e as unknown as SubmissionErrorWrapper; setError(err.data?.msg ?? err.data?.err ?? 'Something went wrong'); diff --git a/src/test/render.tsx b/src/test/render.tsx index 6f11b4207..246a27f21 100644 --- a/src/test/render.tsx +++ b/src/test/render.tsx @@ -2,13 +2,14 @@ import React, { type ReactElement, type ReactNode } from 'react'; import { render, type RenderOptions } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Route, Router } from 'react-router-dom'; -import { AppPluginMeta, DataSourceSettings } from '@grafana/data'; +import { AppPluginMeta, DataSourceSettings, PluginType } from '@grafana/data'; import userEventLib from '@testing-library/user-event'; import { GlobalSettings, GrafanaInstances } from 'types'; import { InstanceContext } from 'contexts/InstanceContext'; import { getInstanceMock, instanceSettings } from 'datasource/__mocks__/DataSource'; import { FeatureFlagProvider } from 'components/FeatureFlagProvider'; +import pluginInfo from 'plugin.json'; export const createInstance = (options?: GrafanaInstances) => { return { @@ -25,10 +26,38 @@ type WrapperProps = { instance?: GrafanaInstances; path?: string; route?: string; + meta?: Partial>; }; -export const createWrapper = ({ featureToggles, instance = createInstance(), path, route }: WrapperProps = {}) => { - const meta = {} as AppPluginMeta; +export const createWrapper = ({ + featureToggles, + instance = createInstance(), + path, + route, + meta, +}: WrapperProps = {}) => { + const defaultMeta = { + id: pluginInfo.id, + name: pluginInfo.name, + type: PluginType.app, + info: { ...pluginInfo.info, links: [] }, + module: `/public/plugins/grafana-synthetic-monitoring-app/module.js`, + baseUrl: `/public/plugins/grafana-synthetic-monitoring-app`, + enabled: true, + jsonData: { + metrics: { + grafanaName: 'prometheus', + hostedId: 123, + }, + logs: { + grafanaName: 'loki', + hostedId: 456, + }, + apiHost: 'https://synthetic-monitoring-api.grafana.net', + stackId: 1, + }, + }; + const history = createMemoryHistory({ initialEntries: path ? [path] : undefined, }); @@ -37,7 +66,16 @@ export const createWrapper = ({ featureToggles, instance = createInstance(), pat // eslint-disable-next-line react/display-name const Wrapper = ({ children }: { children: ReactNode }) => ( - + {children} @@ -48,17 +86,18 @@ export const createWrapper = ({ featureToggles, instance = createInstance(), pat return { Wrapper, instance, history }; }; -type CustomRenderOptions = Omit & { - featureToggles?: Record; - instance?: GrafanaInstances; - path?: string; - route?: string; -}; +export type CustomRenderOptions = Omit & WrapperProps; const customRender = (ui: ReactElement, options: CustomRenderOptions = {}) => { - const { featureToggles, instance: instanceOptions, path, route, ...rest } = options; + const { featureToggles, instance: instanceOptions, path, route, meta, ...rest } = options; const user = userEventLib.setup(); - const { Wrapper, history, instance } = createWrapper({ featureToggles, instance: instanceOptions, path, route }); + const { Wrapper, history, instance } = createWrapper({ + featureToggles, + instance: instanceOptions, + path, + route, + meta, + }); return { user, diff --git a/src/test/setupTests.ts b/src/test/setupTests.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/types.ts b/src/types.ts index eb998b3e7..b5eb8fcb5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -537,8 +537,6 @@ export interface UsageValues { export enum ROUTES { Redirect = 'redirect', Home = 'home', - Setup = 'setup', - Unprovisioned = 'unprovisioned', Probes = 'probes', NewProbe = 'probes/new', EditProbe = 'probes/edit',