Migrate from grafana-toolkit to grafana plugin tools (#3837)

# What this PR does

Migrate from grafana-toolkit to grafana plugin tools

## Which issue(s) this PR fixes

https://github.com/grafana/oncall/issues/3651

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)

---------

Co-authored-by: Michael Derynck <michael.derynck@grafana.com>
Co-authored-by: Dominik <dominik.broj@grafana.com>
This commit is contained in:
Maxim Mordasov 2024-02-21 17:49:10 +03:00 committed by GitHub
parent d0ee58cb68
commit 828b0a3f4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 3040 additions and 4073 deletions

View file

@ -20,8 +20,8 @@ steps:
- name: Sign and Package Plugin
image: node:18.16.0-buster
environment:
GRAFANA_API_KEY:
from_secret: gcom_plugin_publisher_api_key
GRAFANA_ACCESS_POLICY_TOKEN:
from_secret: cloud_access_policy_token
depends_on:
- Build Plugin
commands:
@ -30,9 +30,7 @@ steps:
- cd grafana-plugin
- yarn sign
- if [ ! -f dist/MANIFEST.txt ]; then echo "Sign failed, MANIFEST.txt not created, aborting." && exit 1; fi
- yarn ci-build:finish
- yarn ci-package
- cd ci/dist
- mv dist grafana-oncall-app
- zip -r grafana-oncall-app.zip ./grafana-oncall-app
# yamllint disable rule:line-length
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
@ -192,8 +190,8 @@ steps:
- name: sign and package plugin
image: node:18.16.0-buster
environment:
GRAFANA_API_KEY:
from_secret: gcom_plugin_publisher_api_key
GRAFANA_ACCESS_POLICY_TOKEN:
from_secret: cloud_access_policy_token
depends_on:
- build plugin
commands:
@ -202,9 +200,7 @@ steps:
- cd grafana-plugin
- yarn sign
- if [ ! -f dist/MANIFEST.txt ]; then echo "Sign failed, MANIFEST.txt not created, aborting." && exit 1; fi
- yarn ci-build:finish
- yarn ci-package
- cd ci/dist
- mv dist grafana-oncall-app
- zip -r grafana-oncall-app.zip ./grafana-oncall-app
# yamllint disable rule:line-length
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
@ -355,6 +351,7 @@ get:
path: infra/data/ci/docker_hub
kind: secret
name: docker_username
---
get:
name: password
@ -377,8 +374,17 @@ get:
path: ci/data/repo/grafana/oncall/drone
kind: secret
name: github_api_token
---
# Secret for signing plugin
get:
name: cloud_access_policy_token
path: ci/data/repo/grafana/oncall/sign_plugin
kind: secret
name: cloud_access_policy_token
---
kind: signature
hmac: f717f3a2708568d8d9ad3e8c738544b96a82455ecd1413feec9f502414807397
hmac: 5bf89aaec69ebec6d4489b6ef712f92d10ede5b242b88c8f62930d78ba780f7d
...

View file

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Change plugin build to use new packages instead of deprecated grafana-toolkit @maskin25 ([#3837](https://github.com/grafana/oncall/pull/3837))
## v1.3.106 (2024-02-20)
### Added

View file

@ -0,0 +1,25 @@
/*
* ⚠️⚠️⚠️ 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/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-eslint-config
*/
{
"extends": ["@grafana/eslint-config"],
"root": true,
"rules": {
"react/prop-types": "off"
},
"overrides": [
{
"plugins": ["deprecation"],
"files": ["src/**/*.{ts,tsx}"],
"rules": {
"deprecation/deprecation": "warn"
},
"parserOptions": {
"project": "./tsconfig.json"
}
}
]
}

View file

@ -0,0 +1,16 @@
/*
* THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY.
*
* In order to extend the configuration follow the steps in .config/README.md
*/
module.exports = {
endOfLine: 'auto',
printWidth: 120,
trailingComma: 'es5',
semi: true,
jsxSingleQuote: false,
singleQuote: true,
useTabs: false,
tabWidth: 2,
};

View file

@ -0,0 +1,25 @@
/*
* 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/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-jest-config
*/
import '@testing-library/jest-dom';
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Object.defineProperty(global, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
HTMLCanvasElement.prototype.getContext = () => {};

View file

@ -0,0 +1,43 @@
/*
* 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/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-jest-config
*/
const path = require('path');
const { grafanaESModules, nodeModulesToTransform } = require('./jest/utils');
module.exports = {
moduleNameMapper: {
'\\.(css|scss|sass)$': 'identity-obj-proxy',
'react-inlinesvg': path.resolve(__dirname, 'jest', 'mocks', 'react-inlinesvg.tsx'),
},
modulePaths: ['<rootDir>/src'],
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
testEnvironment: 'jest-environment-jsdom',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test,jest}.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test,jest}.{js,jsx,ts,tsx}',
],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
sourceMaps: 'inline',
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: false,
dynamicImport: true,
},
},
},
],
},
// Jest will throw `Cannot use import statement outside module` if it tries to load an
// ES module without it being transformed first. ./config/README.md#esm-errors-with-jest
transformIgnorePatterns: [nodeModulesToTransform(grafanaESModules)],
};

View file

@ -0,0 +1,25 @@
// Due to the grafana/ui Icon component making fetch requests to
// `/public/img/icon/<icon_name>.svg` we need to mock react-inlinesvg to prevent
// the failed fetch requests from displaying errors in console.
import React from 'react';
type Callback = (...args: any[]) => void;
export interface StorageItem {
content: string;
queue: Callback[];
status: string;
}
export const cacheStore: { [key: string]: StorageItem } = Object.create(null);
const SVG_FILE_NAME_REGEX = /(.+)\/(.+)\.svg$/;
const InlineSVG = ({ src }: { src: string }) => {
// testId will be the file name without extension (e.g. `public/img/icons/angle-double-down.svg` -> `angle-double-down`)
const testId = src.replace(SVG_FILE_NAME_REGEX, '$2');
return <svg xmlns="http://www.w3.org/2000/svg" data-testid={testId} viewBox="0 0 24 24" />;
};
export default InlineSVG;

View file

@ -0,0 +1,31 @@
/*
* THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY.
*
* In order to extend the configuration follow the steps in .config/README.md
*/
/*
* This utility function is useful in combination with jest `transformIgnorePatterns` config
* to transform specific packages (e.g.ES modules) in a projects node_modules folder.
*/
const nodeModulesToTransform = (moduleNames) => `node_modules\/(?!.*(${moduleNames.join('|')})\/.*)`;
// Array of known nested grafana package dependencies that only bundle an ESM version
const grafanaESModules = [
'.pnpm', // Support using pnpm symlinked packages
'@grafana/schema',
'd3',
'd3-color',
'd3-force',
'd3-interpolate',
'd3-scale-chromatic',
'ol',
'react-colorful',
'rxjs',
'uuid',
];
module.exports = {
nodeModulesToTransform,
grafanaESModules,
};

View file

@ -0,0 +1,26 @@
/*
* 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/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-typescript-config
*/
{
"compilerOptions": {
"alwaysStrict": true,
"declaration": false,
"rootDir": "../src",
"baseUrl": "../src",
"typeRoots": ["../node_modules/@types"],
"resolveJsonModule": true
},
"ts-node": {
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
},
"transpileOnly": true
},
"include": ["../src", "./types"],
"extends": "@grafana/tsconfig"
}

View file

@ -0,0 +1,37 @@
// Image declarations
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
const content: string;
export default content;
}
// Font declarations
declare module '*.woff';
declare module '*.woff2';
declare module '*.eot';
declare module '*.ttf';
declare module '*.otf';

View file

@ -0,0 +1,2 @@
export const SOURCE_DIR = 'src';
export const DIST_DIR = 'dist';

View file

@ -0,0 +1,58 @@
import fs from 'fs';
import process from 'process';
import os from 'os';
import path from 'path';
import { glob } from 'glob';
import { SOURCE_DIR } from './constants';
export function isWSL() {
if (process.platform !== 'linux') {
return false;
}
if (os.release().toLowerCase().includes('microsoft')) {
return true;
}
try {
return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
} catch {
return false;
}
}
export function getPackageJson() {
return require(path.resolve(process.cwd(), 'package.json'));
}
export function getPluginJson() {
return require(path.resolve(process.cwd(), `${SOURCE_DIR}/plugin.json`));
}
export function hasReadme() {
return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md'));
}
// Support bundling nested plugins by finding all plugin.json files in src directory
// then checking for a sibling module.[jt]sx? file.
export async function getEntries(): Promise<Record<string, string>> {
const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true });
const plugins = await Promise.all(
pluginsJson.map((pluginJson) => {
const folder = path.dirname(pluginJson);
return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true });
})
);
return plugins.reduce((result, modules) => {
return modules.reduce((result, module) => {
const pluginPath = path.dirname(module);
const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\/?/i, '');
const entryName = pluginName === '' ? 'module' : `${pluginName}/module`;
result[entryName] = module;
return result;
}, result);
}, {});
}

View file

@ -0,0 +1,218 @@
/*
* 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/create-a-plugin/extend-a-plugin/extend-configurations#extend-the-webpack-config
*/
import CopyWebpackPlugin from 'copy-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import LiveReloadPlugin from 'webpack-livereload-plugin';
import path from 'path';
import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
import { Configuration } from 'webpack';
import { getPackageJson, getPluginJson, hasReadme, getEntries, isWSL } from './utils';
import { SOURCE_DIR, DIST_DIR } from './constants';
const pluginJson = getPluginJson();
const config = async (env): Promise<Configuration> => {
const baseConfig: Configuration = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
context: path.join(process.cwd(), SOURCE_DIR),
devtool: env.production ? 'source-map' : 'eval-source-map',
entry: await getEntries(),
externals: [
'lodash',
'jquery',
'moment',
'slate',
'emotion',
'@emotion/react',
'@emotion/css',
'prismjs',
'slate-plain-serializer',
'@grafana/slate-react',
'react',
'react-dom',
'react-redux',
'redux',
'rxjs',
'react-router',
'react-router-dom',
'd3',
'angular',
'@grafana/ui',
'@grafana/runtime',
'@grafana/data',
// Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
({ request }, callback) => {
const prefix = 'grafana/';
const hasPrefix = (request) => request.indexOf(prefix) === 0;
const stripPrefix = (request) => request.substr(prefix.length);
if (hasPrefix(request)) {
return callback(undefined, stripPrefix(request));
}
callback();
},
],
mode: env.production ? 'production' : 'development',
module: {
rules: [
{
exclude: /(node_modules)/,
test: /\.[tj]sx?$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
baseUrl: path.resolve(__dirname, 'src'),
target: 'es2015',
loose: false,
parser: {
syntax: 'typescript',
tsx: true,
decorators: false,
dynamicImport: true,
},
},
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource',
generator: {
// Keep publicPath relative for host.com/grafana/ deployments
publicPath: `public/plugins/${pluginJson.id}/img/`,
outputPath: 'img/',
filename: Boolean(env.production) ? '[hash][ext]' : '[file]',
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset/resource',
generator: {
// Keep publicPath relative for host.com/grafana/ deployments
publicPath: `public/plugins/${pluginJson.id}/fonts/`,
outputPath: 'fonts/',
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
},
},
],
},
output: {
clean: {
keep: new RegExp(`(.*?_(amd64|arm(64)?)(.exe)?|go_plugin_build_manifest)`),
},
filename: '[name].js',
library: {
type: 'amd',
},
path: path.resolve(process.cwd(), DIST_DIR),
publicPath: `public/plugins/${pluginJson.id}/`,
uniqueName: pluginJson.id,
},
plugins: [
new CopyWebpackPlugin({
patterns: [
// If src/README.md exists use it; otherwise the root README
// To `compiler.options.output`
{ from: hasReadme() ? 'README.md' : '../README.md', to: '.', force: true },
{ from: 'plugin.json', to: '.' },
{ from: '../LICENSE', to: '.' },
{ from: '../CHANGELOG.md', to: '.', noErrorOnMissing: true, force: true },
{ from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
{ from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
{ from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
{ from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
{ from: '**/query_help.md', to: '.', noErrorOnMissing: true }, // Optional
],
}),
// Replace certain template-variables in the README and plugin.json
new ReplaceInFileWebpackPlugin([
{
dir: DIST_DIR,
files: ['plugin.json', 'README.md'],
rules: [
{
search: /\%VERSION\%/g,
replace: getPackageJson().version,
},
{
search: /\%TODAY\%/g,
replace: new Date().toISOString().substring(0, 10),
},
{
search: /\%PLUGIN_ID\%/g,
replace: pluginJson.id,
},
],
},
]),
...(env.development
? [
new LiveReloadPlugin(),
new ForkTsCheckerWebpackPlugin({
async: Boolean(env.development),
issue: {
include: [{ file: '**/*.{ts,tsx}' }],
},
typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
}),
new ESLintPlugin({
extensions: ['.ts', '.tsx'],
lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
}),
]
: []),
],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
// handle resolving "rootDir" paths
modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
unsafeCache: true,
},
};
if (isWSL()) {
baseConfig.watchOptions = {
poll: 3000,
ignored: /node_modules/,
};
}
return baseConfig;
};
export default config;

View file

@ -2,7 +2,7 @@ const rulesDirPlugin = require('eslint-plugin-rulesdir');
rulesDirPlugin.RULES_DIR = 'tools/eslint-rules';
module.exports = {
extends: ['@grafana/eslint-config'],
extends: ['./.config/.eslintrc'],
plugins: ['rulesdir', 'import'],
settings: {
'import/internal-regex':

1
grafana-plugin/.nvmrc Normal file
View file

@ -0,0 +1 @@
18.16.0

View file

@ -1,3 +1,4 @@
module.exports = {
...require('@grafana/toolkit/src/config/prettier.plugin.config.json'),
// Prettier configuration provided by Grafana scaffolding
...require('./.config/.prettierrc.js'),
};

View file

@ -1,15 +0,0 @@
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-transform-destructuring", { "useBuiltIns": true }],
"@babel/plugin-transform-runtime",
["@babel/plugin-proposal-class-properties", { "loose": false }],
"@babel/transform-regenerator",
"@babel/plugin-transform-template-literals"
]
}

View file

@ -1,6 +1,11 @@
// force timezone to UTC to allow tests to work regardless of local timezone
// generally used by snapshots, but can affect specific tests
process.env.TZ = 'UTC';
const esModules = ['@grafana', 'uplot', 'ol', 'd3', 'react-colorful', 'uuid', 'openapi-fetch'].join('|');
module.exports = {
...require('./.config/jest.config'),
testEnvironment: 'jsdom',
moduleDirectories: ['node_modules', 'src'],
@ -23,4 +28,20 @@ module.exports = {
testTimeout: 10000,
testPathIgnorePatterns: ['/node_modules/', '/e2e-tests/'],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
sourceMaps: 'inline',
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: true,
dynamicImport: true,
},
},
},
],
},
};

View file

@ -6,6 +6,10 @@ import '@testing-library/jest-dom';
import 'plugin/dayjs';
import { TextEncoder, TextDecoder } from 'util';
Object.assign(global, { TextDecoder, TextEncoder });
// https://stackoverflow.com/a/66055672
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Object.defineProperty(window, 'matchMedia', {

View file

@ -7,8 +7,8 @@
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --quiet ./src ./e2e-tests",
"stylelint": "stylelint ./src/**/*.{css,scss,module.css,module.scss}",
"stylelint:fix": "stylelint --fix ./src/**/*.{css,scss,module.css,module.scss}",
"build": "grafana-toolkit plugin:build",
"build:dev": "grafana-toolkit plugin:build --skipTest --skipLint",
"build": "webpack -c ./webpack.config.ts --env production",
"build:dev": "webpack -c ./webpack.config.ts --env development",
"labels:link": "yarn --cwd ../../gops-labels/frontend link && yarn link \"@grafana/labels\" && yarn --cwd ../../gops-labels/frontend watch",
"labels:unlink": "yarn --cwd ../../gops-labels/frontend unlink",
"test": "jest --verbose",
@ -19,12 +19,9 @@
"test:e2e:gen": "yarn playwright codegen http://localhost:3000",
"e2e-show-report": "yarn playwright show-report",
"generate-types": "cd ./src/network/oncall-api/types-generator && yarn generate",
"dev": "grafana-toolkit plugin:dev",
"watch": "grafana-toolkit plugin:dev --watch",
"sign": "grafana-toolkit plugin:sign",
"ci-build:finish": "grafana-toolkit plugin:ci-build --finish",
"ci-package": "grafana-toolkit plugin:ci-package",
"ci-report": "grafana-toolkit plugin:ci-report",
"dev": "webpack -c ./.config/webpack/webpack.config.ts --env development",
"watch": "webpack -w -c ./.config/webpack/webpack.config.ts --env development",
"sign": "npx --yes @grafana/sign-plugin@latest",
"start": "yarn watch",
"plop": "plop",
"setversion": "setversion",
@ -50,6 +47,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.20.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
@ -64,17 +62,21 @@
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@grafana/eslint-config": "^5.1.0",
"@grafana/toolkit": "^9.5.2",
"@grafana/eslint-config": "^6.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@jest/globals": "^27.5.1",
"@playwright/test": "^1.39.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "12",
"@swc/core": "^1.3.90",
"@swc/helpers": "^0.5.0",
"@swc/jest": "^0.2.26",
"@testing-library/jest-dom": "6.1.4",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/dompurify": "^2.3.4",
"@types/jest": "27.5.1",
"@types/jest": "^29.5.0",
"@types/lodash": "^4.14.194",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.11.9",
"@types/node": "^20.8.7",
"@types/query-string": "^6.3.0",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.6",
@ -82,19 +84,26 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-test-renderer": "^18.0.5",
"@types/react-transition-group": "^4.4.5",
"@types/testing-library__jest-dom": "5.14.8",
"@types/throttle-debounce": "^5.0.0",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"dompurify": "^2.3.12",
"dotenv": "^16.0.3",
"eslint": "^8.25.0",
"eslint-plugin-deprecation": "^2.0.0",
"eslint-plugin-jsdoc": "^44.2.4",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-rulesdir": "^0.2.1",
"jest": "27.5.1",
"jest-environment-jsdom": "^27.5.1",
"eslint-webpack-plugin": "^4.0.1",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"glob": "^10.2.7",
"identity-obj-proxy": "3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^10.2.11",
"lodash-es": "^4.17.21",
"mailslurp-client": "^15.14.1",
@ -102,26 +111,35 @@
"openapi-typescript": "^7.0.0-next.4",
"plop": "^2.7.4",
"postcss-loader": "^7.0.1",
"prettier": "^2.8.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-test-renderer": "^18.0.2",
"replace-in-file-webpack-plugin": "^1.0.6",
"sass": "1.63.2",
"sass-loader": "13.3.1",
"style-loader": "3.3.3",
"stylelint-config-prettier": "^9.0.3",
"stylelint-prettier": "^2.0.0",
"swc-loader": "^0.2.3",
"ts-jest": "29.0.3",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
"typescript": "4.6.4",
"tsconfig-paths": "^4.2.0",
"typescript": "4.8.4",
"webpack": "^5.86.0",
"webpack-bundle-analyzer": "^4.6.1",
"webpack-cli": "^5.1.4",
"webpack-livereload-plugin": "^3.0.2"
},
"engines": {
"node": ">=14"
"node": "~18.16.0"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@emotion/css": "11.10.6",
"@grafana/data": "^10.2.3",
"@grafana/faro-web-sdk": "^1.0.0-beta4",
"@grafana/faro-web-tracing": "^1.0.0-beta4",
@ -133,6 +151,8 @@
"@lifeomic/attempt": "^3.0.3",
"@opentelemetry/api": "^1.3.0",
"array-move": "^4.0.0",
"axios": "^1.6.7",
"babel-loader": "^9.1.3",
"change-case": "^4.1.1",
"circular-dependency-plugin": "^5.2.2",
"dayjs": "^1.11.5",
@ -142,11 +162,12 @@
"mobx-react": "9.1.0",
"object-hash": "^3.0.0",
"openapi-fetch": "^0.8.1",
"prettier": "^2.8.2",
"qrcode.react": "^3.1.0",
"raw-loader": "^4.0.2",
"rc-table": "^7.17.1",
"react": "18.2.0",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "18.2.0",
"react-draggable": "^4.4.5",
"react-emoji-render": "^1.2.4",
"react-modal": "^3.15.1",
@ -155,9 +176,10 @@
"react-sortable-hoc": "^1.11.0",
"react-string-replace": "^0.4.4",
"react-transition-group": "^4.4.5",
"sass-loader": "^13.0.2",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"throttle-debounce": "^2.1.0"
}
"throttle-debounce": "^2.1.0",
"tslib": "2.5.3"
},
"packageManager": "yarn@1.22.21"
}

View file

@ -57,7 +57,7 @@ const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => {
color: ${fontColor};
font-size: ${theme.typography.bodySmall.fontSize};
border-radius: ${theme.shape.borderRadius(2)};
border-radius: ${theme.shape.radius.default};
`,
label: css`
display: flex;
@ -68,8 +68,8 @@ const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => {
background: ${backgroundColor};
border: solid 1px ${borderColor};
border-top-left-radius: ${theme.shape.borderRadius(2)};
border-bottom-left-radius: ${theme.shape.borderRadius(2)};
border-top-left-radius: ${theme.shape.radius.default};
border-bottom-left-radius: ${theme.shape.radius.default};
`,
value: css`
color: inherit;
@ -78,8 +78,8 @@ const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => {
border: solid 1px ${borderColor};
border-left: none;
border-top-right-radius: ${theme.shape.borderRadius(2)};
border-bottom-right-radius: ${theme.shape.borderRadius(2)};
border-top-right-radius: ${theme.shape.radius.default};
border-bottom-right-radius: ${theme.shape.radius.default};
`,
};
};

View file

@ -7,7 +7,7 @@ exports[`Unauthorized renders properly - access control enabled: false 1`] = `
<div
className="css-8tu8mo-vertical-group"
style={
Object {
{
"height": "100%",
"width": "100%",
}
@ -22,7 +22,7 @@ exports[`Unauthorized renders properly - access control enabled: false 1`] = `
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -40,7 +40,7 @@ exports[`Unauthorized renders properly - access control enabled: false 1`] = `
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -65,7 +65,7 @@ exports[`Unauthorized renders properly - access control enabled: true 1`] = `
<div
className="css-8tu8mo-vertical-group"
style={
Object {
{
"height": "100%",
"width": "100%",
}
@ -80,7 +80,7 @@ exports[`Unauthorized renders properly - access control enabled: true 1`] = `
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -98,7 +98,7 @@ exports[`Unauthorized renders properly - access control enabled: true 1`] = `
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -123,7 +123,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Admin 1
<div
className="css-8tu8mo-vertical-group"
style={
Object {
{
"height": "100%",
"width": "100%",
}
@ -138,7 +138,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Admin 1
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -156,7 +156,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Admin 1
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -181,7 +181,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Editor
<div
className="css-8tu8mo-vertical-group"
style={
Object {
{
"height": "100%",
"width": "100%",
}
@ -196,7 +196,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Editor
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -214,7 +214,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Editor
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -239,7 +239,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Viewer
<div
className="css-8tu8mo-vertical-group"
style={
Object {
{
"height": "100%",
"width": "100%",
}
@ -254,7 +254,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Viewer
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}
@ -272,7 +272,7 @@ exports[`Unauthorized renders properly the grammar for different roles - Viewer
<span
className="root text text--undefined text--medium"
style={
Object {
{
"maxWidth": undefined,
}
}

View file

@ -28,6 +28,7 @@ describe('AddRespondersPopup', () => {
},
grafanaTeamStore: {
getSearchResult: jest.fn().mockReturnValue(teams),
updateItems: jest.fn(),
},
userStore: {
search: jest.fn().mockReturnValue({ results: [] }),

View file

@ -127,7 +127,7 @@ describe('reloadPageWithPluginConfiguredQueryParams', () => {
describe('removePluginConfiguredQueryParams', () => {
test('it removes all the query params if history.pushState is available, and plugin is enabled', () => {
removePluginConfiguredQueryParams(true);
expect(window.history.pushState).toBeCalledWith({ path: MOCK_URL }, '', MOCK_URL);
expect(window.history.pushState).toHaveBeenCalledWith({ path: MOCK_URL }, '', MOCK_URL);
});
test('it does not remove all the query params if history.pushState is available, and plugin is disabled', () => {
@ -148,7 +148,7 @@ describe('PluginConfigPage', () => {
await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID);
// assertions
expect(window.history.pushState).toBeCalledWith({ path: MOCK_URL }, '', MOCK_URL);
expect(window.history.pushState).toHaveBeenCalledWith({ path: MOCK_URL }, '', MOCK_URL);
expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1);
expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl);

View file

@ -139,7 +139,7 @@ class _IncidentsPage extends React.Component<IncidentsPageProps, IncidentsPageSt
}
private rootElRef: React.RefObject<HTMLDivElement>;
private pollingIntervalId: NodeJS.Timer = undefined;
private pollingIntervalId: ReturnType<typeof setInterval> = undefined;
componentDidMount() {
const { store } = this.props;

View file

@ -36,7 +36,7 @@ export class _ChatOpsPage extends React.Component<ChatOpsProps, ChatOpsState> {
};
componentDidMount() {
const { query } = this.props;
const { query } = this.props; // eslint-disable-line
this.handleChatopsTabChange(query?.tab || ChatOpsTab.Slack);
}

View file

@ -254,20 +254,23 @@ describe('PluginState.installPlugin', () => {
onCallAPIResponse: mockedResponse,
});
expect(PluginState.createGrafanaToken).toBeCalledTimes(1);
expect(PluginState.createGrafanaToken).toBeCalledWith();
expect(PluginState.createGrafanaToken).toHaveBeenCalledTimes(1);
expect(PluginState.createGrafanaToken).toHaveBeenCalledWith();
expect(PluginState.updateGrafanaPluginSettings).toBeCalledTimes(1);
expect(PluginState.updateGrafanaPluginSettings).toBeCalledWith({
expect(PluginState.updateGrafanaPluginSettings).toHaveBeenCalledTimes(1);
expect(PluginState.updateGrafanaPluginSettings).toHaveBeenCalledWith({
secureJsonData: {
grafanaToken,
},
});
expect(makeRequest).toBeCalledTimes(1);
expect(makeRequest).toBeCalledWith(`${PluginState.ONCALL_BASE_URL}/${selfHosted ? 'self-hosted/' : ''}install`, {
method: 'POST',
});
expect(makeRequest).toHaveBeenCalledTimes(1);
expect(makeRequest).toHaveBeenCalledWith(
`${PluginState.ONCALL_BASE_URL}/${selfHosted ? 'self-hosted/' : ''}install`,
{
method: 'POST',
}
);
});
});

View file

@ -29,8 +29,8 @@ export const getCoords = (elem) => {
const body = document.body;
const docEl = document.documentElement;
const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
const scrollTop = window.scrollY || docEl.scrollTop || body.scrollTop;
const scrollLeft = window.scrollX || docEl.scrollLeft || body.scrollLeft;
const clientTop = docEl.clientTop || body.clientTop || 0;
const clientLeft = docEl.clientLeft || body.clientLeft || 0;

View file

@ -1,5 +1,6 @@
import { OnCallAppPluginMeta } from 'types';
//@ts-ignore
import plugin from '../../package.json'; // eslint-disable-line
// Navbar

View file

@ -1,4 +1,5 @@
import { TimeOption, TimeRange, TimeZone, rangeUtil } from '@grafana/data';
import { TimeOption, TimeRange, rangeUtil } from '@grafana/data';
import { TimeZone } from '@grafana/schema';
// Valid mapping accepted by @grafana/ui and @grafana/data packages
export const quickOptions = [

View file

@ -1,10 +1,9 @@
{
"extends": "@grafana/toolkit/src/config/tsconfig.plugin.json",
"extends": "./.config/tsconfig.json",
"include": ["src", "e2e-tests", "playwright.config.ts"],
"types": ["node", "@emotion/core"],
"compilerOptions": {
"rootDirs": ["src"],
"baseUrl": "src",
"rootDir": "",
"typeRoots": ["./node_modules/@types"],
"noUnusedLocals": false,
"noUnusedParameters": false,

View file

@ -1,158 +0,0 @@
const webpack = require('webpack');
const path = require('path');
const dotenv = require('dotenv');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const MONACO_DIR = path.resolve(__dirname, './node_modules/monaco-editor');
Object.defineProperty(RegExp.prototype, 'toJSON', {
value: RegExp.prototype.toString,
});
dotenv.config({ path: path.resolve(__dirname, '.env') });
module.exports.getWebpackConfig = (config, options) => {
const cssLoader = config.module.rules.find((rule) => rule.test.toString() === '/\\.css$/');
cssLoader.exclude.push(/\.module\.css$/, MONACO_DIR);
const grafanaRules = config.module.rules.filter((a) => a.test.toString() !== /\.s[ac]ss$/.toString());
const newConfig = {
...config,
module: {
...config.module,
rules: [
...grafanaRules,
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
presets: [
[
'@babel/preset-env',
{
modules: false,
},
],
[
'@babel/preset-typescript',
{
allowNamespaces: true,
allowDeclareFields: true,
},
],
['@babel/preset-react'],
],
plugins: [
[
'@babel/plugin-transform-typescript',
{
allowNamespaces: true,
allowDeclareFields: true,
},
],
'@babel/plugin-proposal-class-properties',
[
'@babel/plugin-proposal-object-rest-spread',
{
loose: true,
},
],
[
'@babel/plugin-proposal-decorators',
{
legacy: true,
},
],
'@babel/plugin-transform-react-constant-elements',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-syntax-dynamic-import',
],
},
},
'ts-loader',
],
},
{
test: /\.module\.css$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
modules: {
localIdentName: options.production ? '[name]__[hash:base64]' : '[path][name]__[local]',
},
},
},
],
},
{
test: /\.module\.scss$/i,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
modules: {
localIdentName: options.production ? '[name]__[hash:base64]' : '[path][name]__[local]',
},
},
},
'postcss-loader',
'sass-loader',
],
},
],
},
plugins: [
...config.plugins,
new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /node_modules/,
// include specific files based on a RegExp
// add errors to webpack instead of warnings
failOnError: true,
// allow import cycles that include an asyncronous import,
// e.g. via import(/* webpackMode: "weak" */ './file.js')
allowAsyncCycles: false,
// set the current working directory for displaying module paths
cwd: process.cwd(),
}),
/**
* From docs (https://webpack.js.org/plugins/environment-plugin/):
* Default values of null and undefined behave differently.
* Use undefined for variables that must be provided during bundling, or null if they are optional.
*/
new webpack.EnvironmentPlugin({
ONCALL_API_URL: null,
}),
new webpack.DefinePlugin({
'process.env': JSON.stringify(dotenv.config().parsed),
}),
...(options.production ? [] : [new LiveReloadPlugin({ appendScriptTag: true, useSourceHash: true })]),
],
};
return newConfig;
};

View file

@ -0,0 +1,42 @@
import type { Configuration } from 'webpack';
import { mergeWithRules, CustomizeRule } from 'webpack-merge';
import grafanaConfig from './.config/webpack/webpack.config';
const config = async (env): Promise<Configuration> => {
const baseConfig = await grafanaConfig(env);
const customConfig = {
module: {
rules: [
{
test: /\.[tj]sx?$/,
use: {
options: {
jsc: {
parser: {
decorators: true,
},
},
},
},
},
],
},
watchOptions: {
ignored: ['**/node_modules/', '**/dist'],
},
};
return mergeWithRules({
module: {
rules: {
test: CustomizeRule.Match,
use: CustomizeRule.Merge,
},
},
watchOptions: {
use: CustomizeRule.Merge,
},
})(baseConfig, customConfig);
};
export default config;

File diff suppressed because it is too large Load diff