mirror of https://github.com/grafana/grafana
Plugin Extensions: E2E test addLink and legacy APIs (#92394)
* cleanup tests * more cleanup * added links * test legacy hooks * test legacy hooks * update codeowners * revert package changes * add project specfic example script * remove console log * Update .github/CODEOWNERS Co-authored-by: Timur Olzhabayev <timur.olzhabayev@grafana.com> * Update CODEOWNERS * use correct file names * cleanup tests --------- Co-authored-by: Timur Olzhabayev <timur.olzhabayev@grafana.com>pull/91738/head^2
parent
a2de893ab3
commit
1373b37166
@ -1,51 +0,0 @@ |
|||||||
import { test, expect } from '@grafana/plugin-e2e'; |
|
||||||
|
|
||||||
import { ensureExtensionRegistryIsPopulated } from './utils'; |
|
||||||
|
|
||||||
const testIds = { |
|
||||||
container: 'main-app-body', |
|
||||||
actions: { |
|
||||||
button: 'action-button', |
|
||||||
}, |
|
||||||
modal: { |
|
||||||
container: 'container', |
|
||||||
open: 'open-link', |
|
||||||
}, |
|
||||||
appA: { |
|
||||||
container: 'a-app-body', |
|
||||||
}, |
|
||||||
appB: { |
|
||||||
modal: 'b-app-modal', |
|
||||||
reusableComponent: 'b-app-configure-extension-component', |
|
||||||
}, |
|
||||||
legacyAPIPage: { |
|
||||||
container: 'data-testid pg-two-container', |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
|
||||||
const pluginId = 'grafana-extensionstest-app'; |
|
||||||
|
|
||||||
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => { |
|
||||||
await page.goto(`/a/${pluginId}/legacy-apis`); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
await page.getByTestId(testIds.actions.button).click(); |
|
||||||
await page.getByTestId(testIds.container).getByText('Go to A').click(); |
|
||||||
await page.getByTestId(testIds.modal.open).click(); |
|
||||||
await expect(page.getByTestId(testIds.appA.container)).toBeVisible(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('should extend the actions menu with a command triggered from b-app plugin', async ({ page }) => { |
|
||||||
await page.goto(`/a/${pluginId}/legacy-apis`); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
await expect( |
|
||||||
page.getByTestId(testIds.legacyAPIPage.container).getByTestId(testIds.appB.reusableComponent) |
|
||||||
).toHaveText('Hello World!'); |
|
||||||
}); |
|
||||||
|
|
||||||
test('should extend main app with component extension from app B', async ({ page }) => { |
|
||||||
await page.goto(`/a/${pluginId}/legacy-apis`); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
await page.getByTestId(testIds.actions.button).click(); |
|
||||||
await page.getByTestId(testIds.container).getByText('Open from B').click(); |
|
||||||
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible(); |
|
||||||
}); |
|
@ -1,44 +0,0 @@ |
|||||||
import { selectors } from '@grafana/e2e-selectors'; |
|
||||||
import { expect, test } from '@grafana/plugin-e2e'; |
|
||||||
|
|
||||||
import { ensureExtensionRegistryIsPopulated } from './utils'; |
|
||||||
|
|
||||||
const panelTitle = 'Link with defaults'; |
|
||||||
const extensionTitle = 'Open from time series...'; |
|
||||||
const testIds = { |
|
||||||
modal: { |
|
||||||
container: 'ape-modal-body', |
|
||||||
}, |
|
||||||
mainPage: { |
|
||||||
container: 'main-app-body', |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
|
||||||
const linkOnClickDashboardUid = 'dbfb47c5-e5e5-4d28-8ac7-35f349b95946'; |
|
||||||
const linkPathDashboardUid = 'd1fbb077-cd44-4738-8c8a-d4e66748b719'; |
|
||||||
|
|
||||||
test('should add link extension (path) with defaults to time series panel', async ({ gotoDashboardPage, page }) => { |
|
||||||
const dashboardPage = await gotoDashboardPage({ uid: linkPathDashboardUid }); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
|
||||||
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
|
||||||
await expect(page.getByTestId(testIds.mainPage.container)).toBeVisible(); |
|
||||||
}); |
|
||||||
|
|
||||||
test('should add link extension (onclick) with defaults to time series panel', async ({ gotoDashboardPage, page }) => { |
|
||||||
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid }); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
|
||||||
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
|
||||||
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with defaults"'); |
|
||||||
}); |
|
||||||
|
|
||||||
test('should add link extension (onclick) with new title to pie chart panel', async ({ gotoDashboardPage, page }) => { |
|
||||||
const panelTitle = 'Link with new name'; |
|
||||||
const extensionTitle = 'Open from piechart'; |
|
||||||
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid }); |
|
||||||
await ensureExtensionRegistryIsPopulated(page); |
|
||||||
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
|
||||||
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
|
||||||
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with new name"'); |
|
||||||
}); |
|
@ -1,9 +0,0 @@ |
|||||||
import { test, expect } from '@grafana/plugin-e2e'; |
|
||||||
|
|
||||||
const pluginId = 'grafana-extensionstest-app'; |
|
||||||
const exposedComponentTestId = 'exposed-component'; |
|
||||||
|
|
||||||
test('should display component exposed by another app', async ({ page }) => { |
|
||||||
await page.goto(`/a/${pluginId}/exposed-components`); |
|
||||||
await expect(await page.getByTestId(exposedComponentTestId)).toHaveText('Hello World!'); |
|
||||||
}); |
|
@ -1,11 +0,0 @@ |
|||||||
import { test, expect } from '@grafana/plugin-e2e'; |
|
||||||
|
|
||||||
const pluginId = 'grafana-extensionstest-app'; |
|
||||||
const exposedComponentTestId = 'exposed-component'; |
|
||||||
|
|
||||||
test('should render component with usePluginComponents hook', async ({ page }) => { |
|
||||||
await page.goto(`/a/${pluginId}/added-components`); |
|
||||||
await expect( |
|
||||||
page.getByTestId('data-testid pg-added-components-container').getByTestId('b-app-add-component') |
|
||||||
).toHaveText('Hello World!'); |
|
||||||
}); |
|
@ -1,135 +0,0 @@ |
|||||||
import { ChangeEvent, useState } from 'react'; |
|
||||||
import { lastValueFrom } from 'rxjs'; |
|
||||||
import { css } from '@emotion/css'; |
|
||||||
import { AppPluginMeta, GrafanaTheme2, PluginConfigPageProps, PluginMeta } from '@grafana/data'; |
|
||||||
import { getBackendSrv } from '@grafana/runtime'; |
|
||||||
import { Button, Field, FieldSet, Input, SecretInput, useStyles2 } from '@grafana/ui'; |
|
||||||
import { testIds } from '../testIds'; |
|
||||||
|
|
||||||
export type AppPluginSettings = { |
|
||||||
apiUrl?: string; |
|
||||||
}; |
|
||||||
|
|
||||||
type State = { |
|
||||||
// The URL to reach our custom API.
|
|
||||||
apiUrl: string; |
|
||||||
// Tells us if the API key secret is set.
|
|
||||||
isApiKeySet: boolean; |
|
||||||
// A secret key for our custom API.
|
|
||||||
apiKey: string; |
|
||||||
}; |
|
||||||
|
|
||||||
export interface AppConfigProps extends PluginConfigPageProps<AppPluginMeta<AppPluginSettings>> {} |
|
||||||
|
|
||||||
export const AppConfig = ({ plugin }: AppConfigProps) => { |
|
||||||
const s = useStyles2(getStyles); |
|
||||||
const { enabled, pinned, jsonData, secureJsonFields } = plugin.meta; |
|
||||||
const [state, setState] = useState<State>({ |
|
||||||
apiUrl: jsonData?.apiUrl || '', |
|
||||||
apiKey: '', |
|
||||||
isApiKeySet: Boolean(secureJsonFields?.apiKey), |
|
||||||
}); |
|
||||||
|
|
||||||
const onResetApiKey = () => |
|
||||||
setState({ |
|
||||||
...state, |
|
||||||
apiKey: '', |
|
||||||
isApiKeySet: false, |
|
||||||
}); |
|
||||||
|
|
||||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => { |
|
||||||
setState({ |
|
||||||
...state, |
|
||||||
[event.target.name]: event.target.value.trim(), |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div data-testid={testIds.appConfig.container}> |
|
||||||
<FieldSet label="API Settings"> |
|
||||||
<Field label="API Key" description="A secret key for authenticating to our custom API"> |
|
||||||
<SecretInput |
|
||||||
width={60} |
|
||||||
id="config-api-key" |
|
||||||
data-testid={testIds.appConfig.apiKey} |
|
||||||
name="apiKey" |
|
||||||
value={state.apiKey} |
|
||||||
isConfigured={state.isApiKeySet} |
|
||||||
placeholder={'Your secret API key'} |
|
||||||
onChange={onChange} |
|
||||||
onReset={onResetApiKey} |
|
||||||
/> |
|
||||||
</Field> |
|
||||||
|
|
||||||
<Field label="API Url" description="" className={s.marginTop}> |
|
||||||
<Input |
|
||||||
width={60} |
|
||||||
name="apiUrl" |
|
||||||
id="config-api-url" |
|
||||||
data-testid={testIds.appConfig.apiUrl} |
|
||||||
value={state.apiUrl} |
|
||||||
placeholder={`E.g.: http://mywebsite.com/api/v1`} |
|
||||||
onChange={onChange} |
|
||||||
/> |
|
||||||
</Field> |
|
||||||
|
|
||||||
<div className={s.marginTop}> |
|
||||||
<Button |
|
||||||
type="submit" |
|
||||||
data-testid={testIds.appConfig.submit} |
|
||||||
onClick={() => |
|
||||||
updatePluginAndReload(plugin.meta.id, { |
|
||||||
enabled, |
|
||||||
pinned, |
|
||||||
jsonData: { |
|
||||||
apiUrl: state.apiUrl, |
|
||||||
}, |
|
||||||
// This cannot be queried later by the frontend.
|
|
||||||
// We don't want to override it in case it was set previously and left untouched now.
|
|
||||||
secureJsonData: state.isApiKeySet |
|
||||||
? undefined |
|
||||||
: { |
|
||||||
apiKey: state.apiKey, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
disabled={Boolean(!state.apiUrl || (!state.isApiKeySet && !state.apiKey))} |
|
||||||
> |
|
||||||
Save API settings |
|
||||||
</Button> |
|
||||||
</div> |
|
||||||
</FieldSet> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({ |
|
||||||
colorWeak: css` |
|
||||||
color: ${theme.colors.text.secondary}; |
|
||||||
`,
|
|
||||||
marginTop: css` |
|
||||||
margin-top: ${theme.spacing(3)}; |
|
||||||
`,
|
|
||||||
}); |
|
||||||
|
|
||||||
const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta<AppPluginSettings>>) => { |
|
||||||
try { |
|
||||||
await updatePlugin(pluginId, data); |
|
||||||
|
|
||||||
// Reloading the page as the changes made here wouldn't be propagated to the actual plugin otherwise.
|
|
||||||
// This is not ideal, however unfortunately currently there is no supported way for updating the plugin state.
|
|
||||||
window.location.reload(); |
|
||||||
} catch (e) { |
|
||||||
console.error('Error while updating the plugin', e); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
export const updatePlugin = async (pluginId: string, data: Partial<PluginMeta>) => { |
|
||||||
const response = await getBackendSrv().fetch({ |
|
||||||
url: `/api/plugins/${pluginId}/settings`, |
|
||||||
method: 'POST', |
|
||||||
data, |
|
||||||
}); |
|
||||||
|
|
||||||
return lastValueFrom(response); |
|
||||||
}; |
|
@ -1 +0,0 @@ |
|||||||
export * from './AppConfig'; |
|
@ -1,36 +0,0 @@ |
|||||||
export const testIds = { |
|
||||||
container: 'main-app-body', |
|
||||||
actions: { |
|
||||||
button: 'action-button', |
|
||||||
}, |
|
||||||
modal: { |
|
||||||
container: 'container', |
|
||||||
open: 'open-link', |
|
||||||
}, |
|
||||||
appA: { |
|
||||||
container: 'a-app-body', |
|
||||||
}, |
|
||||||
appB: { |
|
||||||
modal: 'b-app-modal', |
|
||||||
}, |
|
||||||
appConfig: { |
|
||||||
container: 'data-testid ac-container', |
|
||||||
apiKey: 'data-testid ac-api-key', |
|
||||||
apiUrl: 'data-testid ac-api-url', |
|
||||||
submit: 'data-testid ac-submit-form', |
|
||||||
}, |
|
||||||
pageOne: { |
|
||||||
container: 'data-testid pg-one-container', |
|
||||||
navigateToFour: 'data-testid navigate-to-four', |
|
||||||
}, |
|
||||||
pageTwo: { |
|
||||||
container: 'data-testid pg-two-container', |
|
||||||
}, |
|
||||||
addedComponentsPage: { |
|
||||||
container: 'data-testid pg-added-components-container', |
|
||||||
}, |
|
||||||
pageFour: { |
|
||||||
container: 'data-testid pg-four-container', |
|
||||||
navigateBack: 'data-testid navigate-back', |
|
||||||
}, |
|
||||||
}; |
|
@ -0,0 +1,25 @@ |
|||||||
|
import { PluginPage, usePluginLinks } from '@grafana/runtime'; |
||||||
|
|
||||||
|
import { testIds } from '../testIds'; |
||||||
|
|
||||||
|
export const LINKS_EXTENSION_POINT_ID = 'plugins/grafana-extensionstest-app/use-plugin-links/v1'; |
||||||
|
|
||||||
|
export function AddedLinks() { |
||||||
|
const { links, isLoading } = usePluginLinks({ extensionPointId: LINKS_EXTENSION_POINT_ID }); |
||||||
|
|
||||||
|
return ( |
||||||
|
<PluginPage> |
||||||
|
<div data-testid={testIds.addedLinksPage.container}> |
||||||
|
{isLoading ? ( |
||||||
|
<div>Loading...</div> |
||||||
|
) : ( |
||||||
|
links.map(({ id, title, path, onClick }) => ( |
||||||
|
<a href={path} title={title} key={id} onClick={onClick}> |
||||||
|
{title} |
||||||
|
</a> |
||||||
|
)) |
||||||
|
)} |
||||||
|
</div> |
||||||
|
</PluginPage> |
||||||
|
); |
||||||
|
} |
@ -1,44 +0,0 @@ |
|||||||
import { testIds } from '../components/testIds'; |
|
||||||
import { PluginPage, getPluginComponentExtensions, getPluginExtensions } from '@grafana/runtime'; |
|
||||||
import { ActionButton } from '../components/ActionButton'; |
|
||||||
import { Stack } from '@grafana/ui'; |
|
||||||
|
|
||||||
type AppExtensionContext = {}; |
|
||||||
type ReusableComponentProps = { |
|
||||||
name: string; |
|
||||||
}; |
|
||||||
|
|
||||||
export function LegacyAPIs() { |
|
||||||
const extensionPointId = 'plugins/grafana-extensionstest-app/actions'; |
|
||||||
const context: AppExtensionContext = {}; |
|
||||||
|
|
||||||
const { extensions } = getPluginExtensions({ |
|
||||||
extensionPointId, |
|
||||||
context, |
|
||||||
}); |
|
||||||
|
|
||||||
const { extensions: componentExtensions } = getPluginComponentExtensions<ReusableComponentProps>({ |
|
||||||
extensionPointId: 'plugins/grafana-extensionexample2-app/configure-extension-component/v1', |
|
||||||
}); |
|
||||||
|
|
||||||
return ( |
|
||||||
<PluginPage> |
|
||||||
<Stack direction={'column'} gap={4} data-testid={testIds.pageTwo.container}> |
|
||||||
<article> |
|
||||||
<h3>Link extensions defined with configureExtensionLink and retrived using getPluginExtensions</h3> |
|
||||||
<ActionButton extensions={extensions} /> |
|
||||||
</article> |
|
||||||
<article> |
|
||||||
<h3> |
|
||||||
Component extensions defined with configureExtensionComponent and retrived using |
|
||||||
getPluginComponentExtensions |
|
||||||
</h3> |
|
||||||
{componentExtensions.map((extension) => { |
|
||||||
const Component = extension.component; |
|
||||||
return <Component key={extension.id} name="World" />; |
|
||||||
})} |
|
||||||
</article> |
|
||||||
</Stack> |
|
||||||
</PluginPage> |
|
||||||
); |
|
||||||
} |
|
@ -0,0 +1,62 @@ |
|||||||
|
import { |
||||||
|
PluginPage, |
||||||
|
getPluginComponentExtensions, |
||||||
|
getPluginExtensions, |
||||||
|
getPluginLinkExtensions, |
||||||
|
} from '@grafana/runtime'; |
||||||
|
import { Stack } from '@grafana/ui'; |
||||||
|
|
||||||
|
import { ActionButton } from '../components/ActionButton'; |
||||||
|
import { testIds } from '../testIds'; |
||||||
|
|
||||||
|
type AppExtensionContext = {}; |
||||||
|
type ReusableComponentProps = { |
||||||
|
name: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function LegacyGetters() { |
||||||
|
const extensionPointId1 = 'plugins/grafana-extensionstest-app/actions'; |
||||||
|
const extensionPointId2 = 'plugins/grafana-extensionexample2-app/configure-extension-component/v1'; |
||||||
|
const context: AppExtensionContext = {}; |
||||||
|
|
||||||
|
const { extensions } = getPluginExtensions({ |
||||||
|
extensionPointId: extensionPointId1, |
||||||
|
context, |
||||||
|
}); |
||||||
|
|
||||||
|
const { extensions: linkExtensions } = getPluginLinkExtensions({ |
||||||
|
extensionPointId: extensionPointId1, |
||||||
|
}); |
||||||
|
|
||||||
|
const { extensions: componentExtensions } = getPluginComponentExtensions<ReusableComponentProps>({ |
||||||
|
extensionPointId: extensionPointId2, |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<PluginPage> |
||||||
|
<Stack direction={'column'} gap={4} data-testid={testIds.legacyGettersPage.container}> |
||||||
|
<section data-testid={testIds.legacyGettersPage.section1}> |
||||||
|
<h3> |
||||||
|
Link extensions defined with configureExtensionLink or configureExtensionComponent and retrived using |
||||||
|
getPluginExtensions |
||||||
|
</h3> |
||||||
|
<ActionButton extensions={extensions} /> |
||||||
|
</section> |
||||||
|
<section data-testid={testIds.legacyGettersPage.section2}> |
||||||
|
<h3>Link extensions defined with configureExtensionLink and retrived using getPluginLinkExtensions</h3> |
||||||
|
<ActionButton extensions={linkExtensions} /> |
||||||
|
</section> |
||||||
|
<section data-testid={testIds.legacyGettersPage.section3}> |
||||||
|
<h3> |
||||||
|
Component extensions defined with configureExtensionComponent and retrived using |
||||||
|
getPluginComponentExtensions |
||||||
|
</h3> |
||||||
|
{componentExtensions.map((extension) => { |
||||||
|
const Component = extension.component; |
||||||
|
return <Component key={extension.id} name="World" />; |
||||||
|
})} |
||||||
|
</section> |
||||||
|
</Stack> |
||||||
|
</PluginPage> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import { |
||||||
|
PluginPage, |
||||||
|
usePluginComponentExtensions, |
||||||
|
usePluginExtensions, |
||||||
|
usePluginLinkExtensions, |
||||||
|
} from '@grafana/runtime'; |
||||||
|
import { Stack } from '@grafana/ui'; |
||||||
|
|
||||||
|
import { ActionButton } from '../components/ActionButton'; |
||||||
|
import { testIds } from '../testIds'; |
||||||
|
|
||||||
|
type AppExtensionContext = {}; |
||||||
|
type ReusableComponentProps = { |
||||||
|
name: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function LegacyHooks() { |
||||||
|
const extensionPointId1 = 'plugins/grafana-extensionstest-app/actions'; |
||||||
|
const extensionPointId2 = 'plugins/grafana-extensionexample2-app/configure-extension-component/v1'; |
||||||
|
const context: AppExtensionContext = {}; |
||||||
|
|
||||||
|
const { extensions } = usePluginExtensions({ |
||||||
|
extensionPointId: extensionPointId1, |
||||||
|
context, |
||||||
|
}); |
||||||
|
|
||||||
|
const { extensions: linkExtensions } = usePluginLinkExtensions({ |
||||||
|
extensionPointId: extensionPointId1, |
||||||
|
}); |
||||||
|
|
||||||
|
const { extensions: componentExtensions } = usePluginComponentExtensions<ReusableComponentProps>({ |
||||||
|
extensionPointId: extensionPointId2, |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<PluginPage> |
||||||
|
<Stack direction={'column'} gap={4} data-testid={testIds.legacyHooksPage.container}> |
||||||
|
<section data-testid={testIds.legacyHooksPage.section1}> |
||||||
|
<h3> |
||||||
|
Link extensions defined with configureExtensionLink or configureExtensionComponent and retrived using |
||||||
|
usePluginExtensions |
||||||
|
</h3> |
||||||
|
<ActionButton extensions={extensions} /> |
||||||
|
</section> |
||||||
|
<section data-testid={testIds.legacyHooksPage.section2}> |
||||||
|
<h3>Link extensions defined with configureExtensionLink and retrived using usePluginLinkExtensions</h3> |
||||||
|
<ActionButton extensions={linkExtensions} /> |
||||||
|
</section> |
||||||
|
<section data-testid={testIds.legacyHooksPage.section3}> |
||||||
|
<h3> |
||||||
|
Component extensions defined with configureExtensionComponent and retrived using |
||||||
|
usePluginComponentExtensions |
||||||
|
</h3> |
||||||
|
{componentExtensions.map((extension) => { |
||||||
|
const Component = extension.component; |
||||||
|
return <Component key={extension.id} name="World" />; |
||||||
|
})} |
||||||
|
</section> |
||||||
|
</Stack> |
||||||
|
</PluginPage> |
||||||
|
); |
||||||
|
} |
@ -1,3 +1,5 @@ |
|||||||
export { ExposedComponents } from './ExposedComponents'; |
export { ExposedComponents } from './ExposedComponents'; |
||||||
export { LegacyAPIs } from './LegacyAPIs'; |
export { LegacyGetters } from './LegacyGetters'; |
||||||
|
export { LegacyHooks } from './LegacyHooks'; |
||||||
export { AddedComponents } from './AddedComponents'; |
export { AddedComponents } from './AddedComponents'; |
||||||
|
export { AddedLinks } from './AddedLinks'; |
||||||
|
@ -1,16 +0,0 @@ |
|||||||
export const testIds = { |
|
||||||
container: 'main-app-body', |
|
||||||
actions: { |
|
||||||
button: 'action-button', |
|
||||||
}, |
|
||||||
modal: { |
|
||||||
container: 'container', |
|
||||||
open: 'open-link', |
|
||||||
}, |
|
||||||
appA: { |
|
||||||
container: 'a-app-body', |
|
||||||
}, |
|
||||||
appB: { |
|
||||||
modal: 'b-app-modal', |
|
||||||
}, |
|
||||||
}; |
|
@ -1,18 +0,0 @@ |
|||||||
export const testIds = { |
|
||||||
container: 'main-app-body', |
|
||||||
actions: { |
|
||||||
button: 'action-button', |
|
||||||
}, |
|
||||||
modal: { |
|
||||||
container: 'container', |
|
||||||
open: 'open-link', |
|
||||||
}, |
|
||||||
appA: { |
|
||||||
container: 'a-app-body', |
|
||||||
}, |
|
||||||
appB: { |
|
||||||
modal: 'b-app-modal', |
|
||||||
reusableComponent: 'b-app-configure-extension-component', |
|
||||||
reusableAddedComponent: 'b-app-add-component', |
|
||||||
}, |
|
||||||
}; |
|
@ -0,0 +1,40 @@ |
|||||||
|
export const testIds = { |
||||||
|
container: 'main-app-body', |
||||||
|
actions: { |
||||||
|
button: 'action-button', |
||||||
|
}, |
||||||
|
modal: { |
||||||
|
container: 'container', |
||||||
|
open: 'open-link', |
||||||
|
}, |
||||||
|
appA: { |
||||||
|
container: 'a-app-body', |
||||||
|
}, |
||||||
|
appB: { |
||||||
|
modal: 'b-app-modal', |
||||||
|
reusableComponent: 'b-app-configure-extension-component', |
||||||
|
reusableAddedComponent: 'b-app-add-component', |
||||||
|
exposedComponent: 'b-app-exposed-component', |
||||||
|
}, |
||||||
|
legacyGettersPage: { |
||||||
|
container: 'data-testid pg-legacy-getters-container', |
||||||
|
section1: 'get-plugin-extensions', |
||||||
|
section2: 'configure-extension-link-get-plugin-link-extensions', |
||||||
|
section3: 'configure-extension-component-get-plugin-component-extensions', |
||||||
|
}, |
||||||
|
legacyHooksPage: { |
||||||
|
container: 'data-testid pg-legacy-hooks-container', |
||||||
|
section1: 'use-plugin-extensions', |
||||||
|
section2: 'configure-extension-link-use-plugin-link-extensions', |
||||||
|
section3: 'configure-extension-component-use-plugin-component-extensions', |
||||||
|
}, |
||||||
|
exposedComponentsPage: { |
||||||
|
container: 'data-testid pg-exposed-components-container', |
||||||
|
}, |
||||||
|
addedComponentsPage: { |
||||||
|
container: 'data-testid pg-added-components-container', |
||||||
|
}, |
||||||
|
addedLinksPage: { |
||||||
|
container: 'data-testid pg-added-links-container', |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,52 @@ |
|||||||
|
import { test, expect } from '@grafana/plugin-e2e'; |
||||||
|
|
||||||
|
import { ensureExtensionRegistryIsPopulated } from '../utils'; |
||||||
|
import { testIds } from '../../testIds'; |
||||||
|
import pluginJson from '../../plugin.json'; |
||||||
|
|
||||||
|
test.describe('getPluginExtensions + configureExtensionLink', () => { |
||||||
|
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-getters`); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const section = await page.getByTestId(testIds.legacyGettersPage.section1); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Go to A').click(); |
||||||
|
await page.getByTestId(testIds.modal.open).click(); |
||||||
|
await expect(page.getByTestId(testIds.appA.container)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('getPluginExtensions + configureExtensionComponent', () => { |
||||||
|
test('should extend main app with component extension from app B', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-getters`); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const section = await page.getByTestId(testIds.legacyGettersPage.section1); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Open from B').click(); |
||||||
|
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('getPluginLinkExtensions + configureExtensionLink', () => { |
||||||
|
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-getters`); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const section = await page.getByTestId(testIds.legacyGettersPage.section2); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Go to A').click(); |
||||||
|
await page.getByTestId(testIds.modal.open).click(); |
||||||
|
await expect(page.getByTestId(testIds.appA.container)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('getPluginComponentExtensions + configureExtensionComponent', () => { |
||||||
|
test('should extend the actions menu with a command triggered from b-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-getters`); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
await expect( |
||||||
|
page |
||||||
|
.getByTestId('configure-extension-component-get-plugin-component-extensions') |
||||||
|
.getByTestId(testIds.appB.reusableComponent) |
||||||
|
).toHaveText('Hello World!'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,45 @@ |
|||||||
|
import { test, expect } from '@grafana/plugin-e2e'; |
||||||
|
|
||||||
|
import { testIds } from '../../testIds'; |
||||||
|
import pluginJson from '../../plugin.json'; |
||||||
|
|
||||||
|
test.describe('usePluginExtensions + configureExtensionLink', () => { |
||||||
|
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-hooks`); |
||||||
|
const section = await page.getByTestId(testIds.legacyHooksPage.section1); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Go to A').click(); |
||||||
|
await page.getByTestId(testIds.modal.open).click(); |
||||||
|
await expect(page.getByTestId(testIds.appA.container)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('usePluginExtensions + configureExtensionComponent', () => { |
||||||
|
test('should extend main app with component extension from app B', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-hooks`); |
||||||
|
const section = await page.getByTestId(testIds.legacyHooksPage.section1); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Open from B').click(); |
||||||
|
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('usePluginLinkExtensions + configureExtensionLink', () => { |
||||||
|
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-hooks`); |
||||||
|
const section = await page.getByTestId(testIds.legacyHooksPage.section2); |
||||||
|
await section.getByTestId(testIds.actions.button).click(); |
||||||
|
await page.getByTestId(testIds.container).getByText('Go to A').click(); |
||||||
|
await page.getByTestId(testIds.modal.open).click(); |
||||||
|
await expect(page.getByTestId(testIds.appA.container)).toBeVisible(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('usePluginComponentExtensions + configureExtensionComponent', () => { |
||||||
|
test('should extend the actions menu with a command triggered from b-app plugin', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/legacy-hooks`); |
||||||
|
await expect( |
||||||
|
page.getByTestId(testIds.legacyHooksPage.section3).getByTestId(testIds.appB.reusableComponent) |
||||||
|
).toHaveText('Hello World!'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,42 @@ |
|||||||
|
import { expect, test } from '@grafana/plugin-e2e'; |
||||||
|
import { ensureExtensionRegistryIsPopulated } from '../utils'; |
||||||
|
|
||||||
|
const panelTitle = 'Link with defaults'; |
||||||
|
const extensionTitle = 'Open from time series...'; |
||||||
|
|
||||||
|
const linkOnClickDashboardUid = 'dbfb47c5-e5e5-4d28-8ac7-35f349b95946'; |
||||||
|
const linkPathDashboardUid = 'd1fbb077-cd44-4738-8c8a-d4e66748b719'; |
||||||
|
|
||||||
|
test.describe('configureExtensionLink targeting core extension points', () => { |
||||||
|
test('configureExtensionLink - should add link extension (path) with defaults to time series panel.', async ({ |
||||||
|
gotoDashboardPage, |
||||||
|
page, |
||||||
|
}) => { |
||||||
|
const dashboardPage = await gotoDashboardPage({ uid: linkPathDashboardUid }); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
||||||
|
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
||||||
|
await expect(page.getByRole('heading', { name: 'Extensions test app' })).toBeVisible(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should add link extension (onclick) with defaults to time series panel', async ({ |
||||||
|
gotoDashboardPage, |
||||||
|
page, |
||||||
|
}) => { |
||||||
|
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid }); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
||||||
|
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
||||||
|
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with defaults"'); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should add link extension (onclick) with new title to pie chart panel', async ({ gotoDashboardPage, page }) => { |
||||||
|
const panelTitle = 'Link with new name'; |
||||||
|
const extensionTitle = 'Open from piechart'; |
||||||
|
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid }); |
||||||
|
await ensureExtensionRegistryIsPopulated(page); |
||||||
|
const panel = await dashboardPage.getPanelByTitle(panelTitle); |
||||||
|
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' }); |
||||||
|
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with new name"'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,8 @@ |
|||||||
|
import { test, expect } from '@grafana/plugin-e2e'; |
||||||
|
import { testIds } from '../testIds'; |
||||||
|
import pluginJson from '../plugin.json'; |
||||||
|
|
||||||
|
test('should display component exposed by another app', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/exposed-components`); |
||||||
|
await expect(page.getByTestId(testIds.appB.exposedComponent)).toHaveText('Hello World!'); |
||||||
|
}); |
@ -0,0 +1,11 @@ |
|||||||
|
import { test, expect } from '@grafana/plugin-e2e'; |
||||||
|
|
||||||
|
import pluginJson from '../plugin.json'; |
||||||
|
import { testIds } from '../testIds'; |
||||||
|
|
||||||
|
test('should render component with usePluginComponents hook', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/added-components`); |
||||||
|
await expect( |
||||||
|
page.getByTestId(testIds.addedComponentsPage.container).getByTestId(testIds.appB.reusableAddedComponent) |
||||||
|
).toHaveText('Hello World!'); |
||||||
|
}); |
@ -0,0 +1,10 @@ |
|||||||
|
import { test, expect } from '@grafana/plugin-e2e'; |
||||||
|
|
||||||
|
import pluginJson from '../plugin.json'; |
||||||
|
import { testIds } from '../testIds'; |
||||||
|
|
||||||
|
test('path link', async ({ page }) => { |
||||||
|
await page.goto(`/a/${pluginJson.id}/added-links`); |
||||||
|
await page.getByTestId(testIds.addedLinksPage.container).getByText('Basic link').click(); |
||||||
|
await expect(page.getByTestId(testIds.appA.container)).toHaveText('Hello Grafana!'); |
||||||
|
}); |
Loading…
Reference in new issue