From f7eab21f9f555af835eaa60ed3fd1bbae97cfccf Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:09:45 +0200 Subject: [PATCH] Loki: Decouple data source plugin (#107242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP * Update yarn.lock * Align uuid dependency * Add e2e test and update * Update cue version * Fix lint * Update snapshot test * Fix test that was importing from coupled module * Fix lint * Update public/app/plugins/datasource/loki/package.json Co-authored-by: Zoltán Bedi --------- Co-authored-by: Zoltán Bedi --- .golangci.yml | 2 + e2e/plugin-e2e/loki/loki.spec.ts | 8 +++ .../dataquery/x/LokiDataQuery_types.gen.ts | 2 +- .../api/plugins/data/expectedListResp.json | 68 +++++++++---------- pkg/tsdb/loki/standalone/datasource.go | 40 +++++++++++ pkg/tsdb/loki/standalone/main.go | 23 +++++++ playwright.config.ts | 9 +++ .../correlations/CorrelationsPage.test.tsx | 35 +++++++--- .../__mocks__/useCorrelations.mocks.ts | 16 +++++ .../app/features/plugins/built_in_plugins.ts | 2 - .../app/plugins/datasource/loki/CHANGELOG.md | 1 + .../app/plugins/datasource/loki/package.json | 52 ++++++++++++++ .../app/plugins/datasource/loki/plugin.json | 9 ++- .../app/plugins/datasource/loki/project.json | 9 +++ .../app/plugins/datasource/loki/tsconfig.json | 7 ++ .../plugins/datasource/loki/webpack.config.ts | 4 ++ yarn.lock | 42 ++++++++++++ 17 files changed, 280 insertions(+), 49 deletions(-) create mode 100644 e2e/plugin-e2e/loki/loki.spec.ts create mode 100644 pkg/tsdb/loki/standalone/datasource.go create mode 100644 pkg/tsdb/loki/standalone/main.go create mode 100644 public/app/plugins/datasource/loki/CHANGELOG.md create mode 100644 public/app/plugins/datasource/loki/package.json create mode 100644 public/app/plugins/datasource/loki/project.json create mode 100644 public/app/plugins/datasource/loki/tsconfig.json create mode 100644 public/app/plugins/datasource/loki/webpack.config.ts diff --git a/.golangci.yml b/.golangci.yml index 189670d6172..4c7cbc8f644 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -101,6 +101,8 @@ linters: - '**/pkg/tsdb/tempo/**/*' - '**/pkg/tsdb/cloudwatch/*' - '**/pkg/tsdb/cloudwatch/**/*' + - '**/pkg/tsdb/loki/*' + - '**/pkg/tsdb/loki/**/*' deny: - pkg: github.com/grafana/grafana/pkg/api desc: Core plugins are not allowed to depend on Grafana core packages diff --git a/e2e/plugin-e2e/loki/loki.spec.ts b/e2e/plugin-e2e/loki/loki.spec.ts new file mode 100644 index 00000000000..25c459140cc --- /dev/null +++ b/e2e/plugin-e2e/loki/loki.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +test('Smoke test: decoupled frontend plugin loads', async ({ createDataSourceConfigPage, page }) => { + await createDataSourceConfigPage({ type: 'loki' }); + + await expect(await page.getByText('Type: Loki', { exact: true })).toBeVisible(); + await expect(await page.getByRole('heading', { name: 'Connection', exact: true })).toBeVisible(); +}); diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index 59e8f4a13f7..f8f056a3c8d 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "%VERSION%"; export enum QueryEditorMode { Builder = 'builder', diff --git a/pkg/tests/api/plugins/data/expectedListResp.json b/pkg/tests/api/plugins/data/expectedListResp.json index 9e83f151410..45b82107af4 100644 --- a/pkg/tests/api/plugins/data/expectedListResp.json +++ b/pkg/tests/api/plugins/data/expectedListResp.json @@ -209,7 +209,7 @@ "path": "public/app/plugins/datasource/azuremonitor/img/azure_monitor_cpu.png" } ], - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": [ "azure", @@ -880,7 +880,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -934,7 +934,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": [ "grafana", @@ -1190,34 +1190,34 @@ }, "description": "Open source, end-to-end distributed tracing", "links": [ - { - "name": "Learn more", - "url": "https://www.jaegertracing.io" - }, - { - "name": "Jaeger GitHub Project", - "url": "https://github.com/jaegertracing/jaeger" - }, - { - "name": "Repository", - "url": "https://github.com/grafana/grafana" - }, - { - "name": "Raise issue", - "url": "https://github.com/grafana/grafana/issues/new" - }, - { - "name": "Documentation", - "url": "https://grafana.com/docs/grafana/latest/datasources/jaeger/" - } - ], + { + "name": "Learn more", + "url": "https://www.jaegertracing.io" + }, + { + "name": "Jaeger GitHub Project", + "url": "https://github.com/jaegertracing/jaeger" + }, + { + "name": "Repository", + "url": "https://github.com/grafana/grafana" + }, + { + "name": "Raise issue", + "url": "https://github.com/grafana/grafana/issues/new" + }, + { + "name": "Documentation", + "url": "https://grafana.com/docs/grafana/latest/datasources/jaeger/" + } + ], "logos": { "small": "public/app/plugins/datasource/jaeger/img/jaeger_logo.svg", "large": "public/app/plugins/datasource/jaeger/img/jaeger_logo.svg" }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -1325,12 +1325,12 @@ }, "build": {}, "screenshots": null, - "version": "", + "version": "12.1.0-pre", "updated": "", "keywords": null }, "dependencies": { - "grafanaDependency": "", + "grafanaDependency": "\u003e=10.4.0", "grafanaVersion": "*", "plugins": [], "extensions": { @@ -1375,7 +1375,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -1425,7 +1425,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -1629,7 +1629,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": [ "grafana", @@ -1734,7 +1734,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -2042,7 +2042,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -2092,7 +2092,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, @@ -2445,7 +2445,7 @@ }, "build": {}, "screenshots": null, - "version": "11.6.0-pre", + "version": "12.1.0-pre", "updated": "", "keywords": null }, diff --git a/pkg/tsdb/loki/standalone/datasource.go b/pkg/tsdb/loki/standalone/datasource.go new file mode 100644 index 00000000000..3ead071765f --- /dev/null +++ b/pkg/tsdb/loki/standalone/datasource.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" + "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" + + loki "github.com/grafana/grafana/pkg/tsdb/loki" +) + +var ( + _ backend.QueryDataHandler = (*Datasource)(nil) + _ backend.CheckHealthHandler = (*Datasource)(nil) + _ backend.CallResourceHandler = (*Datasource)(nil) +) + +func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { + return &Datasource{ + Service: loki.ProvideService(httpclient.NewProvider(), tracing.DefaultTracer()), + }, nil +} + +type Datasource struct { + Service *loki.Service +} + +func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + return d.Service.QueryData(ctx, req) +} + +func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + return d.Service.CallResource(ctx, req, sender) +} + +func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + return d.Service.CheckHealth(ctx, req) +} diff --git a/pkg/tsdb/loki/standalone/main.go b/pkg/tsdb/loki/standalone/main.go new file mode 100644 index 00000000000..ba934bc6af5 --- /dev/null +++ b/pkg/tsdb/loki/standalone/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + + "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +func main() { + // Start listening to requests sent from Grafana. This call is blocking so + // it won't finish until Grafana shuts down the process or the plugin choose + // to exit by itself using os.Exit. Manage automatically manages life cycle + // of datasource instances. It accepts datasource instance factory as first + // argument. This factory will be automatically called on incoming request + // from Grafana to create different instances of SampleDatasource (per datasource + // ID). When datasource configuration changed Dispose method will be called and + // new datasource instance created using NewSampleDatasource factory. + if err := datasource.Manage("loki", NewDatasource, datasource.ManageOpts{}); err != nil { + log.DefaultLogger.Error(err.Error()) + os.Exit(1) + } +} diff --git a/playwright.config.ts b/playwright.config.ts index ff4e8ae7d10..39ac6aebd5f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -187,5 +187,14 @@ export default defineConfig({ }, dependencies: ['authenticate'], }, + { + name: 'loki', + testDir: path.join(testDirRoot, '/loki'), + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/admin.json', + }, + dependencies: ['authenticate'], + }, ], }); diff --git a/public/app/features/correlations/CorrelationsPage.test.tsx b/public/app/features/correlations/CorrelationsPage.test.tsx index 7299667a025..3e88718ecbf 100644 --- a/public/app/features/correlations/CorrelationsPage.test.tsx +++ b/public/app/features/correlations/CorrelationsPage.test.tsx @@ -4,14 +4,21 @@ import { merge, uniqueId } from 'lodash'; import { openMenu } from 'react-select-event'; import { Observable } from 'rxjs'; import { TestProvider } from 'test/helpers/TestProvider'; +import { MockDataSourceApi } from 'test/mocks/datasource_srv'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; -import { DataSourceInstanceSettings, SupportedTransformationType } from '@grafana/data'; +import { SupportedTransformationType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { BackendSrv, BackendSrvRequest, reportInteraction, setBackendSrv, setAppEvents } from '@grafana/runtime'; +import { + BackendSrv, + BackendSrvRequest, + DataSourceSrv, + reportInteraction, + setAppEvents, + setDataSourceSrv, +} from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { contextSrv } from 'app/core/services/context_srv'; -import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; import { configureStore } from 'app/store/configureStore'; import { mockDataSource } from '../alerting/unified/mocks'; @@ -23,6 +30,7 @@ import { createFetchCorrelationsResponse, createRemoveCorrelationResponse, createUpdateCorrelationResponse, + MockDataSourceSrv, } from './__mocks__/useCorrelations.mocks'; import { Correlation, CreateCorrelationParams, OmitUnion } from './types'; @@ -30,7 +38,7 @@ import { Correlation, CreateCorrelationParams, OmitUnion } from './types'; setAppEvents(appEvents); const renderWithContext = async ( - datasources: Record, + datasources: ConstructorParameters[0] = {}, correlations: Correlation[] = [] ) => { const backend = { @@ -90,8 +98,17 @@ const renderWithContext = async ( }, } as unknown as BackendSrv; const grafanaContext = getGrafanaContextMock({ backend }); - setBackendSrv(backend); - setupDataSources(...Object.values(datasources)); + const dsServer = new MockDataSourceSrv(datasources) as unknown as DataSourceSrv; + dsServer.get = (name: string) => { + const dsApi = new MockDataSourceApi(name); + // Mock the QueryEditor component + dsApi.components = { + QueryEditor: () => <>{name} query editor, + }; + return Promise.resolve(dsApi); + }; + + setDataSourceSrv(dsServer); const renderResult = render( @@ -207,7 +224,7 @@ describe('CorrelationsPage', () => { jsonData: {}, type: 'datasource', }, - { logs: true, module: 'core:plugin/loki' } + { logs: true } ), prometheus: mockDataSource( { @@ -316,7 +333,6 @@ describe('CorrelationsPage', () => { }, { logs: true, - module: 'core:plugin/loki', } ), prometheus: mockDataSource( @@ -582,7 +598,6 @@ describe('CorrelationsPage', () => { }, { logs: true, - module: 'core:plugin/loki', } ), }, @@ -678,7 +693,7 @@ describe('CorrelationsPage', () => { access: 'direct', type: 'datasource', }, - { logs: true, module: 'core:plugin/loki' } + { logs: true } ), }, correlations diff --git a/public/app/features/correlations/__mocks__/useCorrelations.mocks.ts b/public/app/features/correlations/__mocks__/useCorrelations.mocks.ts index 45c49eaed21..b3968cdead4 100644 --- a/public/app/features/correlations/__mocks__/useCorrelations.mocks.ts +++ b/public/app/features/correlations/__mocks__/useCorrelations.mocks.ts @@ -1,6 +1,8 @@ import { merge } from 'lodash'; import { DeepPartial } from 'react-hook-form'; +import { DatasourceSrvMock } from 'test/mocks/datasource_srv'; +import { DataSourceApi, DataSourceInstanceSettings } from '@grafana/data'; import { FetchError, FetchResponse } from '@grafana/runtime'; import { Correlation, CreateCorrelationResponse, RemoveCorrelationResponse, UpdateCorrelationResponse } from '../types'; @@ -56,3 +58,17 @@ export function createRemoveCorrelationResponse(): RemoveCorrelationResponse { message: 'Correlation removed', }; } + +export class MockDataSourceSrv extends DatasourceSrvMock { + private ds: DataSourceInstanceSettings[]; + constructor(datasources: Record) { + super({} as DataSourceApi, {}); + this.ds = Object.values(datasources); + } + getList(): DataSourceInstanceSettings[] { + return this.ds; + } + getInstanceSettings(name?: string): DataSourceInstanceSettings | undefined { + return name ? this.ds.find((ds) => ds.name === name) : undefined; + } +} diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index d093498cbcb..fbf9c432609 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/public/app/features/plugins/built_in_plugins.ts @@ -14,7 +14,6 @@ const grafanaPlugin = async () => await import(/* webpackChunkName: "grafanaPlugin" */ 'app/plugins/datasource/grafana/module'); const influxdbPlugin = async () => await import(/* webpackChunkName: "influxdbPlugin" */ 'app/plugins/datasource/influxdb/module'); -const lokiPlugin = async () => await import(/* webpackChunkName: "lokiPlugin" */ 'app/plugins/datasource/loki/module'); const mixedPlugin = async () => await import(/* webpackChunkName: "mixedPlugin" */ 'app/plugins/datasource/mixed/module'); const prometheusPlugin = async () => @@ -90,7 +89,6 @@ const builtInPlugins: Record Promise=10.4.0", + "plugins": [] } } diff --git a/public/app/plugins/datasource/loki/project.json b/public/app/plugins/datasource/loki/project.json new file mode 100644 index 00000000000..4247352791d --- /dev/null +++ b/public/app/plugins/datasource/loki/project.json @@ -0,0 +1,9 @@ +{ + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "tags": ["scope:plugin", "type:datasource"], + "targets": { + "build": {}, + "dev": {} + } +} diff --git a/public/app/plugins/datasource/loki/tsconfig.json b/public/app/plugins/datasource/loki/tsconfig.json new file mode 100644 index 00000000000..baaaea04185 --- /dev/null +++ b/public/app/plugins/datasource/loki/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "jsx": "react-jsx" + }, + "extends": "@grafana/plugin-configs/tsconfig.json", + "include": ["."] +} diff --git a/public/app/plugins/datasource/loki/webpack.config.ts b/public/app/plugins/datasource/loki/webpack.config.ts new file mode 100644 index 00000000000..55a880fef5c --- /dev/null +++ b/public/app/plugins/datasource/loki/webpack.config.ts @@ -0,0 +1,4 @@ +import config from '@grafana/plugin-configs/webpack.config'; + +// eslint-disable-next-line no-barrel-files/no-barrel-files +export default config; diff --git a/yarn.lock b/yarn.lock index d5c1ff25506..2556424e144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2713,6 +2713,48 @@ __metadata: languageName: unknown linkType: soft +"@grafana-plugins/loki@workspace:public/app/plugins/datasource/loki": + version: 0.0.0-use.local + resolution: "@grafana-plugins/loki@workspace:public/app/plugins/datasource/loki" + dependencies: + "@emotion/css": "npm:11.13.5" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/lezer-logql": "npm:0.2.7" + "@grafana/llm": "npm:0.22.1" + "@grafana/monaco-logql": "npm:^0.0.8" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" + "@testing-library/dom": "npm:10.4.0" + "@testing-library/react": "npm:16.2.0" + "@testing-library/user-event": "npm:14.6.1" + "@types/d3-random": "npm:^3.0.2" + "@types/jest": "npm:29.5.14" + "@types/lodash": "npm:4.17.15" + "@types/node": "npm:22.15.0" + "@types/react": "npm:18.3.18" + "@types/react-dom": "npm:18.3.5" + "@types/uuid": "npm:10.0.0" + d3-random: "npm:^3.0.1" + lodash: "npm:4.17.21" + micro-memoize: "npm:^4.1.2" + react: "npm:18.3.1" + react-dom: "npm:18.3.1" + react-select: "npm:5.10.1" + react-use: "npm:17.6.0" + rxjs: "npm:7.8.2" + ts-node: "npm:10.9.2" + tslib: "npm:2.8.1" + typescript: "npm:5.7.3" + uuid: "npm:11.1.0" + webpack: "npm:5.97.1" + peerDependencies: + "@grafana/runtime": "*" + languageName: unknown + linkType: soft + "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql": version: 0.0.0-use.local resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql"