diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 61419166386..896e0b1fa1f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -499,6 +499,7 @@ /e2e-playwright/various-suite/trace-view-scrolling.spec.ts @grafana/observability-traces-and-profiling /e2e-playwright/various-suite/verify-i18n.spec.ts @grafana/grafana-frontend-platform /e2e-playwright/various-suite/visualization-suggestions.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/perf-test.spec.ts @grafana/grafana-frontend-platform # Packages /packages/README.md @grafana/grafana-frontend-platform @@ -1253,6 +1254,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/analytics-events-report.yml @grafana/grafana-frontend-platform /.github/workflows/pr-e2e-tests.yml @grafana/grafana-developer-enablement-squad /.github/workflows/skye-add-to-project.yml @grafana/grafana-frontend-platform +/.github/workflows/frontend-perf-tests.yaml @grafana/grafana-frontend-platform /.github/zizmor.yml @grafana/grafana-developer-enablement-squad /.github/license_finder.yaml @bergquist /.github/actionlint.yaml @grafana/grafana-developer-enablement-squad diff --git a/.github/workflows/frontend-perf-tests.yaml b/.github/workflows/frontend-perf-tests.yaml new file mode 100644 index 00000000000..5b054231ae5 --- /dev/null +++ b/.github/workflows/frontend-perf-tests.yaml @@ -0,0 +1,133 @@ +name: Frontend performance tests + +on: + schedule: + - cron: "0 * * * *" # Run hourly + workflow_dispatch: # Allow manual triggering + +jobs: + performance-tests: + permissions: + contents: read + id-token: write + runs-on: ubuntu-x64-large + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + persist-credentials: false + + - id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@62722333225a1fae03ae27a63d638f9bc2176edb #get-vault-secrets-v1.2.0 + with: + repo_secrets: | + PROMETHEUS_URL=frontend_perf_tests:prometheus_push_url + PROMETHEUS_USER=frontend_perf_tests:prometheus_user + PROMETHEUS_TOKEN=frontend_perf_tests:prometheus_token + FSPERFBASELINE_USERNAME=frontend_perf_tests:fsperfbaseline_username + FSPERFBASELINE_PASSWORD=frontend_perf_tests:fsperfbaseline_password + FSPERF_USERNAME=frontend_perf_tests:fsperf_username + FSPERF_PASSWORD=frontend_perf_tests:fsperf_password + + - name: Authenticate Docker + uses: grafana/shared-workflows/actions/login-to-gar@main + id: login-to-gar + with: + registry: 'us-docker.pkg.dev' + + - name: Setup Node.js + uses: ./.github/actions/setup-node + + - name: Yarn install + run: yarn install --immutable + env: + PUPPETEER_SKIP_DOWNLOAD: true + CYPRESS_INSTALL_BINARY: 0 + + - name: Install Playwright browsers + run: yarn playwright install chromium --with-deps + + - name: Run Playwright tests (fsperfbaseline) + id: pw-fsperfbaseline + continue-on-error: true + env: + PLAYWRIGHT_JSON_OUTPUT_NAME: ./fsperfbaseline-pw-results.json + METRICS_OUTPUT_PATH: ./fsperfbaseline-metrics.txt + GRAFANA_URL: https://fsperfbaseline.grafana-dev.net + GRAFANA_ADMIN_USER: ${{ env.FSPERFBASELINE_USERNAME }} + GRAFANA_ADMIN_PASSWORD: ${{ env.FSPERFBASELINE_PASSWORD }} + run: yarn e2e:playwright --grep @performance --reporter json + + - name: Run Playwright tests (fsperf) + id: pw-fsperf + continue-on-error: true + env: + PLAYWRIGHT_JSON_OUTPUT_NAME: ./fsperf-pw-results.json + METRICS_OUTPUT_PATH: ./fsperf-metrics.txt + GRAFANA_URL: https://fsperf.grafana-dev.net + GRAFANA_ADMIN_USER: ${{ env.FSPERF_USERNAME }} + GRAFANA_ADMIN_PASSWORD: ${{ env.FSPERF_PASSWORD }} + run: yarn e2e:playwright --grep @performance --reporter json + + - name: Bench report (fsperfbaseline) + id: bench-fsperfbaseline + if: always() + continue-on-error: true + run: | + docker run \ + --rm \ + --volume="./fsperfbaseline-pw-results.json:/pw-results.json" \ + --volume="./fsperfbaseline-metrics.txt:/metrics.txt" \ + -e PROMETHEUS_URL="$PROMETHEUS_URL" \ + -e PROMETHEUS_USER="$PROMETHEUS_USER" \ + -e PROMETHEUS_PASSWORD="$PROMETHEUS_TOKEN" \ + us-docker.pkg.dev/grafanalabs-global/docker-grafana-bench-prod/grafana-bench:v0.6.0 report \ + --grafana-url "http://fsperfbaseline.grafana-dev.net" \ + --test-suite-name "FrontendPerfTests" \ + --report-input playwright \ + --report-output log \ + --prometheus-metrics \ + --run-metrics-file "/metrics.txt" \ + --log-level DEBUG \ + /pw-results.json + + - name: Bench report (fsperf) + id: bench-fsperf + if: always() + continue-on-error: true + run: | + docker run \ + --rm \ + --volume="./fsperf-pw-results.json:/pw-results.json" \ + --volume="./fsperf-metrics.txt:/metrics.txt" \ + -e PROMETHEUS_URL="$PROMETHEUS_URL" \ + -e PROMETHEUS_USER="$PROMETHEUS_USER" \ + -e PROMETHEUS_PASSWORD="$PROMETHEUS_TOKEN" \ + us-docker.pkg.dev/grafanalabs-global/docker-grafana-bench-prod/grafana-bench:v0.6.0 report \ + --grafana-url "http://fsperf.grafana-dev.net" \ + --test-suite-name "FrontendPerfTests" \ + --report-input playwright \ + --report-output log \ + --prometheus-metrics \ + --run-metrics-file "/metrics.txt" \ + --log-level DEBUG \ + /pw-results.json + + - name: Check status + env: + FSPERF_PW_OUTCOME: ${{ steps.pw-fsperf.outcome }} + FSPERF_PW_BASELINE_OUTCOME: ${{ steps.pw-fsperfbaseline.outcome }} + FSPERF_BENCH_OUTCOME: ${{ steps.bench-fsperf.outcome }} + FSPERF_BENCH_BASELINE_OUTCOME: ${{ steps.bench-fsperfbaseline.outcome }} + + run: | + echo "FSPERF_PW_OUTCOME: $FSPERF_PW_OUTCOME" + echo "FSPERF_PW_BASELINE_OUTCOME: $FSPERF_PW_BASELINE_OUTCOME" + echo "FSPERF_BENCH_OUTCOME: $FSPERF_BENCH_OUTCOME" + echo "FSPERF_BENCH_BASELINE_OUTCOME: $FSPERF_BENCH_BASELINE_OUTCOME" + + if [[ "$FSPERF_PW_OUTCOME" != "success" || "$FSPERF_PW_BASELINE_OUTCOME" != "success" || "$FSPERF_BENCH_OUTCOME" != "success" || "$FSPERF_BENCH_BASELINE_OUTCOME" != "success" ]]; then + echo "One or more test steps failed." + exit 1 + fi diff --git a/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json b/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json new file mode 100644 index 00000000000..8f6351e47b8 --- /dev/null +++ b/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json @@ -0,0 +1,98 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 10, + "links": [], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "maxDataPoints": 10, + "options": { + "counters": { + "dataChanged": true, + "render": true, + "schemaChanged": true + }, + "mode": "events", + "stateView": "" + }, + "pluginVersion": "12.2.0-16975765900", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "refId": "A" + } + ], + "title": "Panel Title", + "type": "debug" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Mostly blank dashboard", + "uid": "bds35fot3cv7kb", + "version": 3 +} diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index c050297d430..b26e72e049a 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -69,6 +69,7 @@ "loki_fakedata": (import '../dev-dashboards/datasource-loki/loki_fakedata.json'), "loki_query_splitting": (import '../dev-dashboards/datasource-loki/loki_query_splitting.json'), "migrations": (import '../dev-dashboards/migrations/migrations.json'), + "mostly-blank-dashboard": (import '../dev-dashboards/scenarios/mostly-blank-dashboard.json'), "mssql_fakedata": (import '../dev-dashboards/datasource-mssql/mssql_fakedata.json'), "mssql_unittest": (import '../dev-dashboards/datasource-mssql/mssql_unittest.json'), "mysql_fakedata": (import '../dev-dashboards/datasource-mysql/mysql_fakedata.json'), diff --git a/e2e-playwright/utils/RequestsRecorder.ts b/e2e-playwright/utils/RequestsRecorder.ts new file mode 100644 index 00000000000..f2b4f7f13b1 --- /dev/null +++ b/e2e-playwright/utils/RequestsRecorder.ts @@ -0,0 +1,164 @@ +import { Page, Response, Request } from '@playwright/test'; +import * as prom from 'prom-client'; + +/** + * Records and tracks network request body sizes. + * + * You must call listen() before page.goto() for accurate results - this ensures all responses + * for a page are tracked. + */ +export class RequestsRecorder { + #page: Page; + + #documentUrl: string | undefined; + + #requestsInFlight = 0; + + #currentRequests: Set = new Set(); + + #inflatedSizeBytesCounter: prom.Counter<'type' | 'host_type'>; + #transferSizeBytesCounter: prom.Counter<'type' | 'host_type'>; + #requestCountCounter: prom.Counter<'type' | 'host_type'>; + #resolve?: () => void; + + constructor(page: Page) { + this.#page = page; + + this.#inflatedSizeBytesCounter = new prom.Counter({ + name: 'fe_perf_inflated_size_bytes', + help: 'The size of the inflated response body in bytes', + labelNames: ['type', 'host_type'], + registers: [], + }); + + this.#transferSizeBytesCounter = new prom.Counter({ + name: 'fe_perf_transfer_size_bytes', + help: 'The size of the transfered response body in bytes', + labelNames: ['type', 'host_type'], + registers: [], + }); + + this.#requestCountCounter = new prom.Counter({ + name: 'fe_perf_request_count', + help: 'The number of requests made', + labelNames: ['type', 'host_type'], + registers: [], + }); + } + + listen() { + const handler = this.#handleResponse.bind(this); + const reqHandler = this.#handleRequest.bind(this); + this.#page.on('request', reqHandler); + this.#page.on('response', handler); + + return () => { + this.#page.off('response', handler); + this.#page.off('request', reqHandler); + + if (this.#requestsInFlight === 0) { + return Promise.resolve(); + } + + console.log('waiting for', this.#requestsInFlight, 'requests to finish'); + + return new Promise((resolve) => { + this.#resolve = resolve; + }); + }; + } + + async #handleRequest(request: Request) { + const type = request.resourceType(); + + // Once we've recieved a document response, create a list of requests that we'll count the responses of. + // We also want to count the document request itself. + if (this.#documentUrl || type === 'document') { + this.#currentRequests.add(request); + } + } + + /* + * The Playwright page object has have multiple page loads, and responses for one page may come in after the page + * has been navigated away from. Attempting to get the body of these responses will results in an error, but is also + * not what we want to track. + * + * To solve this, we wait for the 'document' response to come in (a new page has loaded) and then keep track of all + * future requests. We only record responses for requests that were made after the document response. + */ + async #handleResponse(response: Response) { + const request = response.request(); + + const url = response.url(); + const type = response.request().resourceType(); + + // Record when a document response comes in so we can keep track of future requests + if (type === 'document') { + if (this.#documentUrl) { + console.warn('recieved additional document response', url); + } + + this.#documentUrl = url; + } + + // Disregard responses that for requests that were initiated before the current page + if (!this.#currentRequests.has(request)) { + return; + } + + this.#requestsInFlight += 1; + const hostType = getHostType(response.url(), this.#documentUrl ?? ''); + + // Attempting to get the body of an empty response results in an error, so guess if the response will be empty or not + const statusCode = response.status(); + const noBodyStatusCode = statusCode <= 199 || statusCode === 204 || statusCode === 205 || statusCode === 304; + + if (!noBodyStatusCode) { + const body = await response.body(); + this.#inflatedSizeBytesCounter.inc({ type, host_type: hostType }, body.length); + } + + const sizes = await response.request().sizes(); + + this.#transferSizeBytesCounter.inc( + { type, host_type: hostType }, + sizes.responseBodySize + sizes.responseHeadersSize + ); + this.#requestCountCounter.inc({ type, host_type: hostType }); + + this.#requestsInFlight -= 1; + + if (this.#requestsInFlight === 0 && this.#resolve) { + this.#resolve(); + } + } + + getMetrics() { + return [this.#inflatedSizeBytesCounter, this.#transferSizeBytesCounter, this.#requestCountCounter]; + } +} + +/** + * Instead of setting the request host as a label, which may have too high cardinality, we categorise + * the host into a limited set of types. + */ +function getHostType(requestUrl: string, documentUrl: string) { + const url = new URL(requestUrl); + const hostname = url.hostname; // FYI `hostname` doesn't include port, `host` does + + const documentHost = new URL(documentUrl).hostname; + + if (hostname.match(/^grafana-assets\.grafana(-\w+)?\.net$/)) { + return 'assets_cdn'; + } + + if (hostname.match(/^plugins-cdn\.grafana(-\w+)?\.net$/)) { + return 'plugin_cdn'; + } + + if (hostname === documentHost) { + return 'self'; + } + + return 'other'; +} diff --git a/e2e-playwright/various-suite/perf-test.spec.ts b/e2e-playwright/various-suite/perf-test.spec.ts new file mode 100644 index 00000000000..57e93d27f10 --- /dev/null +++ b/e2e-playwright/various-suite/perf-test.spec.ts @@ -0,0 +1,58 @@ +import fs from 'fs'; +import * as prom from 'prom-client'; + +import { test, expect } from '@grafana/plugin-e2e'; + +import { RequestsRecorder } from '../utils/RequestsRecorder'; + +const DASH_PATH = '/d/bds35fot3cv7kb/mostly-blank-dashboard'; + +test('payload-size', { tag: '@performance' }, async ({ page }) => { + const promRegistry = new prom.Registry(); + + const testRunTimeGauge = new prom.Gauge({ + name: 'fe_perf_test_run_time_seconds', + help: 'The time it took for the performance test to run', + registers: [promRegistry], + }); + + const usedJSHeapSizeGauge = new prom.Gauge({ + name: 'fe_perf_used_js_heap_size_bytes', + help: 'The amount of memory used by the JavaScript heap', + registers: [promRegistry], + }); + + const recorder = new RequestsRecorder(page); + const stopListening = recorder.listen(); + + let start = performance.now(); + + await page.goto(DASH_PATH); + + let el = page.getByTestId('data-testid header-container'); + await el.waitFor(); + await expect(el).toBeVisible(); + + let end = performance.now(); + + let client = await page.context().newCDPSession(page); + await client.send('HeapProfiler.collectGarbage'); + let usedJSHeapSize = (await client.send('Runtime.getHeapUsage')).usedSize; + + const responseMetrics = recorder.getMetrics(); + for (const metric of responseMetrics) { + promRegistry.registerMetric(metric); + } + + testRunTimeGauge.set(Math.round(end - start) / 1000); + usedJSHeapSizeGauge.set(+usedJSHeapSize.toFixed(1)); + + const instance = new URL(process.env.GRAFANA_URL || 'http://undefined').host; + promRegistry.setDefaultLabels({ instance }); + const metricsText = await promRegistry.metrics(); + console.log(metricsText); + fs.writeFileSync(process.env.METRICS_OUTPUT_PATH || '/tmp/asset-metrics.txt', metricsText); + + await stopListening(); + page.close(); +}); diff --git a/package.json b/package.json index ede30bbfe43..fe416513c6c 100644 --- a/package.json +++ b/package.json @@ -234,6 +234,7 @@ "postcss-reporter": "7.1.0", "postcss-scss": "4.0.9", "prettier": "3.6.2", + "prom-client": "^15.1.3", "publint": "^0.3.12", "react-refresh": "0.14.0", "react-select-event": "5.5.1", diff --git a/playwright.config.ts b/playwright.config.ts index e1a6b654e73..279ceb8a3f4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices, PlaywrightTestConfig, Project } from '@playwright/test'; import path, { dirname } from 'path'; import { PluginOptions } from '@grafana/plugin-e2e'; @@ -7,6 +7,19 @@ const testDirRoot = 'e2e-playwright'; const pluginDirRoot = path.join(testDirRoot, 'plugin-e2e'); const DEFAULT_URL = 'http://localhost:3001'; +function withAuth(project: Project): Project { + project.dependencies ??= []; + project.use ??= {}; + + project.dependencies = project.dependencies.concat('authenticate'); + project.use = { + ...project.use, + storageState: `playwright/.auth/${process.env.GRAFANA_ADMIN_USER || 'admin'}.json`, + }; + + return project; +} + export default defineConfig({ fullyParallel: true, /* Retry on CI only */ @@ -20,6 +33,7 @@ export default defineConfig({ timeout: 10_000, }, use: { + ...devices['Desktop Chrome'], baseURL: process.env.GRAFANA_URL ?? DEFAULT_URL, trace: 'retain-on-failure', httpCredentials: { @@ -59,15 +73,10 @@ export default defineConfig({ }, }, // Run all tests in parallel using user with admin role - { + withAuth({ name: 'admin', testDir: path.join(pluginDirRoot, '/plugin-e2e-api-tests/as-admin-user'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), // Run all tests in parallel using user with viewer role { name: 'viewer', @@ -78,210 +87,97 @@ export default defineConfig({ }, dependencies: ['createUserAndAuthenticate'], }, - { + withAuth({ name: 'elasticsearch', testDir: path.join(pluginDirRoot, '/elasticsearch'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'mysql', testDir: path.join(pluginDirRoot, '/mysql'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'mssql', testDir: path.join(pluginDirRoot, '/mssql'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'extensions-test-app', testDir: path.join(testDirRoot, '/test-plugins/grafana-extensionstest-app'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'grafana-e2etest-datasource', testDir: path.join(testDirRoot, '/test-plugins/grafana-test-datasource'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloudwatch', testDir: path.join(pluginDirRoot, '/cloudwatch'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'azuremonitor', testDir: path.join(pluginDirRoot, '/azuremonitor'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloudmonitoring', testDir: path.join(pluginDirRoot, '/cloudmonitoring'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'graphite', testDir: path.join(pluginDirRoot, '/graphite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'influxdb', testDir: path.join(pluginDirRoot, '/influxdb'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'opentsdb', testDir: path.join(pluginDirRoot, '/opentsdb'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'jaeger', testDir: path.join(pluginDirRoot, '/jaeger'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'grafana-postgresql-datasource', testDir: path.join(pluginDirRoot, '/grafana-postgresql-datasource'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'canvas', testDir: path.join(testDirRoot, '/canvas'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'zipkin', testDir: path.join(pluginDirRoot, '/zipkin'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), { name: 'unauthenticated', testDir: path.join(testDirRoot, '/unauthenticated'), - use: { - ...devices['Desktop Chrome'], - }, }, - { + withAuth({ name: 'various', testDir: path.join(testDirRoot, '/various-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'panels', testDir: path.join(testDirRoot, '/panels-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'smoke', testDir: path.join(testDirRoot, '/smoke-tests-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'dashboards', testDir: path.join(testDirRoot, '/dashboards-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'loki', testDir: path.join(testDirRoot, '/loki'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloud-plugins', testDir: path.join(testDirRoot, '/cloud-plugins-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'dashboard-new-layouts', testDir: path.join(testDirRoot, '/dashboard-new-layouts'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), ], }); diff --git a/scripts/grafana-server/custom.ini b/scripts/grafana-server/custom.ini index d8e92d8782e..b1f396a57d2 100644 --- a/scripts/grafana-server/custom.ini +++ b/scripts/grafana-server/custom.ini @@ -18,6 +18,9 @@ queryService=true [environment] stack_id = 12345 +[panels] +enable_alpha = true + [plugins] allow_loading_unsigned_plugins=grafana-extensionstest-app,grafana-extensionexample1-app,grafana-extensionexample2-app,grafana-extensionexample3-app,grafana-e2etest-datasource diff --git a/yarn.lock b/yarn.lock index f3d0457fc2c..e7ae89eb4b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5851,7 +5851,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.9.0": +"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 @@ -12201,6 +12201,13 @@ __metadata: languageName: node linkType: hard +"bintrees@npm:1.0.2": + version: 1.0.2 + resolution: "bintrees@npm:1.0.2" + checksum: 10/071896cea5ea5413316c8436e95799444c208630d5c539edd8a7089fc272fc5d3634aa4a2e4847b28350dda1796162e14a34a0eda53108cc5b3c2ff6a036c1fa + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -18542,6 +18549,7 @@ __metadata: postcss-scss: "npm:4.0.9" prettier: "npm:3.6.2" prismjs: "npm:1.30.0" + prom-client: "npm:^15.1.3" publint: "npm:^0.3.12" rc-slider: "npm:11.1.8" rc-tree: "npm:5.13.1" @@ -26297,6 +26305,16 @@ __metadata: languageName: node linkType: hard +"prom-client@npm:^15.1.3": + version: 15.1.3 + resolution: "prom-client@npm:15.1.3" + dependencies: + "@opentelemetry/api": "npm:^1.4.0" + tdigest: "npm:^0.1.1" + checksum: 10/eba75e15ab896845d39359e3a4d6f7913ea05339b3122d8dde8c8c374669ad1a1d1ab2694ab2101c420bd98086a564e4f2a18aa29018fc14a4732e57c1c19aec + languageName: node + linkType: hard + "promise-all-reject-late@npm:^1.0.0": version: 1.0.1 resolution: "promise-all-reject-late@npm:1.0.1" @@ -30724,6 +30742,15 @@ __metadata: languageName: node linkType: hard +"tdigest@npm:^0.1.1": + version: 0.1.2 + resolution: "tdigest@npm:0.1.2" + dependencies: + bintrees: "npm:1.0.2" + checksum: 10/45be99fa52dab74b8edafe150e473cdc45aa1352c75ed516a39905f350a08c3175f6555598111042c3677ba042d7e3cae6b5ce4c663fe609bc634f326aabc9d6 + languageName: node + linkType: hard + "teex@npm:^1.0.1": version: 1.0.1 resolution: "teex@npm:1.0.1"