Add to dashboard: expose add to dashboard form component for external apps (#112609)

* add extension for drilldown to add to dashboard

* reuse configure add to dashboard function callback

* structure for drilldown add to dashboard

* fix imports

* fix tests

* expose as a component

* remove extension link

* get component ready to extend

* lazy load component

* add component to exposed component registry

* update folder structure to not work in explore folder

* keep dependencies clean

* nice structure to let folks know this is a drilldown integration

* update code owners for new file

* make exposed component more generic, step one, update component id

* step 2, expose add to dashboard form component

* add more explicit useAbsolutePath option to form

* remove old implementation code for drilldown specific component

* commit translation

* add comments to avoid breaking changes

* add e2e test for add to dashboard form component

* fix flaky test

* add exposed component id to e2e test app

* remove gridPos in buildPanel fallback fn

* add code comment for useAbsolutePath's purpose

* remove gridPos from e2e test
This commit is contained in:
Brendan O'Handley
2025-10-29 09:15:20 -05:00
committed by GitHub
parent 7dbacddb18
commit 2472555af0
9 changed files with 130 additions and 4 deletions
@@ -10,6 +10,7 @@ export function ExposedComponents() {
const { component: ReusableComponent } = usePluginComponent<ReusableComponentProps>(
'grafana-extensionexample1-app/reusable-component/v1'
);
const { component: AddToDashboardForm } = usePluginComponent('grafana/add-to-dashboard-form/v1');
if (!ReusableComponent) {
return null;
@@ -20,6 +21,22 @@ export function ExposedComponents() {
<div data-testid={testIds.exposedComponentsPage.container}>
<ReusableComponent name={'World'} />
</div>
{AddToDashboardForm && (
<section>
<h3>Save to dashboard (exposed form)</h3>
<AddToDashboardForm
// Create a recognizable panel for assertion
buildPanel={() => ({
type: 'timeseries',
title: 'E2E Add to Dashboard Panel',
targets: [],
})}
// Ensure navigation works correctly from plugin page
options={{ useAbsolutePath: true }}
onClose={() => {}}
/>
</section>
)}
</PluginPage>
);
}
@@ -80,7 +80,7 @@
"grafanaDependency": ">=10.4.0",
"plugins": [],
"extensions": {
"exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1"]
"exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1", "grafana/add-to-dashboard-form/v1"]
}
}
}
@@ -1,6 +1,7 @@
import { test, expect } from '@grafana/plugin-e2e';
import { testIds } from '../testIds';
import pluginJson from '../plugin.json';
import { ensureExtensionRegistryIsPopulated } from './utils';
test.describe(
'grafana-extensionstest-app',
@@ -12,5 +13,34 @@ test.describe(
await page.goto(`/a/${pluginJson.id}/exposed-components`);
await expect(page.getByTestId(testIds.appB.exposedComponent)).toHaveText('Hello World!');
});
test('exposed add-to-dashboard form saves to a new dashboard', async ({ page }) => {
await page.goto(`/a/${pluginJson.id}/exposed-components`);
await ensureExtensionRegistryIsPopulated(page);
// Wait for the exposed form section to be ready
await expect(page.getByRole('heading', { name: 'Save to dashboard (exposed form)' })).toBeVisible();
// Wait for any of the form buttons to render (lazy load) before clicking
const openInNewTab = page.getByRole('button', { name: 'Open in new tab' });
const cancelBtn = page.getByRole('button', { name: 'Cancel' });
await Promise.race([expect(openInNewTab).toBeVisible(), expect(cancelBtn).toBeVisible()]);
// Now wait for the submit button to be visible, then click (role or text)
const openDashboardByRole = page.getByRole('button', { name: 'Open dashboard' });
const openDashboardByText = page.getByText('Open dashboard');
if (await openDashboardByRole.isVisible().catch(() => false)) {
await openDashboardByRole.click();
} else {
await expect(openDashboardByText.first()).toBeVisible();
await openDashboardByText.first().click();
}
// Navigates to /dashboard/new and prepopulates a panel from local storage
await expect(page).toHaveURL(/\/dashboard\/new/);
// Panel should be created with our custom title
await expect(page.getByText('E2E Add to Dashboard Panel').first()).toBeVisible();
});
}
);