diff --git a/.betterer.results b/.betterer.results index 4439c212997..c635282bf6b 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1849,9 +1849,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "5"], [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], - "public/app/features/dashboard/api/v1.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/dashboard/components/AddLibraryPanelWidget/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./AddLibraryPanelWidget\`)", "0"] ], diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml index 97638769526..c3a48f97481 100644 --- a/.github/workflows/i18n-crowdin-upload.yml +++ b/.github/workflows/i18n-crowdin-upload.yml @@ -7,6 +7,8 @@ on: - 'public/locales/en-US/grafana.json' - 'public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json' - 'public/app/plugins/datasource/mssql/locales/en-US/mssql.json' + - 'packages/grafana-sql/src/locales/en-US/grafana-sql.json' + - 'packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json' branches: - main diff --git a/Makefile b/Makefile index 469706de274..837c794858c 100644 --- a/Makefile +++ b/Makefile @@ -140,6 +140,8 @@ endif i18n-extract: i18n-extract-enterprise @echo "Extracting i18n strings for OSS" yarn run i18next --config public/locales/i18next-parser.config.cjs + @echo "Extracting i18n strings for packages" + yarn run packages:i18n-extract @echo "Extracting i18n strings for plugins" yarn run plugin:i18n-extract diff --git a/contribute/internationalization.md b/contribute/internationalization.md index d33e354a5a7..bc117459808 100644 --- a/contribute/internationalization.md +++ b/contribute/internationalization.md @@ -75,9 +75,8 @@ const ErrorMessage = ({ id, message }) => There Sometimes you may need to translate a string cannot be represented in JSX, such as `placeholder` props. Use the `t` macro for this. ```jsx -import { useTranslate } from "@grafana/i18n" +import { t } from "@grafana/i18n" -const { t } = useTranslate(); const placeholder = t('form.username-placeholder','Username'); return @@ -211,9 +210,8 @@ import { Trans } from '@grafana/i18n'; ``` ```js -import { useTranslate } from '@grafana/i18n'; +import { t } from '@grafana/i18n'; -const { t } = useTranslate(); const translatedString = t('inbox.heading', 'You got {{count}} messages', { count: messages.length }); ``` diff --git a/crowdin.yml b/crowdin.yml index 59366d9a3c0..750bf8d1f4a 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -21,4 +21,16 @@ files: [ "type": "i18next_json", "dest": "plugins/mssql/en-US/%original_file_name%" }, + { + "source": "packages/grafana-sql/src/locales/en-US/grafana-sql.json", + "translation": "packages/grafana-sql/src/locales/%locale%/%original_file_name%", + "type": "i18next_json", + "dest": "packages/grafana-sql/en-US/%original_file_name%" + }, + { + "source": "packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json", + "translation": "packages/grafana-prometheus/src/locales/%locale%/%original_file_name%", + "type": "i18next_json", + "dest": "packages/grafana-prometheus/en-US/%original_file_name%" + }, ] diff --git a/docs/sources/developers/http_api/library_element.md b/docs/sources/developers/http_api/library_element.md index 19a5577c202..7af41cfa331 100644 --- a/docs/sources/developers/http_api/library_element.md +++ b/docs/sources/developers/http_api/library_element.md @@ -35,7 +35,7 @@ Returns a list of all library elements the authenticated user has permission to Query parameters: - `searchString`: Part of the name or description searched for. -- `kind`: Kind of element to search for. Use `1` for library panels or `2` for library variables. +- `kind`: Kind of element to search for. Use `1` for library panels. - `sortDirection`: Sort order of elements. Use `alpha-asc` for ascending and `alpha-desc` for descending sort order. - `typeFilter`: A comma separated list of types to filter the elements by. - `excludeUid`: Element UID to exclude from search results. @@ -285,7 +285,7 @@ JSON Body schema: - `folderUid`: Optional, the UID of the folder where the library element is stored, empty string when it is at the root level. - `name`: Optional, the name of the library element. - `model`: The JSON model for the library element. -- `kind`: Kind of element to create, Use `1` for library panels or `2` for library variables. +- `kind`: Kind of element to create, Use `1` for library panels. - `uid`: Optional, the [unique identifier](#identifier-id-vs-unique-identifier-uid). **Example Request**: @@ -364,7 +364,7 @@ JSON Body schema: - `folderUid`: UID of the folder where the library element is stored, empty string when it is at the root level. - `name`: Name of the library element. - `model`: The JSON model for the library element. -- `kind`: Kind of element to create. Use `1` for library panels or `2` for library variables. +- `kind`: Kind of element to create. Use `1` for library panels. - `version`: Version of the library element you are updating. - `uid`: Optional, the [unique identifier](#identifier-id-vs-unique-identifier-uid). diff --git a/docs/sources/observability-as-code/schema-v2/_index.md b/docs/sources/observability-as-code/schema-v2/_index.md index d7c2728947b..61de6337b15 100644 --- a/docs/sources/observability-as-code/schema-v2/_index.md +++ b/docs/sources/observability-as-code/schema-v2/_index.md @@ -20,7 +20,7 @@ weight: 200 {{< admonition type="caution" >}} -Dashboard JSON schema v2 is an experimental feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. To get early access to this feature, request it through [this form](https://docs.google.com/forms/d/e/1FAIpQLSd73nQzuhzcHJOrLFK4ef_uMxHAQiPQh1-rsQUT2MRqbeMLpg/viewform?usp=dialog). +Dashboard JSON schema v2 is an experimental feature. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `dashboardNewLayouts` feature toggle in Grafana to use this feature. **Do not enable this feature in production environments as it may result in the irreversible loss of data.** diff --git a/docs/sources/panels-visualizations/configure-standard-options/index.md b/docs/sources/panels-visualizations/configure-standard-options/index.md index fdad3c786f4..5b42b26f9cf 100644 --- a/docs/sources/panels-visualizations/configure-standard-options/index.md +++ b/docs/sources/panels-visualizations/configure-standard-options/index.md @@ -172,14 +172,15 @@ You can further define a custom unit with specific syntax. For example, to set a The following table lists the special syntax options for custom units: -| Custom unit | Description | -| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `suffix:` | Custom unit that should go after value. | -| `prefix:` | Custom unit that should go before value. | -| `time:` | Custom date time formats type, such as `time:YYYY-MM-DD`. Refer to [formats](https://momentjs.com/docs/#/displaying/) for the format syntax and options. | -| `si:` | Custom SI units, such as `si: mF`. You can specify both a unit and the source data scale. For example, if your source data is represented as milli-something, prefix the unit with the `m` SI scale character. | -| `count:` | Custom count unit. | -| `currency:` | Custom currency unit. | +| Custom unit | Description | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `suffix:` | Custom unit that should go after value. | +| `prefix:` | Custom unit that should go before value. | +| `time:` | Custom date time formats type, such as `time:YYYY-MM-DD`. Refer to [formats](https://momentjs.com/docs/#/displaying/) for the format syntax and options. | +| `si:` | Custom SI units, such as `si: mF`. You can specify both a unit and the source data scale. For example, if your source data is represented as milli-something, prefix the unit with the `m` SI scale character. | +| `count:` | Custom count unit. | +| `currency:` | Custom currency unit. | +| `currency:financial:` | Full format currency unit without abbreviations. Displays complete numeric values instead of scaled abbreviations (K: Thousand, M: Million, B: Billion, T: Trillion). For example, `currency:financial:$` displays `500,555` instead of `$501K`. Add `:suffix` to place the symbol after the number: `currency:financial:€:suffix` displays `500,555€`. | You can also paste a native emoji in the **Unit** drop-down and select it as a custom unit: diff --git a/docs/sources/panels-visualizations/visualizations/traces/index.md b/docs/sources/panels-visualizations/visualizations/traces/index.md index d89ea25b559..a82d0be2662 100644 --- a/docs/sources/panels-visualizations/visualizations/traces/index.md +++ b/docs/sources/panels-visualizations/visualizations/traces/index.md @@ -50,9 +50,13 @@ refs: # Traces +{{< shared id="traces-viz" >}} + Traces visualizations let you follow a request as it traverses the services in your infrastructure. The traces visualization displays traces data in a diagram that allows you to easily interpret it. Traces visualizations currently render one trace traversal based on the traceID used in TraceQL or using a variable. +{{< /shared >}} + For more information about traces and how to use them, refer to the following documentation: - [Tracing in Explore](ref:tracing-in-explore) diff --git a/eslint.config.js b/eslint.config.js index 28bc0da1adb..055bb6e4158 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -128,7 +128,7 @@ module.exports = [ { group: ['react-i18next', 'i18next'], importNames: ['t'], - message: 'Please import useTranslate from @grafana/i18n and use the t function instead', + message: 'Please import from @grafana/i18n instead', }, { group: ['react-i18next'], @@ -304,6 +304,8 @@ module.exports = [ files: [ 'public/app/!(plugins)/**/*.{ts,tsx,js,jsx}', 'packages/grafana-ui/**/*.{ts,tsx,js,jsx}', + 'packages/grafana-sql/**/*.{ts,tsx,js,jsx}', + 'packages/grafana-prometheus/**/*.{ts,tsx,js,jsx}', ...pluginsToTranslate.map((plugin) => `${plugin}/**/*.{ts,tsx,js,jsx}`), ], ignores: [ diff --git a/kinds/gen.go b/kinds/gen.go index d3983becdf6..ac7bd7e7d4e 100644 --- a/kinds/gen.go +++ b/kinds/gen.go @@ -173,6 +173,9 @@ func loadCueFiles(ctx *cue.Context, dirs []os.DirEntry) ([]codegen.SchemaForGen, os.Exit(1) } + if len(entries) == 0 { + continue + } // It's assuming that we only have one file in each folder entry := filepath.Join(dir.Name(), entries[0].Name()) cueFile, err := os.ReadFile(entry) diff --git a/package.json b/package.json index 9897b90f1fe..3c0e0c45d29 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "lint:fix": "yarn lint:ts --fix", "packages:build": "nx run-many -t build --projects='tag:scope:package'", "packages:clean": "rimraf ./npm-artifacts && nx run-many -t clean --projects='tag:scope:package' --maxParallel=100", + "packages:i18n-extract": "nx run-many -t i18n-extract --projects='tag:scope:package'", "packages:prepare": "lerna version --no-push --no-git-tag-version --force-publish --exact", "packages:pack": "mkdir -p ./npm-artifacts && lerna exec --no-private -- yarn pack --out \"../../npm-artifacts/%s-%v.tgz\"", "packages:typecheck": "nx run-many -t typecheck --projects='tag:scope:package'", @@ -285,8 +286,8 @@ "@grafana/plugin-ui": "0.10.6", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.18.0", - "@grafana/scenes-react": "^6.18.0", + "@grafana/scenes": "^6.19.0", + "@grafana/scenes-react": "^6.19.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 0730b803f1e..4de96104985 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -38,7 +38,7 @@ "publishConfig": { "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.cts", + "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -96,7 +96,6 @@ "react-dom": "18.3.1", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "typescript": "5.7.3" diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index f67f12e07d0..98f8341f6de 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -import { entryPoint, plugins, esmOutput, cjsOutput, tsDeclarationOutput } from '../rollup.config.parts'; +import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); @@ -16,18 +16,4 @@ export default [ plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], }, - tsDeclarationOutput(pkg), - tsDeclarationOutput(pkg, { - input: './compiled/unstable.d.ts', - output: [ - { - file: './dist/cjs/unstable.d.cts', - format: 'cjs', - }, - { - file: './dist/esm/unstable.d.mts', - format: 'es', - }, - ], - }), ]; diff --git a/packages/grafana-data/src/valueFormats/symbolFormatters.ts b/packages/grafana-data/src/valueFormats/symbolFormatters.ts index d9cdc85616c..7f68a006805 100644 --- a/packages/grafana-data/src/valueFormats/symbolFormatters.ts +++ b/packages/grafana-data/src/valueFormats/symbolFormatters.ts @@ -26,6 +26,52 @@ export function currency(symbol: string, asSuffix?: boolean): ValueFormatter { }; } +/** + * Formats currency values without scaling abbreviations(K: Thousands, M: Millions, B: Billions), displaying full numeric values. + * Uses cached Intl.NumberFormat objects for performance. + * + * @param symbol - Currency symbol (e.g., '$', '€', '£') + * @param asSuffix - If true, places symbol after number + * + * @example + * fullCurrency('$')(1234.56, 2) // { prefix: '$', text: '1,234.56' } - forces 2 decimals + * fullCurrency('€', true)(42.5) // { suffix: '€', text: '42.5' } + */ +export function fullCurrency(symbol: string, asSuffix?: boolean): ValueFormatter { + const locale = Intl.NumberFormat().resolvedOptions().locale; + const defaultFormatter = new Intl.NumberFormat(locale, { minimumFractionDigits: 0, maximumFractionDigits: 1 }); + const formattersCache = new Map(); + + return (value: number | null, decimals?: DecimalCount) => { + if (value === null) { + return { text: '' }; + } + + const numericValue: number = value; + + let text: string; + if (decimals !== undefined && decimals !== null) { + let formatter = formattersCache.get(decimals); + if (!formatter) { + formatter = new Intl.NumberFormat(locale, { + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + }); + formattersCache.set(decimals, formatter); + } + text = formatter.format(numericValue); + } else { + text = defaultFormatter.format(numericValue); + } + + return { + prefix: asSuffix ? '' : symbol, + suffix: asSuffix ? symbol : '', + text, + }; + }; +} + const SI_PREFIXES = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; const SI_BASE_INDEX = SI_PREFIXES.indexOf(''); diff --git a/packages/grafana-data/src/valueFormats/valueFormats.test.ts b/packages/grafana-data/src/valueFormats/valueFormats.test.ts index a0df43a7e8a..c5eccfa4765 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.test.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.test.ts @@ -15,62 +15,67 @@ interface ValueFormatTest { describe('valueFormats', () => { it.each` - format | decimals | value | expected - ${'currencyUSD'} | ${2} | ${1532.82} | ${'$1.53K'} - ${'currencyKRW'} | ${2} | ${1532.82} | ${'₩1.53K'} - ${'currencyIDR'} | ${2} | ${1532.82} | ${'Rp1.53K'} - ${'none'} | ${undefined} | ${3.23} | ${'3.23'} - ${'none'} | ${undefined} | ${0.0245} | ${'0.0245'} - ${'none'} | ${undefined} | ${1 / 3} | ${'0.333'} - ${'ms'} | ${4} | ${0.0024} | ${'0.0024 ms'} - ${'ms'} | ${0} | ${100} | ${'100 ms'} - ${'ms'} | ${2} | ${1250} | ${'1.25 s'} - ${'ms'} | ${1} | ${10000086.123} | ${'2.8 hours'} - ${'ms'} | ${1} | ${-10000086.123} | ${'-2.8 hours'} - ${'ms'} | ${undefined} | ${1000} | ${'1 s'} - ${'ms'} | ${0} | ${1200} | ${'1 s'} - ${'short'} | ${undefined} | ${1000} | ${'1 K'} - ${'short'} | ${undefined} | ${1200} | ${'1.20 K'} - ${'short'} | ${undefined} | ${1250} | ${'1.25 K'} - ${'short'} | ${undefined} | ${1000000} | ${'1 Mil'} - ${'short'} | ${undefined} | ${1500000} | ${'1.50 Mil'} - ${'short'} | ${undefined} | ${1000120} | ${'1.00 Mil'} - ${'short'} | ${undefined} | ${98765} | ${'98.8 K'} - ${'short'} | ${undefined} | ${9876543} | ${'9.88 Mil'} - ${'short'} | ${undefined} | ${9876543} | ${'9.88 Mil'} - ${'kbytes'} | ${undefined} | ${10000000} | ${'9.54 GiB'} - ${'deckbytes'} | ${undefined} | ${10000000} | ${'10 GB'} - ${'megwatt'} | ${3} | ${1000} | ${'1.000 GW'} - ${'mohm'} | ${3} | ${1000} | ${'1.000 Ω'} - ${'kohm'} | ${3} | ${1000} | ${'1.000 MΩ'} - ${'Mohm'} | ${3} | ${1000} | ${'1.000 GΩ'} - ${'farad'} | ${3} | ${1000} | ${'1.000 kF'} - ${'µfarad'} | ${3} | ${1000} | ${'1.000 mF'} - ${'nfarad'} | ${3} | ${1000} | ${'1.000 µF'} - ${'pfarad'} | ${3} | ${1000} | ${'1.000 nF'} - ${'ffarad'} | ${3} | ${1000} | ${'1.000 pF'} - ${'henry'} | ${3} | ${1000} | ${'1.000 kH'} - ${'mhenry'} | ${3} | ${1000} | ${'1.000 H'} - ${'µhenry'} | ${3} | ${1000} | ${'1.000 mH'} - ${'a'} | ${0} | ${1532.82} | ${'1533 a'} - ${'b'} | ${0} | ${1532.82} | ${'1533 b'} - ${'prefix:b'} | ${undefined} | ${1532.82} | ${'b1533'} - ${'suffix:d'} | ${undefined} | ${1532.82} | ${'1533 d'} - ${'si:µF'} | ${2} | ${0} | ${'0.00 µF'} - ${'si:µF'} | ${2} | ${1234} | ${'1.23 mF'} - ${'si:µF'} | ${2} | ${1234000000} | ${'1.23 kF'} - ${'si:µF'} | ${2} | ${1234000000000000} | ${'1.23 GF'} - ${'count:xpm'} | ${2} | ${1234567} | ${'1.23M xpm'} - ${'count:x/min'} | ${2} | ${1234} | ${'1.23K x/min'} - ${'currency:@'} | ${2} | ${1234567} | ${'@1.23M'} - ${'currency:@'} | ${2} | ${1234} | ${'@1.23K'} - ${'time:YYYY'} | ${0} | ${dateTime(new Date(1999, 6, 2)).valueOf()} | ${'1999'} - ${'time:YYYY.MM'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010.07'} - ${'dateTimeAsIso'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010-07-02 00:00:00'} - ${'dateTimeAsUS'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'07/02/2010 12:00:00 am'} - ${'dateTimeAsSystem'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010-07-02 00:00:00'} - ${'dtdurationms'} | ${undefined} | ${100000} | ${'1 minute'} - ${'dtdurationms'} | ${undefined} | ${150000} | ${'2 minutes'} + format | decimals | value | expected + ${'currencyUSD'} | ${2} | ${1532.82} | ${'$1.53K'} + ${'currencyKRW'} | ${2} | ${1532.82} | ${'₩1.53K'} + ${'currencyIDR'} | ${2} | ${1532.82} | ${'Rp1.53K'} + ${'none'} | ${undefined} | ${3.23} | ${'3.23'} + ${'none'} | ${undefined} | ${0.0245} | ${'0.0245'} + ${'none'} | ${undefined} | ${1 / 3} | ${'0.333'} + ${'ms'} | ${4} | ${0.0024} | ${'0.0024 ms'} + ${'ms'} | ${0} | ${100} | ${'100 ms'} + ${'ms'} | ${2} | ${1250} | ${'1.25 s'} + ${'ms'} | ${1} | ${10000086.123} | ${'2.8 hours'} + ${'ms'} | ${1} | ${-10000086.123} | ${'-2.8 hours'} + ${'ms'} | ${undefined} | ${1000} | ${'1 s'} + ${'ms'} | ${0} | ${1200} | ${'1 s'} + ${'short'} | ${undefined} | ${1000} | ${'1 K'} + ${'short'} | ${undefined} | ${1200} | ${'1.20 K'} + ${'short'} | ${undefined} | ${1250} | ${'1.25 K'} + ${'short'} | ${undefined} | ${1000000} | ${'1 Mil'} + ${'short'} | ${undefined} | ${1500000} | ${'1.50 Mil'} + ${'short'} | ${undefined} | ${1000120} | ${'1.00 Mil'} + ${'short'} | ${undefined} | ${98765} | ${'98.8 K'} + ${'short'} | ${undefined} | ${9876543} | ${'9.88 Mil'} + ${'short'} | ${undefined} | ${9876543} | ${'9.88 Mil'} + ${'kbytes'} | ${undefined} | ${10000000} | ${'9.54 GiB'} + ${'deckbytes'} | ${undefined} | ${10000000} | ${'10 GB'} + ${'megwatt'} | ${3} | ${1000} | ${'1.000 GW'} + ${'mohm'} | ${3} | ${1000} | ${'1.000 Ω'} + ${'kohm'} | ${3} | ${1000} | ${'1.000 MΩ'} + ${'Mohm'} | ${3} | ${1000} | ${'1.000 GΩ'} + ${'farad'} | ${3} | ${1000} | ${'1.000 kF'} + ${'µfarad'} | ${3} | ${1000} | ${'1.000 mF'} + ${'nfarad'} | ${3} | ${1000} | ${'1.000 µF'} + ${'pfarad'} | ${3} | ${1000} | ${'1.000 nF'} + ${'ffarad'} | ${3} | ${1000} | ${'1.000 pF'} + ${'henry'} | ${3} | ${1000} | ${'1.000 kH'} + ${'mhenry'} | ${3} | ${1000} | ${'1.000 H'} + ${'µhenry'} | ${3} | ${1000} | ${'1.000 mH'} + ${'a'} | ${0} | ${1532.82} | ${'1533 a'} + ${'b'} | ${0} | ${1532.82} | ${'1533 b'} + ${'prefix:b'} | ${undefined} | ${1532.82} | ${'b1533'} + ${'suffix:d'} | ${undefined} | ${1532.82} | ${'1533 d'} + ${'si:µF'} | ${2} | ${0} | ${'0.00 µF'} + ${'si:µF'} | ${2} | ${1234} | ${'1.23 mF'} + ${'si:µF'} | ${2} | ${1234000000} | ${'1.23 kF'} + ${'si:µF'} | ${2} | ${1234000000000000} | ${'1.23 GF'} + ${'count:xpm'} | ${2} | ${1234567} | ${'1.23M xpm'} + ${'count:x/min'} | ${2} | ${1234} | ${'1.23K x/min'} + ${'currency:@'} | ${2} | ${1234567} | ${'@1.23M'} + ${'currency:@'} | ${2} | ${1234} | ${'@1.23K'} + ${'currency:financial:$'} | ${2} | ${1234567} | ${'$1,234,567.00'} + ${'currency:financial:$'} | ${0} | ${1234567} | ${'$1,234,567'} + ${'currency:financial:$'} | ${undefined} | ${1234567} | ${'$1,234,567'} + ${'currency:financial:€:suffix'} | ${2} | ${1234.56} | ${'1,234.56€'} + ${'currency:financial:COP:suffix'} | ${0} | ${500000} | ${'500,000COP'} + ${'time:YYYY'} | ${0} | ${dateTime(new Date(1999, 6, 2)).valueOf()} | ${'1999'} + ${'time:YYYY.MM'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010.07'} + ${'dateTimeAsIso'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010-07-02 00:00:00'} + ${'dateTimeAsUS'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'07/02/2010 12:00:00 am'} + ${'dateTimeAsSystem'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010-07-02 00:00:00'} + ${'dtdurationms'} | ${undefined} | ${100000} | ${'1 minute'} + ${'dtdurationms'} | ${undefined} | ${150000} | ${'2 minutes'} `( 'With format=$format decimals=$decimals and value=$value then result should be = $expected', async ({ format, value, decimals, expected }) => { diff --git a/packages/grafana-data/src/valueFormats/valueFormats.ts b/packages/grafana-data/src/valueFormats/valueFormats.ts index 0a4b943357f..a971c914c80 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.ts @@ -5,7 +5,7 @@ import { TimeZone } from '../types/time'; import { getCategories } from './categories'; import { toDateTimeValueFormatter } from './dateTimeFormatters'; -import { getOffsetFromSIPrefix, SIPrefix, currency } from './symbolFormatters'; +import { getOffsetFromSIPrefix, SIPrefix, currency, fullCurrency } from './symbolFormatters'; export interface FormattedValue { text: string; @@ -257,8 +257,23 @@ export function getValueFormat(id?: string | null): ValueFormatter { return simpleCountUnit(sub); } + // Supported formats: + // currency:$ -> scaled currency ($1.2K) + // currency:financial:$ -> full currency ($1,234) + // currency:financial:€:suffix -> full currency with suffix (1,234€) if (key === 'currency') { - return currency(sub); + const keySplit = sub.split(':'); + + if (keySplit[0] === 'financial' && keySplit.length >= 2) { + const symbol = keySplit[1]; + if (!symbol) { + return toFixedUnit(''); // fallback for empty symbol + } + const asSuffix = keySplit[2] === 'suffix'; + return fullCurrency(symbol, asSuffix); + } else { + return currency(sub); + } } if (key === 'bool') { diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json index 26f29296c23..e625f825ef6 100644 --- a/packages/grafana-data/tsconfig.json +++ b/packages/grafana-data/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "jsx": "react-jsx", - "declarationDir": "./compiled", + "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."] diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 96644db666d..44283da0639 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -21,7 +21,7 @@ "publishConfig": { "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.cts", + "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -45,7 +45,6 @@ "esbuild": "0.25.0", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0" }, diff --git a/packages/grafana-e2e-selectors/rollup.config.ts b/packages/grafana-e2e-selectors/rollup.config.ts index 338dae23ac9..9888b316a6a 100644 --- a/packages/grafana-e2e-selectors/rollup.config.ts +++ b/packages/grafana-e2e-selectors/rollup.config.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; +import { cjsOutput, entryPoint, esmOutput, plugins } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); @@ -11,5 +11,4 @@ export default [ plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-e2e-selectors')], }, - tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-e2e-selectors/tsconfig.json b/packages/grafana-e2e-selectors/tsconfig.json index 7b26f9907ed..d4afb7d5045 100644 --- a/packages/grafana-e2e-selectors/tsconfig.json +++ b/packages/grafana-e2e-selectors/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "declarationDir": "./compiled", + "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."] diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index f84432883d1..f3926f46cdf 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -21,7 +21,7 @@ "publishConfig": { "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.cts", + "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -77,7 +77,6 @@ "jest": "^29.6.4", "jest-canvas-mock": "2.5.2", "rollup": "^4.22.4", - "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "ts-jest": "29.2.5", diff --git a/packages/grafana-flamegraph/rollup.config.ts b/packages/grafana-flamegraph/rollup.config.ts index dbd64b4abdb..3bc2d6a540f 100644 --- a/packages/grafana-flamegraph/rollup.config.ts +++ b/packages/grafana-flamegraph/rollup.config.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; +import { cjsOutput, entryPoint, esmOutput, plugins } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); @@ -11,5 +11,4 @@ export default [ plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-flamegraph')], }, - tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts index 8128544abb8..b3e62ba1efa 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts +++ b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts @@ -75,6 +75,30 @@ describe('nestedSetToLevels', () => { expect(levels[0]).toEqual([n1]); expect(levels[1]).toEqual([n2, n3, n4]); }); + + it('handles strings that collide with inherited prototype method names', () => { + const frame = createDataFrame({ + fields: [ + { name: 'level', values: [0, 1, 1, 1] }, + { name: 'value', values: [10, 5, 3, 1] }, + { name: 'label', values: ['toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf'], type: FieldType.string }, + { name: 'self', values: [10, 5, 3, 1] }, + ], + }); + const [levels] = nestedSetToLevels(new FlameGraphDataContainer(frame, { collapsing: true })); + + const n4: LevelItem = { itemIndexes: [3], start: 8, children: [], value: 1, level: 1 }; + const n3: LevelItem = { itemIndexes: [2], start: 5, children: [], value: 3, level: 1 }; + const n2: LevelItem = { itemIndexes: [1], start: 0, children: [], value: 5, level: 1 }; + const n1: LevelItem = { itemIndexes: [0], start: 0, children: [n2, n3, n4], value: 10, level: 0 }; + + n2.parents = [n1]; + n3.parents = [n1]; + n4.parents = [n1]; + + expect(levels[0]).toEqual([n1]); + expect(levels[1]).toEqual([n2, n3, n4]); + }); }); describe('FlameGraphDataContainer', () => { diff --git a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts index 51b41f97397..b4d545e90de 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts +++ b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts @@ -46,7 +46,7 @@ export function nestedSetToLevels( let offset = 0; let parent: LevelItem | undefined = undefined; - const uniqueLabels: Record = {}; + const uniqueLabels: Record = Object.create(null); for (let i = 0; i < container.data.length; i++) { const currentLevel = container.getLevel(i); diff --git a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx index 1ee3f172fa4..b3a1be9a3b3 100644 --- a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx +++ b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx @@ -56,7 +56,7 @@ const FlameGraphTopTableContainer = memo( const table = useMemo(() => { // Group the data by label, we show only one row per label and sum the values // TODO: should be by filename + funcName + linenumber? - let filteredTable: { [key: string]: TableData } = {}; + let filteredTable: { [key: string]: TableData } = Object.create(null); for (let i = 0; i < data.data.length; i++) { const value = data.getValue(i); const valueRight = data.getValueRight(i); diff --git a/packages/grafana-flamegraph/tsconfig.json b/packages/grafana-flamegraph/tsconfig.json index 0092e86ac62..a3f1c194ab8 100644 --- a/packages/grafana-flamegraph/tsconfig.json +++ b/packages/grafana-flamegraph/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "jsx": "react-jsx", "baseUrl": "./", - "declarationDir": "./compiled", + "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."] diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index c957cf1ac8f..b06109d02db 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -35,7 +35,7 @@ "publishConfig": { "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.cts", + "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ diff --git a/packages/grafana-i18n/rollup.config.ts b/packages/grafana-i18n/rollup.config.ts index e7ad4450725..3171639a367 100644 --- a/packages/grafana-i18n/rollup.config.ts +++ b/packages/grafana-i18n/rollup.config.ts @@ -1,7 +1,7 @@ import { createRequire } from 'node:module'; import copy from 'rollup-plugin-copy'; -import { entryPoint, plugins, esmOutput, cjsOutput, tsDeclarationOutput } from '../rollup.config.parts'; +import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); @@ -17,5 +17,4 @@ export default [ ], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-i18n')], }, - tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js index 8c5afef89bc..9913ab10f73 100644 --- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js +++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js @@ -7,9 +7,8 @@ const filename = 'public/app/features/some-feature/nested/SomeFile.tsx'; const packageName = '@grafana/i18n'; const TRANS_IMPORT = `import { Trans } from '${packageName}';`; -const T_IMPORT = `import { t } from '${packageName}/internal';`; -const USE_TRANSLATE_IMPORT = `import { useTranslate } from '${packageName}';`; -const TRANS_AND_USE_TRANSLATE_IMPORT = `import { Trans, useTranslate } from '${packageName}';`; +const T_IMPORT = `import { t } from '${packageName}';`; +const TRANS_AND_T_IMPORT = `import { Trans, t } from '${packageName}';`; const ruleTester = new RuleTester({ languageOptions: { @@ -330,7 +329,7 @@ const Foo = () =>
) @@ -429,10 +427,9 @@ const Foo = () => { { messageId: 'wrapWithT', output: ` -${TRANS_AND_USE_TRANSLATE_IMPORT} +${TRANS_AND_T_IMPORT} const Foo = () => { - const { t } = useTranslate(); -return ( + return (
) }`, @@ -469,36 +466,6 @@ const Foo = () => { }, ], }, - { - name: 'Fixes correctly when useTranslate already exists', - code: ` -${USE_TRANSLATE_IMPORT} -const Foo = () => { - const { t } = useTranslate(); - return ( -
- ) -}`, - filename, - errors: [ - { - messageId: 'noUntranslatedStringsProp', - suggestions: [ - { - messageId: 'wrapWithT', - output: ` -${USE_TRANSLATE_IMPORT} -const Foo = () => { - const { t } = useTranslate(); - return ( -
- ) -}`, - }, - ], - }, - ], - }, { name: 'Fixes and uses ID from attribute if exists', @@ -563,41 +530,6 @@ const Foo = () =>
`, ], }, - { - name: 'Fixes correctly when useTranslate import already exists', - code: ` -${USE_TRANSLATE_IMPORT} -const Foo = () => { - const { t } = useTranslate(); - return (<> -
-
- ) -} -`, - filename, - errors: [ - { - messageId: 'noUntranslatedStringsProp', - suggestions: [ - { - messageId: 'wrapWithT', - output: ` -${USE_TRANSLATE_IMPORT} -const Foo = () => { - const { t } = useTranslate(); - return (<> -
-
- ) -} -`, - }, - ], - }, - ], - }, - { name: 'Fixes correctly when no return statement', code: ` @@ -637,8 +569,7 @@ const Foo = () =>
`, { messageId: 'wrapWithT', output: ` -${T_IMPORT} -${TRANS_IMPORT} +${TRANS_AND_T_IMPORT} const Foo = () =>
`, }, ], @@ -787,10 +718,9 @@ const Foo = () => { { messageId: 'wrapWithT', output: ` -${USE_TRANSLATE_IMPORT} +${T_IMPORT} const Foo = () => { - const { t } = useTranslate(); -const thing = { + const thing = { label: t(\"some-feature.foo.thing.label.test\", \"test\"), } @@ -893,10 +823,9 @@ const Foo = () => { filename, options: [{ forceFix: ['public/app/features/some-feature'] }], output: ` -${USE_TRANSLATE_IMPORT} +${T_IMPORT} const Foo = () => { - const { t } = useTranslate(); -return
+ return
}`, errors: [ { @@ -905,10 +834,9 @@ return
{ messageId: 'wrapWithT', output: ` -${USE_TRANSLATE_IMPORT} +${T_IMPORT} const Foo = () => { - const { t } = useTranslate(); -return
+ return
}`, }, ], diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs b/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs index 77de33dbed0..070e0c5ee53 100644 --- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs +++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs @@ -98,11 +98,10 @@ function canBeFixed(node, context) { } // If we're going to try and fix using `t`, and it already exists in the scope, - // but not from `useTranslate`, then we can't fix/provide a suggestion + // then we can't fix/provide a suggestion if (isPropertyOrAttribute && parentMethod) { const hasTDeclaration = getTDeclaration(parentMethod, context); - const hasUseTranslateDeclaration = methodHasUseTranslate(parentMethod, context); - if (hasTDeclaration && !hasUseTranslateDeclaration) { + if (hasTDeclaration) { return false; } } @@ -135,6 +134,15 @@ function canBeFixed(node, context) { return true; } +/** + * For a given node, check the scope and find a variable declaration of `t` + * @param {Node} node + * @param {RuleContextWithOptions} context + */ +function getTDeclaration(node, context) { + return context.sourceCode.getScope(node).variables.find((v) => v.name === 't'); +} + /** * Gets the translation prefix from the filename * @param {RuleContextWithOptions} context @@ -233,44 +241,11 @@ function getComponentNames(node, context) { return names; } -/** - * For a given node, check the scope and find a variable declaration of `t` - * @param {Node} node - * @param {RuleContextWithOptions} context - */ -function getTDeclaration(node, context) { - return context.sourceCode.getScope(node).variables.find((v) => v.name === 't'); -} - -/** - * Checks if a node has a variable declaration of `t` - * that came from a `useTranslate` call - * @param {Node} node The node - * @param {RuleContextWithOptions} context - */ -function methodHasUseTranslate(node, context) { - const tDeclaration = getTDeclaration(node, context); - return ( - tDeclaration && - tDeclaration.defs.find((definition) => { - const isVariableDeclaration = definition.node.type === AST_NODE_TYPES.VariableDeclarator; - const declarationInit = isVariableDeclaration ? definition.node.init : null; - return ( - isVariableDeclaration && - declarationInit && - declarationInit.type === AST_NODE_TYPES.CallExpression && - declarationInit.callee.type === AST_NODE_TYPES.Identifier && - declarationInit.callee.name === 'useTranslate' - ); - }) - ); -} - /** * Gets the import fixer for a node * @param {JSXElement|JSXFragment|JSXAttribute|Property} node * @param {RuleFixer} fixer The fixer - * @param {'Trans'|'t'|'useTranslate'} importName The member to import from either `@grafana/i18n` or `@grafana/i18n/internal` + * @param {'Trans'|'t'} importName The member to import from `@grafana/i18n` * @param {RuleContextWithOptions} context * @returns {import('@typescript-eslint/utils/ts-eslint').RuleFix|undefined} The fix */ @@ -280,22 +255,9 @@ function getImportsFixer(node, fixer, importName, context) { /** Map of where we expect to import each translation util from */ const importPackage = { Trans: '@grafana/i18n', - useTranslate: '@grafana/i18n', - t: '@grafana/i18n/internal', + t: '@grafana/i18n', }; - const parentMethod = getParentMethod(node, context); - - if (importName === 't') { - // If we're trying to import `t`, - // and there's already a `t` variable declaration in the parent method that came from `useTranslate`, - // do nothing - const declarationFromUseTranslate = parentMethod ? methodHasUseTranslate(parentMethod, context) : false; - if (declarationFromUseTranslate) { - return; - } - } - const expectedImport = importPackage[importName]; const existingAppCoreI18n = body.find( @@ -354,73 +316,6 @@ const getTransFixers = (node, context) => (fixer) => { return fixes; }; -/** - * @param {string} str - */ -const firstCharIsUpper = (str) => { - return str.charAt(0) === str.charAt(0).toUpperCase(); -}; - -/** - * @param {JSXAttribute|Property} node - * @param {RuleFixer} fixer - * @param {RuleContextWithOptions} context - * @returns {import('@typescript-eslint/utils/ts-eslint').RuleFix|undefined} The fix - */ -const getUseTranslateFixer = (node, fixer, context) => { - const parentMethod = getParentMethod(node, context); - - const functionIsNotUpperCase = - parentMethod && - parentMethod.type === AST_NODE_TYPES.FunctionDeclaration && - (!parentMethod.id || !firstCharIsUpper(parentMethod.id.name)); - - const variableDeclaratorIsNotUpperCase = - parentMethod && - parentMethod.parent.type === AST_NODE_TYPES.VariableDeclarator && - parentMethod.parent.id.type === AST_NODE_TYPES.Identifier && - !firstCharIsUpper(parentMethod.parent.id.name); - - // If the node is not within a function, or the parent method does not start with an uppercase letter, - // then we can't reliably add `useTranslate`, as this may not be a React component - if ( - !parentMethod || - functionIsNotUpperCase || - variableDeclaratorIsNotUpperCase || - parentMethod.body.type !== AST_NODE_TYPES.BlockStatement - ) { - return; - } - - const returnStatement = parentMethod.body.body.find((node) => node.type === AST_NODE_TYPES.ReturnStatement); - if (!returnStatement) { - return; - } - - const returnStatementIsJsx = - returnStatement.argument && - (returnStatement.argument.type === AST_NODE_TYPES.JSXElement || - returnStatement.argument.type === AST_NODE_TYPES.JSXFragment); - - if (!returnStatementIsJsx) { - return; - } - const tDeclarationExists = getTDeclaration(parentMethod, context); - const useTranslateExists = methodHasUseTranslate(parentMethod, context); - - if (tDeclarationExists && useTranslateExists) { - return; - } - - // If we've got all this way, then: - // - There is a parent method - // - It returns JSX - // - The method name starts with a capital letter - // - There is not already a call to `useTranslate` in the parent method - // In that scenario, we assume that we can fix and add a usage of the hook to the start of the body of the method - return fixer.insertTextBefore(parentMethod.body.body[0], 'const { t } = useTranslate();\n'); -}; - /** * @param {JSXAttribute|Property} node * @param {RuleContextWithOptions} context @@ -440,14 +335,7 @@ const getTFixers = (node, context) => (fixer) => { ); } - // Check if we need to add `useTranslate` to the node - const useTranslateFixer = getUseTranslateFixer(node, fixer, context); - if (useTranslateFixer) { - fixes.push(useTranslateFixer); - } - - // Check if we need to add `t` or `useTranslate` to the imports - const importToAdd = useTranslateFixer ? 'useTranslate' : 't'; + const importToAdd = 't'; const importsFixer = getImportsFixer(node, fixer, importToAdd, context); if (importsFixer) { fixes.push(importsFixer); diff --git a/packages/grafana-i18n/src/i18n.tsx b/packages/grafana-i18n/src/i18n.tsx index 343061a6c42..6a5f3f25c05 100644 --- a/packages/grafana-i18n/src/i18n.tsx +++ b/packages/grafana-i18n/src/i18n.tsx @@ -192,11 +192,6 @@ export const t: TFunction = (id: string, defaultMessage: string, values?: Record return tFunc(id, defaultMessage, values); }; -export function useTranslate() { - initDefaultI18nInstance(); - return { t }; -} - export function Trans(props: TransProps) { initDefaultI18nInstance(); const Component = transComponent ?? I18NextTrans; diff --git a/packages/grafana-i18n/src/index.ts b/packages/grafana-i18n/src/index.ts index 1aa6432ed39..a4ebd2b5289 100644 --- a/packages/grafana-i18n/src/index.ts +++ b/packages/grafana-i18n/src/index.ts @@ -22,6 +22,6 @@ export { PSEUDO_LOCALE, DEFAULT_LANGUAGE, } from './constants'; -export { initPluginTranslations, Trans, useTranslate } from './i18n'; +export { initPluginTranslations, t, Trans } from './i18n'; export type { ResourceLoader, Resources, TFunction, TransProps } from './types'; export { formatDate, formatDuration, formatDateRange } from './dates'; diff --git a/packages/grafana-i18n/src/internal/index.ts b/packages/grafana-i18n/src/internal/index.ts index c54d0ef93fb..7757d7b622d 100644 --- a/packages/grafana-i18n/src/internal/index.ts +++ b/packages/grafana-i18n/src/internal/index.ts @@ -17,5 +17,4 @@ export { getLanguage, getResolvedLanguage, initializeI18n, - t, } from '../i18n'; diff --git a/packages/grafana-i18n/tsconfig.json b/packages/grafana-i18n/tsconfig.json index 73494649046..3fa337c4f19 100644 --- a/packages/grafana-i18n/tsconfig.json +++ b/packages/grafana-i18n/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "jsx": "react-jsx", - "declarationDir": "./compiled", + "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."] diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index de7aba31fe0..e1de7c8a8b4 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -26,13 +26,14 @@ "publishConfig": { "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/cjs/index.d.cts", + "types": "./dist/types/index.d.ts", "access": "public" }, "scripts": { "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", + "i18n-extract": "i18next --config src/locales/i18next-parser.config.cjs", "typecheck": "tsc --emitDeclarationOnly false --noEmit", "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", "postpack": "mv package.json.bak package.json" @@ -42,6 +43,7 @@ "@floating-ui/react": "0.27.12", "@grafana/data": "12.1.0-pre", "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/i18n": "12.1.0-pre", "@grafana/plugin-ui": "0.10.6", "@grafana/runtime": "12.1.0-pre", "@grafana/schema": "12.1.0-pre", @@ -87,13 +89,13 @@ "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", "esbuild": "0.25.0", + "i18next-parser": "9.3.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "react": "18.3.1", "react-dom": "18.3.1", "react-select-event": "5.5.1", "rollup": "^4.22.4", - "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.2.0", "rollup-plugin-node-externals": "^8.0.0", "testing-library-selector": "0.3.1", diff --git a/packages/grafana-prometheus/rollup.config.ts b/packages/grafana-prometheus/rollup.config.ts index 5c7d9dd3c04..509884c7a1a 100644 --- a/packages/grafana-prometheus/rollup.config.ts +++ b/packages/grafana-prometheus/rollup.config.ts @@ -1,7 +1,7 @@ import image from '@rollup/plugin-image'; import { createRequire } from 'node:module'; -import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; +import { cjsOutput, entryPoint, esmOutput, plugins } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); @@ -12,5 +12,4 @@ export default [ plugins: [...plugins, image()], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-prometheus')], }, - tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx index 0cce4331a46..b6f10b7d556 100644 --- a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx +++ b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx @@ -83,12 +83,12 @@ describe('AnnotationQueryEditor', () => { it('displays an error message when annotation data is missing', () => { render(); - expect(screen.getByText('annotation data load error!')).toBeInTheDocument(); + expect(screen.getByText('Annotation data load error!')).toBeInTheDocument(); }); it('displays an error message when onAnnotationChange is missing', () => { render(); - expect(screen.getByText('annotation data load error!')).toBeInTheDocument(); + expect(screen.getByText('Annotation data load error!')).toBeInTheDocument(); }); it('renders correctly with an empty annotation object', () => { @@ -96,7 +96,7 @@ describe('AnnotationQueryEditor', () => { // Should render normally with empty values but not show an error expect(screen.getByText('Min step')).toBeInTheDocument(); expect(screen.getByText('Title')).toBeInTheDocument(); - expect(screen.queryByText('annotation data load error!')).not.toBeInTheDocument(); + expect(screen.queryByText('Annotation data load error!')).not.toBeInTheDocument(); }); it('calls onChange when min step is updated', () => { diff --git a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx index bc7746f2bdf..cff87f522fa 100644 --- a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx +++ b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx @@ -4,6 +4,7 @@ import { memo } from 'react'; import { AnnotationQuery } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { Trans, t } from '@grafana/i18n'; import { EditorField, EditorRow, EditorRows, EditorSwitch } from '@grafana/plugin-ui'; import { AutoSizeInput, Input, Space } from '@grafana/ui'; @@ -30,7 +31,13 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props: const { annotation, onAnnotationChange, onChange, onRunQuery, query } = props; if (!annotation || !onAnnotationChange) { - return

annotation data load error!

; + return ( +

+ + Annotation data load error! + +

+ ); } const handleMinStepChange = (value: string) => { @@ -71,18 +78,24 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props: + An additional lower limit for the step parameter of the Prometheus query and for the{' '} - $__interval and $__rate_interval variables. - + {'{{intervalVar}}'} and {'{{rateIntervalVar}}'} variables. + } > handleMinStepChange(e.currentTarget.value)} @@ -94,10 +107,12 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props: - + ) => { return (
-

PromQL Cheat Sheet

+

+ PromQL Cheat Sheet +

{CHEAT_SHEET_ITEMS.map((item, index) => (
{item.title}
diff --git a/packages/grafana-prometheus/src/components/PromExemplarField.tsx b/packages/grafana-prometheus/src/components/PromExemplarField.tsx index 2e1f0272965..093737e0b28 100644 --- a/packages/grafana-prometheus/src/components/PromExemplarField.tsx +++ b/packages/grafana-prometheus/src/components/PromExemplarField.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { usePrevious } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; import { IconButton, InlineLabel, Tooltip, useStyles2 } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; @@ -48,10 +49,14 @@ export function PromExemplarField({ datasource, onChange, query, ...rest }: Prop
- Exemplars + Exemplars { diff --git a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx index 02ef6d4d461..8e4256dde3a 100644 --- a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx +++ b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { usePrevious } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; +import { t, Trans } from '@grafana/i18n'; import { InlineFormLabel, RadioButtonGroup, useStyles2 } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; @@ -54,7 +55,7 @@ export const PromExploreExtraField = memo(({ query, datasource, onChange, onRunQ return (
@@ -68,9 +69,11 @@ export const PromExploreExtraField = memo(({ query, datasource, onChange, onRunQ flexWrap: 'nowrap', }) )} - aria-label="Query type field" + aria-label={t('components.prom-explore-extra-field.aria-label-query-type-field', 'Query type field')} > - Query type + + Query type + - Min step + Min step { onChange={onChangeQuery} onRunQuery={onRunQuery} initialValue={query.expr ?? ''} - placeholder="Enter a PromQL query…" + placeholder={t('components.prom-query-field.placeholder-enter-a-prom-ql-query', 'Enter a PromQL query…')} datasource={datasource} timeRange={range ?? getDefaultTimeRange()} /> diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx index 2e5afaef131..65ca6fccfeb 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx @@ -3,6 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { dateTime, TimeRange } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { PrometheusDatasource } from '../datasource'; import PrometheusLanguageProvider from '../language_provider'; @@ -270,7 +271,9 @@ describe('PromVariableQueryEditor', () => { render(); await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values'); - const labelSelect = screen.getByLabelText('label-select'); + const labelSelect = screen.getByTestId( + selectors.components.DataSource.Prometheus.variableQueryEditor.labelValues.labelSelect + ); await userEvent.type(labelSelect, 'this'); await selectOptionInTest(labelSelect, 'this'); //display label in label select @@ -296,7 +299,9 @@ describe('PromVariableQueryEditor', () => { render(); await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values'); - const labelSelect = screen.getByLabelText('label-select'); + const labelSelect = screen.getByTestId( + selectors.components.DataSource.Prometheus.variableQueryEditor.labelValues.labelSelect + ); await userEvent.type(labelSelect, 'this'); await selectOptionInTest(labelSelect, 'this'); diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx index 97b61161536..5b9720d4c5d 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx @@ -4,6 +4,7 @@ import { FormEvent, useCallback, useEffect, useState } from 'react'; import { getDefaultTimeRange, QueryEditorProps, SelectableValue, toOption } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { t, Trans } from '@grafana/i18n'; import { AsyncSelect, InlineField, InlineFieldRow, Input, Select, TextArea } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; @@ -124,6 +125,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: }); } else { // fetch the labels filtered by the metric + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings const labelToConsider = [{ label: '__name__', op: '=', value: metric }]; const expr = promQueryModeller.renderLabels(labelToConsider); @@ -258,15 +260,19 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: <> The Prometheus data source plugin provides the following query types for template variables.
+
+ + The Prometheus data source plugin provides the following query types for template variables. + +
} > { setLabelNamesMatch(event.currentTarget.value); @@ -344,15 +357,21 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: {qryType === QueryType.MetricNames && ( Returns a list of metrics matching the specified metric regex.
} + tooltip={ +
+ + Returns a list of metrics matching the specified metric regex. + +
+ } > { setMetric(e.currentTarget.value); @@ -371,19 +390,24 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }: {qryType === QueryType.VarQueryResult && ( - Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e. - sum(go_goroutines). + + Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e. + {'{{exampleQuery}}'}. +
} >