diff --git a/docusaurus/docs/e2e-test-a-plugin/authentication.md b/docusaurus/docs/e2e-test-a-plugin/authentication.md index bbd5318f6b..d6053bf6da 100644 --- a/docusaurus/docs/e2e-test-a-plugin/authentication.md +++ b/docusaurus/docs/e2e-test-a-plugin/authentication.md @@ -26,14 +26,13 @@ In the following example, there's a [setup project](https://playwright.dev/docs/ The second project, `run-tests`, runs all tests in the `./tests` directory. This project reuses the authentication state from the `auth` project. As a consequence, login only happens once, and all tests in the `run-tests` project start already authenticated. ```ts title="playwright.config.ts" -import { dirname } from 'path'; -import { defineConfig, devices } from '@playwright/test'; +import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; -const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; - -export default defineConfig({ - ... - projects: [ +export default defineConfig(baseConfig, { + projects: [ + ...baseConfig.projects!, { name: 'auth', testDir: pluginE2eAuth, @@ -48,7 +47,7 @@ export default defineConfig({ storageState: 'playwright/.auth/admin.json', }, dependencies: ['auth'], - } + }, ], }); ``` @@ -60,14 +59,14 @@ If your plugin uses RBAC, you may want to write tests that verify that certain p The `@grafana/plugin-e2e` tool lets you define users with roles in the Playwright config file. In the following example, a new user with the role `Viewer` is created in the `createViewerUserAndAuthenticate` setup project. In the next project, authentication state for the user with the viewer role is reused when running the tests. Note that tests that are specific for the `Viewer` role have been added to a dedicated `testDir`. ```ts title="playwright.config.ts" -import { dirname } from 'path'; -import { defineConfig, devices } from '@playwright/test'; - -const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; +import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; -export default defineConfig({ +export default defineConfig(baseConfig, { ... projects: [ + ...baseConfig.projects!, { name: 'createViewerUserAndAuthenticate', testDir: pluginE2eAuth, @@ -100,12 +99,11 @@ export default defineConfig({ When a `user` is defined in a setup project (like in the RBAC example above) `plugin-e2e` will use the Grafana HTTP API to create the user account. This action requires elevated permissions, so by default the server administrator credentials `admin:admin` will be used. If the end-to-end tests are targeting the [development environment](../set-up/) scaffolded with `create-plugin`, this will work fine. However for other test environments the server administrator password may be different. In that case, we search for GRAFANA_ADMIN_USER and GRAFANA_ADMIN_PASSWORD environment variables. Additionally you can provide the correct credentials by setting `grafanaAPICredentials` in the global options. ```ts title="playwright.config.ts" -import { dirname } from 'path'; -import { defineConfig, devices } from '@playwright/test'; - -const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; +import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; -export default defineConfig({ +export default defineConfig(baseConfig, { testDir: './tests', use: { baseURL: process.env.GRAFANA_URL || 'http://localhost:3000', @@ -115,6 +113,7 @@ export default defineConfig({ }, }, projects: [ + ...baseConfig.projects!, ... ] }) diff --git a/docusaurus/docs/e2e-test-a-plugin/feature-toggles.md b/docusaurus/docs/e2e-test-a-plugin/feature-toggles.md index 18c217110a..c59744f19f 100644 --- a/docusaurus/docs/e2e-test-a-plugin/feature-toggles.md +++ b/docusaurus/docs/e2e-test-a-plugin/feature-toggles.md @@ -27,10 +27,11 @@ The `@grafana/plugin-e2e` tool allows you to override the frontend feature toggl ```typescript // playwright.config.ts -import { defineConfig, devices } from '@playwright/test'; -import { PluginOptions } from '@grafana/plugin-e2e'; +import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; -export default defineConfig({ +export default defineConfig(baseConfig, { testDir: './tests', reporter: 'html', use: { diff --git a/docusaurus/docs/e2e-test-a-plugin/migrate-from-grafana-e2e.md b/docusaurus/docs/e2e-test-a-plugin/migrate-from-grafana-e2e.md index 64e54347a7..b5117505b8 100644 --- a/docusaurus/docs/e2e-test-a-plugin/migrate-from-grafana-e2e.md +++ b/docusaurus/docs/e2e-test-a-plugin/migrate-from-grafana-e2e.md @@ -58,15 +58,16 @@ Open the Playwright config file that was generated when Playwright was installed Your Playwright config should have the following project configuration: ```ts title="playwright.config.ts" -import { dirname } from 'path'; -import { defineConfig, devices } from '@playwright/test'; import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; -export default defineConfig({ +export default defineConfig(baseConfig, { ... projects: [ + ...baseConfig.projects!, { name: 'auth', testDir: pluginE2eAuth, diff --git a/docusaurus/docs/how-to-guides/app-plugins/implement-rbac-in-app-plugins.md b/docusaurus/docs/how-to-guides/app-plugins/implement-rbac-in-app-plugins.md index 62c61cfa70..d5235c1b23 100644 --- a/docusaurus/docs/how-to-guides/app-plugins/implement-rbac-in-app-plugins.md +++ b/docusaurus/docs/how-to-guides/app-plugins/implement-rbac-in-app-plugins.md @@ -147,7 +147,7 @@ Note: Extending anything other than the action sets results in an error. ```bash -logger=plugins.actionsets.registration pluginId=grafana-lokiexplore-app error="[accesscontrol.actionSetInvalid] +logger=plugins.actionsets.registration pluginId=grafana-lokiexplore-app error="[accesscontrol.actionSetInvalid] currently only folder and dashboard action sets are supported, provided action set grafana-lokiexplore-app:view is not a folder or dashboard action set" ``` @@ -156,13 +156,14 @@ currently only folder and dashboard action sets are supported, provided action s Grafana has several action sets for folders that can be extended: **Folder Action Sets:** + - `folders:view` → `["folders:read", "dashboards:read"]` - `folders:edit` → `["folders:read", "folders:write", "dashboards:read", "dashboards:write", "folders:create"]` - `folders:admin` → `["folders:read", "folders:write", "folders:delete", "folders.permissions:read", "folders.permissions:write", ...]` ### Codepaths -[RegistrationsOfActionSets](https://github.com/grafana/grafana/blob/main/pkg/services/accesscontrol/resourcepermissions/service.go#L574 -) + +[RegistrationsOfActionSets](https://github.com/grafana/grafana/blob/main/pkg/services/accesscontrol/resourcepermissions/service.go#L574) ### Plugin Action Sets Definition @@ -171,23 +172,15 @@ Plugins can define action sets in their `plugin.json`: ```json { "id": "my-plugin", - "type": "app", + "type": "app", "actionSets": [ { "action": "folders:edit", - "actions": [ - "my-plugin.documents:create", - "my-plugin.documents:update", - "my-plugin.templates:write" - ] + "actions": ["my-plugin.documents:create", "my-plugin.documents:update", "my-plugin.templates:write"] }, { - "action": "folders:admin", - "actions": [ - "my-plugin.settings:write", - "my-plugin.users:manage", - "my-plugin.permissions:write" - ] + "action": "folders:admin", + "actions": ["my-plugin.settings:write", "my-plugin.users:manage", "my-plugin.permissions:write"] } ] } @@ -196,6 +189,7 @@ Plugins can define action sets in their `plugin.json`: ### Practical Example **With Action Sets (extending existing sets):** + ```json { "actionSets": [ @@ -212,6 +206,7 @@ Plugins can define action sets in their `plugin.json`: ``` **Result:** When a user is granted Edit access to a folder, they get: + - All original `folders:edit` actions (folders:read, folders:write, folders:create) - Plus the plugin's additional actions (my-plugin.docs:create, my-plugin.docs:edit) diff --git a/docusaurus/docs/how-to-guides/app-plugins/use-llms-and-mcp.md b/docusaurus/docs/how-to-guides/app-plugins/use-llms-and-mcp.md index 1b4fdf3f86..74be6e0cfb 100644 --- a/docusaurus/docs/how-to-guides/app-plugins/use-llms-and-mcp.md +++ b/docusaurus/docs/how-to-guides/app-plugins/use-llms-and-mcp.md @@ -73,11 +73,11 @@ async function getLLMResponse(): Promise { const messages: llm.Message[] = [ { role: 'system', - content: 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana.' + content: 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana.', }, { role: 'user', - content: 'What metric should I use to monitor CPU usage of a container?' + content: 'What metric should I use to monitor CPU usage of a container?', }, ]; @@ -119,11 +119,11 @@ async function getStreamingLLMResponse(): Promise> { const messages: llm.Message[] = [ { role: 'system', - content: 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana.' + content: 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana.', }, { role: 'user', - content: 'What metric should I use to monitor CPU usage of a container?' + content: 'What metric should I use to monitor CPU usage of a container?', }, ]; @@ -150,7 +150,7 @@ async function getStreamingLLMResponse(): Promise> { complete: () => { console.log('Stream complete'); // Mark the response as complete in your UI - } + }, }); return accumulatedStream; @@ -191,7 +191,7 @@ async function setupMCPClient(): Promise> { // Use your actual plugin name and version for better debugging const mcpClient = new mcp.Client({ name: 'my-monitoring-plugin', // Replace with your plugin name - version: '1.0.0', // Replace with your plugin version + version: '1.0.0', // Replace with your plugin version }); // Establish HTTP connection to Grafana's MCP server @@ -226,11 +226,12 @@ async function useMCPWithLLM(): Promise { const messages: llm.Message[] = [ { role: 'system', - content: 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana. Use the available tools to gather real-time information about the system before providing recommendations.' + content: + 'You are an experienced, competent SRE with knowledge of PromQL, LogQL and Grafana. Use the available tools to gather real-time information about the system before providing recommendations.', }, { role: 'user', - content: 'What alerts are currently firing in my system?' + content: 'What alerts are currently firing in my system?', }, ]; @@ -238,7 +239,7 @@ async function useMCPWithLLM(): Promise { const toolsResponse = await mcpClient.listTools(); const tools = mcp.convertToolsToOpenAI(toolsResponse.tools); - console.log(`Available tools: ${tools.map(t => t.function.name).join(', ')}`); + console.log(`Available tools: ${tools.map((t) => t.function.name).join(', ')}`); // Send initial request with tools available let response = await llm.chatCompletions({ @@ -321,10 +322,7 @@ function App() { return
Error with MCP: {error.message}
; } return ( - + ); @@ -353,7 +351,11 @@ function MyComponent() { const { client, enabled } = mcp.useMCPClient(); // Fetch available tools asynchronously with proper dependency tracking - const { loading, error, value: toolsResponse } = useAsync(async () => { + const { + loading, + error, + value: toolsResponse, + } = useAsync(async () => { if (!enabled || !client) { return null; } @@ -392,9 +394,7 @@ function MyComponent() { {tools.map((tool, index) => (
  • {tool.name} - {tool.description && ( - <>: {tool.description} - )} + {tool.description && <>: {tool.description}}
  • ))} @@ -416,11 +416,13 @@ The following debugging strategies help you identify and resolve common problems **Problem**: `llm.enabled()` returns `false` or throws an error. **Debug steps**: + 1. **Check plugin installation**: Navigate to **Administration** > **Plugins** and verify the Grafana LLM app is installed and enabled 2. **Verify LLM configuration**: In the LLM app settings, ensure at least one LLM provider is configured with valid credentials 3. **Test the connection**: Use the LLM app's built-in connection test to verify your provider setup **Code debugging**: + ```typescript // Add detailed logging to understand the failure try { @@ -440,6 +442,7 @@ try { ``` **Common solutions**: + - Restart the Grafana server after installing the LLM plugin - Check browser network tab for failed API requests to `/api/plugins/grafana-llm-app/` - Verify your plugin has the necessary capabilities in its `plugin.json` @@ -449,11 +452,13 @@ try { **Problem**: MCP client connection fails or `mcp.enabled()` returns `false`. **Debug steps**: + 1. **Version check**: Ensure you're using Grafana LLM app version 0.22 or later 2. **Network debugging**: Open browser DevTools and check for failed WebSocket or HTTP connections 3. **Service status**: Verify the MCP server is running by checking the LLM app status page **Code debugging**: + ```typescript // Add connection debugging async function debugMCPConnection() { @@ -481,18 +486,18 @@ async function debugMCPConnection() { // Test basic functionality const capabilities = await client.getServerCapabilities(); console.log('Server capabilities:', capabilities); - } catch (error) { console.error('MCP connection failed:', { error: error.message, stack: error.stack, - url: mcp.streamableHTTPUrl() + url: mcp.streamableHTTPUrl(), }); } } ``` **Common solutions**: + - Update the Grafana LLM app to the latest version - Check if proxy or firewall settings block WebSocket connections - Verify the MCP server URL is accessible from your browser @@ -502,47 +507,54 @@ async function debugMCPConnection() { **Problem**: LLM attempts to call tools but the calls fail. **Debug steps**: + 1. **Validate tool availability**: List available tools before making calls 2. **Check argument format**: Ensure tool arguments match the expected schema 3. **Monitor tool execution**: Add logging around tool calls to identify failure points **Code debugging**: + ```typescript // Add comprehensive tool call debugging async function debugToolCalls(mcpClient: mcp.Client) { try { // First, list available tools const toolsResponse = await mcpClient.listTools(); - console.log('Available tools:', toolsResponse.tools.map(t => ({ - name: t.name, - description: t.description, - inputSchema: t.inputSchema - }))); + console.log( + 'Available tools:', + toolsResponse.tools.map((t) => ({ + name: t.name, + description: t.description, + inputSchema: t.inputSchema, + })) + ); // Test a specific tool call const toolName = 'your-tool-name'; - const args = { /* your arguments */ }; + const args = { + /* your arguments */ + }; console.log(`Calling tool: ${toolName}`, args); const result = await mcpClient.callTool({ name: toolName, - arguments: args + arguments: args, }); console.log('Tool call result:', result); - } catch (error) { console.error('Tool call debugging failed:', { error: error.message, toolName: toolName, arguments: args, - stack: error.stack + stack: error.stack, }); } } ``` **Common solutions**: + - Validate tool arguments against the tool's input schema before calling - Handle tool call timeouts with appropriate retry logic - Check Grafana logs for detailed MCP server error messages @@ -552,11 +564,13 @@ async function debugToolCalls(mcpClient: mcp.Client) { **Problem**: React components using MCP hooks throw errors. **Debug steps**: + 1. **Check provider hierarchy**: Ensure `MCPClientProvider` wraps all components using MCP hooks 2. **Verify hook usage**: Confirm you're using hooks inside functional components 3. **Add error boundaries**: Implement proper error handling in your component tree **Code debugging**: + ```typescript // Debug React component issues function DebugMCPComponent() { @@ -617,6 +631,7 @@ class MCPErrorBoundary extends React.Component { ``` **Common solutions**: + - Always wrap MCP components with both `MCPClientProvider` and error boundaries - Use conditional rendering to handle loading and error states - Add proper TypeScript types for better debugging support @@ -625,6 +640,7 @@ class MCPErrorBoundary extends React.Component { ## Next steps After implementing LLM and MCP integration in your plugin, you have built functionality that can: + - Make intelligent recommendations using LLMs - Stream responses for better user experience - Execute real actions through MCP tools diff --git a/docusaurus/docs/set-up/set-up-github.md b/docusaurus/docs/set-up/set-up-github.md index 6b38859bf6..934a7a2a44 100644 --- a/docusaurus/docs/set-up/set-up-github.md +++ b/docusaurus/docs/set-up/set-up-github.md @@ -174,3 +174,44 @@ jobs: #### Main stats artifact could not be found If you see this warning during bundle size workflow runs it means that the workflow failed to find the github artifact that contains the main branch stats file. The file can be generated by either merging a PR to main, pushing a commit to main, or manually running the workflow with workflow_dispatch. + +### Extend the Playwright config + +To extend the Playwright configuration, edit the `playwright.config.ts` file in the project root: + +```ts title="playwright.config.ts" +export default defineConfig(baseConfig, { + projects: [ + ...baseConfig.projects!, + { + // Firefox example + name: 'firefox', + use: { ...devices['Desktop Firefox'], storageState: 'playwright/.auth/admin.json' }, + dependencies: ['auth'], + }, + // Add your custom project here... + ], + // Add your custom config here... +}); +``` + +note that the projects array has to be explicity destructured to merge the `baseConfig` projects (defined in `.config/`) with your custom projects. If you don't do this, the default projects will be replaced by your custom ones. + +Another alternative is to use `webpacka-merge` to merge the base config with your custom config: + +```ts title="playwright.config.ts" +import { merge } from 'webpack-merge'; +import defaultConfig from './.config/playwright.config'; +const config = merge(baseConfig, { + projects: [ + { + name: 'firefox', + use: { ...devices['Desktop Firefox'], storageState: 'playwright/.auth/admin.json' }, + dependencies: ['auth'], + }, + // Add your custom project here... + ], + // Add your custom config here... +}); +export default defineConfig(baseConfig); +``` diff --git a/docusaurus/docs/tutorials/build-a-logs-data-source-plugin.md b/docusaurus/docs/tutorials/build-a-logs-data-source-plugin.md index 05c7986840..a01a0043ab 100644 --- a/docusaurus/docs/tutorials/build-a-logs-data-source-plugin.md +++ b/docusaurus/docs/tutorials/build-a-logs-data-source-plugin.md @@ -554,7 +554,7 @@ export class ExampleDatasource return undefined; } } - + private getLogsSampleDataProvider( request: DataQueryRequest, options?: LogsSampleOptions diff --git a/packages/create-plugin/templates/common/.config/playwright.config.ts b/packages/create-plugin/templates/common/.config/playwright.config.ts new file mode 100644 index 0000000000..390a72f10f --- /dev/null +++ b/packages/create-plugin/templates/common/.config/playwright.config.ts @@ -0,0 +1,54 @@ +/* + * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️ + * + * In order to extend the configuration follow the steps in + * https://grafana.com/developers/plugin-tools/get-started/set-up-development-environment#extend-the-playwright-config + */ + +import type { PluginOptions } from '@grafana/plugin-e2e'; +import { defineConfig, devices } from '@playwright/test'; +import { dirname } from 'node:path'; + +const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.GRAFANA_URL || 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + // 1. Login to Grafana and store the cookie on disk for use in other tests. + { + name: 'auth', + testDir: pluginE2eAuth, + testMatch: [/.*\.js/], + }, + // 2. Run tests in Google Chrome. Every test will start authenticated as admin user. + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/admin.json', + }, + dependencies: ['auth'], + }, + ], +}); diff --git a/packages/create-plugin/templates/common/playwright.config b/packages/create-plugin/templates/common/playwright.config index 9c8c609f60..ebb38c8512 100644 --- a/packages/create-plugin/templates/common/playwright.config +++ b/packages/create-plugin/templates/common/playwright.config @@ -1,8 +1,6 @@ import type { PluginOptions } from '@grafana/plugin-e2e'; -import { defineConfig, devices } from '@playwright/test'; -import { dirname } from 'node:path'; - -const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; +import { defineConfig } from '@playwright/test'; +import baseConfig from './.config/playwright.config'; /** * Read environment variables from file. @@ -13,42 +11,6 @@ const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; /** * See https://playwright.dev/docs/test-configuration. */ -export default defineConfig({ - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.GRAFANA_URL || 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - // 1. Login to Grafana and store the cookie on disk for use in other tests. - { - name: 'auth', - testDir: pluginE2eAuth, - testMatch: [/.*\.js/], - }, - // 2. Run tests in Google Chrome. Every test will start authenticated as admin user. - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['auth'], - }, - ], - +export default defineConfig(baseConfig, { + // Add your own configuration here });