Compare commits

...

119 Commits

Author SHA1 Message Date
Jack Westbrook
9c7aac77d3 ci: attempt to fix a load of broken stuff. Again 2025-03-03 16:27:02 +01:00
Jack Westbrook
d17b2d3939 refactor(betterer): brute force betterer to work by moving to subdirectory with package.json 2025-03-03 11:32:12 +01:00
Jack Westbrook
7da5a86f7e feat(webassets): shoddy fixes for preload and make tests pass 2025-03-03 09:52:53 +01:00
Jack Westbrook
67c4bd604a fix(vite): fix failed builds due to emotion/react alias 2025-03-03 09:52:53 +01:00
Jack Westbrook
9bbbdd3d6e chore(playwright): fix up broken testDirs 2025-03-03 09:52:53 +01:00
Jack Westbrook
80ea8a0901 style(webassets): fix golangci-lint complaining 2025-03-03 09:52:53 +01:00
Jack Westbrook
5e8f0c6f52 feat(trustedtypes): update vite config and the webassets integration to work with TT 2025-03-03 09:52:52 +01:00
Jack Westbrook
db4ef2d0be chore(frontend): add comment related to using workers 2025-03-03 09:52:52 +01:00
Jack Westbrook
1feace3bbe fix(e2e): set playwright-report to project root for drone upload step 2025-03-03 09:52:52 +01:00
Jack Westbrook
d3469ceca0 fix(frontend): workers should be imported as url to correctly resolve 2025-03-03 09:52:52 +01:00
Jack Westbrook
846afe2e68 chore(cloudwatch): vendor jsurl stringify function 2025-03-03 09:52:51 +01:00
Jack Westbrook
d830e2085d chore(codeowners): update playwright config path 2025-03-03 09:52:51 +01:00
Jack Westbrook
1bd546568f fix(cloudwatch): migrate to esm compatible jsurl2 npm package 2025-03-03 09:52:50 +01:00
Jack Westbrook
f75463e170 refactor(playwright): move playwright config and add package.json to prevent esm errors 2025-03-03 09:52:08 +01:00
Jack Westbrook
bf68d041fa build(vite): update build:stats script to use rollup visualiser 2025-03-03 09:51:13 +01:00
Jack Westbrook
dbb2097f40 revert(playwright): put back ts extension, cts did nothing 2025-03-03 09:51:13 +01:00
Jack Westbrook
0911638c91 chore(playwright): rename config to cts in case it fixes cli errors related to esm 2025-03-03 09:51:12 +01:00
Jack Westbrook
a6ab645029 chore(codeowners): update file post rename of jquery.ts 2025-03-03 09:51:12 +01:00
Tom Ratcliffe
63dc588531 fix(vite): require env utils correctly 2025-03-03 09:51:12 +01:00
Tom Ratcliffe
c5d89b53d8 fix(frontend): make linting work with cjs util file 2025-03-03 09:51:12 +01:00
Tom Ratcliffe
f6b49880df fix(frontend): rename env utils and expose frontend_dev_ settings via env plugin 2025-03-03 09:51:12 +01:00
Jack Westbrook
030eb759a9 fix(vite): provide global to patch webpacks defaults 2025-03-03 09:51:11 +01:00
Jack Westbrook
eb7af07b19 chore(vite): major bump to v6 and associated deps 2025-03-03 09:51:10 +01:00
Jack Westbrook
6550ffcc23 feat(webassets): set cdn url for preloadJSFiles and update tests 2025-03-03 09:50:32 +01:00
Jack Westbrook
12b2a63b55 feat(webassets): take advantage of preloadmodule to improve performance 2025-03-03 09:50:30 +01:00
Tom Ratcliffe
762d9a0801 fix(frontend): Fix loading translation files due to hyphens in names 2025-03-03 09:49:23 +01:00
Tom Ratcliffe
9da6111c05 fix(frontend): get all workspace dependencies so we don't optimise them away 2025-03-03 09:49:22 +01:00
Tom Ratcliffe
f016bfe60e fix(frontend): use vite logger for moveAssets 2025-03-03 09:49:22 +01:00
Tom Ratcliffe
3b44fab842 fix(frontend): Remove unneeded as in vite 2025-03-03 09:49:22 +01:00
Jack Westbrook
6f150098fd fix(frontend): get start command working 2025-03-03 09:49:22 +01:00
Jack Westbrook
caaa1fe62f chore(backend): comment out swagger related assets and redundant css keys 2025-03-03 09:49:21 +01:00
Jack Westbrook
54b4cbf3f1 test(dashboard-scene): fix import of CorsWorker 2025-03-03 09:49:21 +01:00
Jack Westbrook
6690095d18 chore(codeowners): fixes for renaming betterer and eslint configs to cjs 2025-03-03 09:49:21 +01:00
Jack Westbrook
e54ce29a29 refactor(crash): clean up cors sharedworker 2025-03-03 09:49:21 +01:00
Jack Westbrook
416afce642 refactor(frontend): clean up usage of corsworker across codebase 2025-03-03 09:49:20 +01:00
Jack Westbrook
5c10f2677b chore(frontend): fix failing imports preventing vite from building 2025-03-03 09:49:20 +01:00
Jack Westbrook
16ae94dadc feat(webassets): get prod builds loading in browser again 2025-03-03 09:49:20 +01:00
Jack Westbrook
43189a3848 chore(betterer): make it work, again 2025-03-03 09:49:20 +01:00
Jack Westbrook
af0d904bfe revert(frontend): put back process.env for jest 2025-03-03 09:49:20 +01:00
Jack Westbrook
22955700ca chore(internationalization): re-export type to make enterprise a happy bunny 2025-03-03 09:49:19 +01:00
Jack Westbrook
727a3723a1 chore(storybook): put back esbuild-loader for e2e-storybook drone stage 2025-03-03 09:49:18 +01:00
Jack Westbrook
edda259bad test(internationalization): globally mock extensions to avoid using import.meta.glob in jest 2025-03-03 09:45:32 +01:00
Jack Westbrook
5e21abeed7 test(internationalisation): move files around so we can mock any use of import.meta.glob 2025-03-03 09:45:32 +01:00
Jack Westbrook
0a01254c68 chore(vite): bump dependencies to latest 2025-03-03 09:45:31 +01:00
Jack Westbrook
6c812409fe chore(saga-icons): use prettier directly to avoid ERR_REQUIRE_ESM error from svgr prettier plugin 2025-03-03 09:44:52 +01:00
Jack Westbrook
4ba0bbb602 chore(frontend): get typechecking to pass 2025-03-03 09:44:51 +01:00
Jack Westbrook
ad36be9612 chore(tempo): put back missing css-loader 2025-03-03 09:44:51 +01:00
Jack Westbrook
60a7faca5b build(typescript): reenable jsx react-jsx in ts config 2025-03-03 09:44:51 +01:00
Jack Westbrook
830187d688 build(vite): make sure NODE_ENV is set for dev and prod 2025-03-03 09:44:51 +01:00
Jack Westbrook
5298d168cc fix(frontend): attempt to fix enterprise locales require.context imports 2025-03-03 09:44:50 +01:00
Jack Westbrook
57f74c0740 chore(backend): put back missing FrontendDevServer cfg 2025-03-03 09:44:50 +01:00
Jack Westbrook
0e643f352b build(frontend): add missing dependencies to core and decoupled plugins 2025-03-03 09:44:49 +01:00
Jack Westbrook
be481d2780 chore(frontend): fix lint issues with tether-drop 2025-03-03 09:44:17 +01:00
Jack Westbrook
1115cbb722 refactor(frontend): fix tether-drop import path 2025-03-03 09:44:17 +01:00
Jack Westbrook
1fd1bb5dff refactor(frontend): update imports of tether-drop to point to vendor directory 2025-03-03 09:44:16 +01:00
Jack Westbrook
732dc952de refactor(frontend): bring tether-drop into vendor folder to build from source 2025-03-03 09:44:16 +01:00
Jack Westbrook
904084614b refactor(frontend): replace usage of process.env.NODE_ENV with import.meta.env.MODE 2025-03-03 09:44:16 +01:00
Jack Westbrook
4c9f0387fa feat(vite): add basic vendor chunking and vizualizer plugins 2025-03-03 09:44:14 +01:00
Jack Westbrook
8d3d389362 chore(angular): remove nonexistant template import 2025-03-03 09:43:32 +01:00
Jack Westbrook
8f2ebceddf test(integration): fix testinfra due to asset manifest.json changes 2025-03-03 09:43:32 +01:00
Jack Westbrook
ac5eb6afab chore(e2e): rename reporter as commonjs 2025-03-03 09:43:31 +01:00
Jack Westbrook
f7be501f9a test(webassets): trim down sample json 2025-03-03 09:43:31 +01:00
Jack Westbrook
d7f6f87873 ci(e2e): bump cypress container to 13.6.6 2025-03-03 09:43:31 +01:00
Jack Westbrook
c9731da894 wip(e2e): set reporter file path 2025-03-03 09:43:31 +01:00
Jack Westbrook
6ccec33c6a wip(e2e): brute force cypress install 2025-03-03 09:43:30 +01:00
Jack Westbrook
785c05f185 feat(monaco): add compatiblity for loading with vite 2025-03-03 09:43:30 +01:00
Jack Westbrook
6b55313560 feat(frontend): provide a corsworker for vite 2025-03-03 09:43:30 +01:00
Jack Westbrook
06d4038d4f feat(vite): support loading fe assets via cdn 2025-03-03 09:43:30 +01:00
Jack Westbrook
e56168fdff chore(codeowners): update cypress config filename 2025-03-03 09:43:30 +01:00
Jack Westbrook
30aa59179a wip(e2e): disable trusted types - can it work with vite? 2025-03-03 09:43:29 +01:00
Jack Westbrook
a3f7e26220 feat(backend): add a frontendDevServer config setting 2025-03-03 09:43:29 +01:00
Jack Westbrook
9f8eb74d45 refactor(cypress): rename config files and imports to use cjs so cypress runs locally 2025-03-03 09:43:29 +01:00
Jack Westbrook
41d72308ae test(webassets): update tests inline with vite manifests 2025-03-03 09:43:29 +01:00
Jack Westbrook
8fdd1f530d wip(graph): update test snapshots 2025-03-03 09:43:28 +01:00
Jack Westbrook
447a65e09a test(playwright): add createRequire for require.resolve usage 2025-03-03 09:43:28 +01:00
Jack Westbrook
2752ed52ff style(typescript): fix errors from ts checks 2025-03-03 09:43:28 +01:00
Jack Westbrook
b56928bc89 chore(typescript): introduce vite client types 2025-03-03 09:43:28 +01:00
Jack Westbrook
ea1bc89872 build(plugins): align isolatedModules in plugin-configs with root tsconfig setting 2025-03-03 09:43:28 +01:00
Jack Westbrook
6b2f16ae9d build(typescript): adjust config for typechecking 2025-03-03 09:43:27 +01:00
Jack Westbrook
8a3870f297 style(typescript): disable noUnusedLocals so typechecking doesnt complain about react imports 2025-03-03 09:43:27 +01:00
Jack Westbrook
4101d2adbf chore(frontend): migrate stylelint and i18next-parser configs to esm 2025-03-03 09:43:27 +01:00
Jack Westbrook
b14f695bf7 test(gauge): fix failing test due to mock not working 2025-03-03 09:43:27 +01:00
Jack Westbrook
1a3a100de5 style: fix failing fe and be linting 2025-03-03 09:43:27 +01:00
Jack Westbrook
7c10280b7f ci(levitate): migrate scripts to es modules 2025-03-03 09:43:26 +01:00
Jack Westbrook
c6cc559b5a chore(packages): remove prepare packagejson script 2025-03-03 09:43:26 +01:00
Jack Westbrook
619ce4bb8b chore(codeowners): add tsconfig.test.json to file 2025-03-03 09:43:26 +01:00
Jack Westbrook
bf053dc744 test(jest): refactor config/setup to work with Vite tsconfig changes 2025-03-03 09:43:26 +01:00
Jack Westbrook
886be61495 chore(codeowners): update based on changes in this branch 2025-03-03 09:43:25 +01:00
Jack Westbrook
7e924511b3 feat(vite): make production builds work 2025-03-03 09:43:25 +01:00
Jack Westbrook
f77294c268 chore(vite): bump related dependencies 2025-03-03 09:43:23 +01:00
Jack Westbrook
feff659957 chore(vite): put back previous assetsDir 2025-03-03 09:42:53 +01:00
Jack Westbrook
c7368baa30 feat(frontend): make enterprise work 2025-03-03 09:42:52 +01:00
Jack Westbrook
b89599a014 wip(grafana/ui): put back LayoutItemContext so vite builds 2025-03-03 09:40:34 +01:00
Jack Westbrook
ecc1fbdb54 chore(vite): add vite types to tsconfig 2025-03-03 09:40:34 +01:00
Jack Westbrook
024773f33d feat(vite): align path is template vars in angular components 2025-03-03 09:40:34 +01:00
Jack Westbrook
b53f9e97f0 fix(icons): import svgs as strings using raw loader 2025-03-03 09:40:34 +01:00
Jack Westbrook
03960b45f8 feat(angular): fix up plugin for dev and prod, inline for prod builds 2025-03-03 09:40:33 +01:00
Jack Westbrook
a5a024610c revert(app): undo commented out bundle_loaded to prevent flash of preload error 2025-03-03 09:40:33 +01:00
Jack Westbrook
d8531fcb44 chore(grafana-data): named imports for xss in sanitize.ts 2025-03-03 09:40:33 +01:00
Jack Westbrook
18064278c8 feat(vite): allow angular templates to build in dev and prod 2025-03-03 09:40:33 +01:00
Jack Westbrook
58b4ed1411 fix(betterer): make it work in an esm environment 2025-03-03 09:40:31 +01:00
Jack Westbrook
17bb5cec36 fix(jquery): make available globally and to plugins 2025-03-03 09:08:14 +01:00
Jack Westbrook
11b6a42bb6 fix(vite): escape angular template html and load as strings 2025-03-03 09:08:14 +01:00
Jack Westbrook
8ad543a486 chore(yarn): refresh lock file 2025-03-03 09:08:12 +01:00
Jack Westbrook
f20c275bc8 chore(app): wip: comment out iconCache and mock extensions import for vite to build 2025-03-03 09:07:53 +01:00
Jack Westbrook
44d534b80e chore(angular): wip: comment out partials for now so vite build succeeds 2025-03-03 09:07:53 +01:00
Jack Westbrook
ca490c9ba1 build(vite): attempt to get angular partials loading with vite plugin 2025-03-03 09:07:53 +01:00
Jack Westbrook
c8996b2dd4 chore(routing): comment out tether-drop code so vite build succeeds 2025-03-03 09:07:53 +01:00
Jack Westbrook
9d8bcd636f refactor(monaco): wip: update to load with vite worker support 2025-03-03 09:07:53 +01:00
Jack Westbrook
4c2ca3247d chore(frontend): wip: comment out __webpack_public_path vars for build to succeed 2025-03-03 09:07:52 +01:00
Jack Westbrook
248fb12625 feat(vite): wip backend integration 2025-03-03 09:05:29 +01:00
Jack Westbrook
e2bacbc74a refactor(sandbox): replace require with import 2025-03-03 09:05:29 +01:00
Jack Westbrook
8daeb1b8e3 chore(frontend): fix more jquery imports 2025-03-03 09:05:29 +01:00
Jack Westbrook
e7c65e61b8 chore(frontend): fix jquery imports 2025-03-03 09:05:28 +01:00
Jack Westbrook
c765ef878a chore(package.json): use type: module, update scripts to run vite 2025-03-03 09:05:28 +01:00
Jack Westbrook
77412b96a7 build(vite): update config with aliases, entrypoint files, bind to ip4 2025-03-03 09:05:28 +01:00
Jack Westbrook
4a82103632 build(vite): up tsconfig and copy over configs from a create-vite app 2025-03-03 09:05:28 +01:00
Jack Westbrook
db6d3e458f build(frontend): add vite npm packages 2025-03-03 09:05:25 +01:00
Jack Westbrook
18cbc6cd00 build(webpack): remove unused dev dependencies 2025-03-03 09:04:59 +01:00
117 changed files with 3127 additions and 11343 deletions

File diff suppressed because it is too large Load Diff

16
.github/CODEOWNERS vendored
View File

@@ -439,9 +439,10 @@
/project.json @grafana/frontend-ops
/.nxignore @grafana/frontend-ops
/tsconfig.json @grafana/frontend-ops
/tsconfig.test.json @grafana/frontend-ops
/.editorconfig @grafana/frontend-ops
/eslint.config.js @grafana/frontend-ops
/.betterer.eslint.config.js @grafana/frontend-ops
/eslint.config.cjs @grafana/frontend-ops
/.betterer.eslint.config.cjs @grafana/frontend-ops
/.gitattributes @grafana/frontend-ops
/.gitignore @grafana/frontend-ops
/.ignore @grafana/frontend-ops
@@ -453,16 +454,16 @@
/lerna.json @grafana/frontend-ops
/.prettierrc.js @grafana/frontend-ops
/.vim @zoltanbedi
/jest.config.js @grafana/frontend-ops
/jest.config.cjs @grafana/frontend-ops
/latest.json @grafana/frontend-ops
/stylelint.config.js @grafana/frontend-ops
/tools/ @grafana/frontend-ops
/lefthook.yml @grafana/frontend-ops
/lefthook.rc @grafana/frontend-ops
/.husky/pre-commit @grafana/frontend-ops
/cypress.config.js @grafana/grafana-frontend-platform
/cypress.config.cjs @grafana/grafana-frontend-platform
/.levignore.js @grafana/plugins-platform-frontend
playwright.config.ts @grafana/plugins-platform-frontend
e2e/plugin-e2e/playwright.config.ts @grafana/plugins-platform-frontend
# public folder
/public/app/api/ @grafana/grafana-frontend-platform
@@ -603,6 +604,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/dev.ts @grafana/frontend-ops
/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend
/public/app/index.ts @grafana/frontend-ops
/public/app/viteGlobals.ts @grafana/frontend-ops
/public/app/AppWrapper.tsx @grafana/frontend-ops
/public/app/partials/ @grafana/grafana-frontend-platform
@@ -620,7 +622,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/scripts/import_many_dashboards.sh @torkelo
/scripts/mixin-check.sh @bergquist
/scripts/openapi3/ @grafana/grafana-operator-experience-squad
/scripts/prepare-packagejson.js @grafana/frontend-ops
/scripts/protobuf-check.sh @grafana/plugins-platform-backend
/scripts/stripnulls.sh @grafana/grafana-as-code
/scripts/tag_release.sh @grafana/grafana-developer-enablement-squad
@@ -642,7 +643,8 @@ playwright.config.ts @grafana/plugins-platform-frontend
.pa11yci.conf.js @grafana/grafana-frontend-platform
.pa11yci-pr.conf.js @grafana/grafana-frontend-platform
.betterer.results @grafanabot
.betterer.ts @grafana/grafana-frontend-platform
.betterer.cjs @grafana/grafana-frontend-platform
vite.config.ts @grafana/frontend-ops
# Design system
/public/img/icons/unicons/ @grafana/design-system

View File

@@ -202,7 +202,7 @@ jobs:
with:
script: |
const filePath = 'result.json';
const script = require('./.github/workflows/scripts/json-file-to-job-output.js');
const { default: script } = await import ('${{ github.workspace }}/.github/workflows/scripts/json-file-to-job-output.js')
await script({ core, filePath });
# Check if label exists

View File

@@ -1,18 +1,20 @@
module.exports = async ({ core, filePath }) => {
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath)
const result = JSON.parse(content);
core.startGroup('Parsing json file...');
const jsonToJobOutput = async ({ core, filePath }) => {
try {
const fs = await import('fs/promises');
const content = await fs.readFile(filePath)
const result = JSON.parse(content);
for (const property in result) {
core.info(`${property} <- ${result[property]}`);
core.setOutput(property, result[property]);
}
core.startGroup('Parsing json file...');
core.endGroup();
} catch (error) {
core.setFailed(error.message);
}
}
for (const property in result) {
core.info(`${property} <- ${result[property]}`);
core.setOutput(property, result[property]);
}
core.endGroup();
} catch (error) {
core.setFailed(error.message);
}
}
export default jsonToJobOutput

15
.gitignore vendored
View File

@@ -7,6 +7,7 @@ awsconfig
/.awcache
/dist
/public/build
/public/build_tmp
/emails/dist
/reports
/e2e/tmp
@@ -179,11 +180,12 @@ compilation-stats.json
/e2e/extensions
!/e2e/extensions/.keep
/e2e/extensions-suite
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
test-results/
playwright-report/
blob-report/
playwright/.cache/
playwright/.auth/
# grafana server
/scripts/grafana-server/server.log
@@ -233,3 +235,6 @@ public/app/plugins/**/dist/
public/mockServiceWorker.js
/e2e/test-plugins/*/dist
# vite
stats.html

View File

@@ -1,5 +1,7 @@
module.exports = {
const config = {
trailingComma: 'es5',
singleQuote: true,
printWidth: 120,
};
export default config;

View File

@@ -89,6 +89,9 @@ cdn_url =
# `0` means there is no timeout for reading the request.
read_timeout = 0
# Tell the backend server where to load frontend assets from when developing Grafana.
frontend_dev_server =
# This setting enables you to specify additional headers that the server adds to HTTP(S) responses.
[server.custom_response_headers]
#exampleHeader1 = exampleValue1

View File

@@ -92,6 +92,9 @@
# `0` means there is no timeout for reading the request.
;read_timeout = 0
# Tell the backend server where to load frontend assets from when developing Grafana.
;frontend_dev_server =
# This setting enables you to specify additional headers that the server adds to HTTP(S) responses.
[server.custom_response_headers]
#exampleHeader1 = exampleValue1

View File

@@ -2,10 +2,10 @@ const { defineConfig } = require('cypress');
const fs = require('fs');
const path = require('path');
const benchmarkPlugin = require('./e2e/cypress/plugins/benchmark/index');
const readProvisions = require('./e2e/cypress/plugins/readProvisions');
const smtpTester = require('./e2e/cypress/plugins/smtpTester');
const typescriptPreprocessor = require('./e2e/cypress/plugins/typescriptPreprocessor');
const benchmarkPlugin = require('./e2e/cypress/plugins/benchmark/index.cjs');
const readProvisions = require('./e2e/cypress/plugins/readProvisions.cjs');
const smtpTester = require('./e2e/cypress/plugins/smtpTester.cjs');
const typescriptPreprocessor = require('./e2e/cypress/plugins/typescriptPreprocessor.cjs');
module.exports = defineConfig({
projectId: 'zb7k1c',

View File

@@ -1,8 +1,8 @@
const fs = require('fs');
const { fromPairs } = require('lodash');
const { CDPDataCollector } = require('./CDPDataCollector');
const { formatResults } = require('./formatting');
const { CDPDataCollector } = require('./CDPDataCollector.cjs');
const { formatResults } = require('./formatting.cjs');
const remoteDebuggingPortOptionPrefix = '--remote-debugging-port=';

View File

@@ -2,10 +2,10 @@ const fs = require('fs');
const path = require('path');
const benchmarkPlugin = require('./benchmark');
const extendConfig = require('./extendConfig');
const readProvisions = require('./readProvisions');
const smtpTester = require('./smtpTester');
const typescriptPreprocessor = require('./typescriptPreprocessor');
const extendConfig = require('./extendConfig.cjs');
const readProvisions = require('./readProvisions.cjs');
const smtpTester = require('./smtpTester.cjs');
const typescriptPreprocessor = require('./typescriptPreprocessor.cjs');
module.exports = (on, config) => {
if (config.env['BENCHMARK_PLUGIN_ENABLED'] === true) {

View File

@@ -1,4 +1,4 @@
require('./commands');
import './commands.js';
Cypress.Screenshot.defaults({
screenshotOnRunFailure: false,

View File

@@ -0,0 +1,5 @@
{
"name": "grafana-e2e-playwright-tests",
"description": "This package.json exists to override the 'type': 'module' setting in root package.json",
"type": "commonjs"
}

View File

@@ -3,15 +3,13 @@ import path, { dirname } from 'path';
import { PluginOptions } from '@grafana/plugin-e2e';
const testDirRoot = 'e2e/plugin-e2e/';
export default defineConfig<PluginOptions>({
fullyParallel: true,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
reporter: [['html', { outputFolder: '../../playwright-report' }]],
use: {
baseURL: `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`,
trace: 'retain-on-failure',
@@ -44,7 +42,7 @@ export default defineConfig<PluginOptions>({
// Run all tests in parallel using user with admin role
{
name: 'admin',
testDir: path.join(testDirRoot, '/plugin-e2e-api-tests/as-admin-user'),
testDir: './plugin-e2e-api-tests/as-admin-user',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -54,7 +52,7 @@ export default defineConfig<PluginOptions>({
// Run all tests in parallel using user with viewer role
{
name: 'viewer',
testDir: path.join(testDirRoot, '/plugin-e2e-api-tests/as-viewer-user'),
testDir: './plugin-e2e-api-tests/as-viewer-user',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/viewer.json',
@@ -63,7 +61,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'elasticsearch',
testDir: path.join(testDirRoot, '/elasticsearch'),
testDir: './elasticsearch',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -72,7 +70,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'mysql',
testDir: path.join(testDirRoot, '/mysql'),
testDir: './mysql',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -81,7 +79,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'mssql',
testDir: path.join(testDirRoot, '/mssql'),
testDir: './mssql',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -108,7 +106,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'cloudwatch',
testDir: path.join(testDirRoot, '/cloudwatch'),
testDir: './cloudwatch',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -117,7 +115,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'azuremonitor',
testDir: path.join(testDirRoot, '/azuremonitor'),
testDir: 'e2e/test-plugins/azuremonitor',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -126,7 +124,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'cloudmonitoring',
testDir: path.join(testDirRoot, '/cloudmonitoring'),
testDir: 'e2e/test-plugins/cloudmonitoring',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -135,7 +133,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'graphite',
testDir: path.join(testDirRoot, '/graphite'),
testDir: 'e2e/test-plugins/graphite',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -144,7 +142,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'influxdb',
testDir: path.join(testDirRoot, '/influxdb'),
testDir: 'e2e/test-plugins/influxdb',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -153,7 +151,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'opentsdb',
testDir: path.join(testDirRoot, '/opentsdb'),
testDir: 'e2e/test-plugins/opentsdb',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -162,7 +160,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'jaeger',
testDir: path.join(testDirRoot, '/jaeger'),
testDir: 'e2e/test-plugins/jaeger',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -171,7 +169,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'grafana-postgresql-datasource',
testDir: path.join(testDirRoot, '/grafana-postgresql-datasource'),
testDir: 'e2e/test-plugins/grafana-postgresql-datasource',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
@@ -180,7 +178,7 @@ export default defineConfig<PluginOptions>({
},
{
name: 'zipkin',
testDir: path.join(testDirRoot, '/zipkin'),
testDir: 'e2e/test-plugins/zipkin',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',

View File

@@ -41,7 +41,7 @@ declare -A cypressConfig=(
[viewportWidth]=1920
[viewportHeight]=1080
[trashAssetsBeforeRuns]=false
[reporter]=./e2e/log-reporter.js
[reporter]=./e2e/log-reporter.cjs
[baseUrl]=${BASE_URL:-"http://$HOST:$PORT"}
)

View File

@@ -13,11 +13,14 @@ const unicornPlugin = require('eslint-plugin-unicorn');
const grafanaConfig = require('@grafana/eslint-config/flat');
const grafanaPlugin = require('@grafana/eslint-plugin');
const bettererConfig = require('./.betterer.eslint.config');
const getEnvConfig = require('./scripts/webpack/env-util');
const bettererConfig = require('./scripts/betterer/.betterer.eslint.config.js');
const getEnvConfig = require('./scripts/webpack/env-util.cjs');
const envConfig = getEnvConfig();
const enableBettererRules = envConfig.frontend_dev_betterer_eslint_rules;
/**
* @type {Record<`frontend_dev_${string}`, unknown>}
*/
const frontendEnvConfig = getEnvConfig();
const enableBettererRules = frontendEnvConfig.frontend_dev_betterer_eslint_rules;
/**
* @type {Array<import('eslint').Linter.Config>}

View File

@@ -27,7 +27,10 @@ module.exports = {
verbose: false,
testEnvironment: 'jsdom',
transform: {
'^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest'), { isolatedModules: true }],
'^.+\\.(ts|tsx|js|jsx)$': [
require.resolve('ts-jest'),
{ tsconfig: '<rootDir>/tsconfig.test.json', isolatedModules: true },
],
},
transformIgnorePatterns: [
`/node_modules/(?!${esModules})`, // exclude es modules to prevent TS complaining
@@ -38,7 +41,7 @@ module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
setupFiles: ['jest-canvas-mock', './public/test/jest-setup.ts'],
testTimeout: 30000,
resolver: `<rootDir>/public/test/jest-resolver.js`,
resolver: '<rootDir>/public/test/jest-resolver.cjs',
setupFilesAfterEnv: ['./public/test/setupTests.ts'],
globals: {
__webpack_public_path__: '', // empty string
@@ -58,6 +61,6 @@ module.exports = {
'@bsull/augurs': '<rootDir>/public/test/mocks/augurs.ts',
},
// Log the test results with dynamic Loki tags. Drone CI only
reporters: ['default', ['<rootDir>/public/test/log-reporter.js', { enable: process.env.DRONE === 'true' }]],
reporters: ['default', ['<rootDir>/public/test/log-reporter.cjs', { enable: process.env.DRONE === 'true' }]],
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
};

View File

@@ -5,11 +5,12 @@
"name": "grafana",
"version": "11.6.0-pre",
"repository": "github:grafana/grafana",
"type": "module",
"scripts": {
"build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress",
"build:nominify": "yarn run build -- --env noMinify=1",
"build:stats": "NODE_ENV=production webpack --progress --config scripts/webpack/webpack.stats.js",
"dev": "NODE_ENV=dev nx exec -- webpack --config scripts/webpack/webpack.dev.js",
"build": "NODE_ENV=production nx exec --verbose -- vite build",
"build:nominify": "NO_MINIFY=1 yarn run build",
"build:stats": "ANALYZE_BUNDLE=1 yarn run build",
"dev": "NODE_ENV=dev nx exec -- vite",
"e2e": "./e2e/start-and-run-suite",
"e2e:old-arch": "./e2e/start-and-run-suite old-arch",
"e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2",
@@ -19,7 +20,7 @@
"e2e:enterprise": "./e2e/start-and-run-suite enterprise",
"e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev",
"e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug",
"e2e:playwright": "yarn playwright test",
"e2e:playwright": "yarn playwright test -c ./e2e/plugin-e2e/playwright.config.ts",
"e2e:playwright:server": "yarn e2e:plugin:build && ./e2e/plugin-e2e/start-and-run-suite",
"e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true",
"e2e:plugin:build": "nx run-many -t build --projects='@test-plugins/*'",
@@ -41,7 +42,7 @@
"prettier:check": "prettier --check --list-different=false --log-level=warn \"**/*.{ts,tsx,scss,md,mdx,json,js}\"",
"prettier:checkDocs": "prettier --check --list-different=false --log-level=warn \"docs/**/*.md\" \"*.md\" \"packages/**/*.{ts,tsx,scss,md,mdx,json}\"",
"prettier:write": "prettier --list-different \"**/*.{js,ts,tsx,scss,md,mdx,json}\" --write",
"start": "NODE_ENV=dev nx exec -- webpack --config scripts/webpack/webpack.dev.js --watch",
"start": "NODE_ENV=development nx exec -- vite",
"start:liveReload": "yarn start -- --env liveReload=1",
"start:noTsCheck": "yarn start -- --env noTsCheck=1",
"start:noLint": "yarn start -- --env noTsCheck=1 --env noLint=1",
@@ -55,16 +56,16 @@
"watch": "yarn start -d watch,start core:start --watchTheme",
"ci:test-frontend": "yarn run test:ci",
"i18n:stats": "node ./scripts/cli/reportI18nStats.mjs",
"betterer": "betterer --tsconfig ./scripts/cli/tsconfig.json",
"betterer": "betterer --config ./scripts/betterer/.betterer.ts --results ./scripts/betterer/.betterer.results --tsconfig ./scripts/cli/tsconfig.json",
"betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts",
"betterer:issues": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/generateBettererIssues.ts",
"betterer:ci": "betterer ci --tsconfig ./scripts/cli/tsconfig.json",
"betterer:ci": "betterer ci --config ./scripts/betterer/.betterer.ts --results ./scripts/betterer/.betterer.results --tsconfig ./scripts/cli/tsconfig.json",
"plugin:build": "nx run-many -t build --projects='tag:scope:plugin'",
"plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'",
"plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100",
"generate-icons": "nx run grafana-icons:generate",
"process-specs": "node --experimental-strip-types scripts/process-specs.ts",
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts"
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.cts"
},
"grafana": {
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/",
@@ -84,7 +85,6 @@
"@grafana/tsconfig": "^2.0.0",
"@manypkg/get-packages": "^2.2.0",
"@playwright/test": "1.50.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
"@react-types/button": "3.10.2",
"@react-types/menu": "3.9.14",
"@react-types/overlays": "3.8.12",
@@ -113,15 +113,16 @@
"@types/eslint": "9.6.1",
"@types/eslint-scope": "^3.7.7",
"@types/file-saver": "2.0.7",
"@types/fs-extra": "11.0.4",
"@types/glob": "^8.0.0",
"@types/google.analytics": "^0.0.46",
"@types/gtag.js": "^0.0.20",
"@types/history": "4.7.11",
"@types/html-minifier-terser": "^7",
"@types/ini": "^4",
"@types/jest": "29.5.14",
"@types/jquery": "3.5.32",
"@types/js-yaml": "^4.0.5",
"@types/jsurl": "^1.2.28",
"@types/lodash": "4.17.15",
"@types/logfmt": "^1.2.3",
"@types/lucene": "^2",
@@ -151,11 +152,10 @@
"@types/systemjs": "6.15.1",
"@types/tinycolor2": "1.4.6",
"@types/uuid": "10.0.0",
"@types/webpack-assets-manifest": "^5",
"@types/webpack-env": "^1.18.4",
"@types/yargs": "17.0.33",
"@typescript-eslint/eslint-plugin": "8.22.0",
"@typescript-eslint/parser": "8.22.0",
"@vitejs/plugin-react-swc": "^3.7.2",
"autoprefixer": "10.4.20",
"babel-loader": "9.2.1",
"blob-polyfill": "9.0.20240710",
@@ -163,11 +163,8 @@
"chance": "^1.0.10",
"chrome-remote-interface": "0.33.2",
"codeowners": "^5.1.1",
"copy-webpack-plugin": "12.0.2",
"core-js": "3.40.0",
"crashme": "0.0.15",
"css-loader": "7.1.2",
"css-minimizer-webpack-plugin": "7.0.0",
"cypress": "13.10.0",
"cypress-file-upload": "5.0.8",
"cypress-recurse": "^1.35.3",
@@ -188,13 +185,10 @@
"eslint-plugin-testing-library": "^7.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-scope": "^8.1.0",
"eslint-webpack-plugin": "4.2.0",
"expose-loader": "5.0.0",
"fishery": "^2.2.2",
"fork-ts-checker-webpack-plugin": "9.0.2",
"fs-extra": "11.2.0",
"glob": "11.0.1",
"html-loader": "5.1.0",
"html-webpack-plugin": "5.6.3",
"html-minifier-terser": "^7.2.0",
"http-server": "14.1.1",
"i18next-parser": "9.3.0",
"ini": "^5.0.0",
@@ -209,45 +203,35 @@
"jimp": "^1.6.0",
"jsdom-testing-mocks": "^1.13.1",
"lerna": "8.1.8",
"mini-css-extract-plugin": "2.9.2",
"msw": "2.7.0",
"mutationobserver-shim": "0.3.7",
"ngtemplate-loader": "2.1.0",
"node-notifier": "10.0.1",
"nx": "19.8.2",
"openapi-types": "^12.1.3",
"pdf-parse": "^1.1.1",
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"postcss-reporter": "7.1.0",
"postcss-scss": "4.0.9",
"prettier": "3.4.2",
"pseudoizer": "^0.1.0",
"react-refresh": "0.14.0",
"react-select-event": "5.5.1",
"redux-mock-store": "1.5.5",
"rimraf": "6.0.1",
"rollup-plugin-visualizer": "^5.12.0",
"rudder-sdk-js": "2.48.44",
"sass": "1.83.4",
"sass-loader": "16.0.4",
"smtp-tester": "^2.1.0",
"style-loader": "4.0.0",
"stylelint": "16.14.1",
"stylelint-config-sass-guidelines": "12.1.0",
"terser-webpack-plugin": "5.3.11",
"testing-library-selector": "0.3.1",
"tracelib": "1.0.1",
"ts-jest": "29.2.5",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"vite": "^6.0.3",
"vite-plugin-environment": "^1.1.3",
"webpack": "5.97.1",
"webpack-assets-manifest": "^5.1.0",
"webpack-cli": "6.0.1",
"webpack-dev-server": "5.2.0",
"webpack-livereload-plugin": "3.0.2",
"webpack-manifest-plugin": "5.0.0",
"webpack-merge": "6.0.1",
"webpackbar": "^7.0.0",
"webpack-cli": "5.1.4",
"yaml": "^2.0.0",
"yargs": "^17.5.1"
},
@@ -341,7 +325,6 @@
"js-yaml": "^4.1.0",
"json-markup": "^1.1.0",
"json-source-map": "0.6.1",
"jsurl": "^0.1.5",
"kbar": "0.1.0-beta.45",
"leven": "^4.0.0",
"lodash": "4.17.21",
@@ -410,7 +393,6 @@
"symbol-observable": "4.0.0",
"systemjs": "6.15.1",
"systemjs-cjs-extra": "0.2.1",
"tether-drop": "https://github.com/torkelo/drop",
"tinycolor2": "1.6.0",
"tslib": "2.8.1",
"tween-functions": "^1.2.0",

View File

@@ -30,9 +30,7 @@
"scripts": {
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",

View File

@@ -4,7 +4,7 @@ import { Registry, RegistryItem } from '../utils/Registry';
* @alpha
*/
export interface MonacoLanguageRegistryItem extends RegistryItem {
init: () => Worker;
init: () => string;
}
/**

View File

@@ -1,9 +1,9 @@
import { sanitizeUrl as braintreeSanitizeUrl } from '@braintree/sanitize-url';
import DOMPurify from 'dompurify';
import * as xss from 'xss';
import { FilterXSS, type IWhiteList, escapeAttrValue, getDefaultCSSWhiteList, whiteList } from 'xss';
const XSSWL = Object.keys(xss.whiteList).reduce<xss.IWhiteList>((acc, element) => {
acc[element] = xss.whiteList[element]?.concat(['class', 'style']);
const XSSWL = Object.keys(whiteList).reduce<IWhiteList>((acc, element) => {
acc[element] = whiteList[element]?.concat(['class', 'style']);
return acc;
}, {});
@@ -11,12 +11,12 @@ const XSSWL = Object.keys(xss.whiteList).reduce<xss.IWhiteList>((acc, element) =
// We don't allow the sandbox attribute, since it can be overridden, instead we add it below.
XSSWL.iframe = ['src', 'width', 'height'];
const sanitizeTextPanelWhitelist = new xss.FilterXSS({
const sanitizeTextPanelWhitelist = new FilterXSS({
// Add sandbox attribute to iframe tags if an attribute is allowed.
onTagAttr: function (tag, name, value, isWhiteAttr) {
if (tag === 'iframe') {
return isWhiteAttr
? ` ${name}="${xss.escapeAttrValue(sanitizeUrl(value))}" sandbox credentialless referrerpolicy=no-referrer`
? ` ${name}="${escapeAttrValue(sanitizeUrl(value))}" sandbox credentialless referrerpolicy=no-referrer`
: '';
}
return;
@@ -24,7 +24,7 @@ const sanitizeTextPanelWhitelist = new xss.FilterXSS({
whiteList: XSSWL,
css: {
whiteList: {
...xss.getDefaultCSSWhiteList(),
...getDefaultCSSWhiteList(),
'flex-direction': true,
'flex-wrap': true,
'flex-basis': true,

View File

@@ -34,9 +34,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "16.0.0",

View File

@@ -34,9 +34,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"browserslist": [
"defaults",

View File

@@ -28,9 +28,10 @@
],
"scripts": {
"clean": "rimraf ./dist ./compiled ./package.tgz ./src/icons-gen",
"generate": "yarn clean && npx @svgr/cli ./svg --silent && mv ./src/icons-gen/index.ts ./src",
"typecheck": "yarn generate && tsc --emitDeclarationOnly false --noEmit",
"generate": "yarn clean && npx @svgr/cli ./svg --silent --no-prettier && mv ./src/icons-gen/index.ts ./src && yarn prettier:write",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"lint": "eslint --ext .ts,.tsx ./src",
"prettier:write": "prettier --list-different \"**/*.{js,ts,tsx}\" --write",
"prettier:check": "prettier --check --list-different=false --log-level=warn \"**/*.{ts,tsx,scss,md,mdx,json}\"",
"build": "yarn generate && rollup -c rollup.config.ts --configPlugin esbuild"
},

View File

@@ -3,7 +3,8 @@
"jsx": "react-jsx",
"alwaysStrict": true,
"declaration": false,
"resolveJsonModule": true
"resolveJsonModule": true,
"isolatedModules": true
},
"ts-node": {
"compilerOptions": {

View File

@@ -227,6 +227,8 @@ const config = async (env: Record<string, unknown>): Promise<Configuration> => {
stats: 'minimal',
target: 'web',
watchOptions: {
ignored: ['**/node_modules', '**/dist', '**/.yarn'],
},

View File

@@ -31,9 +31,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"dependencies": {
"@emotion/css": "11.13.5",

View File

@@ -32,9 +32,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"dependencies": {
"@grafana/data": "11.6.0-pre",

View File

@@ -31,9 +31,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"devDependencies": {
"@grafana/tsconfig": "^2.0.0",

View File

@@ -37,9 +37,7 @@
"clean": "rimraf ./dist ./compiled ./package.tgz",
"storybook": "storybook dev -p 9001 -c .storybook --no-open",
"storybook:build": "storybook build -o ./dist/storybook -c .storybook",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
"postpack": "mv package.json.bak package.json"
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"browserslist": [
"defaults",

View File

@@ -1,14 +1,11 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import $ from 'jquery';
import { ThresholdsMode, FieldConfig, FieldColorModeId, createTheme } from '@grafana/data';
import { Gauge, Props } from './Gauge';
jest.mock('jquery', () => ({
plot: jest.fn(),
}));
const field: FieldConfig = {
min: 0,
max: 100,
@@ -38,6 +35,9 @@ const props: Props = {
};
describe('Gauge', () => {
// @ts-ignore - mock jquery plot to prevent console.errors
$.plot = jest.fn();
it('should render without blowing up', () => {
expect(() => render(<Gauge {...props} />)).not.toThrow();
});

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { PureComponent } from 'react';
import * as React from 'react';

View File

@@ -0,0 +1,21 @@
import { createContext } from 'react';
export interface LayoutItemContextProps {
boostZIndex(): () => void;
}
/**
* Provides an API for downstream components (e.g. within panels) to inform the layout
* that anchored tooltips or context menus could overflow the panel bounds. The layout
* system can then boost the z-index of items with any anchored contents to prevent the overflown
* content from rendering underneath adjacent layout items (e.g. other panels) that naturally
* render later/higher in the stacking order
*
* This is used by VizTooltips and Annotations, which anchor to data points or time range within
* the viz drawing area
*
* @internal
*/
export const LayoutItemContext = createContext<LayoutItemContextProps>({
boostZIndex: () => () => {},
});

View File

@@ -35,7 +35,7 @@ export interface State {
export class Typeahead extends PureComponent<Props, State> {
static contextType = ThemeContext;
context!: React.ContextType<typeof ThemeContext>;
declare context: React.ContextType<typeof ThemeContext>;
listRef = createRef<FixedSizeList>();
state: State = {

View File

@@ -284,6 +284,8 @@ export { Divider } from './Divider/Divider';
export { getDragStyles, type DragHandlePosition } from './DragHandle/DragHandle';
export { useSplitter } from './Splitter/useSplitter';
export { LayoutItemContext, type LayoutItemContextProps } from './Layout/LayoutItemContext';
/** @deprecated Please use non-legacy versions of these components */
const LegacyForms = {
SecretFormField,

View File

@@ -1,5 +1,5 @@
// Libraries
import $ from 'jquery';
import 'jquery';
import { uniqBy } from 'lodash';
import { PureComponent } from 'react';
import * as React from 'react';

View File

@@ -23,6 +23,7 @@ type IndexViewData struct {
NewGrafanaVersion string
AppName string
AppNameBodyClass string
FrontendDevServer bool
FavIcon template.URL
AppleTouchIcon template.URL
AppTitle string
@@ -39,11 +40,12 @@ type IndexViewData struct {
type EntryPointAssets struct {
ContentDeliveryURL string `json:"cdn,omitempty"`
JSFiles []EntryPointAsset `json:"jsFiles"`
CSSFiles []EntryPointAsset `json:"cssFiles"`
Dark string `json:"dark"`
Light string `json:"light"`
Swagger []EntryPointAsset `json:"swagger"`
SwaggerCSSFiles []EntryPointAsset `json:"swaggerCssFiles"`
PreloadJSFiles []EntryPointAsset `json:"preloadJsFiles"`
// CSSFiles []EntryPointAsset `json:"cssFiles"`
Dark string `json:"dark"`
Light string `json:"light"`
// Swagger []EntryPointAsset `json:"swagger"`
// SwaggerCSSFiles []EntryPointAsset `json:"swaggerCssFiles"`
}
type EntryPointAsset struct {
@@ -61,13 +63,16 @@ func (a *EntryPointAssets) SetContentDeliveryURL(prefix string) {
for i, p := range a.JSFiles {
a.JSFiles[i].FilePath = prefix + p.FilePath
}
for i, p := range a.CSSFiles {
a.CSSFiles[i].FilePath = prefix + p.FilePath
}
for i, p := range a.Swagger {
a.Swagger[i].FilePath = prefix + p.FilePath
}
for i, p := range a.SwaggerCSSFiles {
a.SwaggerCSSFiles[i].FilePath = prefix + p.FilePath
for i, p := range a.PreloadJSFiles {
a.PreloadJSFiles[i].FilePath = prefix + p.FilePath
}
// for i, p := range a.CSSFiles {
// a.CSSFiles[i].FilePath = prefix + p.FilePath
// }
// for i, p := range a.Swagger {
// a.Swagger[i].FilePath = prefix + p.FilePath
// }
// for i, p := range a.SwaggerCSSFiles {
// a.SwaggerCSSFiles[i].FilePath = prefix + p.FilePath
// }
}

View File

@@ -120,6 +120,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
ThemeType: theme.Type,
AppUrl: appURL,
AppSubUrl: appSubURL,
FrontendDevServer: hs.Cfg.FrontendDevServer,
NewsFeedEnabled: hs.Cfg.NewsFeedEnabled,
GoogleAnalyticsId: settings.GoogleAnalyticsId,
GoogleAnalytics4Id: settings.GoogleAnalytics4Id,

View File

@@ -1,135 +1,195 @@
{
"AdminAuthentication.js": {
"src": "public/build/AdminAuthentication.abcc504db867d1fa3469.js",
"integrity": "sha256-GtJEBbWfWAU2h2hoW+OiHUHNhHuKDzJ6mBEsVKyRc6M= sha384-SfBCROVbdjMZbxWZzYcYq1Ur4qVOmMgCQwRj9rc23op01045mOoRnCSwnENSNnhY sha512-RwKiHjN7M4Wf5BtO0nKFMoZTp0mCPVttkg4SU76+93VVdf8XEnTIpM6Trd+v4GX37wNrWEd34lh/Xaidq5t33g=="
"_vendor-!~{004}~.js": {
"file": "public/build/vendor-BrUJaDlA.css",
"src": "_vendor-!~{004}~.js"
},
"AdminEditOrgPage.js": {
"src": "public/build/AdminEditOrgPage.960dc1174d73f4fd46c3.js",
"integrity": "sha256-y9TYWD3xHk5MQiBpvSpKvFgtp7yHovE0hfrUppxsQuw= sha384-kCrsPv6DmRzbRW/ajBaGu5X67nNRRAuOaZZYONCm8YA6KQclrEHG8navPSsMROnk sha512-2oE8w7f0s/vo6SOgDteuYSvuvAAjqMFjPpD4t7b11sah4B007L9dIVs9MF3jTSKsSHlG61hK6L2QCgv+9FtrFg=="
"_vendor-r65R1Ntf.js": {
"file": "public/build/vendor-r65R1Ntf.js",
"name": "vendor",
"isDynamicEntry": true,
"dynamicImports": [
"../packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx",
"../node_modules/monaco-promql/promql/promql.js"
],
"css": [
"public/build/vendor-BrUJaDlA.css"
],
"assets": [
"public/build/grot-cta-B42JPuvP.svg",
"public/build/grot-not-found-Bjhtecdw.svg",
"public/build/grot-completed-D-h3ZMGB.svg"
]
},
"AdminFeatureTogglesPage.js": {
"src": "public/build/AdminFeatureTogglesPage.999db7797f1efddde074.js",
"integrity": "sha256-fhZz2BcvirKLQlxqcEprDplsSRAWYu+FiZWMRtFig1g= sha384-V2e+1wmlsKjorbNlJNzDfizXd1E4q8VpqaIX8S8FkumdZwFPPCXMsnHd77rjFKks sha512-LSiPxyiP0tmZ7fS/QOQBnC98pqgLDSATU0V9He5ubNEgziolxd/XmqoSDTGtp1WWZfjDxW6SUoKVmwstc0VzxA=="
"_braintree-CW7sY1ng.js": {
"file": "public/build/braintree-CW7sY1ng.js",
"name": "braintree"
},
"AdminListOrgsPage.js": {
"src": "public/build/AdminListOrgsPage.c573cf876050fc991990.js",
"integrity": "sha256-RSxk6cc52+j9cITdwi4k5lgzER+sKF7Mx5pn6atMYdE= sha384-xE53wdhi0O9e9A+9NKHGlvJgEeSew6ooXYl6oS8e5Qq9slP2TQygvO48/3CCYWJM sha512-YfXZHm9jyZbHAzcAimOv96ZJWEpQrzjZPxxvyVZZVPfB/8Z1FL7rhKhk0HGIhufXTTLhobZp/9CkFKT4no7zTA=="
"_viridis--y8fz-dE.js": {
"file": "public/build/viridis--y8fz-dE.js",
"name": "viridis",
"imports": [
"_linear-D23a9ZoO.js",
"_vendor-r65R1Ntf.js"
]
},
"AdminSettings.js": {
"src": "public/build/AdminSettings.cfdfc2e959a29968d661.js",
"integrity": "sha256-v/5L4MXpTnSZXjApozisGbt+y+DPfpjWhH3D6bY83zE= sha384-FTIo/3tLgPKqgxwpxXSa8SLoU5hz4HJcgrVgXD2g06/GUnBFLrJcN3lYqYrvsgLu sha512-kBBoMB2XBtXM/iWW0zAFdI9O3ggetQZdpj6LhsiPDLy9pwRhwPQlnwZjlFkJMMEX0YK2uYl3dn9DUUyTKjtVMg=="
"_vis-B4F-Fj9n.js": {
"file": "public/build/vis-B4F-Fj9n.js",
"name": "vis",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js"
]
},
"AlertAmRoutes.js": {
"src": "public/build/AlertAmRoutes.bd900447d272cba91413.js",
"integrity": "sha256-K6hY1ruuidLNg5aYV4XZVBpTFCcS7T2q4b1mF9lPOf4= sha384-SFNQUdxgwLjHW/JsukI7Juhs6f2+qdxMCNbsD4ix3Ha44HbiXTKPBTmvxMl4r+CS sha512-eSrh2wrOXlLp9bXd+0zsZS4v8xjJTufSi0Kfgx0FSB8WhnjhOnIOX37wszsV00w1AwyO6VE6yN3q2PGTrpUoeg=="
"app/features/teams/TeamList.tsx": {
"file": "public/build/TeamList-DSwjs7Wa.js",
"name": "TeamList",
"src": "app/features/teams/TeamList.tsx",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js",
"app/index.ts",
"_api-y46G0_KO.js",
"_TeamRolePicker-Yl7zWxKI.js",
"_actions-DdmCGKOg.js",
"_ProBadge-DkW08ySd.js",
"_utils-CQ7yl5br.js"
]
},
"AlertGroups.js": {
"src": "public/build/AlertGroups.d04aee7a0d92a3726e5c.js",
"integrity": "sha256-GrsAPOHbrj3qJx97ugYdqF7fEtD1KU+VGZC9EnCzXhM= sha384-WjJwpX218WLCpDgRSkYbHvwjFy0ttlIb9fYpG0pLDgRu9iYQKBZXaYMU9PDKB4Iu sha512-hpE6YahXnP4RSOJ3Xd+oS55gmtDWHDnMrDNr2UOXpWbLuQQJu8e8uRAW5cIBl8u2S63F1SC1vGx6d535hECq2A=="
"app/features/teams/TeamPages.tsx": {
"file": "public/build/TeamPages-1zHLHq7V.js",
"name": "TeamPages",
"src": "app/features/teams/TeamPages.tsx",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js",
"app/index.ts",
"_EmptyListCTA-C7ObQVwC.js",
"_utils-CQ7yl5br.js",
"_actions-DdmCGKOg.js",
"_TeamRolePicker-Yl7zWxKI.js",
"_hooks-t-SNFABd.js",
"_SharedPreferences-flYo2s4z.js",
"_ProBadge-DkW08ySd.js",
"_api-y46G0_KO.js"
]
},
"AlertRuleListIndex.js": {
"src": "public/build/AlertRuleListIndex.4682f9ab8dd5a8e68722.js",
"integrity": "sha256-xlMu10aTkvXAw8hmtsaEZXTrAqQjaglNkwS6GLRISnU= sha384-hTb2gf6p1Q/M7fULhnDzGOlVjuYjnrVAJCrQ+yNNr0T0EWZkJ+yxrjNh9Q7/9F+G sha512-8xMEU7qfa+rlm62FHWYFb4JNN3E0WNqbhjVdNA2Awq+ZbhSHuzb4XPnmDJoOzakZoG1s+ZgK7GL1wSXBrKbPkw=="
"app/features/trails/DataTrailsPage.tsx": {
"file": "public/build/DataTrailsPage-CVUXQGkO.js",
"name": "DataTrailsPage",
"src": "app/features/trails/DataTrailsPage.tsx",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js",
"app/index.ts"
]
},
"AlertSilences.js": {
"src": "public/build/AlertSilences.84de2bedd3634a40f4a4.js",
"integrity": "sha256-LamykmTC703pkL5q7hPdOp9zTQWn6ZsMKzRv7v35ubU= sha384-VWDeB7KZFOYL0RWBBXcmvzKds+Yxn2cDU8fSo4Srg165GY29s8+jO6sEG6OY9zj9 sha512-UvlIwoREIwdwkRWdut8+p+GGRgWTyAijW3pNQ1lQRUE8wGNYqBuHa1lGK6Jq4mmhkp1Pqo4/iI4Uk7vrbYFKow=="
"app/index.ts": {
"file": "public/build/index-fkCrlmGK.js",
"name": "index",
"src": "app/index.ts",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js"
],
"dynamicImports": [
"locales/de-DE/grafana.json",
"locales/en-US/grafana.json",
"locales/pseudo-LOCALE/grafana.json",
"app/plugins/panel/flamegraph/module.tsx",
"app/plugins/panel/geomap/module.tsx",
"app/features/teams/TeamList.tsx",
"app/features/teams/CreateTeam.tsx",
"app/features/teams/TeamPages.tsx",
"app/features/trails/DataTrailsPage.tsx",
"_vendor-r65R1Ntf.js"
],
"css": [
"public/build/index-DTTxS2na.css"
],
"assets": [
"public/build/outlier_bg-hJPFLdCg.wasm"
]
},
"app.css": {
"src": "public/build/grafana.app.91aaa9d81398c147a57c.css",
"integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ=="
"app/core/trustedTypePolicies.ts": {
"file": "public/build/trustedTypePolicies-pOmZ7Wr2.js",
"name": "trustedTypePolicies",
"src": "app/core/trustedTypePolicies.ts",
"isEntry": true,
"imports": [
"_braintree-CW7sY1ng.js"
]
},
"app.js": {
"src": "public/build/app.js",
"integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w=="
"app/plugins/panel/flamegraph/module.tsx": {
"file": "public/build/module-J53A9Kyn.js",
"name": "module",
"src": "app/plugins/panel/flamegraph/module.tsx",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js",
"_FlameGraphContainer-DF3CoxWe.js",
"app/index.ts",
"_transform-Bj7g0BsI.js",
"_linear-D23a9ZoO.js",
"_useDebounce-Cf1ms9C9.js"
]
},
"dark.css": {
"src": "public/build/grafana.dark.722d809dba5a31f57d49.css",
"integrity": "sha256-kyPBeKRFdG+i4aLA6vZ0nIkSD25YTHjcCfQ3e2+M6AA= sha384-o2yYT3suDP1EtvTj4UzLsSS+AAlv0F+n5YVaxDePRM6LSGAEPTxFOfxB3myAqD/x sha512-H3Eos9Ff7d+tB7e+6RORDXY49LjNMa8X3ZxiFn8T/OlHUFRnsfXsvj65p9cMqaRom/4qqx0JtmhtnuBoeaysrg=="
"app/plugins/panel/geomap/module.tsx": {
"file": "public/build/module-0YAl9hqA.js",
"name": "module",
"src": "app/plugins/panel/geomap/module.tsx",
"isDynamicEntry": true,
"imports": [
"_vendor-r65R1Ntf.js",
"_ExemplarHoverView-BHzhrBsl.js",
"app/index.ts",
"_DataHoverView-v8a68bIA.js",
"_ColorScale-CltZV5Bm.js",
"_utils-GV5L4DHo.js",
"_AddLayerButton-B7iJGNLs.js",
"_utils-Dvc-eevq.js",
"_LayerName-kazir38s.js",
"_VizTooltipRow-B1eXWnxp.js",
"_quadtree-BhY-3CT1.js"
],
"css": [
"public/build/module-C4D2GwWP.css"
]
},
"dark.js": {
"src": "public/build/dark.js",
"integrity": "sha256-ON8tNAimyYNoju+WIQP7ux1Aq/oYspSbqoOKvVkeEJY= sha384-figjklDEMn19kCLZ9aq9pUeMGVOC3nlFkQI0PT016cep5KvAd4nyrAl4WA1MALHX sha512-P1xsw+PNS2AVneN4UFfYhYiS3FeEIDPAj4VXAjiQtHqw7qLETqv2K0sYRogoXma23Q/b+h3VL+fw6qtwhJ8WCQ=="
"fonts/fontawesome-webfont.woff2": {
"file": "public/build/fontawesome-webfont-B-jkhYfk.woff2",
"src": "fonts/fontawesome-webfont.woff2"
},
"default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js": {
"src": "public/build/default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js",
"integrity": "sha256-BfaH57oBHA9HC5GwpCNAREwyuEGsxbPR+eOEarSMqr0= sha384-elHEqjfd9Guir6jsvCIBsh/wCF4oJ6/wTFfgK9TsKyfyUJecmK3y1qlczBevMotL sha512-92Uk9x490/MViSclM7PjI6iQrM3FNRuiHGP5CWqNFjKX7PWLTcBirSs1xkoZ3tc0QG0m+klaJg6eNVEvpF25dQ=="
"fonts/grafana-icons.woff": {
"file": "public/build/grafana-icons-Dd-C-eHA.woff",
"src": "fonts/grafana-icons.woff"
},
"default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js": {
"src": "public/build/default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js",
"integrity": "sha256-elDlrzSExWWpznC49+xQWQletnWQ1Fl6VaarANz2oLk= sha384-2UdE8X6NbOjt/i0WXpvew+eSBXKSTySfFdfxrIrALT0erSyvdbB9oASWPfRuTZyi sha512-so/nTAcSfmcReoZrJUusUbj6xxtGdrKiJ50n0CMZ1yqBh2hB7Y15s4POeX8axeJTNlo59wJ5GAbsdVTy4c0QhA=="
"locales/de-DE/grafana.json": {
"file": "public/build/grafana-BJ4JxwFS.js",
"name": "grafana",
"src": "locales/de-DE/grafana.json",
"isDynamicEntry": true
},
"default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js": {
"src": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js",
"integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw=="
"locales/en-US/grafana.json": {
"file": "public/build/grafana-DkCX6RXR.js",
"name": "grafana",
"src": "locales/en-US/grafana.json",
"isDynamicEntry": true
},
"defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js": {
"src": "public/build/defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js",
"integrity": "sha256-6et8tLQscwAftNYIM4u8L42Xi+tFIlkPsKIlyATTuY4= sha384-9AKIelnSKqxpMktU6Fw1uE1Kz6ZuEtI2ExvQ1PTjwPmpAE41LS6WxQ3pSV6qlLyz sha512-zcqwJExa8qzqZ1cCIK/y+HrWhJxP6rgSC+SbqxRStQUZrU4S1DQa1pwe6eSoZ4DPht/8rSeIii1wnl3DyhdVVg=="
"locales/pseudo-LOCALE/grafana.json": {
"file": "public/build/grafana-BwPdh-XN.js",
"name": "grafana",
"src": "locales/pseudo-LOCALE/grafana.json",
"isDynamicEntry": true
},
"defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js": {
"src": "public/build/defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js",
"integrity": "sha256-bVtkMWml/TQfwbkV9ueteGi+GhLh7ajxtVevi3gb4Aw= sha384-8+VsDObIkyeW90JyQELbiOsPKFJaBOmScMhBuWKmhtBmV9/7YvjIvqEPvpz1H5gP sha512-L3PzrsTEqfR+ruNzEaIqt0V0ZyX4V1fqbOe7aXWMIOr8JjJSJT1rMPVqJixy5DOL2f0zLDEc/DJvk4SN4BrDuw=="
"sass/grafana.dark.scss": {
"file": "public/build/grafana-B9F4fUOy.css",
"src": "sass/grafana.dark.scss",
"isEntry": true
},
"defaultVendors-node_modules_swagger-ui-react_index_mjs.js": {
"src": "public/build/defaultVendors-node_modules_swagger-ui-react_index_mjs.js",
"integrity": "sha256-7qtYNxawpAwQUd6KR+2IgH95hkr0w5hZLGFeubAUkcc= sha384-29fQncD6F/vaoKmk4Ccht9JbnrPo2BSyUdHSkTkF4RWEFkhgVEUo1TlSTDc3iOcd sha512-UhXtPd5nXyvyVR0vNYoO5wINJgb49oeYoqfnNaVQndg3n9uIvPi4lr3iUk/jkBsnVH1dEDWiZIErDPiTyHe4+w=="
},
"light.css": {
"src": "public/build/grafana.light.2fbd901d840329c18394.css",
"integrity": "sha256-OumSnRJ7qttDoFmbB3LMxVvrfpFFjABX5b1iZDcRMmk= sha384-Cq4wA1zPU1cqxA/pc2V+iMwVjzsSdL09vM8xg4fSofGen7YIj6sVUgs2X4AdTzm/ sha512-6J9O/4UFL8undkyqbEGAlNnEjm5N8Us5k9rJdcjhZwYKt1UIKXapw8f320k3tv1N0GB0Wt4xO6EKD9MsrIkCSw=="
},
"light.js": {
"src": "public/build/light.js",
"integrity": "sha256-TDwcqlc6Pc8yTRNLOk0jp3Wnp06cnF/OSaFvwOnwCVQ= sha384-xJv6KB8Qm9Skru8PY5CRdU9a59e94tcYWonpy5rqaci6fiecQM3aH+eHe41succB sha512-sPXciUAa7rpRofN6b93v9+tWpLP88ER6TqAGpbHkz69mxuQLJv1p580fEfxLeIs3HfLkDXnVPK7rGF0d5Evrog=="
},
"moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js": {
"src": "public/build/moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js",
"integrity": "sha256-BvrNPeEZbt43PpUlZ4r5pUougn3OzHIHKZN9pa5fotw= sha384-gMrZ0j/kyHwx2bhyt5ZjZOPlK7P7TRN6rBnk/0c9iF2X83GVTWNjhHcldmlYJXnf sha512-B+39Sk+Rwdo9hURcDoWL/61/sbMsTlqfES+FCsMC1r2xUH/Fyw/U2H0CgIi+c3AJyu1GLEvp//QBN0RZxaizhw=="
},
"runtime.js": {
"src": "public/build/runtime.js",
"integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ=="
},
"swagger.css": {
"src": "public/build/grafana.swagger.2733d417270d5dd49373.css",
"integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg=="
},
"swagger.js": {
"src": "public/build/swagger.js",
"integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg=="
},
"entrypoints": {
"app": {
"assets": {
"js": [
"public/build/runtime.js",
"public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js",
"public/build/app.js"
],
"css": ["public/build/grafana.app.91aaa9d81398c147a57c.css"]
}
},
"swagger": {
"assets": {
"js": [
"public/build/runtime.js",
"public/build/swagger.js"
],
"css": ["public/build/grafana.swagger.2733d417270d5dd49373.css"]
}
},
"dark": {
"assets": {
"js": ["public/build/runtime.js", "public/build/dark.js"],
"css": ["public/build/grafana.dark.722d809dba5a31f57d49.css"]
}
},
"light": {
"assets": {
"js": ["public/build/runtime.js", "public/build/light.js"],
"css": ["public/build/grafana.light.2fbd901d840329c18394.css"]
}
}
"sass/grafana.light.scss": {
"file": "public/build/grafana-Cu0f8nVU.css",
"src": "sass/grafana.light.scss",
"isEntry": true
}
}

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/grafana/grafana/pkg/api/dtos"
@@ -26,6 +27,18 @@ type ManifestInfo struct {
Swagger *EntryPointInfo `json:"swagger,omitempty"`
}
// ViteManifestEntry represents a single entry in the Vite manifest.
type ViteManifestEntry struct {
File string `json:"file"`
Imports []string `json:"imports"`
IsDynamicEntry bool `json:"isDynamicEntry"`
IsEntry bool `json:"isEntry"`
Src string `json:"src"`
Name string `json:"name"`
}
type Manifest map[string]ViteManifestEntry
type EntryPointInfo struct {
Assets struct {
JS []string `json:"js,omitempty"`
@@ -43,6 +56,28 @@ func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licen
ret := entryPointAssetsCache
entryPointAssetsCacheMu.RUnlock()
// see https://vitejs.dev/guide/backend-integration.html
if cfg.Env == setting.Dev && cfg.FrontendDevServer {
devServerHost := "http://localhost:5173"
viteDev := &dtos.EntryPointAssets{
JSFiles: []dtos.EntryPointAsset{
{
FilePath: fmt.Sprintf("%s/@vite/client", devServerHost),
Integrity: "",
},
{
FilePath: fmt.Sprintf("%s/app/index.ts", devServerHost),
Integrity: "",
},
},
Dark: fmt.Sprintf("%s/sass/grafana.dark.scss", devServerHost),
Light: fmt.Sprintf("%s/sass/grafana.light.scss", devServerHost),
}
return viteDev, nil
}
if cfg.Env != setting.Dev && ret != nil {
return ret, nil
}
@@ -58,7 +93,7 @@ func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licen
}
if result == nil {
result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json"))
result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", ".vite", "manifest.json"))
if err == nil {
cdn, _ = cfg.GetContentDeliveryURL(license.ContentDeliveryPrefix())
if cdn != "" {
@@ -84,7 +119,7 @@ func readWebAssetsFromFile(manifestpath string) (*dtos.EntryPointAssets, error)
}
func readWebAssetsFromCDN(ctx context.Context, baseURL string) (*dtos.EntryPointAssets, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/build/assets-manifest.json", nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/build/.vite/manifest.json", nil)
if err != nil {
return nil, err
}
@@ -103,68 +138,145 @@ func readWebAssetsFromCDN(ctx context.Context, baseURL string) (*dtos.EntryPoint
}
func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) {
manifest := map[string]ManifestInfo{}
manifest := Manifest{}
if err := json.NewDecoder(r).Decode(&manifest); err != nil {
return nil, fmt.Errorf("failed to read assets-manifest.json %w", err)
}
integrity := make(map[string]string, 100)
for _, v := range manifest {
if v.Integrity != "" && v.FilePath != "" {
integrity[v.FilePath] = v.Integrity
// TODO: This is temporary to get the entrypoints for the vite frontend loading.
// We need trusted types up front otherwise the entire app will fail to load if they're enabled.
var trustedTypePolicyAsset *dtos.EntryPointAsset
var otherJSAssets []dtos.EntryPointAsset
var preloadJSAssets []dtos.EntryPointAsset
var darkCSS, lightCSS string
for _, entry := range manifest {
if entry.IsEntry {
asset := dtos.EntryPointAsset{
FilePath: entry.File,
// Not sure what should happen with integrity here
Integrity: "",
}
if strings.HasSuffix(entry.File, ".js") {
if entry.Name == "trustedTypePolicies" {
trustedTypePolicyAsset = &asset
} else {
otherJSAssets = append(otherJSAssets, asset)
}
preloadJSAssets = append(preloadJSAssets, getPreloadChunks(manifest, entry.Src)...)
}
if entry.Src == "sass/grafana.dark.scss" && entry.IsEntry {
darkCSS = entry.File
}
if entry.Src == "sass/grafana.light.scss" && entry.IsEntry {
lightCSS = entry.File
}
}
}
entryPoints, ok := manifest["entrypoints"]
if !ok {
return nil, fmt.Errorf("could not find entrypoints in asssets-manifest")
}
// integrity := make(map[string]string, 100)
// for _, v := range manifest {
// if v.Integrity != "" && v.FilePath != "" {
// integrity[v.FilePath] = v.Integrity
// }
// }
if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 {
return nil, fmt.Errorf("missing app entry, try running `yarn build`")
}
if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 {
return nil, fmt.Errorf("missing dark entry, try running `yarn build`")
}
if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 {
return nil, fmt.Errorf("missing light entry, try running `yarn build`")
}
if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 {
return nil, fmt.Errorf("missing swagger entry, try running `yarn build`")
// entryPoints, ok := manifest["entrypoints"]
// if !ok {
// return nil, fmt.Errorf("could not find entrypoints in asssets-manifest")
// }
// if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 {
// return nil, fmt.Errorf("missing app entry, try running `yarn build`")
// }
// if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 {
// return nil, fmt.Errorf("missing dark entry, try running `yarn build`")
// }
// if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 {
// return nil, fmt.Errorf("missing light entry, try running `yarn build`")
// }
// if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 {
// return nil, fmt.Errorf("missing swagger entry, try running `yarn build`")
// }
var entryPointJSAssets = append([]dtos.EntryPointAsset{*trustedTypePolicyAsset}, otherJSAssets...)
if entryPointJSAssets == nil {
return nil, fmt.Errorf("no entrypoints found in assets manifest, try running `yarn build`")
}
rsp := &dtos.EntryPointAssets{
JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)),
CSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.CSS)),
Dark: entryPoints.Dark.Assets.CSS[0],
Light: entryPoints.Light.Assets.CSS[0],
Swagger: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.JS)),
SwaggerCSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.CSS)),
JSFiles: entryPointJSAssets,
PreloadJSFiles: preloadJSAssets,
Dark: darkCSS,
Light: lightCSS,
// TODO: Need to figure this out. Ideally the swagger assets are built in a separate build process.
// Swagger: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.JS)),
// SwaggerCSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.CSS)),
}
for _, entry := range entryPoints.App.Assets.JS {
rsp.JSFiles = append(rsp.JSFiles, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
for _, entry := range entryPoints.App.Assets.CSS {
rsp.CSSFiles = append(rsp.CSSFiles, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
for _, entry := range entryPoints.Swagger.Assets.JS {
rsp.Swagger = append(rsp.Swagger, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
for _, entry := range entryPoints.Swagger.Assets.CSS {
rsp.SwaggerCSSFiles = append(rsp.SwaggerCSSFiles, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
// for _, entry := range entryPoints.App.Assets.JS {
// rsp.JSFiles = append(rsp.JSFiles, dtos.EntryPointAsset{
// FilePath: entry,
// Integrity: integrity[entry],
// })
// }
// for _, entry := range entryPoints.App.Assets.CSS {
// rsp.CSSFiles = append(rsp.CSSFiles, dtos.EntryPointAsset{
// FilePath: entry,
// Integrity: integrity[entry],
// })
// }
// for _, entry := range entryPoints.Swagger.Assets.JS {
// rsp.Swagger = append(rsp.Swagger, dtos.EntryPointAsset{
// FilePath: entry,
// Integrity: integrity[entry],
// })
// }
// for _, entry := range entryPoints.Swagger.Assets.CSS {
// rsp.SwaggerCSSFiles = append(rsp.SwaggerCSSFiles, dtos.EntryPointAsset{
// FilePath: entry,
// Integrity: integrity[entry],
// })
// }
return rsp, nil
}
// Create a list of all the chunks that need to be preloaded for a given entrypoint.
func getPreloadChunks(manifest Manifest, name string) []dtos.EntryPointAsset {
seen := make(map[string]bool)
var chunks []dtos.EntryPointAsset
var getImportedChunks func(chunk ViteManifestEntry)
getImportedChunks = func(chunk ViteManifestEntry) {
for _, file := range chunk.Imports {
importee, exists := manifest[file]
if !exists {
continue
}
if seen[file] {
continue
}
seen[file] = true
getImportedChunks(importee)
if !seen[importee.File] {
chunks = append(chunks, dtos.EntryPointAsset{
FilePath: importee.File,
Integrity: "",
})
seen[importee.File] = true
}
}
}
entryChunk, exists := manifest[name]
if !exists {
return nil
}
getImportedChunks(entryChunk)
return chunks
}

View File

@@ -3,107 +3,89 @@ package webassets
import (
"context"
"encoding/json"
"fmt"
"sort"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/stretchr/testify/require"
)
func sortAssets(assets *dtos.EntryPointAssets) {
sort.Slice(assets.JSFiles, func(i, j int) bool {
return assets.JSFiles[i].FilePath < assets.JSFiles[j].FilePath
})
sort.Slice(assets.PreloadJSFiles, func(i, j int) bool {
return assets.PreloadJSFiles[i].FilePath < assets.PreloadJSFiles[j].FilePath
})
}
func TestReadWebassets(t *testing.T) {
assets, err := readWebAssetsFromFile("testdata/sample-assets-manifest.json")
require.NoError(t, err)
sortAssets(assets)
dto, err := json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
// fmt.Printf("%s\n", string(dto))
require.JSONEq(t, `{
"jsFiles": [
{
"filePath": "public/build/runtime.js",
"integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ=="
},
{
"filePath": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js",
"integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw=="
},
{
"filePath": "public/build/app.js",
"integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w=="
}
],
"cssFiles": [
{
"filePath": "public/build/grafana.app.91aaa9d81398c147a57c.css",
"integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ=="
}
],
"dark": "public/build/grafana.dark.722d809dba5a31f57d49.css",
"light": "public/build/grafana.light.2fbd901d840329c18394.css",
"swagger": [
{
"filePath": "public/build/runtime.js",
"integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ=="
},
{
"filePath": "public/build/swagger.js",
"integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg=="
}
],
"swaggerCssFiles": [
{
"filePath": "public/build/grafana.swagger.2733d417270d5dd49373.css",
"integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg=="
}
]
}`, string(dto))
"jsFiles": [
{
"filePath": "public/build/index-fkCrlmGK.js",
"integrity": ""
},
{
"filePath": "public/build/trustedTypePolicies-pOmZ7Wr2.js",
"integrity": ""
}
],
"dark": "public/build/grafana-B9F4fUOy.css",
"light": "public/build/grafana-Cu0f8nVU.css",
"preloadJsFiles": [
{
"filePath": "public/build/braintree-CW7sY1ng.js",
"integrity": ""
},
{
"filePath": "public/build/vendor-r65R1Ntf.js",
"integrity": ""
}
]
}`, string(dto))
assets.SetContentDeliveryURL("https://grafana-assets.grafana.net/grafana/10.3.0-64123/")
sortAssets(assets)
dto, err = json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
fmt.Printf("%s\n", string(dto))
// fmt.Printf("%s\n", string(dto))
require.JSONEq(t, `{
"cdn": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/",
"jsFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js",
"integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js",
"integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/app.js",
"integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w=="
}
],
"cssFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.app.91aaa9d81398c147a57c.css",
"integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ=="
}
],
"dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.722d809dba5a31f57d49.css",
"light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.2fbd901d840329c18394.css",
"swagger": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js",
"integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ=="
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/swagger.js",
"integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg=="
}
],
"swaggerCssFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.swagger.2733d417270d5dd49373.css",
"integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg=="
}
]
"cdn": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/",
"jsFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/index-fkCrlmGK.js",
"integrity": ""
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/trustedTypePolicies-pOmZ7Wr2.js",
"integrity": ""
}
],
"preloadJsFiles": [
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/braintree-CW7sY1ng.js",
"integrity": ""
},
{
"filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/vendor-r65R1Ntf.js",
"integrity": ""
}
],
"dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana-B9F4fUOy.css",
"light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana-Cu0f8nVU.css"
}`, string(dto))
}
@@ -113,6 +95,8 @@ func TestReadWebassetsFromCDN(t *testing.T) {
assets, err := readWebAssetsFromCDN(context.Background(), "https://grafana-assets.grafana.net/grafana/10.3.0-64123/")
require.NoError(t, err)
sortAssets(assets)
dto, err := json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
//fmt.Printf("%s\n", string(dto))
@@ -147,5 +131,6 @@ func TestReadWebassetsFromCDN(t *testing.T) {
],
"dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.b44253d019cd9cb46428.css",
"light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.e8e11c59b604d62836be.css"
"preloadJsFiles": null
}`, string(dto))
}

View File

@@ -100,6 +100,7 @@ type Cfg struct {
AppSubURL string
InstanceName string
ServeFromSubPath bool
FrontendDevServer bool
StaticRootPath string
Protocol Scheme
SocketGid int
@@ -1886,6 +1887,8 @@ func (cfg *Cfg) readServerSettings(iniFile *ini.File) error {
return fmt.Errorf("TLS version not configured correctly:%v, allowed values are TLS1.2 and TLS1.3", cfg.MinTLSVersion)
}
// TODO: sanitize this for trailing slashes
cfg.FrontendDevServer = server.Key("frontend_dev_server").MustBool(false)
cfg.Domain = valueAsString(server, "domain", "localhost")
cfg.HTTPAddr = valueAsString(server, "http_addr", DefaultHTTPAddr)
cfg.HTTPPort = valueAsString(server, "http_port", "3000")

View File

@@ -205,32 +205,48 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) {
buildDir := filepath.Join(publicDir, "build")
err = os.MkdirAll(buildDir, 0750)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(buildDir, "assets-manifest.json"), []byte(`{
"entrypoints": {
"app": {
"assets": {
"js": ["public/build/runtime.XYZ.js"]
}
},
"swagger": {
"assets": {
"js": ["public/build/runtime.XYZ.js"]
}
},
"dark": {
"assets": {
"css": ["public/build/dark.css"]
}
},
"light": {
"assets": {
"css": ["public/build/light.css"]
}
}
manifestDir := filepath.Join(buildDir, ".vite")
err = os.MkdirAll(manifestDir, 0750)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(manifestDir, "manifest.json"), []byte(`{
"app/core/trustedTypePolicies.ts": {
"file": "public/build/trustedTypePolicies-pOmZ7Wr2.js",
"name": "trustedTypePolicies",
"src": "app/core/trustedTypePolicies.ts",
"isEntry": true,
"imports": [
"_braintree-CW7sY1ng.js"
]
},
"_braintree-CW7sY1ng.js": {
"file": "public/build/braintree-CW7sY1ng.js",
"name": "braintree"
},
"runtime.50398398ecdeaf58968c.js": {
"src": "public/build/runtime.XYZ.js",
"integrity": "sha256-k1g7TksMHFQhhQGE"
"app/index.ts": {
"file": "public/build/index-3exGqndc.js",
"src": "app/index.ts",
"isEntry": true,
"isDynamicEntry": true,
"css": ["public/build/index-Dd9uKMaJ.css"],
"assets": ["public/build/codicon-SZR-f31-.ttf"]
},
"swagger/index.ts": {
"file": "public/build/swagger-3exGqndc.js",
"src": "swagger/index.ts",
"isEntry": true,
"isDynamicEntry": true,
"css": ["public/build/swagger-Dd9uKMaJ.css"],
"assets": ["public/build/codicon-SZR-f31-.ttf"]
},
"sass/grafana.dark.scss": {
"file": "public/build/grafana-BwD1GyB5.css",
"src": "sass/grafana.dark.scss",
"isEntry": true
},
"sass/grafana.light.scss": {
"file": "public/build/grafana-RC8BVZtE.css",
"src": "sass/grafana.light.scss",
"isEntry": true
}
}
`), 0750)

View File

@@ -1,5 +1,5 @@
import { IRootScopeService, IAngularEvent, auto } from 'angular';
import $ from 'jquery';
import 'jquery';
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
import { AppEvent } from '@grafana/data';

View File

@@ -1,5 +1,5 @@
import angular from 'angular';
import $ from 'jquery';
import 'jquery';
import coreModule from './core_module';

View File

@@ -1,5 +1,5 @@
import angular from 'angular';
import $ from 'jquery';
import 'jquery';
import { isFunction } from 'lodash';
import coreModule from './core_module';

View File

@@ -1,9 +1,10 @@
import { each } from 'lodash';
// @ts-ignore
import Drop from 'tether-drop';
import coreModule from 'app/angular/core_module';
// @ts-ignore
import Drop from '../../../vendor/tether-drop';
export function infoPopover() {
return {
restrict: 'E',
@@ -54,6 +55,7 @@ export function infoPopover() {
// Create drop in next digest after directive content is rendered.
scope.$applyAsync(() => {
// @ts-ignore
const drop = new Drop(dropOptions);
const unbind = scope.$on('$destroy', () => {

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { debounce, each, map, partial, escape, unescape } from 'lodash';
import coreModule from 'app/angular/core_module';

View File

@@ -1,6 +1,6 @@
// @ts-ignore
import baron from 'baron';
import $ from 'jquery';
import 'jquery';
import coreModule from 'app/angular/core_module';

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { debounce, each, indexOf, map, partial, escape, unescape } from 'lodash';
import coreModule from 'app/angular/core_module';

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { each, reduce } from 'lodash';
import coreModule from './core_module';

View File

@@ -1,5 +1,5 @@
import angular from 'angular';
import $ from 'jquery';
import 'jquery';
import { extend } from 'lodash';
const $win = $(window);

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { debounce, find, indexOf, map, escape, unescape } from 'lodash';
import { TemplateSrv } from 'app/features/templating/template_srv';

View File

@@ -1,4 +1,24 @@
let templates = (require as any).context('../', true, /\.html$/);
templates.keys().forEach((key: string) => {
templates(key);
});
// TODO: Vite has no require.context support so we're importing them individually here.
// See the vite.config.ts angularHtmlImport plugin for the code that compiles these templates.
import 'app/angular/panel/partials/query_editor_row.html?inline';
import 'app/angular/partials/http_settings_next.html?inline';
import 'app/angular/partials/tls_auth_settings.html?inline';
import 'app/features/annotations/partials/event_editor.html?inline';
import 'app/partials/confirm_modal.html?inline';
import 'app/partials/modal.html?inline';
import 'app/partials/reset_password.html?inline';
import 'app/partials/signup_invited.html?inline';
import 'app/plugins/panel/graph/axes_editor.html?inline';
import 'app/plugins/panel/graph/tab_display.html?inline';
import 'app/plugins/panel/graph/tab_legend.html?inline';
import 'app/plugins/panel/graph/tab_series_overrides.html?inline';
import 'app/plugins/panel/graph/tab_thresholds.html?inline';
import 'app/plugins/panel/graph/tab_time_regions.html?inline';
import 'app/plugins/panel/graph/thresholds_form.html?inline';
import 'app/plugins/panel/graph/time_regions_form.html?inline';
import 'app/plugins/panel/heatmap/partials/axes_editor.html?inline';
import 'app/plugins/panel/heatmap/partials/display_editor.html?inline';
import 'app/plugins/panel/table-old/column_options.html?inline';
import 'app/plugins/panel/table-old/editor.html?inline';
import 'app/plugins/panel/table-old/module.html?inline';

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import coreModule from './core_module';

View File

@@ -1,10 +1,11 @@
import { extend } from 'lodash';
// @ts-ignore
import Drop from 'tether-drop';
import { GrafanaRootScope } from 'app/angular/GrafanaCtrl';
import coreModule from 'app/angular/core_module';
// @ts-ignore
import Drop from '../../../vendor/tether-drop';
coreModule.service('popoverSrv', ['$compile', '$rootScope', '$timeout', popoverSrv]);
function popoverSrv(this: any, $compile: any, $rootScope: GrafanaRootScope, $timeout: any) {
@@ -51,6 +52,7 @@ function popoverSrv(this: any, $compile: any, $rootScope: GrafanaRootScope, $tim
$compile(contentElement)(scope);
$timeout(() => {
// @ts-ignore
drop = new Drop({
target: options.element,
content: contentElement,

View File

@@ -1,5 +1,5 @@
import angular from 'angular';
import $ from 'jquery';
import 'jquery';
import { getTagColorsFromName } from '@grafana/ui';

View File

@@ -104,11 +104,14 @@ import { createSystemVariableAdapter } from './features/variables/system/adapter
import { createTextBoxVariableAdapter } from './features/variables/textbox/adapter';
import { configureStore } from './store/configureStore';
// import symlinked extensions
const extensionsIndex = require.context('.', true, /extensions\/index.ts/);
const extensionsExports = extensionsIndex.keys().map((key) => {
return extensionsIndex(key);
});
type ExtensionExports = {
init: () => void;
addExtensionReducers: () => void;
};
// import symlinked extensions (use glob so vite doesn't error if no files are found)
const extensions: Record<string, ExtensionExports> = import.meta.glob('./extensions/index.ts', { eager: true });
const extensionsExports = Object.values(extensions);
if (process.env.NODE_ENV === 'development') {
initDevFeatures();

View File

@@ -3,12 +3,14 @@ import { BaseStateReport } from 'crashme/dist/types';
import { nanoid } from 'nanoid';
import { config, createMonitoringLogger } from '@grafana/runtime';
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from 'app/core/utils/CorsWorker';
import { contextSrv } from '../services/context_srv';
import { CorsSharedWorker as SharedWorker, sharedWorkersSupported } from '../utils/CorsSharedWorker';
import { corsSharedWorker, sharedWorkersSupported } from '../utils/CorsSharedWorker';
import clientWorkerUrl from './client.worker?worker&url';
import { isChromePerformance, prepareContext } from './crash.utils';
import detectorWorkerUrl from './detector.worker?sharedworker&url';
const logger = createMonitoringLogger('core.crash-detection');
@@ -48,7 +50,7 @@ export function initializeCrashDetection() {
dbName: 'grafana.crashes',
createClientWorker(): Worker {
return new Worker(new URL('./client.worker', import.meta.url));
return corsWorker(clientWorkerUrl, { name: 'crash' });
},
/**
@@ -61,8 +63,7 @@ export function initializeCrashDetection() {
* We guarantee the type assertion is correct by returning a SharedWorker in CorsSharedWorker constructor.
*/
createDetectorWorker() {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return new SharedWorker(new URL('./detector.worker', import.meta.url)) as globalThis.SharedWorker;
return corsSharedWorker(detectorWorkerUrl, { name: 'crashDetector' });
},
reportCrash: async (report) => {

View File

@@ -0,0 +1 @@
export const localeExtensionImports = {};

View File

@@ -1,6 +1,11 @@
import { ResourceKey } from 'i18next';
import { uniq } from 'lodash';
// we mock this in jest as import.meta.glob breaks things, so we don't even attempt to load enterprise translations...
import { localeExtensionImports, type LocaleFileLoader } from './extensions';
// TODO: this is just to satisfy extensions which import this type from this file.
export type { LocaleFileLoader };
export const ENGLISH_US = 'en-US';
export const FRENCH_FRANCE = 'fr-FR';
export const SPANISH_SPAIN = 'es-ES';
@@ -11,7 +16,8 @@ export const PSEUDO_LOCALE = 'pseudo';
export const DEFAULT_LANGUAGE = ENGLISH_US;
export type LocaleFileLoader = () => Promise<ResourceKey>;
const importLanguageFile = (languageCode: string) =>
import(`../../../locales/${languageCode}/grafana.json`).then((data) => data.default);
export interface LanguageDefinition<Namespace extends string = string> {
/** IETF language tag for the language e.g. en-US */
@@ -29,7 +35,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: ENGLISH_US,
name: 'English',
loader: {
grafana: () => import('../../../locales/en-US/grafana.json'),
grafana: () => importLanguageFile('en-US'),
},
},
@@ -37,7 +43,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: FRENCH_FRANCE,
name: 'Français',
loader: {
grafana: () => import('../../../locales/fr-FR/grafana.json'),
grafana: () => importLanguageFile('fr-FR'),
},
},
@@ -45,7 +51,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: SPANISH_SPAIN,
name: 'Español',
loader: {
grafana: () => import('../../../locales/es-ES/grafana.json'),
grafana: () => importLanguageFile('es-ES'),
},
},
@@ -53,7 +59,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: GERMAN_GERMANY,
name: 'Deutsch',
loader: {
grafana: () => import('../../../locales/de-DE/grafana.json'),
grafana: () => importLanguageFile('de-DE'),
},
},
@@ -61,7 +67,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: CHINESE_SIMPLIFIED,
name: '中文(简体)',
loader: {
grafana: () => import('../../../locales/zh-Hans/grafana.json'),
grafana: () => importLanguageFile('zh-Hans'),
},
},
@@ -69,7 +75,7 @@ export const LANGUAGES: LanguageDefinition[] = [
code: BRAZILIAN_PORTUGUESE,
name: 'Português Brasileiro',
loader: {
grafana: () => import('../../../locales/pt-BR/grafana.json'),
grafana: () => importLanguageFile('pt-BR'),
},
},
] satisfies Array<LanguageDefinition<'grafana'>>;
@@ -79,7 +85,7 @@ if (process.env.NODE_ENV === 'development') {
code: PSEUDO_LOCALE,
name: 'Pseudo-locale',
loader: {
grafana: () => import('../../../locales/pseudo-LOCALE/grafana.json'),
grafana: () => importLanguageFile('pseudo-LOCALE'),
},
});
}
@@ -87,13 +93,11 @@ if (process.env.NODE_ENV === 'development') {
// Optionally load enterprise locale extensions, if they are present.
// It is important that this happens before NAMESPACES is defined so it has the correct value
//
// require.context doesn't work in jest, so we don't even attempt to load enterprise translations...
if (process.env.NODE_ENV !== 'test') {
const extensionRequireContext = require.context('../../', true, /app\/extensions\/locales\/localeExtensions/);
if (extensionRequireContext.keys().includes('app/extensions/locales/localeExtensions')) {
const { LOCALE_EXTENSIONS, ENTERPRISE_I18N_NAMESPACE } = extensionRequireContext(
'app/extensions/locales/localeExtensions'
);
const localeExtensionExports = Object.values(localeExtensionImports);
if (localeExtensionExports.length > 0) {
const { LOCALE_EXTENSIONS, ENTERPRISE_I18N_NAMESPACE } = localeExtensionExports[0];
for (const language of LANGUAGES) {
const localeLoader = LOCALE_EXTENSIONS[language.code];

View File

@@ -0,0 +1,14 @@
import { ResourceKey } from 'i18next';
export type LocaleFileLoader = () => Promise<ResourceKey>;
type LocaleExtensionExports = {
LOCALE_EXTENSIONS: Record<string, LocaleFileLoader | undefined>;
ENTERPRISE_I18N_NAMESPACE: 'string';
};
export const localeExtensionImports: Record<string, LocaleExtensionExports> = import.meta.glob(
'../../app/extensions/locales/localeExtensions.ts',
{
eager: true,
}
);

View File

@@ -1,5 +1,12 @@
import editorWorkerUrl from 'monaco-editor/esm/vs/editor/editor.worker.js?worker&url';
import cssWorkerUrl from 'monaco-editor/esm/vs/language/css/css.worker?worker&url';
import htmlWorkerUrl from 'monaco-editor/esm/vs/language/html/html.worker?worker&url';
import jsonWorkerUrl from 'monaco-editor/esm/vs/language/json/json.worker?worker&url';
import typescriptWorkerUrl from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker&url';
import { monacoLanguageRegistry } from '@grafana/data';
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from './utils/CorsWorker';
export function setMonacoEnv() {
self.MonacoEnvironment = {
@@ -7,26 +14,27 @@ export function setMonacoEnv() {
const language = monacoLanguageRegistry.getIfExists(label);
if (language) {
return language.init();
const moduleUrl = language.init();
return corsWorker(moduleUrl, { name: label });
}
if (label === 'json') {
return new Worker(new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url));
return corsWorker(jsonWorkerUrl, { name: label });
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url));
return corsWorker(cssWorkerUrl, { name: label });
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new Worker(new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url));
return corsWorker(htmlWorkerUrl, { name: label });
}
if (label === 'typescript' || label === 'javascript') {
return new Worker(new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url));
return corsWorker(typescriptWorkerUrl, { name: label });
}
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
return corsWorker(editorWorkerUrl, { name: label });
},
};
}

View File

@@ -1,11 +1,11 @@
import { Suspense, useEffect, useLayoutEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom-v5-compat';
// @ts-ignore
import Drop from 'tether-drop';
import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime';
import { ErrorBoundary } from '@grafana/ui';
// @ts-ignore
import Drop from '../../../vendor/tether-drop';
import { useGrafana } from '../context/GrafanaContext';
import { contextSrv } from '../services/context_srv';
@@ -101,6 +101,8 @@ function cleanupDOM() {
}
// cleanup tether-drop
// TODO: Vite doesn't like this. Pretty sure the lib is incompatible with Vite.
// @ts-ignore
for (const drop of Drop.drops) {
drop.destroy();
}

View File

@@ -1,7 +1,6 @@
import { textUtil } from '@grafana/data';
import { config } from '@grafana/runtime';
import { sanitizeUrl } from '@braintree/sanitize-url';
const CSP_REPORT_ONLY_ENABLED = config.bootData.settings.cspReportOnlyEnabled;
const CSP_REPORT_ONLY_ENABLED = window.grafanaBootData!.settings.cspReportOnlyEnabled;
export const defaultTrustedTypesPolicy = {
createHTML: (string: string, source: string, sink: string) => {
@@ -14,7 +13,7 @@ export const defaultTrustedTypesPolicy = {
createScript: (string: string) => string,
createScriptURL: (string: string, source: string, sink: string) => {
if (!CSP_REPORT_ONLY_ENABLED) {
return textUtil.sanitizeUrl(string);
return sanitizeUrl(string);
}
console.error('[ScriptURL not sanitized with Trusted Types]', string, source, sink);
return string;
@@ -22,7 +21,7 @@ export const defaultTrustedTypesPolicy = {
};
if (
config.bootData.settings.trustedTypesDefaultPolicyEnabled &&
window.grafanaBootData!.settings.trustedTypesDefaultPolicyEnabled &&
window.trustedTypes &&
window.trustedTypes.createPolicy
) {

View File

@@ -1,33 +1,15 @@
// Almost identical to CorsWorker.ts. Main difference being it allows loading a SharedWorker if browser supports it
export function sharedWorkersSupported() {
return typeof window.SharedWorker !== 'undefined';
}
/**
* Creating CorsSharedWorker should be called only if sharedWorkersSupported() is truthy
*/
export class CorsSharedWorker {
constructor(url: URL, options?: WorkerOptions) {
if (!sharedWorkersSupported()) {
throw new Error('SharedWorker is not supported');
}
// by default, worker inherits HTML document's location and pathname which leads to wrong public path value
// the CorsWorkerPlugin will override it with the value based on the initial worker chunk, ie.
// initial worker chunk: http://host.com/cdn/scripts/worker-123.js
// resulting public path: http://host.com/cdn/scripts
const scriptUrl = url.toString();
const scriptsBasePathUrl = new URL('.', url).toString();
const importScripts = `importScripts('${scriptUrl}');`;
const objectURL = URL.createObjectURL(
new Blob([`__webpack_worker_public_path__ = '${scriptsBasePathUrl}'; ${importScripts}`], {
type: 'application/javascript',
})
);
const worker = new SharedWorker(objectURL, options);
URL.revokeObjectURL(objectURL);
return worker;
}
// This function is used to create a sharedworker that can load across domains.
export function corsSharedWorker(workerUrl: string, options: WorkerOptions) {
const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}`;
const blob = new Blob([js], { type: 'application/javascript' });
const objURL = URL.createObjectURL(blob);
const worker = new SharedWorker(objURL, { type: 'module', name: options?.name });
worker.addEventListener('error', (e) => {
URL.revokeObjectURL(objURL);
});
return worker;
}

View File

@@ -1,21 +1,17 @@
// works with webpack plugin: scripts/webpack/plugins/CorsWorkerPlugin.js
export class CorsWorker extends window.Worker {
constructor(url: URL, options?: WorkerOptions) {
// by default, worker inherits HTML document's location and pathname which leads to wrong public path value
// the CorsWorkerPlugin will override it with the value based on the initial worker chunk, ie.
// initial worker chunk: http://host.com/cdn/scripts/worker-123.js
// resulting public path: http://host.com/cdn/scripts
const scriptUrl = url.toString();
const scriptsBasePathUrl = new URL('.', url).toString();
const importScripts = `importScripts('${scriptUrl}');`;
const objectURL = URL.createObjectURL(
new Blob([`__webpack_worker_public_path__ = '${scriptsBasePathUrl}'; ${importScripts}`], {
type: 'application/javascript',
})
);
super(objectURL, options);
URL.revokeObjectURL(objectURL);
}
// This function is used to create a worker that can load across domains.
// To use it, you need to import the workerUrl and pass it as the first argument.
//
// import clientWorkerUrl from './client.worker?worker&url';
//
// corsWorker(clientWorkerUrl, { name: 'myWorker' });
//
export function corsWorker(workerUrl: string, options: WorkerOptions) {
const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}`;
const blob = new Blob([js], { type: 'application/javascript' });
const objURL = URL.createObjectURL(blob);
const worker = new Worker(objURL, { type: 'module', name: options?.name });
worker.addEventListener('error', (e) => {
URL.revokeObjectURL(objURL);
});
return worker;
}

View File

@@ -1,5 +1,7 @@
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from 'app/core/utils/CorsWorker';
import matcherWorkerUrl from './routeGroupsMatcher.worker?worker&url';
// CorsWorker is needed as a workaround for CORS issue caused
// by static assets served from an url different from origin
export const createWorker = () => new Worker(new URL('./routeGroupsMatcher.worker.ts', import.meta.url));
export const createWorker = () => corsWorker(matcherWorkerUrl, { name: 'routeGroupsMatcher' });

View File

@@ -1,6 +1,5 @@
import { SceneObjectStateChangedEvent } from '@grafana/scenes';
import { Dashboard } from '@grafana/schema';
import { CorsWorker } from 'app/core/utils/CorsWorker';
import * as createDetectChangesWorker from 'app/features/dashboard-scene/saving/createDetectChangesWorker';
import { DashboardScene } from '../scene/DashboardScene';
@@ -25,7 +24,7 @@ describe('DashboardSceneChangeTracker', () => {
() =>
({
terminate,
}) as unknown as CorsWorker
}) as unknown as Worker
);
const changeTracker = new DashboardSceneChangeTracker({
subscribeToEvent: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
@@ -44,7 +43,7 @@ describe('DashboardSceneChangeTracker', () => {
jest.spyOn(createDetectChangesWorker, 'createWorker').mockImplementation(() => {
return {
postMessage,
} as unknown as CorsWorker;
} as unknown as Worker;
});
jest.spyOn(DashboardSceneChangeTracker, 'isUpdatingPersistedState').mockImplementation(() => {
return true;

View File

@@ -1,3 +1,5 @@
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from 'app/core/utils/CorsWorker';
export const createWorker = () => new Worker(new URL('./DetectChangesWorker.ts', import.meta.url));
import detectChangesWorkerUrl from './DetectChangesWorker?worker&url';
export const createWorker = () => corsWorker(detectChangesWorkerUrl, { name: 'detectChanges' });

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import _, { isFunction } from 'lodash'; // eslint-disable-line lodash/import-scope
import moment from 'moment'; // eslint-disable-line no-restricted-imports

View File

@@ -1,3 +1,5 @@
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from 'app/core/utils/CorsWorker';
export const createWorker = () => new Worker(new URL('./service.worker.ts', import.meta.url));
import centrifugeWorkerUrl from './service.worker?worker&url';
export const createWorker = () => corsWorker(centrifugeWorkerUrl, { name: 'centrifuge' });

View File

@@ -26,7 +26,7 @@ import * as ticks from 'app/core/utils/ticks';
// Help the 6.4 to 6.5 migration
// The base classes were moved from @grafana/ui to @grafana/data
// This exposes the same classes on both import paths
const grafanaUI: Record<string, unknown> = grafanaUIraw;
const grafanaUI: System.Module = { ...grafanaUIraw };
grafanaUI.PanelPlugin = grafanaData.PanelPlugin;
grafanaUI.DataSourcePlugin = grafanaData.DataSourcePlugin;
grafanaUI.AppPlugin = grafanaData.AppPlugin;

View File

@@ -22,7 +22,8 @@ import { logError, logInfo } from './utils';
// Loads near membrane custom formatter for near membrane proxy objects.
if (process.env.NODE_ENV !== 'production') {
require('@locker/near-membrane-dom/custom-devtools-formatter');
// @ts-ignore - this is aliased in vite config but ts complains because it cannot resolve it.
import('@locker/near-membrane-dom/custom-devtools-formatter');
}
const pluginImportCache = new Map<string, Promise<System.Module>>();

View File

@@ -1,19 +1,22 @@
import './core/trustedTypePolicies';
declare let __webpack_public_path__: string;
declare let __webpack_nonce__: string;
import './viteGlobals';
import 'vite/modulepreload-polyfill';
// Check if we are hosting files on cdn and set webpack public path
if (window.public_cdn_path) {
__webpack_public_path__ = window.public_cdn_path;
}
// TODO: Vite does this differently...
// declare let __webpack_public_path__: string;
// declare let __webpack_nonce__: string;
// This is a path to the public folder without '/build'
window.__grafana_public_path__ =
__webpack_public_path__.substring(0, __webpack_public_path__.lastIndexOf('build/')) || __webpack_public_path__;
// // Check if we are hosting files on cdn and set webpack public path
// if (window.public_cdn_path) {
// __webpack_public_path__ = window.public_cdn_path;
// }
if (window.nonce) {
__webpack_nonce__ = window.nonce;
}
// // This is a path to the public folder without '/build'
// window.__grafana_public_path__ =
// __webpack_public_path__.substring(0, __webpack_public_path__.lastIndexOf('build/')) || __webpack_public_path__;
// if (window.nonce) {
// __webpack_nonce__ = window.nonce;
// }
// This is an indication to the window.onLoad failure check that the app bundle has loaded.
window.__grafana_app_bundle_loaded = true;
@@ -22,7 +25,7 @@ import app from './app';
const prepareInit = async () => {
if (process.env.frontend_dev_mock_api) {
return import('test/mock-api/worker').then((workerModule) => {
return import('../test/mock-api/worker').then((workerModule) => {
workerModule.default.start({ onUnhandledRequest: 'bypass' });
});
}

View File

@@ -37,7 +37,9 @@
"@types/prismjs": "1.26.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"css-loader": "7.1.2",
"react-select-event": "5.5.1",
"style-loader": "4.0.0",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"webpack": "5.97.1"

View File

@@ -1,5 +1,3 @@
const JSURL = require('jsurl');
export interface AwsUrl {
end: string;
start: string;
@@ -29,5 +27,64 @@ export function getLogsEndpoint(region: string): string {
export function encodeUrl(obj: AwsUrl, region: string): string {
return `https://${getLogsEndpoint(
region
)}/cloudwatch/home?region=${region}#logs-insights:queryDetail=${JSURL.stringify(obj)}`;
)}/cloudwatch/home?region=${region}#logs-insights:queryDetail=${stringify(obj)}`;
}
// Lifted from https://github.com/Sage/jsurl
function stringify<T>(v: T): string {
function encode(s: string): string {
return !/[^\w-.]/.test(s)
? s
: s.replace(/[^\w-.]/g, (ch: string): string => {
if (ch === '$') {
return '!';
}
const charCode = ch.charCodeAt(0);
// Thanks to Douglas Crockford for the negative slice trick
return charCode < 0x100
? '*' + ('00' + charCode.toString(16)).slice(-2)
: '**' + ('0000' + charCode.toString(16)).slice(-4);
});
}
let tmpAry;
switch (typeof v) {
case 'number':
return isFinite(v) ? '~' + v : '~null';
case 'boolean':
return '~' + v;
case 'string':
return "~'" + encode(v);
case 'object':
if (!v) {
return '~null';
}
tmpAry = [];
if (Array.isArray(v)) {
for (let i = 0; i < v.length; i++) {
tmpAry[i] = stringify(v[i]) || '~null';
}
return '~(' + (tmpAry.join('') || '~') + ')';
} else {
for (const key in v) {
if (v.hasOwnProperty(key)) {
const val = stringify(v[key]);
// skip undefined and functions
if (val) {
tmpAry.push(encode(key) + val);
}
}
}
return '~(' + tmpAry.join('~') + ')';
}
default:
// function, undefined
return '';
}
}

View File

@@ -52,8 +52,10 @@
"@types/react-dom": "18.3.5",
"@types/semver": "7.5.8",
"@types/uuid": "10.0.0",
"css-loader": "7.1.2",
"glob": "11.0.1",
"react-select-event": "5.5.1",
"style-loader": "4.0.0",
"ts-node": "10.9.2",
"typescript": "5.7.3",
"webpack": "5.97.1"

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import { isString, escape } from 'lodash';
import coreModule from 'app/angular/core_module';

View File

@@ -8,7 +8,7 @@ import 'vendor/flot/jquery.flot.crosshair';
import 'vendor/flot/jquery.flot.dashes';
import './jquery.flot.events';
import $ from 'jquery';
import 'jquery';
import { clone, find, flatten, isUndefined, map, max as _max, min as _min, sortBy as _sortBy, toNumber } from 'lodash';
import { createElement } from 'react';
import { createRoot, Root } from 'react-dom/client';

View File

@@ -1,4 +1,4 @@
import $ from 'jquery';
import 'jquery';
import {
textUtil,

View File

@@ -1,11 +1,12 @@
import $ from 'jquery';
import 'jquery';
import { partition, each } from 'lodash';
//@ts-ignore
import Drop from 'tether-drop';
import { CreatePlotOverlay } from '@grafana/data';
import { getLegacyAngularInjector } from '@grafana/runtime';
// @ts-ignore
import Drop from '../../../../vendor/tether-drop';
const createAnnotationToolip: CreatePlotOverlay = (element, event, plot) => {
const injector = getLegacyAngularInjector();
const content = document.createElement('div');
@@ -26,6 +27,7 @@ const createAnnotationToolip: CreatePlotOverlay = (element, event, plot) => {
tmpScope.$digest();
tmpScope.$destroy();
// @ts-ignore
const drop = new Drop({
target: element[0],
content: content,
@@ -86,7 +88,7 @@ const createEditPopover: CreatePlotOverlay = (element, event, plot) => {
$compile(content)(scope);
scope.$digest();
// @ts-ignore
drop = new Drop({
target: markerElementToAttachTo[0],
content: content,

View File

@@ -5,10 +5,13 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
TimeSeries {
"alias": "Value",
"aliasEscaped": "Value",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#7EB26D",
},
"color": "#7EB26D",
"dashes": undefined,
"dataFrameIndex": 0,
"datapoints": [
[
@@ -24,22 +27,38 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1003,
],
],
"decimals": undefined,
"fieldIndex": 1,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "Value",
"isOutsideRange": undefined,
"label": "Value",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": "watt",
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
TimeSeries {
"alias": "table_data v1",
"aliasEscaped": "table_data v1",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#EAB839",
},
"color": "#EAB839",
"dashes": undefined,
"dataFrameIndex": 1,
"datapoints": [
[
@@ -55,22 +74,38 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1003,
],
],
"decimals": undefined,
"fieldIndex": 1,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "table_data v1",
"isOutsideRange": undefined,
"label": "table_data v1",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": "ohm",
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
TimeSeries {
"alias": "table_data v2",
"aliasEscaped": "table_data v2",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#6ED0E0",
},
"color": "#6ED0E0",
"dashes": undefined,
"dataFrameIndex": 1,
"datapoints": [
[
@@ -86,22 +121,38 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1003,
],
],
"decimals": undefined,
"fieldIndex": 2,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "table_data v2",
"isOutsideRange": undefined,
"label": "table_data v2",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": undefined,
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
TimeSeries {
"alias": "series v1",
"aliasEscaped": "series v1",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#EF843C",
},
"color": "#EF843C",
"dashes": undefined,
"dataFrameIndex": 2,
"datapoints": [
[
@@ -117,22 +168,38 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1003,
],
],
"decimals": undefined,
"fieldIndex": 0,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "series v1",
"isOutsideRange": undefined,
"label": "series v1",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": undefined,
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
TimeSeries {
"alias": "series v2",
"aliasEscaped": "series v2",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#E24D42",
},
"color": "#E24D42",
"dashes": undefined,
"dataFrameIndex": 2,
"datapoints": [
[
@@ -148,22 +215,38 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1003,
],
],
"decimals": undefined,
"fieldIndex": 1,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "series v2",
"isOutsideRange": undefined,
"label": "series v2",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": undefined,
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
TimeSeries {
"alias": "series with time as strings v1",
"aliasEscaped": "series with time as strings v1",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#1F78C1",
},
"color": "#1F78C1",
"dashes": undefined,
"dataFrameIndex": 3,
"datapoints": [
[
@@ -179,14 +262,27 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1609466400000,
],
],
"decimals": undefined,
"fieldIndex": 0,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "series with time as strings v1",
"isOutsideRange": undefined,
"label": "series with time as strings v1",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": undefined,
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
]
`;
@@ -196,10 +292,13 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
TimeSeries {
"alias": "Count",
"aliasEscaped": "Count",
"allIsNull": undefined,
"allIsZero": undefined,
"bars": {
"fillColor": "#7EB26D",
},
"color": "#7EB26D",
"dashes": undefined,
"dataFrameIndex": 0,
"datapoints": [
[
@@ -275,14 +374,27 @@ exports[`Graph DataProcessor getTimeSeries from LegacyResponseData Should return
1609466400000,
],
],
"decimals": undefined,
"fieldIndex": 1,
"fillBelowTo": undefined,
"flotpairs": undefined,
"hasMsResolution": false,
"hiddenSeries": undefined,
"hideTooltip": undefined,
"id": "Value",
"isOutsideRange": undefined,
"label": "Value",
"legend": true,
"lines": undefined,
"nullPointMode": undefined,
"points": undefined,
"stack": undefined,
"stats": {},
"transform": undefined,
"unit": "watt",
"valueFormater": [Function],
"yaxis": undefined,
"zindex": undefined,
},
]
`;

View File

@@ -1,5 +1,5 @@
import 'vendor/flot/jquery.flot';
import $ from 'jquery';
import 'jquery';
import { isNumber } from 'lodash';
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';

View File

@@ -1,4 +1,7 @@
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { corsWorker } from 'app/core/utils/CorsWorker';
export const createWorker = () => new Worker(new URL('./layout.worker.js', import.meta.url));
export const createMsaglWorker = () => new Worker(new URL('./layeredLayout.worker.js', import.meta.url));
import layeredLayoutWorkerUrl from './layeredLayout.worker?worker&url';
import layoutWorkerUrl from './layout.worker?worker&url';
export const createWorker = () => corsWorker(layoutWorkerUrl, { name: 'nodeGraphLayout' });
export const createMsaglWorker = () => corsWorker(layeredLayoutWorkerUrl, { name: 'nodeGraphLayeredLayout' });

View File

@@ -1,5 +1,5 @@
import { IScope, IAngularStatic } from 'angular';
import $ from 'jquery';
import 'jquery';
import { defaults } from 'lodash';
import { isTableData, PanelEvents, PanelPlugin } from '@grafana/data';

2
public/app/types/vite.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vite/types/importMeta" />

View File

@@ -0,0 +1,5 @@
// Vite: hacky approach to making jQuery globally available
import jQuery from 'jquery';
Object.assign(window, { $: jQuery, jQuery });
window.global ||= window;

View File

@@ -1,5 +1,5 @@
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import kustoWorkerUrl from '@kusto/monaco-kusto/release/esm/kusto.worker?worker&url';
export default function loadKusto() {
return new Worker(new URL('@kusto/monaco-kusto/release/esm/kusto.worker', import.meta.url));
return kustoWorkerUrl;
}

View File

@@ -7,11 +7,11 @@ import i18next from 'i18next';
import failOnConsole from 'jest-fail-on-console';
import { initReactI18next } from 'react-i18next';
import getEnvConfig from '../../scripts/webpack/env-util';
import getEnvConfig from '../../scripts/webpack/env-util.cjs';
import { matchers } from './matchers';
const config = getEnvConfig() as Record<string, string | boolean>;
const config = getEnvConfig() as Record<`frontend_dev_${string}`, unknown>;
if (config.frontend_dev_fail_tests_on_console || process.env.CI) {
failOnConsole({
@@ -34,6 +34,9 @@ i18next.use(initReactI18next).init({
// the factory uses import.meta.url so we can't use it in CommonJS modules.
jest.mock('app/features/dashboard-scene/saving/createDetectChangesWorker.ts');
// mock extension internationalization as it uses import.meta.glob which we can't use in CommonJS modules.
jest.mock('app/core/internationalization/extensions.ts');
// our tests are heavy in CI due to parallelisation and monaco and kusto
// so we increase the default timeout to 2secs to avoid flakiness
configure({ asyncUtilTimeout: 2000 });

495
public/vendor/tether-drop/index.js vendored Normal file
View File

@@ -0,0 +1,495 @@
import {
extend,
addClass,
removeClass,
hasClass,
Evented,
} from "./tetherUtils";
function sortAttach(str) {
let [first, second] = str.split(" ");
if (["left", "right"].indexOf(first) >= 0) {
[first, second] = [second, first];
}
return [first, second].join(" ");
}
function removeFromArray(arr, item) {
let index;
let results = [];
while ((index = arr.indexOf(item)) !== -1) {
results.push(arr.splice(index, 1));
}
return results;
}
let clickEvents = ["click"];
if ("ontouchstart" in document.documentElement) {
clickEvents.push("touchstart");
}
const transitionEndEvents = {
WebkitTransition: "webkitTransitionEnd",
MozTransition: "transitionend",
OTransition: "otransitionend",
transition: "transitionend",
};
let transitionEndEvent = "";
for (let name in transitionEndEvents) {
if ({}.hasOwnProperty.call(transitionEndEvents, name)) {
let tempEl = document.createElement("p");
if (typeof tempEl.style[name] !== "undefined") {
transitionEndEvent = transitionEndEvents[name];
}
}
}
const MIRROR_ATTACH = {
left: "right",
right: "left",
top: "bottom",
bottom: "top",
middle: "middle",
center: "center",
};
let allDrops = {};
// Drop can be included in external libraries. Calling createContext gives you a fresh
// copy of drop which won't interact with other copies on the page (beyond calling the document events).
function createContext(options = {}) {
let drop = (...args) => new DropInstance(...args);
extend(drop, {
createContext: createContext,
drops: [],
defaults: {},
});
const defaultOptions = {
classPrefix: "drop",
defaults: {
position: "bottom left",
openOn: "click",
beforeClose: null,
constrainToScrollParent: true,
constrainToWindow: true,
classes: "",
remove: false,
openDelay: 0,
closeDelay: 50,
// inherited from openDelay and closeDelay if not explicitly defined
focusDelay: null,
blurDelay: null,
hoverOpenDelay: null,
hoverCloseDelay: null,
tetherOptions: {},
},
};
extend(drop, defaultOptions, options);
extend(drop.defaults, defaultOptions.defaults, options.defaults);
if (typeof allDrops[drop.classPrefix] === "undefined") {
allDrops[drop.classPrefix] = [];
}
drop.updateBodyClasses = () => {
// There is only one body, so despite the context concept, we still iterate through all
// drops which share our classPrefix.
let anyOpen = false;
const drops = allDrops[drop.classPrefix];
const len = drops.length;
for (let i = 0; i < len; ++i) {
if (drops[i].isOpened()) {
anyOpen = true;
break;
}
}
if (anyOpen) {
addClass(document.body, `${drop.classPrefix}-open`);
} else {
removeClass(document.body, `${drop.classPrefix}-open`);
}
};
class DropInstance extends Evented {
constructor(opts) {
super();
this.options = extend({}, drop.defaults, opts);
this.target = this.options.target;
if (typeof this.target === "undefined") {
throw new Error("Drop Error: You must provide a target.");
}
const dataPrefix = `data-${drop.classPrefix}`;
const contentAttr = this.target.getAttribute(dataPrefix);
if (contentAttr && this.options.content == null) {
this.options.content = contentAttr;
}
const attrsOverride = ["position", "openOn"];
for (let i = 0; i < attrsOverride.length; ++i) {
const override = this.target.getAttribute(
`${dataPrefix}-${attrsOverride[i]}`
);
if (override && this.options[attrsOverride[i]] == null) {
this.options[attrsOverride[i]] = override;
}
}
if (this.options.classes && this.options.addTargetClasses !== false) {
addClass(this.target, this.options.classes);
}
drop.drops.push(this);
allDrops[drop.classPrefix].push(this);
this._boundEvents = [];
this.bindMethods();
this.setupElements();
this.setupEvents();
this.setupTether();
}
_on(element, event, handler) {
this._boundEvents.push({ element, event, handler });
element.addEventListener(event, handler);
}
bindMethods() {
this.transitionEndHandler = this._transitionEndHandler.bind(this);
}
setupElements() {
this.drop = document.createElement("div");
addClass(this.drop, drop.classPrefix);
if (this.options.classes) {
addClass(this.drop, this.options.classes);
}
this.content = document.createElement("div");
addClass(this.content, `${drop.classPrefix}-content`);
if (typeof this.options.content === "function") {
const generateAndSetContent = () => {
// content function might return a string or an element
const contentElementOrHTML = this.options.content.call(this, this);
if (typeof contentElementOrHTML === "string") {
this.content.innerHTML = contentElementOrHTML;
} else if (typeof contentElementOrHTML === "object") {
this.content.innerHTML = "";
this.content.appendChild(contentElementOrHTML);
} else {
throw new Error(
"Drop Error: Content function should return a string or HTMLElement."
);
}
};
this.on("beforeOpen", generateAndSetContent.bind(this));
} else if (typeof this.options.content === "object") {
this.content.appendChild(this.options.content);
} else {
this.content.innerHTML = this.options.content;
}
this.drop.appendChild(this.content);
}
setupTether() {
// Tether expects two attachment points, one in the target element, one in the
// drop. We use a single one, and use the order as well, to allow us to put
// the drop on either side of any of the four corners. This magic converts between
// the two:
let dropAttach = this.options.position.split(" ");
dropAttach[0] = MIRROR_ATTACH[dropAttach[0]];
dropAttach = dropAttach.join(" ");
let constraints = [];
if (this.options.constrainToScrollParent) {
constraints.push({
to: "scrollParent",
pin: "top, bottom",
attachment: "together none",
});
} else {
// To get 'out of bounds' classes
constraints.push({
to: "scrollParent",
});
}
if (this.options.constrainToWindow !== false) {
constraints.push({
to: "window",
attachment: "together",
});
} else {
// To get 'out of bounds' classes
constraints.push({
to: "window",
});
}
const opts = {
element: this.drop,
target: this.target,
attachment: sortAttach(dropAttach),
targetAttachment: sortAttach(this.options.position),
classPrefix: drop.classPrefix,
offset: "0 0",
targetOffset: "0 0",
enabled: false,
constraints: constraints,
addTargetClasses: this.options.addTargetClasses,
};
if (this.options.tetherOptions !== false) {
this.tether = new Tether(extend({}, opts, this.options.tetherOptions));
}
}
setupEvents() {
if (!this.options.openOn) {
return;
}
if (this.options.openOn === "always") {
setTimeout(this.open.bind(this));
return;
}
const events = this.options.openOn.split(" ");
if (events.indexOf("click") >= 0) {
const openHandler = (event) => {
this.toggle(event);
event.preventDefault();
};
const closeHandler = (event) => {
if (!this.isOpened()) {
return;
}
// Clicking inside dropdown
if (event.target === this.drop || this.drop.contains(event.target)) {
return;
}
// Clicking target
if (
event.target === this.target ||
this.target.contains(event.target)
) {
return;
}
this.close(event);
};
for (let i = 0; i < clickEvents.length; ++i) {
const clickEvent = clickEvents[i];
this._on(this.target, clickEvent, openHandler);
this._on(document, clickEvent, closeHandler);
}
}
let inTimeout = null;
let outTimeout = null;
const inHandler = (event) => {
if (outTimeout !== null) {
clearTimeout(outTimeout);
} else {
inTimeout = setTimeout(() => {
this.open(event);
inTimeout = null;
}, (event.type === "focus" ? this.options.focusDelay : this.options.hoverOpenDelay) || this.options.openDelay);
}
};
const outHandler = (event) => {
if (inTimeout !== null) {
clearTimeout(inTimeout);
} else {
outTimeout = setTimeout(() => {
this.close(event);
outTimeout = null;
}, (event.type === "blur" ? this.options.blurDelay : this.options.hoverCloseDelay) || this.options.closeDelay);
}
};
if (events.indexOf("hover") >= 0) {
this._on(this.target, "mouseover", inHandler);
this._on(this.drop, "mouseover", inHandler);
this._on(this.target, "mouseout", outHandler);
this._on(this.drop, "mouseout", outHandler);
}
if (events.indexOf("focus") >= 0) {
this._on(this.target, "focus", inHandler);
this._on(this.drop, "focus", inHandler);
this._on(this.target, "blur", outHandler);
this._on(this.drop, "blur", outHandler);
}
}
isOpened() {
if (this.drop) {
return hasClass(this.drop, `${drop.classPrefix}-open`);
}
}
toggle(event) {
if (this.isOpened()) {
this.close(event);
} else {
this.open(event);
}
}
open(event) {
/* eslint no-unused-vars: 0 */
if (this.isOpened()) {
return;
}
if (!this.drop.parentNode) {
document.body.appendChild(this.drop);
}
if (typeof this.tether !== "undefined") {
this.tether.enable();
}
addClass(this.drop, `${drop.classPrefix}-open`);
addClass(this.drop, `${drop.classPrefix}-open-transitionend`);
setTimeout(() => {
if (this.drop) {
addClass(this.drop, `${drop.classPrefix}-after-open`);
}
});
this.trigger("beforeOpen");
if (typeof this.tether !== "undefined") {
this.tether.position();
}
this.trigger("open");
drop.updateBodyClasses();
}
_transitionEndHandler(e) {
if (e.target !== e.currentTarget) {
return;
}
if (!hasClass(this.drop, `${drop.classPrefix}-open`)) {
removeClass(this.drop, `${drop.classPrefix}-open-transitionend`);
}
this.drop.removeEventListener(
transitionEndEvent,
this.transitionEndHandler
);
}
beforeCloseHandler(event) {
let shouldClose = true;
if (!this.isClosing && typeof this.options.beforeClose === "function") {
this.isClosing = true;
shouldClose = this.options.beforeClose(event, this) !== false;
}
this.isClosing = false;
return shouldClose;
}
close(event) {
if (!this.isOpened()) {
return;
}
if (!this.beforeCloseHandler(event)) {
return;
}
removeClass(this.drop, `${drop.classPrefix}-open`);
removeClass(this.drop, `${drop.classPrefix}-after-open`);
this.drop.addEventListener(transitionEndEvent, this.transitionEndHandler);
this.trigger("close");
if (typeof this.tether !== "undefined") {
this.tether.disable();
}
drop.updateBodyClasses();
if (this.options.remove) {
this.remove(event);
}
}
remove(event) {
this.close(event);
if (this.drop.parentNode) {
this.drop.parentNode.removeChild(this.drop);
}
}
position() {
if (this.isOpened() && typeof this.tether !== "undefined") {
this.tether.position();
}
}
destroy() {
this.remove();
if (typeof this.tether !== "undefined") {
this.tether.destroy();
}
for (let i = 0; i < this._boundEvents.length; ++i) {
const { element, event, handler } = this._boundEvents[i];
element.removeEventListener(event, handler);
}
this._boundEvents = [];
this.tether = null;
this.drop = null;
this.content = null;
this.target = null;
removeFromArray(allDrops[drop.classPrefix], this);
removeFromArray(drop.drops, this);
}
}
return drop;
}
const Drop = createContext();
export default Drop;
document.addEventListener("DOMContentLoaded", () => {
Drop.updateBodyClasses();
});

Some files were not shown because too many files have changed in this diff Show More