E2E: migrate to playwright (#107241)
* separate playwright into its own folder * better separation * add login scenario, add tags * remove ui option * update CODEOWNERS * add a panels suite test * migrate queries test to playwright * rename + add dashlist test * add panelEdit_base * add geomap map controls test * add geomap-layer-types * add geomap-spatial-operations tests * add frontend-sandbox-panel tests * add smoke-tests-suite * add comment about adding datasource * add dashboard-browse-nested * add dashboard-browse * add dashboard-export-json * add dashboard-keybindings test * remove @wip tag * turn on screenshots and add comment for why this test fails * add dashboard-links-without-slug test * try adding permissions in the test as well * add dashboard-live-streaming * context in the test doesn't work - sad * create dashboard-public-templating * add dashboard-public-create and make live streaming more resilient * add share externally test * add dashboard-share-internally * add share-snapshot-create test * add dashboard-templating * add timepicker tests * add embedded-dashboard test * add general_dashboards test * add import-dashboard test * add load-options-from-url test * add new-constant-variable test * add custom-variable test * add new-datasource-variable test * add new-interval-variable test * add text-box-variable test * add new-query-variable test * add horizontal repeat test * add panel-vertical-repeat test * add empty-row-repeat test * add set-options-from-ui test * add snapshot-create test * add templating test * add textbox-variables test * add cloud-plugins-suite * add storybook verification tests * add playwright storybook verification workflow * add playwright browsers * update CODEOWNERS * test change to trigger storybook verification workflows * try container instead * get the version right... * go back to installing - less chance of forgetting to update * Basic Github Actions Squashed commit of the following: commitf84c650a71Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 13:23:46 2025 +0100 add arg for sharding, but not using it yet commit7bcf0512c6Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 12:30:30 2025 +0100 less newline commitb643911882Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 12:24:31 2025 +0100 less logs commit38f871e9c2Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 10:00:26 2025 +0100 fix yaml commitdb9a773136Author: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 09:57:47 2025 +0100 clean up files commitc0525f41faAuthor: joshhunt <josh.hunt@grafana.com> Date: Tue Jul 1 09:44:56 2025 +0100 gha workflow commit895bea7c52Author: joshhunt <josh.hunt@grafana.com> Date: Mon Jun 30 19:33:08 2025 +0100 working dagger commitcea1f84437Author: joshhunt <josh.hunt@grafana.com> Date: Mon Jun 30 16:17:46 2025 +0100 wip * shard gha * some tidy up * add flags for exporting results, and a gha step to merge runs * fix shard gha * add dashboard-duplicate-panel test * add dashboard-outline test * add dashboards-add-panel * remove some commented out code * add dashboards-title-description test * add dashboards-remove-panel * don't install cypress * gha: check playwright results * add dashboards-edit-adhoc-variables test * fix check-jobs * add dagger cloud token * add dagger cloud token * add edit-datasource-variable test * update CODEOWNERS * add dashboards-edit-group-by-variables (skipped for now) * add dashboards-edit-panel-title-description test * add dashboards-edit-transparent-bg test * add dashboards-edit-query-variables test * run with 8 shards * add dashboards-edit-variables * tidy up gha * add dashboard-group-panels * fix action * try to cache the grafana build * fix missing action becuase no checkout, use builtin continue-on-error instead * fix missing id * cat out.txt * debug build cache * fix debug build cache * add dashboards-panel-layouts test * tidy up * no more debug * fix grafana dir * add dashboards-move-panel test * skip some failing tests * mark up plugins tests with @plugins tag, only run @plugins tests in drone * Hackathon/Playwright Conversion - Various Suite (#107516) * Playwright Migration: Various Suite tests * skipping bad tests * fix some tests that can fail * fix uid * separate user for the verify-i18n test * build test plugins for grafana server * properly blur input fields * login manually * get dashboardPage from goto * ignore a couple of type assertions * remove a couple of timeouts * remove timeouts on dashboard-share-internally * use toBeHidden * make dashboard-share-internally more stable * remove TEMP_DAGGER_TOKEN * clean up visaulization-suggestions * unskip gauge test * unskip trace-view-scrolling * attempt to make create variable utils stable * unskip loki tests * make go linter happy * unskip edit-group-by-variables test * unskip move panel tests * isolate dashboard-timepicker tests with separate user * create data source as part of smoke test * make sure we're awaiting in dashboard-edit-adhoc-variables * make dashboards-edit-variables test more robust * Hackathon Playwright: Dashboards Search (#107580) * Hackathon Playwright: Dashboards Search * Feedback changes * make trace-view-scrolling more stable * add json report and bench step * fix bench version * move fail step to after the playwright report so we can report test failures * fix output file name * fix typo * try wrap in expect.poll * stability * bit more tidy up * fix dashboard-new-layouts tests * move test-plugins to e2e-playwright * fix go code for drone e2e run * move loki plugin-e2e test * make v2 dashboards work again --------- Co-authored-by: joshhunt <josh.hunt@grafana.com> Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com> Co-authored-by: Collin Fingar <collin.fingar@grafana.com> Co-authored-by: Jeff Levin <jeff@levinology.com>
This commit is contained in:
co-authored by
joshhunt
Josh Hunt
Collin Fingar
Jeff Levin
parent
ea0ddb3fc9
commit
b6580ccb10
@@ -0,0 +1 @@
|
||||
# Changelog
|
||||
@@ -0,0 +1,96 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { Checkbox, InlineField, InlineSwitch, Input, SecretInput, Select } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps, SelectableValue, toOption } from '@grafana/data';
|
||||
import { MyDataSourceOptions, MySecureJsonData } from '../types';
|
||||
|
||||
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
|
||||
|
||||
export function ConfigEditor(props: Props) {
|
||||
const { onOptionsChange, options } = props;
|
||||
const { jsonData, secureJsonFields, secureJsonData } = options;
|
||||
|
||||
const onJsonDataChange = (key: string, value: string | number | boolean) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
jsonData: {
|
||||
...jsonData,
|
||||
[key]: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Secure field (only sent to the backend)
|
||||
const onSecureJsonDataChange = (key: string, value: string | number) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
secureJsonData: {
|
||||
[key]: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onResetAPIKey = () => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
secureJsonFields: {
|
||||
...options.secureJsonFields,
|
||||
apiKey: false,
|
||||
},
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
apiKey: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineField label="Path" labelWidth={14} interactive tooltip={'Json field returned to frontend'}>
|
||||
<Input
|
||||
id="config-editor-path"
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => onJsonDataChange('path', e.target.value)}
|
||||
value={jsonData.path}
|
||||
placeholder="Enter the path, e.g. /api/v1"
|
||||
width={40}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="API Key" labelWidth={14} interactive tooltip={'Secure json field (backend only)'}>
|
||||
<SecretInput
|
||||
required
|
||||
id="config-editor-api-key"
|
||||
isConfigured={secureJsonFields.apiKey}
|
||||
value={secureJsonData?.apiKey}
|
||||
placeholder="Enter your API key"
|
||||
width={40}
|
||||
onReset={onResetAPIKey}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => onSecureJsonDataChange('path', e.target.value)}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Switch Enabled">
|
||||
<InlineSwitch
|
||||
width={40}
|
||||
label="Switch Enabled"
|
||||
value={jsonData.switchEnabled ?? false}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => onJsonDataChange('switchEnabled', e.target.checked)}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Checkbox Enabled">
|
||||
<Checkbox
|
||||
width={40}
|
||||
id="config-checkbox-enabled"
|
||||
value={jsonData.checkboxEnabled}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => onJsonDataChange('checkboxEnabled', e.target.checked)}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Auth type">
|
||||
<Select
|
||||
width={40}
|
||||
inputId="config-auth-type"
|
||||
value={jsonData.authType ?? 'keys'}
|
||||
options={['keys', 'credentials'].map(toOption)}
|
||||
onChange={(e: SelectableValue<string>) => onJsonDataChange('authType', e.value!)}
|
||||
/>
|
||||
</InlineField>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { InlineField, Input, Stack } from '@grafana/ui';
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
import { DataSource } from '../datasource';
|
||||
import { MyDataSourceOptions, MyQuery } from '../types';
|
||||
|
||||
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
|
||||
|
||||
export function QueryEditor({ query, onChange, onRunQuery }: Props) {
|
||||
const onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...query, queryText: event.target.value });
|
||||
};
|
||||
|
||||
const onConstantChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...query, constant: parseFloat(event.target.value) });
|
||||
// executes the query
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const { queryText, constant } = query;
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<InlineField label="Constant">
|
||||
<Input
|
||||
id="query-editor-constant"
|
||||
onChange={onConstantChange}
|
||||
value={constant}
|
||||
width={8}
|
||||
type="number"
|
||||
step="0.1"
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Query Text" labelWidth={16} tooltip="Not used yet">
|
||||
<Input
|
||||
id="query-editor-query-text"
|
||||
onChange={onQueryTextChange}
|
||||
value={queryText || ''}
|
||||
required
|
||||
placeholder="Enter a query"
|
||||
/>
|
||||
</InlineField>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import {
|
||||
CoreApp,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
createDataFrame,
|
||||
FieldType,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { MyQuery, MyDataSourceOptions, DEFAULT_QUERY, DataSourceResponse } from './types';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { VariableSupport } from './variables';
|
||||
|
||||
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
|
||||
baseUrl: string;
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
|
||||
super(instanceSettings);
|
||||
this.baseUrl = instanceSettings.url!;
|
||||
this.variables = new VariableSupport();
|
||||
}
|
||||
|
||||
getDefaultQuery(_: CoreApp): Partial<MyQuery> {
|
||||
return DEFAULT_QUERY;
|
||||
}
|
||||
|
||||
filterQuery(query: MyQuery): boolean {
|
||||
// if no query has been provided, prevent the query from being executed
|
||||
return !!query.queryText;
|
||||
}
|
||||
|
||||
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
|
||||
const { range } = options;
|
||||
const from = range!.from.valueOf();
|
||||
const to = range!.to.valueOf();
|
||||
|
||||
return {
|
||||
data: [
|
||||
createDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'Time', values: [from, to], type: FieldType.time },
|
||||
{ name: 'Value', values: ['A', 'B'], type: FieldType.string },
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async request(url: string, params?: string) {
|
||||
const response = getBackendSrv().fetch<DataSourceResponse>({
|
||||
url: `${this.baseUrl}${url}${params?.length ? `?${params}` : ''}`,
|
||||
});
|
||||
return lastValueFrom(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we can connect to the API.
|
||||
*/
|
||||
async testDatasource() {
|
||||
const defaultErrorMessage = 'Cannot connect to API';
|
||||
|
||||
try {
|
||||
const response = await this.request('/health');
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
status: 'success',
|
||||
message: 'Success',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 'error',
|
||||
message: response.statusText ? response.statusText : defaultErrorMessage,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
let message = '';
|
||||
if (typeof err === 'string') {
|
||||
message = err;
|
||||
} else if (isFetchError(err)) {
|
||||
message = 'Fetch error: ' + (err.statusText ? err.statusText : defaultErrorMessage);
|
||||
if (err.data && err.data.error && err.data.error.code) {
|
||||
message += ': ' + err.data.error.code + '. ' + err.data.error.message;
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 'error',
|
||||
message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81.9 71.52"><defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:#3865ab;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="42.95" y1="16.88" x2="81.9" y2="16.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M55.46,62.43A2,2,0,0,1,54.07,59l4.72-4.54a2,2,0,0,1,2.2-.39l3.65,1.63,3.68-3.64a2,2,0,1,1,2.81,2.84l-4.64,4.6a2,2,0,0,1-2.22.41L60.6,58.26l-3.76,3.61A2,2,0,0,1,55.46,62.43Z"/><path class="cls-2" d="M37,0H2A2,2,0,0,0,0,2V31.76a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V2A2,2,0,0,0,37,0ZM4,29.76V8.84H35V29.76Z"/><path class="cls-3" d="M79.9,0H45a2,2,0,0,0-2,2V31.76a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V2A2,2,0,0,0,79.9,0ZM47,29.76V8.84h31V29.76Z"/><path class="cls-2" d="M37,37.76H2a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V39.76A2,2,0,0,0,37,37.76ZM4,67.52V46.6H35V67.52Z"/><path class="cls-2" d="M79.9,37.76H45a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V39.76A2,2,0,0,0,79.9,37.76ZM47,67.52V46.6h31V67.52Z"/><rect class="cls-1" x="10.48" y="56.95" width="4" height="5.79"/><rect class="cls-1" x="17.43" y="53.95" width="4" height="8.79"/><rect class="cls-1" x="24.47" y="50.95" width="4" height="11.79"/><path class="cls-1" d="M19.47,25.8a6.93,6.93,0,1,1,6.93-6.92A6.93,6.93,0,0,1,19.47,25.8Zm0-9.85a2.93,2.93,0,1,0,2.93,2.93A2.93,2.93,0,0,0,19.47,16Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,9 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import { DataSource } from './datasource';
|
||||
import { ConfigEditor } from './components/ConfigEditor';
|
||||
import { QueryEditor } from './components/QueryEditor';
|
||||
import { MyQuery, MyDataSourceOptions } from './types';
|
||||
|
||||
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setQueryEditor(QueryEditor);
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@test-plugins/grafana-e2etest-datasource",
|
||||
"version": "12.1.0-pre",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
|
||||
"dev": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -w -c ./webpack.config.ts --env development",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx ."
|
||||
},
|
||||
"author": "Grafana",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@grafana/plugin-configs": "workspace:*",
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "22.15.0",
|
||||
"@types/prismjs": "1.26.4",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/uuid": "9.0.8",
|
||||
"glob": "10.4.1",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.5.4",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.11.2",
|
||||
"@grafana/data": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grafana/runtime": "*"
|
||||
},
|
||||
"packageManager": "yarn@4.4.0"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
|
||||
"type": "datasource",
|
||||
"name": "Test",
|
||||
"id": "grafana-e2etest-datasource",
|
||||
"metrics": true,
|
||||
"info": {
|
||||
"description": "",
|
||||
"author": {
|
||||
"name": "Grafana"
|
||||
},
|
||||
"keywords": ["datasource"],
|
||||
"logos": {
|
||||
"small": "img/logo.svg",
|
||||
"large": "img/logo.svg"
|
||||
},
|
||||
"links": [],
|
||||
"screenshots": [],
|
||||
"version": "%VERSION%",
|
||||
"updated": "%TODAY%"
|
||||
},
|
||||
"dependencies": {
|
||||
"grafanaDependency": ">=10.4.0",
|
||||
"plugins": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { test, expect, DataSourceConfigPage } from '@grafana/plugin-e2e';
|
||||
|
||||
// The following tests verify that label and input field association is working correctly.
|
||||
// If these tests break, e2e tests in external plugins will break too.
|
||||
|
||||
test.describe(
|
||||
'grafana-test-datasource',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test.describe('config editor ', () => {
|
||||
let configPage: DataSourceConfigPage;
|
||||
test.beforeEach(async ({ createDataSourceConfigPage }) => {
|
||||
configPage = await createDataSourceConfigPage({ type: 'grafana-e2etest-datasource' });
|
||||
});
|
||||
|
||||
test('text input field', async ({ page }) => {
|
||||
const field = page.getByRole('textbox', { name: 'API key' });
|
||||
await expect(field).toBeEmpty();
|
||||
await field.fill('test text');
|
||||
await expect(field).toHaveValue('test text');
|
||||
});
|
||||
|
||||
test('switch field', async ({ page }) => {
|
||||
const field = page.getByLabel('Switch Enabled');
|
||||
await expect(field).not.toBeChecked();
|
||||
await field.check();
|
||||
await expect(field).toBeChecked();
|
||||
});
|
||||
|
||||
test('checkbox field', async ({ page }) => {
|
||||
const field = page.getByRole('checkbox', { name: 'Checkbox Enabled' });
|
||||
await expect(field).not.toBeChecked();
|
||||
await field.check({ force: true });
|
||||
await expect(field).toBeChecked();
|
||||
});
|
||||
|
||||
test('select field', async ({ page, selectors }) => {
|
||||
const field = page.getByRole('combobox', { name: 'Auth type' });
|
||||
await field.click();
|
||||
const option = selectors.components.Select.option;
|
||||
await expect(configPage.getByGrafanaSelector(option)).toHaveText(['keys', 'credentials']);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
test.describe(
|
||||
'grafana-test-datasource',
|
||||
{
|
||||
tag: ['@plugins'],
|
||||
},
|
||||
() => {
|
||||
test('should render variable editor', async ({ variableEditPage, page }) => {
|
||||
await variableEditPage.datasource.set('gdev-e2etestdatasource');
|
||||
await expect(page.getByRole('textbox', { name: 'Query Text' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('create new, successful variable query', async ({ variableEditPage, page }) => {
|
||||
await variableEditPage.datasource.set('gdev-e2etestdatasource');
|
||||
await page.getByRole('textbox', { name: 'Query Text' }).fill('variableQuery');
|
||||
await variableEditPage.runQuery();
|
||||
await expect(variableEditPage).toDisplayPreviews(['A', 'B']);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"]
|
||||
},
|
||||
"extends": "@grafana/plugin-configs/tsconfig.json",
|
||||
"include": ["."]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { DataSourceJsonData } from '@grafana/data';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
|
||||
export interface MyQuery extends DataQuery {
|
||||
queryText?: string;
|
||||
constant: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_QUERY: Partial<MyQuery> = {
|
||||
constant: 6.5,
|
||||
};
|
||||
|
||||
export interface DataPoint {
|
||||
Time: number;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
export interface DataSourceResponse {
|
||||
datapoints: DataPoint[];
|
||||
}
|
||||
|
||||
/**
|
||||
* These are options configured for each DataSource instance
|
||||
*/
|
||||
export interface MyDataSourceOptions extends DataSourceJsonData {
|
||||
switchEnabled: boolean;
|
||||
checkboxEnabled: boolean;
|
||||
authType: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value that is used in the backend, but never sent over HTTP to the frontend
|
||||
*/
|
||||
export interface MySecureJsonData {
|
||||
apiKey?: string;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { VariableSupportBase, VariableSupportType } from '@grafana/data';
|
||||
import { DEFAULT_QUERY, MyQuery } from './types';
|
||||
import { DataSource } from './datasource';
|
||||
|
||||
export class VariableSupport extends VariableSupportBase<DataSource> {
|
||||
getType(): VariableSupportType {
|
||||
return VariableSupportType.Datasource;
|
||||
}
|
||||
|
||||
getDefaultQuery(): Partial<MyQuery> {
|
||||
return DEFAULT_QUERY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import grafanaConfig, { type Env } from '@grafana/plugin-configs/webpack.config.ts';
|
||||
import { mergeWithCustomize, unique } from 'webpack-merge';
|
||||
import { type Configuration } from 'webpack';
|
||||
|
||||
function skipFiles(f: string): boolean {
|
||||
if (f.includes('/dist/')) {
|
||||
// avoid copying files already in dist
|
||||
return false;
|
||||
}
|
||||
if (f.includes('/node_modules/')) {
|
||||
// avoid copying tsconfig.json
|
||||
return false;
|
||||
}
|
||||
if (f.includes('/package.json')) {
|
||||
// avoid copying package.json
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const config = async (env: Env): Promise<Configuration> => {
|
||||
const baseConfig = await grafanaConfig(env);
|
||||
const customConfig = {
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
// To `compiler.options.output`
|
||||
{ from: 'README.md', to: '.', force: true },
|
||||
{ from: 'plugin.json', to: '.' },
|
||||
{ from: 'CHANGELOG.md', to: '.', force: true },
|
||||
{ from: '**/*.json', to: '.', filter: skipFiles },
|
||||
{ from: '**/*.svg', to: '.', noErrorOnMissing: true, filter: skipFiles }, // Optional
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
return mergeWithCustomize({
|
||||
customizeArray: unique('plugins', ['CopyPlugin'], (plugin) => plugin.constructor && plugin.constructor.name),
|
||||
})(baseConfig, customConfig);
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user