Internationalisation: Mark up @grafana/prometheus package (#105861)

* scaffolding for grafana-prometheus i18n

* markup

* extract translations

* fix tests

* add loadResources + fix object properties

* fix typo + prettier

* remove useTranslate

* update workflow to trigger on changes to grafana-sql/grafana-prometheus packages
This commit is contained in:
Ashley Harrison 2025-06-12 11:29:33 +01:00 committed by GitHub
parent d284cceb67
commit 0879479c15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 1195 additions and 287 deletions

View file

@ -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

View file

@ -27,4 +27,10 @@ files: [
"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%"
},
]

View file

@ -305,6 +305,7 @@ module.exports = [
'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: [

View file

@ -33,6 +33,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",
"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,6 +89,7 @@
"@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",

View file

@ -83,12 +83,12 @@ describe('AnnotationQueryEditor', () => {
it('displays an error message when annotation data is missing', () => {
render(<AnnotationQueryEditor {...defaultProps} annotation={undefined} />);
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(<AnnotationQueryEditor {...defaultProps} onAnnotationChange={undefined} />);
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', () => {

View file

@ -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 <h3>annotation data load error!</h3>;
return (
<h3>
<Trans i18nKey="components.annotation-query-editor.annotation-data-load-error">
Annotation data load error!
</Trans>
</h3>
);
}
const handleMinStepChange = (value: string) => {
@ -71,18 +78,24 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
<PromQueryCodeEditor {...props} query={query} showExplain={false} onRunQuery={onRunQuery} onChange={onChange} />
<EditorRow>
<EditorField
label="Min step"
label={t('components.annotation-query-editor.label-min-step', 'Min step')}
tooltip={
<>
<Trans
i18nKey="components.annotation-query-editor.tooltip-min-step"
values={{ intervalVar: '$__interval', rateIntervalVar: '$__rate_interval' }}
>
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>$__interval</code> and <code>$__rate_interval</code> variables.
</>
<code>{'{{intervalVar}}'}</code> and <code>{'{{rateIntervalVar}}'}</code> variables.
</Trans>
}
>
<AutoSizeInput
type="text"
aria-label="Set lower limit for the step parameter"
placeholder={'auto'}
aria-label={t(
'components.annotation-query-editor.aria-label-lower-limit-parameter',
'Set lower limit for the step parameter'
)}
placeholder={t('components.annotation-query-editor.placeholder-auto', 'auto')}
minWidth={10}
value={query.interval ?? ''}
onChange={(e) => handleMinStepChange(e.currentTarget.value)}
@ -94,10 +107,12 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
<Space v={0.5} />
<EditorRow>
<EditorField
label="Title"
tooltip={
'Use either the name or a pattern. For example, {{instance}} is replaced with label value for the label instance.'
}
label={t('components.annotation-query-editor.label-title', 'Title')}
tooltip={t(
'components.annotation-query-editor.tooltip-either-pattern-example-instance-replaced-label',
'Use either the name or a pattern. For example, {{labelTemplate}} is replaced with label value for the label {{labelName}}.',
{ labelName: 'instance', labelTemplate: '{{instance}}' }
)}
>
<Input
type="text"
@ -107,7 +122,7 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
data-testid={selectors.components.DataSource.Prometheus.annotations.title}
/>
</EditorField>
<EditorField label="Tags">
<EditorField label={t('components.annotation-query-editor.label-tags', 'Tags')}>
<Input
type="text"
placeholder={PLACEHOLDER_TAGS}
@ -117,10 +132,12 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
/>
</EditorField>
<EditorField
label="Text"
tooltip={
'Use either the name or a pattern. For example, {{instance}} is replaced with label value for the label instance.'
}
label={t('components.annotation-query-editor.label-text', 'Text')}
tooltip={t(
'components.annotation-query-editor.tooltip-either-pattern-example-instance-replaced-label',
'Use either the name or a pattern. For example, {{labelTemplate}} is replaced with label value for the label {{labelName}}.',
{ labelName: 'instance', labelTemplate: '{{instance}}' }
)}
>
<Input
type="text"
@ -131,10 +148,11 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
/>
</EditorField>
<EditorField
label="Series value as timestamp"
tooltip={
label={t('components.annotation-query-editor.label-series-value-as-timestamp', 'Series value as timestamp')}
tooltip={t(
'components.annotation-query-editor.tooltip-timestamp-milliseconds-series-value-seconds-multiply',
'The unit of timestamp is milliseconds. If the unit of the series value is seconds, multiply its range vector by 1000.'
}
)}
>
<EditorSwitch
value={annotation.useValueForTime ?? false}

View file

@ -2,6 +2,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, QueryEditorHelpProps } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { useStyles2 } from '@grafana/ui';
import { PromQuery } from '../types';
@ -35,7 +36,9 @@ export const PromCheatSheet = (props: QueryEditorHelpProps<PromQuery>) => {
return (
<div>
<h2>PromQL Cheat Sheet</h2>
<h2>
<Trans i18nKey="components.prom-cheat-sheet.prom-ql-cheat-sheet">PromQL Cheat Sheet</Trans>
</h2>
{CHEAT_SHEET_ITEMS.map((item, index) => (
<div className={styles.cheatSheetItem} key={index}>
<div className={styles.cheatSheetItemTitle}>{item.title}</div>

View file

@ -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
<InlineLabel width="auto" data-testid={rest['data-testid']}>
<Tooltip content={error ?? ''}>
<div className={styles.iconWrapper}>
Exemplars
<Trans i18nKey="components.prom-exemplar-field.exemplars">Exemplars</Trans>
<IconButton
name="eye"
tooltip={!!query.exemplar ? 'Disable query with exemplars' : 'Enable query with exemplars'}
tooltip={
!!query.exemplar
? t('components.prom-exemplar-field.tooltip-disable-query', 'Disable query with exemplars')
: t('components.prom-exemplar-field.tooltip-enable-query', 'Enable query with exemplars')
}
disabled={!!error}
className={iconButtonStyles}
onClick={() => {

View file

@ -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 (
<div
aria-label="Prometheus extra field"
aria-label={t('components.prom-explore-extra-field.aria-label-prometheus-extra-field', 'Prometheus extra field')}
className="gf-form-inline"
data-testid={promExploreExtraFieldTestIds.extraFieldEditor}
>
@ -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')}
>
<InlineFormLabel width="auto">Query type</InlineFormLabel>
<InlineFormLabel width="auto">
<Trans i18nKey="components.prom-explore-extra-field.query-type">Query type</Trans>
</InlineFormLabel>
<RadioButtonGroup
options={rangeOptions}
@ -87,20 +90,32 @@ export const PromExploreExtraField = memo(({ query, datasource, onChange, onRunQ
flexWrap: 'nowrap',
})
)}
aria-label="Step field"
aria-label={t('components.prom-explore-extra-field.aria-label-step-field', 'Step field')}
>
<InlineFormLabel
width={6}
tooltip={
'Time units and built-in variables can be used here, for example: $__interval, $__rate_interval, 5s, 1m, 3h, 1d, 1y (Default if no unit is specified: s)'
}
tooltip={t(
'components.prom-explore-extra-field.tooltip-units-builtin-variables-example-interval-rateinterval',
'Time units and built-in variables can be used here, for example: {{example1}}, {{example2}}, {{example3}}, {{example4}}, {{example5}}, {{example6}}, {{example7}} (Default if no unit is specified: {{default}})',
{
example1: '$__interval',
example2: '$__rate_interval',
example3: '5s',
example4: '1m',
example5: '3h',
example6: '1d',
example7: '1y',
default: 's',
}
)}
>
Min step
<Trans i18nKey="components.prom-explore-extra-field.min-step">Min step</Trans>
</InlineFormLabel>
<input
type={'text'}
className="gf-form-input width-4"
placeholder={'auto'}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="auto"
onChange={onStepChange}
onKeyDown={onReturnKeyDown}
value={query.interval ?? ''}
@ -116,16 +131,30 @@ PromExploreExtraField.displayName = 'PromExploreExtraField';
export function getQueryTypeOptions(includeBoth: boolean) {
const rangeOptions = [
{ value: 'range', label: 'Range', description: 'Run query over a range of time' },
{
value: 'range',
label: t('components.get-query-type-options.range-options.label.range', 'Range'),
description: t(
'components.get-query-type-options.range-options.description.query-range',
'Run query over a range of time'
),
},
{
value: 'instant',
label: 'Instant',
label: t('components.get-query-type-options.range-options.label.instant', 'Instant'),
description: 'Run query against a single point in time. For this query, the "To" time is used',
},
];
if (includeBoth) {
rangeOptions.push({ value: 'both', label: 'Both', description: 'Run an Instant query and a Range query' });
rangeOptions.push({
value: 'both',
label: t('components.get-query-type-options.label.both', 'Both'),
description: t(
'components.get-query-type-options.description.instant-query-range',
'Run an Instant query and a Range query'
),
});
}
return rangeOptions;

View file

@ -4,6 +4,7 @@ import { MutableRefObject, ReactNode, useCallback, useState } from 'react';
import { getDefaultTimeRange, isDataFrame, QueryEditorProps, QueryHint, toLegacyResponseData } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { clearButtonStyles, Icon, useTheme2 } from '@grafana/ui';
@ -165,7 +166,7 @@ export const PromQueryField = (props: PromQueryFieldProps) => {
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()}
/>

View file

@ -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(<PromVariableQueryEditor {...props} onChange={onChange} />);
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(<PromVariableQueryEditor {...props} onChange={onChange} />);
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');

View file

@ -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 }:
<>
<InlineFieldRow>
<InlineField
label="Query type"
label={t('components.prom-variable-query-editor.label-query-type', 'Query type')}
labelWidth={20}
tooltip={
<div>The Prometheus data source plugin provides the following query types for template variables.</div>
<div>
<Trans i18nKey="components.prom-variable-query-editor.tooltip-query-type">
The Prometheus data source plugin provides the following query types for template variables.
</Trans>
</div>
}
>
<Select
placeholder="Select query type"
aria-label="Query type"
placeholder={t('components.prom-variable-query-editor.placeholder-select-query-type', 'Select query type')}
aria-label={t('components.prom-variable-query-editor.aria-label-query-type', 'Query type')}
onChange={onQueryTypeChange}
value={qryType}
options={variableOptions}
@ -280,18 +286,19 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
<>
<InlineFieldRow>
<InlineField
label="Label"
label={t('components.prom-variable-query-editor.label-label', 'Label')}
labelWidth={20}
required
aria-labelledby="label-select"
tooltip={
<div>
Returns a list of label values for the label name in all metrics unless the metric is specified.
<Trans i18nKey="components.prom-variable-query-editor.tooltip-label">
Returns a list of label values for the label name in all metrics unless the metric is specified.
</Trans>
</div>
}
>
<AsyncSelect
aria-label="label-select"
onChange={onLabelChange}
value={label ? toOption(label) : null}
defaultOptions={truncatedLabelOptions}
@ -317,15 +324,21 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.LabelNames && (
<InlineFieldRow>
<InlineField
label="Metric regex"
label={t('components.prom-variable-query-editor.label-metric-regex', 'Metric regex')}
labelWidth={20}
aria-labelledby="Metric regex"
tooltip={<div>Returns a list of label names, optionally filtering by specified metric regex.</div>}
tooltip={
<div>
<Trans i18nKey="components.prom-variable-query-editor.tooltip-metric-regex">
Returns a list of label names, optionally filtering by specified metric regex.
</Trans>
</div>
}
>
<Input
type="text"
aria-label="Metric regex"
placeholder="Metric regex"
aria-label={t('components.prom-variable-query-editor.aria-label-metric-regex', 'Metric regex')}
placeholder={t('components.prom-variable-query-editor.placeholder-metric-regex', 'Metric regex')}
value={labelNamesMatch}
onBlur={(event) => {
setLabelNamesMatch(event.currentTarget.value);
@ -344,15 +357,21 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.MetricNames && (
<InlineFieldRow>
<InlineField
label="Metric regex"
label={t('components.prom-variable-query-editor.label-metric-regex', 'Metric regex')}
labelWidth={20}
aria-labelledby="Metric selector"
tooltip={<div>Returns a list of metrics matching the specified metric regex.</div>}
tooltip={
<div>
<Trans i18nKey="components.prom-variable-query-editor.returns-metrics-matching-specified-metric-regex">
Returns a list of metrics matching the specified metric regex.
</Trans>
</div>
}
>
<Input
type="text"
aria-label="Metric selector"
placeholder="Metric regex"
aria-label={t('components.prom-variable-query-editor.aria-label-metric-selector', 'Metric selector')}
placeholder={t('components.prom-variable-query-editor.placeholder-metric-regex', 'Metric regex')}
value={metric}
onChange={(e) => {
setMetric(e.currentTarget.value);
@ -371,19 +390,24 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.VarQueryResult && (
<InlineFieldRow>
<InlineField
label="Query"
label={t('components.prom-variable-query-editor.label-query', 'Query')}
labelWidth={20}
tooltip={
<div>
Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
sum(go_goroutines).
<Trans
i18nKey="components.prom-variable-query-editor.tooltip-query"
values={{ exampleQuery: 'sum(go_goroutines)' }}
>
Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
{'{{exampleQuery}}'}.
</Trans>
</div>
}
>
<TextArea
type="text"
aria-label="Prometheus Query"
placeholder="Prometheus Query"
aria-label={t('components.prom-variable-query-editor.aria-label-prometheus-query', 'Prometheus Query')}
placeholder={t('components.prom-variable-query-editor.placeholder-prometheus-query', 'Prometheus Query')}
value={varQuery}
onChange={onVarQueryChange}
onBlur={() => {
@ -401,21 +425,29 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.SeriesQuery && (
<InlineFieldRow>
<InlineField
label="Series Query"
label={t('components.prom-variable-query-editor.label-series-query', 'Series Query')}
labelWidth={20}
tooltip={
<div>
Enter a metric with labels, only a metric or only labels, i.e.
go_goroutines&#123;instance=&quot;localhost:9090&quot;&#125;, go_goroutines, or
&#123;instance=&quot;localhost:9090&quot;&#125;. Returns a list of time series associated with the
entered data.
<Trans
i18nKey="components.prom-variable-query-editor.tooltip-series-query"
values={{
example1: 'go_goroutines{instance="localhost:9090"}',
example2: 'go_goroutines',
example3: '{instance="localhost:9090"}',
}}
>
Enter a metric with labels, only a metric or only labels, i.e.
{'{{example1}}'}, {'{{example2}}'}, or {'{{example3}}'}. Returns a list of time series associated with
the entered data.
</Trans>
</div>
}
>
<Input
type="text"
aria-label="Series Query"
placeholder="Series Query"
aria-label={t('components.prom-variable-query-editor.aria-label-series-query', 'Series Query')}
placeholder={t('components.prom-variable-query-editor.placeholder-series-query', 'Series Query')}
value={seriesQuery}
onChange={onSeriesQueryChange}
onBlur={() => {
@ -433,19 +465,26 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.ClassicQuery && (
<InlineFieldRow>
<InlineField
label="Classic Query"
label={t('components.prom-variable-query-editor.label-classic-query', 'Classic Query')}
labelWidth={20}
tooltip={
<div>
The original implemetation of the Prometheus variable query editor. Enter a string with the correct
query type and parameters as described in these docs. For example, label_values(label, metric).
<Trans
i18nKey="components.prom-variable-query-editor.tooltip-classic-query"
values={{
exampleQuery: 'label_values(label, metric)',
}}
>
The original implemetation of the Prometheus variable query editor. Enter a string with the correct
query type and parameters as described in these docs. For example, {'{{exampleQuery}}'}.
</Trans>
</div>
}
>
<Input
type="text"
aria-label="Classic Query"
placeholder="Classic Query"
aria-label={t('components.prom-variable-query-editor.aria-label-classic-query', 'Classic Query')}
placeholder={t('components.prom-variable-query-editor.placeholder-classic-query', 'Classic Query')}
value={classicQuery}
onChange={onClassicQueryChange}
onBlur={() => {

View file

@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { BrowserLabel as PromLabel, Input, Label, useStyles2 } from '@grafana/ui';
import { useMetricsBrowser } from './MetricsBrowserContext';
@ -20,13 +21,21 @@ export function LabelSelector() {
return (
<div className={styles.section}>
<Label description="Once label values are selected, only possible label combinations are shown.">
2. Select labels to search in
<Label
description={t(
'components.label-selector.description-select-labels',
'Once label values are selected, only possible label combinations are shown.'
)}
>
<Trans i18nKey="components.label-selector.select-labels-to-search-in">2. Select labels to search in</Trans>
</Label>
<div>
<Input
onChange={(e) => setLabelSearchTerm(e.currentTarget.value)}
aria-label="Filter expression for label"
aria-label={t(
'components.label-selector.aria-label-filter-expression-for-label',
'Filter expression for label'
)}
value={labelSearchTerm}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelNamesFilter}
/>

View file

@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { FixedSizeList } from 'react-window';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { BrowserLabel as PromLabel, Input, Label, useStyles2 } from '@grafana/ui';
import { useMetricsBrowser } from './MetricsBrowserContext';
@ -20,24 +21,40 @@ export function MetricSelector() {
return (
<div>
<div className={styles.section}>
<Label description="Once a metric is selected only possible labels are shown. Labels are limited by the series limit below.">
1. Select a metric
<Label
description={t(
'components.metric-selector.label-select-metric',
'Once a metric is selected only possible labels are shown. Labels are limited by the series limit below.'
)}
>
<Trans i18nKey="components.metric-selector.select-a-metric">1. Select a metric</Trans>
</Label>
<div>
<Input
onChange={(e) => setMetricSearchTerm(e.currentTarget.value)}
aria-label="Filter expression for metric"
aria-label={t(
'components.metric-selector.aria-label-filter-expression-for-metric',
'Filter expression for metric'
)}
value={metricSearchTerm}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric}
/>
</div>
<Label description="The limit applies to all metrics, labels, and values. Leave the field empty to use the default limit. Set to 0 to disable the limit and fetch everything — this may cause performance issues.">
Series limit
<Label
description={t(
'components.metric-selector.description-series-limit',
'The limit applies to all metrics, labels, and values. Leave the field empty to use the default limit. Set to 0 to disable the limit and fetch everything — this may cause performance issues.'
)}
>
<Trans i18nKey="components.metric-selector.series-limit">Series limit</Trans>
</Label>
<div>
<Input
onChange={(e) => setSeriesLimit(e.currentTarget.value.trim())}
aria-label="Limit results from series endpoint"
aria-label={t(
'components.metric-selector.aria-label-limit-results-from-series-endpoint',
'Limit results from series endpoint'
)}
value={seriesLimit}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.seriesLimit}
/>

View file

@ -2,6 +2,7 @@ import { cx } from '@emotion/css';
import { useMemo } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { Button, Label, Stack, useStyles2 } from '@grafana/ui';
import { useMetricsBrowser } from './MetricsBrowserContext';
@ -27,45 +28,53 @@ export function SelectorActions() {
return (
<div className={styles.section}>
<Label>4. Resulting selector</Label>
<div aria-label="selector" className={styles.selector}>
<Label>
<Trans i18nKey="components.selector-actions.resulting-selector">4. Resulting selector</Trans>
</Label>
<div aria-label={t('components.selector-actions.aria-label-selector', 'selector')} className={styles.selector}>
{selector}
</div>
{validationStatus && <div className={styles.validationStatus}>{validationStatus}</div>}
<Stack>
<Button
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery}
aria-label="Use selector for query button"
aria-label={t(
'components.selector-actions.aria-label-use-selector-for-query-button',
'Use selector for query button'
)}
disabled={empty}
onClick={onClickRunQuery}
>
Use query
<Trans i18nKey="components.selector-actions.use-query">Use query</Trans>
</Button>
<Button
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useAsRateQuery}
aria-label="Use selector as metrics button"
aria-label={t(
'components.selector-actions.aria-label-use-selector-as-metrics-button',
'Use selector as metrics button'
)}
variant="secondary"
disabled={empty}
onClick={onClickRunRateQuery}
>
Use as rate query
<Trans i18nKey="components.selector-actions.use-as-rate-query">Use as rate query</Trans>
</Button>
<Button
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.validateSelector}
aria-label="Validate submit button"
aria-label={t('components.selector-actions.aria-label-validate-submit-button', 'Validate submit button')}
variant="secondary"
disabled={empty}
onClick={onValidationClick}
>
Validate selector
<Trans i18nKey="components.selector-actions.validate-selector">Validate selector</Trans>
</Button>
<Button
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.clear}
aria-label="Selector clear button"
aria-label={t('components.selector-actions.aria-label-selector-clear-button', 'Selector clear button')}
variant="secondary"
onClick={onClearClick}
>
Clear
<Trans i18nKey="components.selector-actions.clear">Clear</Trans>
</Button>
<div className={cx(styles.status, (status || err) && styles.statusShowing)}>
<span className={err ? styles.error : ''}>{err || status}</span>

View file

@ -2,6 +2,7 @@ import { useState } from 'react';
import { FixedSizeList } from 'react-window';
import { selectors } from '@grafana/e2e-selectors';
import { t, Trans } from '@grafana/i18n';
import { BrowserLabel as PromLabel, Input, Label, useStyles2 } from '@grafana/ui';
import { useMetricsBrowser } from './MetricsBrowserContext';
@ -15,13 +16,23 @@ export function ValueSelector() {
return (
<div className={styles.section}>
<Label description="Use the search field to find values across selected labels.">
3. Select (multiple) values for your labels
<Label
description={t(
'components.value-selector.description-search-field-values-across-selected-labels',
'Use the search field to find values across selected labels.'
)}
>
<Trans i18nKey="components.value-selector.select-multiple-values-for-your-labels">
3. Select (multiple) values for your labels
</Trans>
</Label>
<div>
<Input
onChange={(e) => setValueSearchTerm(e.currentTarget.value)}
aria-label="Filter expression for label values"
aria-label={t(
'components.value-selector.aria-label-filter-expression-for-label-values',
'Filter expression for label values'
)}
value={valueSearchTerm}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.labelValuesFilter}
/>
@ -33,7 +44,14 @@ export function ValueSelector() {
return null;
}
return (
<div role="list" key={lk} aria-label={`Values for ${lk}`} className={styles.valueListWrapper}>
<div
role="list"
key={lk}
aria-label={t('components.value-selector.aria-label-values-for', 'Values for {{labelKey}}', {
labelKey: lk,
})}
className={styles.valueListWrapper}
>
<div className={styles.valueTitle}>
<PromLabel
name={lk}

View file

@ -3,6 +3,7 @@ import { cx } from '@emotion/css';
import { DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { ConfigSubSection } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { InlineField, Switch, useTheme2 } from '@grafana/ui';
@ -26,18 +27,27 @@ export function AlertingSettingsOverhaul<T extends AlertingConfig>({
const styles = overhaulStyles(theme);
return (
<ConfigSubSection title="Alerting" className={cx(styles.container, styles.alertingTop)}>
<ConfigSubSection
title={t('configuration.alerting-settings-overhaul.title-alerting', 'Alerting')}
className={cx(styles.container, styles.alertingTop)}
>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
<InlineField
labelWidth={30}
label="Manage alerts via Alerting UI"
label={t(
'configuration.alerting-settings-overhaul.label-manage-alerts-via-alerting-ui',
'Manage alerts via Alerting UI'
)}
disabled={options.readOnly}
tooltip={
<>
Manage alert rules for this data source. To manage other alerting resources, add an Alertmanager data
source. {docsTip()}
<Trans i18nKey="configuration.alerting-settings-overhaul.tooltip-manage-alerts-via-alerting-ui">
Manage alert rules for this data source. To manage other alerting resources, add an Alertmanager
data source.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}

View file

@ -2,9 +2,10 @@
import { css } from '@emotion/css';
import { DataSourcePluginOptionsEditorProps, GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { ConfigSection, DataSourceDescription, AdvancedHttpSettings } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { Alert, FieldValidationMessage, useTheme2 } from '@grafana/ui';
import { Alert, FieldValidationMessage, TextLink, useTheme2 } from '@grafana/ui';
import { PromOptions } from '../types';
@ -24,8 +25,10 @@ export const ConfigEditor = (props: PrometheusConfigProps) => {
return (
<>
{options.access === 'direct' && (
<Alert title="Error" severity="error">
Browser access mode in the Prometheus data source is no longer available. Switch to server access mode.
<Alert title={t('configuration.config-editor.title-error', 'Error')} severity="error">
<Trans i18nKey="configuration.config-editor.browser-access-mode-error">
Browser access mode in the Prometheus data source is no longer available. Switch to server access mode.
</Trans>
</Alert>
)}
<DataSourceDescription
@ -41,8 +44,11 @@ export const ConfigEditor = (props: PrometheusConfigProps) => {
<hr />
<ConfigSection
className={styles.advancedSettings}
title="Advanced settings"
description="Additional settings are optional settings that can be configured for more control over your data source."
title={t('configuration.config-editor.title-advanced-settings', 'Advanced settings')}
description={t(
'configuration.config-editor.description-advanced-settings',
'Additional settings are optional settings that can be configured for more control over your data source.'
)}
>
<AdvancedHttpSettings
className={styles.advancedHTTPSettingsMargin}
@ -65,9 +71,9 @@ export function docsTip(url?: string) {
const docsUrl = 'https://grafana.com/docs/grafana/latest/datasources/prometheus/configure-prometheus-data-source/';
return (
<a href={url ? url : docsUrl} target="_blank" rel="noopener noreferrer">
Visit docs for more details here.
</a>
<TextLink href={url ? url : docsUrl} external>
<Trans i18nKey="configuration.docs-tip.visit-docs-for-more-details-here">Visit docs for more details here.</Trans>
</TextLink>
);
}

View file

@ -1,5 +1,6 @@
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/DataSourceHttpSettingsOverhaul.tsx
import { DataSourceSettings } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { Auth, AuthMethod, ConnectionSettings, convertLegacyAuthProps } from '@grafana/plugin-ui';
import { SecureSocksProxySettings, useTheme2 } from '@grafana/ui';
@ -34,7 +35,9 @@ export const DataSourceHttpSettingsOverhaul = (props: DataSourceHttpSettingsProp
case 'direct':
urlTooltip = (
<>
Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser.
<Trans i18nKey="configuration.data-source-http-settings-overhaul.tooltip-browser-access-mode">
Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser.
</Trans>
{docsTip()}
</>
);
@ -42,14 +45,26 @@ export const DataSourceHttpSettingsOverhaul = (props: DataSourceHttpSettingsProp
case 'proxy':
urlTooltip = (
<>
Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana
backend/server.
<Trans i18nKey="configuration.data-source-http-settings-overhaul.tooltip-server-access-mode">
Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana
backend/server.
</Trans>
{docsTip()}
</>
);
break;
default:
urlTooltip = <>Specify a complete HTTP URL (for example http://your_server:8080) {docsTip()}</>;
urlTooltip = (
<>
<Trans
i18nKey="configuration.data-source-http-settings-overhaul.tooltip-http-url"
values={{ exampleURL: 'http://your_server:8080' }}
>
Specify a complete HTTP URL (for example {'{{exampleURL}}'})
</Trans>
{docsTip()}
</>
);
}
return (

View file

@ -3,6 +3,7 @@ import { useState } from 'react';
import { DataSourceInstanceSettings } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { config, DataSourcePicker } from '@grafana/runtime';
import { Button, InlineField, Input, Switch, useTheme2 } from '@grafana/ui';
@ -26,13 +27,16 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
return (
<div className="gf-form-group">
<InlineField
label="Internal link"
label={t('configuration.exemplar-setting.label-internal-link', 'Internal link')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
disabled={disabled}
tooltip={
<>
Enable this option if you have an internal link. When enabled, this reveals the data source selector. Select
the backend tracing data store for your exemplar data. {docsTip()}
<Trans i18nKey="configuration.exemplar-setting.tooltip-internal-link">
Enable this option if you have an internal link. When enabled, this reveals the data source selector.
Select the backend tracing data store for your exemplar data.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
@ -49,9 +53,16 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
{isInternalLink ? (
<InlineField
label="Data source"
label={t('configuration.exemplar-setting.label-data-source', 'Data source')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>The data source the exemplar is going to navigate to. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.exemplar-setting.tooltip-data-source">
The data source the exemplar is going to navigate to.
</Trans>{' '}
{docsTip()}
</>
}
disabled={disabled}
interactive={true}
>
@ -76,14 +87,24 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
</InlineField>
) : (
<InlineField
label="URL"
label={t('configuration.exemplar-setting.label-url', 'URL')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>The URL of the trace backend the user would go to see its trace. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.exemplar-setting.tooltip-url">
The URL of the trace backend the user would go to see its trace
</Trans>{' '}
{docsTip()}
</>
}
disabled={disabled}
interactive={true}
>
<Input
placeholder="https://example.com/${__value.raw}"
placeholder={t(
'configuration.exemplar-setting.placeholder-httpsexamplecomvalueraw',
'https://example.com/${__value.raw}'
)}
spellCheck={false}
width={40}
value={value.url}
@ -99,14 +120,21 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
)}
<InlineField
label="URL Label"
label={t('configuration.exemplar-setting.label-url-label', 'URL Label')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>Use to override the button label on the exemplar traceID field. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.exemplar-setting.tooltip-url-label">
Use to override the button label on the exemplar traceID field.
</Trans>{' '}
{docsTip()}
</>
}
disabled={disabled}
interactive={true}
>
<Input
placeholder="Go to example.com"
placeholder={t('configuration.exemplar-setting.placeholder-go-to-examplecom', 'Go to example.com')}
spellCheck={false}
width={40}
value={value.urlDisplayLabel}
@ -119,14 +147,21 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
/>
</InlineField>
<InlineField
label="Label name"
label={t('configuration.exemplar-setting.label-label-name', 'Label name')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>The name of the field in the labels object that should be used to get the traceID. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.exemplar-setting.tooltip-label-name">
The name of the field in the labels object that should be used to get the traceID.
</Trans>{' '}
{docsTip()}
</>
}
disabled={disabled}
interactive={true}
>
<Input
placeholder="traceID"
placeholder={t('configuration.exemplar-setting.placeholder-trace-id', 'traceID')}
spellCheck={false}
width={40}
value={value.name}
@ -139,10 +174,14 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
/>
</InlineField>
{!disabled && (
<InlineField label="Remove exemplar link" labelWidth={PROM_CONFIG_LABEL_WIDTH} disabled={disabled}>
<InlineField
label={t('configuration.exemplar-setting.label-remove-exemplar-link', 'Remove exemplar link')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
disabled={disabled}
>
<Button
variant="destructive"
title="Remove exemplar link"
title={t('configuration.exemplar-setting.title-remove-exemplar-link', 'Remove exemplar link')}
icon="times"
onClick={(event) => {
event.preventDefault();

View file

@ -2,6 +2,7 @@
import { css } from '@emotion/css';
import { selectors } from '@grafana/e2e-selectors';
import { t, Trans } from '@grafana/i18n';
import { ConfigSubSection } from '@grafana/plugin-ui';
import { Button, useTheme2 } from '@grafana/ui';
@ -21,7 +22,10 @@ export function ExemplarsSettings({ options, onChange, disabled }: Props) {
const styles = overhaulStyles(theme);
return (
<div className={styles.sectionBottomPadding}>
<ConfigSubSection title="Exemplars" className={styles.container}>
<ConfigSubSection
title={t('configuration.exemplars-settings.title-exemplars', 'Exemplars')}
className={styles.container}
>
{options &&
options.map((option, index) => {
return (
@ -57,10 +61,16 @@ export function ExemplarsSettings({ options, onChange, disabled }: Props) {
onChange(newOptions);
}}
>
Add
<Trans i18nKey="configuration.exemplars-settings.add">Add</Trans>
</Button>
)}
{disabled && !options && <i>No exemplars configurations</i>}
{disabled && !options && (
<i>
<Trans i18nKey="configuration.exemplars-settings.no-exemplars-configurations">
No exemplars configurations
</Trans>
</i>
)}
</ConfigSubSection>
</div>
);

View file

@ -9,9 +9,10 @@ import {
updateDatasourcePluginJsonDataOption,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { ConfigSubSection } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { InlineField, Input, Select, Switch, useTheme2 } from '@grafana/ui';
import { InlineField, Input, Select, Switch, TextLink, useTheme2 } from '@grafana/ui';
import { SUGGESTIONS_LIMIT } from '../language_provider';
import { QueryEditorMode } from '../querybuilder/shared/types';
@ -100,20 +101,26 @@ export const PromSettings = (props: Props) => {
return (
<>
<ConfigSubSection title="Interval behaviour" className={styles.container}>
<ConfigSubSection
title={t('configuration.prom-settings.title-interval-behaviour', 'Interval behaviour')}
className={styles.container}
>
<div className="gf-form-group">
{/* Scrape interval */}
<div className="gf-form-inline">
<div className="gf-form">
<InlineField
label="Scrape interval"
label={t('configuration.prom-settings.label-scrape-interval', 'Scrape interval')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
This interval is how frequently Prometheus scrapes targets. Set this to the typical scrape and
evaluation interval configured in your Prometheus config file. If you set this to a greater value
than your Prometheus config file interval, Grafana will evaluate the data according to this interval
and you will see less data points. Defaults to 15s. {docsTip()}
<Trans i18nKey="configuration.prom-settings.tooltip-scrape-interval" values={{ default: '15s' }}>
This interval is how frequently Prometheus scrapes targets. Set this to the typical scrape and
evaluation interval configured in your Prometheus config file. If you set this to a greater value
than your Prometheus config file interval, Grafana will evaluate the data according to this
interval and you will see less data points. Defaults to {'{{default}}'}.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
@ -124,6 +131,7 @@ export const PromSettings = (props: Props) => {
className="width-20"
value={optionsWithDefaults.jsonData.timeInterval}
spellCheck={false}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="15s"
onChange={onChangeHandler('timeInterval', optionsWithDefaults, onOptionsChange)}
onBlur={(e) =>
@ -143,9 +151,16 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form">
<InlineField
label="Query timeout"
label={t('configuration.prom-settings.label-query-timeout', 'Query timeout')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>Set the Prometheus query timeout. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.prom-settings.tooltip-query-timeout">
Set the Prometheus query timeout.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
disabled={optionsWithDefaults.readOnly}
>
@ -155,6 +170,7 @@ export const PromSettings = (props: Props) => {
value={optionsWithDefaults.jsonData.queryTimeout}
onChange={onChangeHandler('queryTimeout', optionsWithDefaults, onOptionsChange)}
spellCheck={false}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="60s"
onBlur={(e) =>
updateValidDuration({
@ -172,18 +188,31 @@ export const PromSettings = (props: Props) => {
</div>
</ConfigSubSection>
<ConfigSubSection title="Query editor" className={styles.container}>
<ConfigSubSection
title={t('configuration.prom-settings.title-query-editor', 'Query editor')}
className={styles.container}
>
<div className="gf-form-group">
<div className="gf-form">
<InlineField
label="Default editor"
label={t('configuration.prom-settings.label-default-editor', 'Default editor')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>Set default editor option for all users of this data source. {docsTip()}</>}
tooltip={
<>
<Trans i18nKey="configuration.prom-settings.tooltip-default-editor">
Set default editor option for all users of this data source.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
disabled={optionsWithDefaults.readOnly}
>
<Select
aria-label={`Default Editor (Code or Builder)`}
aria-label={t(
'configuration.prom-settings.aria-label-default-editor',
'Default Editor (Code or Builder)'
)}
options={editorOptions}
value={
editorOptions.find((o) => o.value === optionsWithDefaults.jsonData.defaultEditor) ??
@ -198,11 +227,14 @@ export const PromSettings = (props: Props) => {
<div className="gf-form">
<InlineField
labelWidth={PROM_CONFIG_LABEL_WIDTH}
label="Disable metrics lookup"
label={t('configuration.prom-settings.label-disable-metrics-lookup', 'Disable metrics lookup')}
tooltip={
<>
Checking this option will disable the metrics chooser and metric/label support in the query
field&apos;s autocomplete. This helps if you have performance issues with bigger Prometheus instances.{' '}
<Trans i18nKey="configuration.prom-settings.tooltip-disable-metrics-lookup">
Checking this option will disable the metrics chooser and metric/label support in the query
field&apos;s autocomplete. This helps if you have performance issues with bigger Prometheus
instances.{' '}
</Trans>
{docsTip()}
</>
}
@ -220,43 +252,48 @@ export const PromSettings = (props: Props) => {
</div>
</ConfigSubSection>
<ConfigSubSection title="Performance" className={styles.container}>
<ConfigSubSection
title={t('configuration.prom-settings.title-performance', 'Performance')}
className={styles.container}
>
{!optionsWithDefaults.jsonData.prometheusType &&
!optionsWithDefaults.jsonData.prometheusVersion &&
optionsWithDefaults.readOnly && (
<div className={styles.versionMargin}>
For more information on configuring prometheus type and version in data sources, see the{' '}
<a
className={styles.textUnderline}
href="https://grafana.com/docs/grafana/latest/administration/provisioning/"
>
provisioning documentation
</a>
.
<Trans i18nKey="configuration.prom-settings.more-info">
For more information on configuring prometheus type and version in data sources, see the{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/administration/provisioning/">
provisioning documentation
</TextLink>
.
</Trans>
</div>
)}
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
<InlineField
label="Prometheus type"
label={t('configuration.prom-settings.label-prometheus-type', 'Prometheus type')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
{/* , and attempt to detect the version */}
Set this to the type of your prometheus database, e.g. Prometheus, Cortex, Mimir or Thanos. Changing
this field will save your current settings. Certain types of Prometheus supports or does not support
various APIs. For example, some types support regex matching for label queries to improve
performance. Some types have an API for metadata. If you set this incorrectly you may experience odd
behavior when querying metrics and labels. Please check your Prometheus documentation to ensure you
enter the correct type. {docsTip()}
<Trans i18nKey="configuration.prom-settings.tooltip-prometheus-type">
Set this to the type of your prometheus database, e.g. Prometheus, Cortex, Mimir or Thanos.
Changing this field will save your current settings. Certain types of Prometheus supports or does
not support various APIs. For example, some types support regex matching for label queries to
improve performance. Some types have an API for metadata. If you set this incorrectly you may
experience odd behavior when querying metrics and labels. Please check your Prometheus
documentation to ensure you enter the correct type.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
disabled={optionsWithDefaults.readOnly}
>
<Select
aria-label="Prometheus type"
aria-label={t('configuration.prom-settings.aria-label-prometheus-type', 'Prometheus type')}
options={prometheusFlavorSelectItems}
value={prometheusFlavorSelectItems.find(
(o) => o.value === optionsWithDefaults.jsonData.prometheusType
@ -272,19 +309,29 @@ export const PromSettings = (props: Props) => {
{optionsWithDefaults.jsonData.prometheusType && (
<div className="gf-form">
<InlineField
label={`${optionsWithDefaults.jsonData.prometheusType} version`}
label={t('configuration.prom-settings.label-prom-type-version', '{{promType}} version', {
promType: optionsWithDefaults.jsonData.prometheusType,
})}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
Use this to set the version of your {optionsWithDefaults.jsonData.prometheusType} instance if it
is not automatically configured. {docsTip()}
<Trans
i18nKey="configuration.prom-settings.tooltip-prom-type-version"
values={{ promType: optionsWithDefaults.jsonData.prometheusType }}
>
Use this to set the version of your {'{{promType}}'} instance if it is not automatically
configured.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
disabled={optionsWithDefaults.readOnly}
>
<Select
aria-label={`${optionsWithDefaults.jsonData.prometheusType} type`}
aria-label={t('configuration.prom-settings.aria-label-prom-type-type', '{{promType}} type', {
promType: optionsWithDefaults.jsonData.prometheusType,
})}
options={PromFlavorVersions[optionsWithDefaults.jsonData.prometheusType]}
value={PromFlavorVersions[optionsWithDefaults.jsonData.prometheusType]?.find(
(o) => o.value === optionsWithDefaults.jsonData.prometheusVersion
@ -301,12 +348,14 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form max-width-30">
<InlineField
label="Cache level"
label={t('configuration.prom-settings.label-cache-level', 'Cache level')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
Sets the browser caching level for editor queries. Higher cache settings are recommended for high
cardinality data sources.
<Trans i18nKey="configuration.prom-settings.tooltip-cache-level">
Sets the browser caching level for editor queries. Higher cache settings are recommended for high
cardinality data sources.
</Trans>
</>
}
interactive={true}
@ -330,12 +379,17 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form">
<InlineField
label="Metric names suggestion limit"
label={t(
'configuration.prom-settings.label-metric-names-suggestion-limit',
'Metric names suggestion limit'
)}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
The maximum number of metric names that may appear as autocomplete suggestions in the query
editor&apos;s Code mode.
<Trans i18nKey="configuration.prom-settings.tooltip-metric-names-suggestion-limit">
The maximum number of metric names that may appear as autocomplete suggestions in the query
editor&apos;s Code mode.
</Trans>
</>
}
interactive={true}
@ -376,13 +430,15 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form max-width-30">
<InlineField
label="Incremental querying (beta)"
label={t('configuration.prom-settings.label-incremental-querying-beta', 'Incremental querying (beta)')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
This feature will change the default behavior of relative queries to always request fresh data from
the prometheus instance, instead query results will be cached, and only new records are requested.
Turn this on to decrease database and network load.
<Trans i18nKey="configuration.prom-settings.tooltip-incremental-querying-beta">
This feature will change the default behavior of relative queries to always request fresh data
from the prometheus instance, instead query results will be cached, and only new records are
requested. Turn this on to decrease database and network load.
</Trans>
</>
}
interactive={true}
@ -401,12 +457,22 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
{optionsWithDefaults.jsonData.incrementalQuerying && (
<InlineField
label="Query overlap window"
label={t('configuration.prom-settings.label-query-overlap-window', 'Query overlap window')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
Set a duration like 10m or 120s or 0s. Default of 10 minutes. This duration will be added to the
duration of each incremental request.
<Trans
i18nKey="configuration.prom-settings.tooltip-query-overlap-window"
values={{
example1: '10m',
example2: '120s',
example3: '0s',
default: '10m',
}}
>
Set a duration like {'{{example1}}'} or {'{{example2}}'} or {'{{example3}}'}. Default of{' '}
{'{{default}}'}. This duration will be added to the duration of each incremental request.
</Trans>
</>
}
interactive={true}
@ -437,9 +503,18 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-inline">
<div className="gf-form max-width-30">
<InlineField
label="Disable recording rules (beta)"
label={t(
'configuration.prom-settings.label-disable-recording-rules-beta',
'Disable recording rules (beta)'
)}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={<>This feature will disable recording rules Turn this on to improve dashboard performance</>}
tooltip={
<>
<Trans i18nKey="configuration.prom-settings.tooltip-disable-recording-rules-beta">
This feature will disable recording rules. Turn this on to improve dashboard performance
</Trans>
</>
}
interactive={true}
className={styles.switchField}
disabled={optionsWithDefaults.readOnly}
@ -455,17 +530,31 @@ export const PromSettings = (props: Props) => {
</div>
</ConfigSubSection>
<ConfigSubSection title="Other" className={styles.container}>
<ConfigSubSection title={t('configuration.prom-settings.title-other', 'Other')} className={styles.container}>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form max-width-30">
<InlineField
label="Custom query parameters"
label={t('configuration.prom-settings.label-custom-query-parameters', 'Custom query parameters')}
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
Add custom parameters to the Prometheus query URL. For example timeout, partial_response, dedup, or
max_source_resolution. Multiple parameters should be concatenated together with an &. {docsTip()}
<Trans
i18nKey="configuration.prom-settings.tooltip-custom-query-parameters"
values={{
example1: 'timeout',
example2: 'partial_response',
example3: 'dedup',
example4: 'max_source_resolution',
concatenationChar: '&',
}}
>
Add custom parameters to the Prometheus query URL. For example {'{{example1}}'}, {'{{example2}}'},{' '}
{'{{example3}}'}, or
{'{{example4}}'}. Multiple parameters should be concatenated together with{' '}
{'{{concatenationChar}}'}.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
@ -476,7 +565,11 @@ export const PromSettings = (props: Props) => {
value={optionsWithDefaults.jsonData.customQueryParameters}
onChange={onChangeHandler('customQueryParameters', optionsWithDefaults, onOptionsChange)}
spellCheck={false}
placeholder="Example: max_source_resolution=5m&timeout=10"
placeholder={t(
'configuration.prom-settings.placeholder-example-maxsourceresolutionmtimeout',
'Example: {{example}}',
{ example: 'max_source_resolution=5m&timeout=10' }
)}
data-testid={selectors.components.DataSource.Prometheus.configPage.customQueryParameters}
/>
</InlineField>
@ -489,18 +582,21 @@ export const PromSettings = (props: Props) => {
labelWidth={PROM_CONFIG_LABEL_WIDTH}
tooltip={
<>
You can use either POST or GET HTTP method to query your Prometheus data source. POST is the
recommended method as it allows bigger queries. Change this to GET if you have a Prometheus version
older than 2.1 or if POST requests are restricted in your network. {docsTip()}
<Trans i18nKey="configuration.prom-settings.tooltip-http-method">
You can use either POST or GET HTTP method to query your Prometheus data source. POST is the
recommended method as it allows bigger queries. Change this to GET if you have a Prometheus
version older than 2.1 or if POST requests are restricted in your network.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}
label="HTTP method"
label={t('configuration.prom-settings.label-http-method', 'HTTP method')}
disabled={optionsWithDefaults.readOnly}
>
<Select
width={40}
aria-label="Select HTTP method"
aria-label={t('configuration.prom-settings.aria-label-select-http-method', 'Select HTTP method')}
options={httpOptions}
value={httpOptions.find((o) => o.value === optionsWithDefaults.jsonData.httpMethod)}
onChange={onChangeHandler('httpMethod', optionsWithDefaults, onOptionsChange)}
@ -511,13 +607,19 @@ export const PromSettings = (props: Props) => {
</div>
<InlineField
labelWidth={PROM_CONFIG_LABEL_WIDTH}
label="Use series endpoint"
label={t('configuration.prom-settings.label-use-series-endpoint', 'Use series endpoint')}
tooltip={
<>
Checking this option will favor the series endpoint with match[] parameter over the label values
endpoint with match[] parameter. While the label values endpoint is considered more performant, some
users may prefer the series because it has a POST method while the label values endpoint only has a GET
method. {docsTip()}
<Trans
i18nKey="configuration.prom-settings.tooltip-use-series-endpoint"
values={{ exampleParameter: 'match[]' }}
>
Checking this option will favor the series endpoint with {'{{exampleParameter}}'} parameter over the
label values endpoint with {'{{exampleParameter}}'} parameter. While the label values endpoint is
considered more performant, some users may prefer the series because it has a POST method while the
label values endpoint only has a GET method.
</Trans>{' '}
{docsTip()}
</>
}
interactive={true}

View file

@ -62,6 +62,7 @@ export { PrometheusDatasource, InstantQueryRefIdIndex } from './datasource';
// The parts
export { addLabelToQuery } from './add_label_to_query';
export { type QueryEditorMode, type PromQueryFormat, type Prometheus } from './dataquery';
export { loadResources } from './loadResources';
export { PrometheusMetricFindQuery } from './metric_find_query';
export { promqlGrammar } from './promql';
export { getQueryHints, getInitHints } from './query_hints';

View file

@ -0,0 +1,11 @@
import { LANGUAGES, ResourceLoader, Resources } from '@grafana/i18n';
const resources = LANGUAGES.reduce<Record<string, () => Promise<{ default: Resources }>>>((acc, lang) => {
acc[lang.code] = async () => await import(`./locales/${lang.code}/grafana-prometheus.json`);
return acc;
}, {});
export const loadResources: ResourceLoader = async (resolvedLanguage: string) => {
const translation = await resources[resolvedLanguage]();
return translation.default;
};

View file

@ -0,0 +1,338 @@
{
"components": {
"annotation-query-editor": {
"annotation-data-load-error": "Annotation data load error!",
"aria-label-lower-limit-parameter": "Set lower limit for the step parameter",
"label-min-step": "Min step",
"label-series-value-as-timestamp": "Series value as timestamp",
"label-tags": "Tags",
"label-text": "Text",
"label-title": "Title",
"placeholder-auto": "auto",
"tooltip-either-pattern-example-instance-replaced-label": "Use either the name or a pattern. For example, {{labelTemplate}} is replaced with label value for the label {{labelName}}.",
"tooltip-min-step": "An additional lower limit for the step parameter of the Prometheus query and for the <2>{{intervalVar}}</2> and <4>{{rateIntervalVar}}</4> variables.",
"tooltip-timestamp-milliseconds-series-value-seconds-multiply": "The unit of timestamp is milliseconds. If the unit of the series value is seconds, multiply its range vector by 1000."
},
"get-query-type-options": {
"description": {
"instant-query-range": "Run an Instant query and a Range query"
},
"label": {
"both": "Both"
},
"range-options": {
"description": {
"query-range": "Run query over a range of time"
},
"label": {
"instant": "Instant",
"range": "Range"
}
}
},
"label-selector": {
"aria-label-filter-expression-for-label": "Filter expression for label",
"description-select-labels": "Once label values are selected, only possible label combinations are shown.",
"select-labels-to-search-in": "2. Select labels to search in"
},
"metric-selector": {
"aria-label-filter-expression-for-metric": "Filter expression for metric",
"aria-label-limit-results-from-series-endpoint": "Limit results from series endpoint",
"description-series-limit": "The limit applies to all metrics, labels, and values. Leave the field empty to use the default limit. Set to 0 to disable the limit and fetch everything — this may cause performance issues.",
"label-select-metric": "Once a metric is selected only possible labels are shown. Labels are limited by the series limit below.",
"select-a-metric": "1. Select a metric",
"series-limit": "Series limit"
},
"prom-cheat-sheet": {
"prom-ql-cheat-sheet": "PromQL Cheat Sheet"
},
"prom-exemplar-field": {
"exemplars": "Exemplars",
"tooltip-disable-query": "Disable query with exemplars",
"tooltip-enable-query": "Enable query with exemplars"
},
"prom-explore-extra-field": {
"aria-label-prometheus-extra-field": "Prometheus extra field",
"aria-label-query-type-field": "Query type field",
"aria-label-step-field": "Step field",
"min-step": "Min step",
"query-type": "Query type",
"tooltip-units-builtin-variables-example-interval-rateinterval": "Time units and built-in variables can be used here, for example: {{example1}}, {{example2}}, {{example3}}, {{example4}}, {{example5}}, {{example6}}, {{example7}} (Default if no unit is specified: {{default}})"
},
"prom-query-field": {
"placeholder-enter-a-prom-ql-query": "Enter a PromQL query…"
},
"prom-variable-query-editor": {
"aria-label-classic-query": "Classic Query",
"aria-label-metric-regex": "Metric regex",
"aria-label-metric-selector": "Metric selector",
"aria-label-prometheus-query": "Prometheus Query",
"aria-label-query-type": "Query type",
"aria-label-series-query": "Series Query",
"label-classic-query": "Classic Query",
"label-label": "Label",
"label-metric-regex": "Metric regex",
"label-query": "Query",
"label-query-type": "Query type",
"label-series-query": "Series Query",
"placeholder-classic-query": "Classic Query",
"placeholder-metric-regex": "Metric regex",
"placeholder-prometheus-query": "Prometheus Query",
"placeholder-select-query-type": "Select query type",
"placeholder-series-query": "Series Query",
"returns-metrics-matching-specified-metric-regex": "Returns a list of metrics matching the specified metric regex.",
"tooltip-classic-query": "The original implemetation of the Prometheus variable query editor. Enter a string with the correct query type and parameters as described in these docs. For example, {{exampleQuery}}.",
"tooltip-label": "Returns a list of label values for the label name in all metrics unless the metric is specified.",
"tooltip-metric-regex": "Returns a list of label names, optionally filtering by specified metric regex.",
"tooltip-query": "Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.{{exampleQuery}}.",
"tooltip-query-type": "The Prometheus data source plugin provides the following query types for template variables.",
"tooltip-series-query": "Enter a metric with labels, only a metric or only labels, i.e.{{example1}}, {{example2}}, or {{example3}}. Returns a list of time series associated with the entered data."
},
"selector-actions": {
"aria-label-selector": "selector",
"aria-label-selector-clear-button": "Selector clear button",
"aria-label-use-selector-as-metrics-button": "Use selector as metrics button",
"aria-label-use-selector-for-query-button": "Use selector for query button",
"aria-label-validate-submit-button": "Validate submit button",
"clear": "Clear",
"resulting-selector": "4. Resulting selector",
"use-as-rate-query": "Use as rate query",
"use-query": "Use query",
"validate-selector": "Validate selector"
},
"value-selector": {
"aria-label-filter-expression-for-label-values": "Filter expression for label values",
"aria-label-values-for": "Values for {{labelKey}}",
"description-search-field-values-across-selected-labels": "Use the search field to find values across selected labels.",
"select-multiple-values-for-your-labels": "3. Select (multiple) values for your labels"
}
},
"configuration": {
"alerting-settings-overhaul": {
"label-manage-alerts-via-alerting-ui": "Manage alerts via Alerting UI",
"title-alerting": "Alerting",
"tooltip-manage-alerts-via-alerting-ui": "Manage alert rules for this data source. To manage other alerting resources, add an Alertmanager data source."
},
"config-editor": {
"browser-access-mode-error": "Browser access mode in the Prometheus data source is no longer available. Switch to server access mode.",
"description-advanced-settings": "Additional settings are optional settings that can be configured for more control over your data source.",
"title-advanced-settings": "Advanced settings",
"title-error": "Error"
},
"data-source-http-settings-overhaul": {
"tooltip-browser-access-mode": "Your access method is <1>Browser</1>, this means the URL needs to be accessible from the browser.",
"tooltip-http-url": "Specify a complete HTTP URL (for example {{exampleURL}})",
"tooltip-server-access-mode": "Your access method is <1>Server</1>, this means the URL needs to be accessible from the grafana backend/server."
},
"docs-tip": {
"visit-docs-for-more-details-here": "Visit docs for more details here."
},
"exemplar-setting": {
"label-data-source": "Data source",
"label-internal-link": "Internal link",
"label-label-name": "Label name",
"label-remove-exemplar-link": "Remove exemplar link",
"label-url": "URL",
"label-url-label": "URL Label",
"placeholder-go-to-examplecom": "Go to example.com",
"placeholder-httpsexamplecomvalueraw": "https://example.com/${__value.raw}",
"placeholder-trace-id": "traceID",
"title-remove-exemplar-link": "Remove exemplar link",
"tooltip-data-source": "The data source the exemplar is going to navigate to.",
"tooltip-internal-link": "Enable this option if you have an internal link. When enabled, this reveals the data source selector. Select the backend tracing data store for your exemplar data.",
"tooltip-label-name": "The name of the field in the labels object that should be used to get the traceID.",
"tooltip-url": "The URL of the trace backend the user would go to see its trace",
"tooltip-url-label": "Use to override the button label on the exemplar traceID field."
},
"exemplars-settings": {
"add": "Add",
"no-exemplars-configurations": "No exemplars configurations",
"title-exemplars": "Exemplars"
},
"prom-settings": {
"aria-label-default-editor": "Default Editor (Code or Builder)",
"aria-label-prom-type-type": "{{promType}} type",
"aria-label-prometheus-type": "Prometheus type",
"aria-label-select-http-method": "Select HTTP method",
"label-cache-level": "Cache level",
"label-custom-query-parameters": "Custom query parameters",
"label-default-editor": "Default editor",
"label-disable-metrics-lookup": "Disable metrics lookup",
"label-disable-recording-rules-beta": "Disable recording rules (beta)",
"label-http-method": "HTTP method",
"label-incremental-querying-beta": "Incremental querying (beta)",
"label-metric-names-suggestion-limit": "Metric names suggestion limit",
"label-prom-type-version": "{{promType}} version",
"label-prometheus-type": "Prometheus type",
"label-query-overlap-window": "Query overlap window",
"label-query-timeout": "Query timeout",
"label-scrape-interval": "Scrape interval",
"label-use-series-endpoint": "Use series endpoint",
"more-info": "For more information on configuring prometheus type and version in data sources, see the <2>provisioning documentation</2>.",
"placeholder-example-maxsourceresolutionmtimeout": "Example: {{example}}",
"title-interval-behaviour": "Interval behaviour",
"title-other": "Other",
"title-performance": "Performance",
"title-query-editor": "Query editor",
"tooltip-cache-level": "Sets the browser caching level for editor queries. Higher cache settings are recommended for high cardinality data sources.",
"tooltip-custom-query-parameters": "Add custom parameters to the Prometheus query URL. For example {{example1}}, {{example2}}, {{example3}}, or{{example4}}. Multiple parameters should be concatenated together with {{concatenationChar}}.",
"tooltip-default-editor": "Set default editor option for all users of this data source.",
"tooltip-disable-metrics-lookup": "Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. ",
"tooltip-disable-recording-rules-beta": "This feature will disable recording rules. Turn this on to improve dashboard performance",
"tooltip-http-method": "You can use either POST or GET HTTP method to query your Prometheus data source. POST is the recommended method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network.",
"tooltip-incremental-querying-beta": "This feature will change the default behavior of relative queries to always request fresh data from the prometheus instance, instead query results will be cached, and only new records are requested. Turn this on to decrease database and network load.",
"tooltip-metric-names-suggestion-limit": "The maximum number of metric names that may appear as autocomplete suggestions in the query editor's Code mode.",
"tooltip-prom-type-version": "Use this to set the version of your {{promType}} instance if it is not automatically configured.",
"tooltip-prometheus-type": "Set this to the type of your prometheus database, e.g. Prometheus, Cortex, Mimir or Thanos. Changing this field will save your current settings. Certain types of Prometheus supports or does not support various APIs. For example, some types support regex matching for label queries to improve performance. Some types have an API for metadata. If you set this incorrectly you may experience odd behavior when querying metrics and labels. Please check your Prometheus documentation to ensure you enter the correct type.",
"tooltip-query-overlap-window": "Set a duration like {{example1}} or {{example2}} or {{example3}}. Default of {{default}}. This duration will be added to the duration of each incremental request.",
"tooltip-query-timeout": "Set the Prometheus query timeout.",
"tooltip-scrape-interval": "This interval is how frequently Prometheus scrapes targets. Set this to the typical scrape and evaluation interval configured in your Prometheus config file. If you set this to a greater value than your Prometheus config file interval, Grafana will evaluate the data according to this interval and you will see less data points. Defaults to {{default}}.",
"tooltip-use-series-endpoint": "Checking this option will favor the series endpoint with {{exampleParameter}} parameter over the label values endpoint with {{exampleParameter}} parameter. While the label values endpoint is considered more performant, some users may prefer the series because it has a POST method while the label values endpoint only has a GET method."
}
},
"querybuilder": {
"additional-settings": {
"content-filter-metric-names-regex-search-using": "Filter metric names by regex search, using an additional call on the Prometheus API.",
"disable-text-wrap": "Disable text wrap"
},
"feedback-link": {
"give-feedback": "Give feedback",
"title-give-feedback": "The metrics explorer is new, please let us know how we can improve it"
},
"handle-function": {
"text": {
"query-parsing-is-ambiguous": "Query parsing is ambiguous."
}
},
"label-filter-item": {
"aria-label-remove": "Remove {{name}}",
"placeholder-select-label": "Select label",
"placeholder-select-value": "Select value"
},
"label-filters": {
"label-filters": "Label filters",
"label-label-filters": "Label filters",
"tooltip-label-filters": "Optional: used to filter the metric select for this query type."
},
"metric-combobox": {
"async-select": {
"aria-label-open-metrics-explorer": "Open metrics explorer",
"placeholder-select-metric": "Select metric",
"tooltip-open-metrics-explorer": "Open metrics explorer"
},
"label-metric": "Metric",
"tooltip-metric": "Optional: returns a list of label values for the label name in the specified metric."
},
"metrics-modal": {
"additional-settings": "Additional Settings",
"aria-label-additional-settings": "Additional settings",
"aria-label-browse-metrics": "Browse metrics",
"currently-selected": "Currently selected: {{selected}}",
"metrics-pre-filtered": "These metrics have been pre-filtered by labels chosen in the label filters.",
"placeholder-results-per-page": "results per page",
"results-amount_one": "Showing {{num}} of {{count}} result",
"results-amount_other": "Showing {{num}} of {{count}} results",
"results-per-page": "Results per page",
"title-metrics-explorer": "Metrics explorer"
},
"nested-query": {
"label": {
"ignoring": "Ignoring",
"on": "On"
},
"operator": "Operator",
"tooltip-remove-match": "Remove match",
"vector-matches": "Vector matches"
},
"operation-editor": {
"not-found": "Operation {{id}} not found",
"title-remove": "Remove {{name}}"
},
"operation-header": {
"placeholder-replace-with": "Replace with",
"title-click-to-view-alternative-operations": "Click to view alternative operations",
"title-remove-operation": "Remove operation"
},
"operation-info-button": {
"title-click-to-show-description": "Click to show description",
"title-remove-operation": "Remove operation"
},
"operation-list": {
"operations": "Operations",
"placeholder-search": "Search",
"title-add-operation": "Add operation"
},
"operation-param-editor": {
"title-add": "Add {{name}}",
"title-remove": "Remove {{name}}"
},
"prom-query-builder-options": {
"aria-label-lower-limit-parameter": "Set lower limit for the step parameter",
"aria-label-select-resolution": "Select resolution",
"label-exemplars": "Exemplars",
"label-format": "Format",
"label-min-step": "Min step",
"label-resolution": "Resolution",
"label-type": "Type",
"placeholder-auto": "auto",
"title-options": "Options",
"tooltip-min-step": "An additional lower limit for the step parameter of the Prometheus query and for the <2>{{interval}}</2> and <4>{{rateInterval}}</4> variables."
},
"prom-query-code-editor-autocomplete-info": {
"autocomplete-suggestions-limited": "Autocomplete suggestions limited",
"tooltip-autocomplete-suggestions-limited": "The number of metric names exceeds the autocomplete limit. Only the {{autocompleteLimit}}-most relevant metrics are displayed. You can adjust the threshold in the data source settings."
},
"prom-query-editor-selector": {
"kick-start-your-query": "Kick start your query",
"label-explain": "Explain",
"run-queries": "Run queries",
"title-parsing-error-switch-builder": "Parsing error: Switch to the builder mode?"
},
"prom-query-legend-editor": {
"label-legend": "Legend",
"placeholder-select-legend-mode": "Select legend mode",
"tooltip-legend": "Series name override or template. Ex. {{templateExample}} will be replaced with label value for {{labelName}}."
},
"query-builder-hints": {
"hint-details": "hint: {{hintDetails}}"
},
"query-editor-hints": {
"hint-details": "hint: {{hintDetails}}"
},
"query-pattern": {
"apply-query": "Apply query",
"aria-label-apply-query-starter-button": "apply query starter button",
"aria-label-back-button": "back button",
"aria-label-create-new-query-button": "create new query button",
"aria-label-raw-query": "{{patternName}} raw query",
"aria-label-use-this-query-button": "use this query button",
"back": "Back",
"create-new-query": "Create new query",
"use-this-query": "Use this query"
},
"query-patterns-modal": {
"aria-label-close-kick-start-your-query-modal": "close kick start your query modal",
"aria-label-kick-start-your-query-modal": "Kick start your query modal",
"aria-label-toggle-query-starter": "open and close {{patternType}} query starter card",
"close": "Close",
"description-kick-start-your-query": "Kick start your query by selecting one of these queries. You can then continue to complete your query.",
"label-toggle-query-starter": "{{patternType}} query starters",
"title-kick-start-your-query": "Kick start your query"
},
"raw-query": {
"aria-label-selector": "selector"
},
"results-table": {
"content-descriptive-type": "When creating a {{descriptiveType}}, Prometheus exposes multiple series with the type counter. ",
"description": "Description",
"name": "Name",
"select": "Select",
"type": "Type"
},
"update-function-args": {
"text": {
"query-parsing-is-ambiguous": "Query parsing is ambiguous."
}
}
}
}

View file

@ -0,0 +1,12 @@
module.exports = {
locales: ['en-US'], // Only en-US is updated - Crowdin will PR with other languages
sort: true,
createOldCatalogs: false,
failOnWarnings: true,
verbose: false,
resetDefaultValueLocale: 'en-US', // Updates extracted values when they change in code
defaultNamespace: 'grafana-prometheus',
input: ['../**/*.{tsx,ts}'],
output: './src/locales/$LOCALE/$NAMESPACE.json',
};

View file

@ -2,6 +2,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { Button, Card, useStyles2 } from '@grafana/ui';
import promqlGrammar from '../promql';
@ -31,7 +32,9 @@ export const QueryPattern = (props: Props) => {
<Card.Heading>{pattern.name}</Card.Heading>
<div className={styles.rawQueryContainer}>
<RawQuery
aria-label={`${pattern.name} raw query`}
aria-label={t('querybuilder.query-pattern.aria-label-raw-query', '{{patternName}} raw query', {
patternName: pattern.name,
})}
query={promQueryModeller.renderQuery({
labels: [],
operations: pattern.operations,
@ -45,7 +48,7 @@ export const QueryPattern = (props: Props) => {
{selectedPatternName !== pattern.name ? (
<Button
size="sm"
aria-label="use this query button"
aria-label={t('querybuilder.query-pattern.aria-label-use-this-query-button', 'use this query button')}
onClick={() => {
if (hasPreviousQuery) {
// If user has previous query, we need to confirm that they want to apply this query pattern
@ -55,7 +58,7 @@ export const QueryPattern = (props: Props) => {
}
}}
>
Use this query
<Trans i18nKey="querybuilder.query-pattern.use-this-query">Use this query</Trans>
</Button>
) : (
<>
@ -66,27 +69,38 @@ export const QueryPattern = (props: Props) => {
: 'this query pattern will be applied to your current query'
}.`}
</div>
<Button size="sm" aria-label="back button" fill="outline" onClick={() => setSelectedPatternName(null)}>
Back
<Button
size="sm"
aria-label={t('querybuilder.query-pattern.aria-label-back-button', 'back button')}
fill="outline"
onClick={() => setSelectedPatternName(null)}
>
<Trans i18nKey="querybuilder.query-pattern.back">Back</Trans>
</Button>
<Button
size="sm"
aria-label="apply query starter button"
aria-label={t(
'querybuilder.query-pattern.aria-label-apply-query-starter-button',
'apply query starter button'
)}
onClick={() => {
onPatternSelect(pattern);
}}
>
Apply query
<Trans i18nKey="querybuilder.query-pattern.apply-query">Apply query</Trans>
</Button>
{hasNewQueryOption && (
<Button
size="sm"
aria-label="create new query button"
aria-label={t(
'querybuilder.query-pattern.aria-label-create-new-query-button',
'create new query button'
)}
onClick={() => {
onPatternSelect(pattern, true);
}}
>
Create new query
<Trans i18nKey="querybuilder.query-pattern.create-new-query">Create new query</Trans>
</Button>
)}
</>

View file

@ -4,6 +4,7 @@ import { capitalize } from 'lodash';
import { useMemo, useState } from 'react';
import { CoreApp, DataQuery, getNextRefId, GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
@ -72,17 +73,33 @@ export const QueryPatternsModal = (props: Props) => {
};
return (
<Modal aria-label="Kick start your query modal" isOpen={isOpen} title="Kick start your query" onDismiss={onClose}>
<Modal
aria-label={t(
'querybuilder.query-patterns-modal.aria-label-kick-start-your-query-modal',
'Kick start your query modal'
)}
isOpen={isOpen}
title={t('querybuilder.query-patterns-modal.title-kick-start-your-query', 'Kick start your query')}
onDismiss={onClose}
>
<div className={styles.spacing}>
Kick start your query by selecting one of these queries. You can then continue to complete your query.
<Trans i18nKey="querybuilder.query-patterns-modal.description-kick-start-your-query">
Kick start your query by selecting one of these queries. You can then continue to complete your query.
</Trans>
</div>
{Object.values(PromQueryPatternType).map((patternType) => {
const isOpen = openTabs.includes(patternType);
return (
<Collapse
aria-label={`open and close ${patternType} query starter card`}
aria-label={t(
'querybuilder.query-patterns-modal.aria-label-toggle-query-starter',
'open and close {{patternType}} query starter card',
{ patternType }
)}
key={patternType}
label={`${capitalize(patternType)} query starters`}
label={t('querybuilder.query-patterns-modal.label-toggle-query-starter', '{{patternType}} query starters', {
patternType: capitalize(patternType),
})}
isOpen={isOpen}
collapsible={true}
onToggle={() => {
@ -117,8 +134,15 @@ export const QueryPatternsModal = (props: Props) => {
</Collapse>
);
})}
<Button aria-label="close kick start your query modal" variant="secondary" onClick={onClose}>
Close
<Button
aria-label={t(
'querybuilder.query-patterns-modal.aria-label-close-kick-start-your-query-modal',
'close kick start your query modal'
)}
variant="secondary"
onClick={onClose}
>
<Trans i18nKey="querybuilder.query-patterns-modal.close">Close</Trans>
</Button>
</Modal>
);

View file

@ -4,6 +4,7 @@ import { useState } from 'react';
import { SelectableValue, toOption } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { AccessoryButton, InputGroup } from '@grafana/plugin-ui';
import { AsyncSelect, Select } from '@grafana/ui';
@ -93,7 +94,7 @@ export function LabelFilterItem({
<InputGroup>
{/* Label name select, loads all values at once */}
<AsyncSelect
placeholder="Select label"
placeholder={t('querybuilder.label-filter-item.placeholder-select-label', 'Select label')}
data-testid={selectors.components.QueryBuilder.labelSelect}
inputId="prometheus-dimensions-filter-item-key"
width="auto"
@ -150,7 +151,7 @@ export function LabelFilterItem({
{/* Label value async select: autocomplete calls prometheus API */}
<AsyncSelect
placeholder="Select value"
placeholder={t('querybuilder.label-filter-item.placeholder-select-value', 'Select value')}
data-testid={selectors.components.QueryBuilder.valueSelect}
inputId="prometheus-dimensions-filter-item-value"
width="auto"
@ -201,7 +202,12 @@ export function LabelFilterItem({
}}
invalid={invalidValue}
/>
<AccessoryButton aria-label={`remove-${item.label}`} icon="times" variant="secondary" onClick={onDelete} />
<AccessoryButton
aria-label={t('querybuilder.label-filter-item.aria-label-remove', 'Remove {{name}}', { name: item.label })}
icon="times"
variant="secondary"
onClick={onDelete}
/>
</InputGroup>
</div>
);

View file

@ -81,7 +81,7 @@ describe('LabelFilters', () => {
it('removes label', async () => {
const { onChange } = setup({ labelsFilters: [{ label: 'foo', op: '=', value: 'bar' }] });
await userEvent.click(screen.getByLabelText(/remove-foo/));
await userEvent.click(screen.getByLabelText(/Remove foo/));
expect(onChange).toHaveBeenCalledWith([]);
});
@ -93,7 +93,7 @@ describe('LabelFilters', () => {
{ label: 'le', op: '=', value: '' },
],
});
await userEvent.click(screen.getByLabelText(/remove-foo/));
await userEvent.click(screen.getByLabelText(/Remove foo/));
expect(onChange).toHaveBeenCalledWith([
{ label: 'lab', op: '=', value: 'bel' },
{ label: 'le', op: '=', value: '' },

View file

@ -4,6 +4,7 @@ import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { EditorField, EditorFieldGroup, EditorList } from '@grafana/plugin-ui';
import { InlineFieldRow, InlineLabel } from '@grafana/ui';
@ -94,9 +95,15 @@ export function LabelFilters({
>
<InlineLabel
width={20}
tooltip={<div>Optional: used to filter the metric select for this query type.</div>}
tooltip={
<div>
<Trans i18nKey="querybuilder.label-filters.tooltip-label-filters">
Optional: used to filter the metric select for this query type.
</Trans>
</div>
}
>
Label filters
<Trans i18nKey="querybuilder.label-filters.label-filters">Label filters</Trans>
</InlineLabel>
{editorList()}
</div>
@ -104,7 +111,7 @@ export function LabelFilters({
) : (
<EditorFieldGroup>
<EditorField
label="Label filters"
label={t('querybuilder.label-filters.label-label-filters', 'Label filters')}
error={MISSING_LABEL_FILTER_ERROR_MESSAGE}
invalid={labelFilterRequired && !hasLabelFilter}
>

View file

@ -53,6 +53,7 @@ async function loadGroupByLabels(
// This function is used by both Prometheus and Loki and this the only difference.
if (datasource.type === 'prometheus') {
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
labels = [{ label: '__name__', op: '=', value: query.metric }, ...query.labels];
}

View file

@ -2,6 +2,7 @@ import { useCallback, useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { EditorField, EditorFieldGroup, InputGroup } from '@grafana/plugin-ui';
import { Button, InlineField, InlineFieldRow, Combobox, ComboboxOption } from '@grafana/ui';
@ -87,7 +88,7 @@ export function MetricCombobox({
return (
<InputGroup>
<Combobox
placeholder="Select metric"
placeholder={t('querybuilder.metric-combobox.async-select.placeholder-select-metric', 'Select metric')}
width="auto"
minWidth={25}
options={loadOptions}
@ -97,8 +98,14 @@ export function MetricCombobox({
data-testid={selectors.components.DataSource.Prometheus.queryEditor.builder.metricSelect}
/>
<Button
tooltip="Open metrics explorer"
aria-label="Open metrics explorer"
tooltip={t(
'querybuilder.metric-combobox.async-select.tooltip-open-metrics-explorer',
'Open metrics explorer'
)}
aria-label={t(
'querybuilder.metric-combobox.async-select.aria-label-open-metrics-explorer',
'Open metrics explorer'
)}
variant="secondary"
icon="book-open"
onClick={() => {
@ -125,16 +132,22 @@ export function MetricCombobox({
{variableEditor ? (
<InlineFieldRow>
<InlineField
label="Metric"
label={t('querybuilder.metric-combobox.label-metric', 'Metric')}
labelWidth={20}
tooltip={<div>Optional: returns a list of label values for the label name in the specified metric.</div>}
tooltip={
<div>
<Trans i18nKey="querybuilder.metric-combobox.tooltip-metric">
Optional: returns a list of label values for the label name in the specified metric.
</Trans>
</div>
}
>
{asyncSelect()}
</InlineField>
</InlineFieldRow>
) : (
<EditorFieldGroup>
<EditorField label="Metric">{asyncSelect()}</EditorField>
<EditorField label={t('querybuilder.metric-combobox.label-metric', 'Metric')}>{asyncSelect()}</EditorField>
</EditorFieldGroup>
)}
</>

View file

@ -68,6 +68,7 @@ export function MetricsLabelsSection({
}
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
labelsToConsider.push({ label: '__name__', op: '=', value: query.metric });
const expr = promQueryModeller.renderLabels(labelsToConsider);
@ -91,6 +92,7 @@ export function MetricsLabelsSection({
const labelsToConsider = query.labels.filter((x) => x.label !== forLabel.label);
labelsToConsider.push(forLabel);
if (query.metric) {
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
labelsToConsider.push({ label: '__name__', op: '=', value: query.metric });
}
const interpolatedLabelsToConsider = labelsToConsider.map((labelObject) => ({
@ -173,6 +175,7 @@ export function MetricsLabelsSection({
}
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
labelsToConsider.push({ label: '__name__', op: '=', value: query.metric });
const interpolatedLabelsToConsider = labelsToConsider.map((labelObject) => ({

View file

@ -3,6 +3,7 @@ import { css } from '@emotion/css';
import { memo } from 'react';
import { GrafanaTheme2, toOption } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { EditorRows, FlexItem } from '@grafana/plugin-ui';
import { AutoSizeInput, IconButton, Select, useStyles2 } from '@grafana/ui';
@ -29,7 +30,9 @@ export const NestedQuery = memo<NestedQueryProps>((props) => {
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles.name}>Operator</div>
<div className={styles.name}>
<Trans i18nKey="querybuilder.nested-query.operator">Operator</Trans>
</div>
<Select
width="auto"
options={operators}
@ -41,15 +44,17 @@ export const NestedQuery = memo<NestedQueryProps>((props) => {
});
}}
/>
<div className={styles.name}>Vector matches</div>
<div className={styles.name}>
<Trans i18nKey="querybuilder.nested-query.vector-matches">Vector matches</Trans>
</div>
<div className={styles.vectorMatchWrapper}>
<Select<PromVisualQueryBinary['vectorMatchesType']>
width="auto"
value={nestedQuery.vectorMatchesType || 'on'}
allowCustomValue
options={[
{ value: 'on', label: 'on' },
{ value: 'ignoring', label: 'ignoring' },
{ value: 'on', label: t('querybuilder.nested-query.label.on', 'On') },
{ value: 'ignoring', label: t('querybuilder.nested-query.label.ignoring', 'Ignoring') },
]}
onChange={(val) => {
onChange(index, {
@ -72,7 +77,12 @@ export const NestedQuery = memo<NestedQueryProps>((props) => {
/>
</div>
<FlexItem grow={1} />
<IconButton name="times" size="sm" onClick={() => onRemove(index)} tooltip="Remove match" />
<IconButton
name="times"
size="sm"
onClick={() => onRemove(index)}
tooltip={t('querybuilder.nested-query.tooltip-remove-match', 'Remove match')}
/>
</div>
<div className={styles.body}>
<EditorRows>

View file

@ -4,6 +4,7 @@ import * as React from 'react';
import { CoreApp, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { EditorField, EditorRow, EditorSwitch } from '@grafana/plugin-ui';
import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
@ -68,7 +69,7 @@ export const PromQueryBuilderOptions = React.memo<PromQueryBuilderOptionsProps>(
<EditorRow>
<div data-testid={selectors.components.DataSource.Prometheus.queryEditor.options}>
<QueryOptionGroup
title="Options"
title={t('querybuilder.prom-query-builder-options.title-options', 'Options')}
collapsedInfo={getCollapsedInfo(query, formatOption.label!, queryTypeLabel, app)}
>
<PromQueryLegendEditor
@ -77,25 +78,36 @@ export const PromQueryBuilderOptions = React.memo<PromQueryBuilderOptionsProps>(
onRunQuery={onRunQuery}
/>
<EditorField
label="Min step"
label={t('querybuilder.prom-query-builder-options.label-min-step', 'Min step')}
tooltip={
<>
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>$__interval</code> and <code>$__rate_interval</code> variables.
<Trans
i18nKey="querybuilder.prom-query-builder-options.tooltip-min-step"
values={{
interval: '$__interval',
rateInterval: '$__rate_interval',
}}
>
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>{'{{interval}}'}</code> and <code>{'{{rateInterval}}'}</code> variables.
</Trans>
</>
}
>
<AutoSizeInput
type="text"
aria-label="Set lower limit for the step parameter"
placeholder={'auto'}
aria-label={t(
'querybuilder.prom-query-builder-options.aria-label-lower-limit-parameter',
'Set lower limit for the step parameter'
)}
placeholder={t('querybuilder.prom-query-builder-options.placeholder-auto', 'auto')}
minWidth={10}
onCommitChange={onChangeStep}
defaultValue={query.interval}
data-test-id="prometheus-step"
/>
</EditorField>
<EditorField label="Format">
<EditorField label={t('querybuilder.prom-query-builder-options.label-format', 'Format')}>
<Select
data-testid={selectors.components.DataSource.Prometheus.queryEditor.format}
value={formatOption}
@ -104,11 +116,14 @@ export const PromQueryBuilderOptions = React.memo<PromQueryBuilderOptionsProps>(
options={FORMAT_OPTIONS}
/>
</EditorField>
<EditorField label="Type" data-testid={selectors.components.DataSource.Prometheus.queryEditor.type}>
<EditorField
label={t('querybuilder.prom-query-builder-options.label-type', 'Type')}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.type}
>
<RadioButtonGroup options={queryTypeOptions} value={queryTypeValue} onChange={onQueryTypeChange} />
</EditorField>
{shouldShowExemplarSwitch(query, app) && (
<EditorField label="Exemplars">
<EditorField label={t('querybuilder.prom-query-builder-options.label-exemplars', 'Exemplars')}>
<EditorSwitch
value={query.exemplar || false}
onChange={onExemplarChange}
@ -117,9 +132,12 @@ export const PromQueryBuilderOptions = React.memo<PromQueryBuilderOptionsProps>(
</EditorField>
)}
{query.intervalFactor && query.intervalFactor > 1 && (
<EditorField label="Resolution">
<EditorField label={t('querybuilder.prom-query-builder-options.label-resolution', 'Resolution')}>
<Select
aria-label="Select resolution"
aria-label={t(
'querybuilder.prom-query-builder-options.aria-label-select-resolution',
'Select resolution'
)}
isSearchable={false}
options={INTERVAL_FACTOR_OPTIONS}
onChange={onIntervalFactorChange}

View file

@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { IconButton, Text, Stack } from '@grafana/ui';
@ -57,11 +58,17 @@ export function PromQueryCodeEditorAutocompleteInfo(props: Readonly<Props>) {
<div data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsCountInfo}>
<Stack direction="row" gap={1}>
<Text color="secondary" element="p" italic={true}>
Autocomplete suggestions limited
<Trans i18nKey="querybuilder.prom-query-code-editor-autocomplete-info.autocomplete-suggestions-limited">
Autocomplete suggestions limited
</Trans>
</Text>
<IconButton
name="info-circle"
tooltip={`The number of metric names exceeds the autocomplete limit. Only the ${autocompleteLimit}-most relevant metrics are displayed. You can adjust the threshold in the data source settings.`}
tooltip={t(
'querybuilder.prom-query-code-editor-autocomplete-info.tooltip-autocomplete-suggestions-limited',
'The number of metric names exceeds the autocomplete limit. Only the {{autocompleteLimit}}-most relevant metrics are displayed. You can adjust the threshold in the data source settings.',
{ autocompleteLimit }
)}
/>
</Stack>
</div>

View file

@ -4,6 +4,7 @@ import { memo, SyntheticEvent, useCallback, useEffect, useState } from 'react';
import { CoreApp, LoadingState, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t, Trans } from '@grafana/i18n';
import { EditorHeader, EditorRows, FlexItem } from '@grafana/plugin-ui';
import { reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Space } from '@grafana/ui';
@ -105,7 +106,10 @@ export const PromQueryEditorSelector = memo<Props>((props) => {
<>
<ConfirmModal
isOpen={parseModalOpen}
title="Parsing error: Switch to the builder mode?"
title={t(
'querybuilder.prom-query-editor-selector.title-parsing-error-switch-builder',
'Parsing error: Switch to the builder mode?'
)}
body="There is a syntax error, or the query structure cannot be visualized when switching to the builder mode. Parts of the query may be lost. "
confirmText="Continue"
onConfirm={() => {
@ -130,10 +134,14 @@ export const PromQueryEditorSelector = memo<Props>((props) => {
size="sm"
onClick={handleOpenQueryPatternsModal}
>
Kick start your query
<Trans i18nKey="querybuilder.prom-query-editor-selector.kick-start-your-query">Kick start your query</Trans>
</Button>
<div data-testid={selectors.components.DataSource.Prometheus.queryEditor.explain}>
<QueryHeaderSwitch label="Explain" value={explain} onChange={onShowExplainChange} />
<QueryHeaderSwitch
label={t('querybuilder.prom-query-editor-selector.label-explain', 'Explain')}
value={explain}
onChange={onShowExplainChange}
/>
</div>
<FlexItem grow={1} />
{app !== CoreApp.Explore && app !== CoreApp.Correlations && (
@ -144,7 +152,7 @@ export const PromQueryEditorSelector = memo<Props>((props) => {
icon={data?.state === LoadingState.Loading ? 'spinner' : undefined}
disabled={data?.state === LoadingState.Loading}
>
Run queries
<Trans i18nKey="querybuilder.prom-query-editor-selector.run-queries">Run queries</Trans>
</Button>
)}
<PromQueryCodeEditorAutocompleteInfo datasourceUid={props.datasource.uid} editorMode={editorMode} />

View file

@ -4,6 +4,7 @@ import * as React from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { EditorField } from '@grafana/plugin-ui';
import { AutoSizeInput, Select } from '@grafana/ui';
@ -66,8 +67,12 @@ export const PromQueryLegendEditor = React.memo<PromQueryLegendEditorProps>(
return (
<EditorField
label="Legend"
tooltip="Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname."
label={t('querybuilder.prom-query-legend-editor.label-legend', 'Legend')}
tooltip={t(
'querybuilder.prom-query-legend-editor.tooltip-legend',
'Series name override or template. Ex. {{templateExample}} will be replaced with label value for {{labelName}}.',
{ templateExample: '{{hostname}}', labelName: 'hostname' }
)}
data-testid={selectors.components.DataSource.Prometheus.queryEditor.legend}
>
<>
@ -75,6 +80,7 @@ export const PromQueryLegendEditor = React.memo<PromQueryLegendEditorProps>(
<AutoSizeInput
id="legendFormat"
minWidth={22}
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
placeholder="auto"
defaultValue={legendFormat}
onCommitChange={onLegendFormatChanged}
@ -85,7 +91,10 @@ export const PromQueryLegendEditor = React.memo<PromQueryLegendEditorProps>(
<Select
inputId="legend.mode"
isSearchable={false}
placeholder="Select legend mode"
placeholder={t(
'querybuilder.prom-query-legend-editor.placeholder-select-legend-mode',
'Select legend mode'
)}
options={legendModeOptions}
width={22}
onChange={onLegendModeChanged}

View file

@ -2,6 +2,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Icon, Switch, Tooltip, useTheme2 } from '@grafana/ui';
import { metricsModaltestIds } from './MetricsModal';
@ -44,7 +45,9 @@ export function AdditionalSettings(props: AdditionalSettingsProps) {
</div>
<div className={styles.selectItem}>
<Switch value={state.disableTextWrap} onChange={() => onChangeDisableTextWrap()} />
<div className={styles.selectItemLabel}>Disable text wrap</div>
<div className={styles.selectItemLabel}>
<Trans i18nKey="querybuilder.additional-settings.disable-text-wrap">Disable text wrap</Trans>
</div>
</div>
<div className={styles.selectItem}>
<Switch
@ -54,7 +57,10 @@ export function AdditionalSettings(props: AdditionalSettingsProps) {
/>
<div className={styles.selectItemLabel}>{placeholders.setUseBackend}&nbsp;</div>
<Tooltip
content={'Filter metric names by regex search, using an additional call on the Prometheus API.'}
content={t(
'querybuilder.additional-settings.content-filter-metric-names-regex-search-using',
'Filter metric names by regex search, using an additional call on the Prometheus API.'
)}
placement="bottom-end"
>
<Icon name="info-circle" size="xs" className={styles.settingsIcon} />

View file

@ -2,6 +2,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Icon, useStyles2, Stack } from '@grafana/ui';
export interface Props {
@ -16,11 +17,15 @@ export function FeedbackLink({ feedbackUrl }: Props) {
<a
href={feedbackUrl}
className={styles.link}
title="The metrics explorer is new, please let us know how we can improve it"
title={t(
'querybuilder.feedback-link.title-give-feedback',
'The metrics explorer is new, please let us know how we can improve it'
)}
target="_blank"
rel="noreferrer noopener"
>
<Icon name="comment-alt-message" /> Give feedback
<Icon name="comment-alt-message" />{' '}
<Trans i18nKey="querybuilder.feedback-link.give-feedback">Give feedback</Trans>
</a>
</Stack>
);

View file

@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useReducer } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t, Trans } from '@grafana/i18n';
import {
Button,
ButtonGroup,
@ -187,9 +188,9 @@ export const MetricsModal = (props: MetricsModalProps) => {
<Modal
data-testid={metricsModaltestIds.metricModal}
isOpen={isOpen}
title="Metrics explorer"
title={t('querybuilder.metrics-modal.title-metrics-explorer', 'Metrics explorer')}
onDismiss={onClose}
aria-label="Browse metrics"
aria-label={t('querybuilder.metrics-modal.aria-label-browse-metrics', 'Browse metrics')}
className={styles.modal}
>
<FeedbackLink feedbackUrl="https://forms.gle/DEMAJHoAMpe3e54CA" />
@ -227,7 +228,7 @@ export const MetricsModal = (props: MetricsModalProps) => {
</div>
<div className={styles.inputItem}>
<Toggletip
aria-label="Additional settings"
aria-label={t('querybuilder.metrics-modal.aria-label-additional-settings', 'Additional settings')}
content={additionalSettings}
placement="bottom-end"
closeButton={false}
@ -240,7 +241,7 @@ export const MetricsModal = (props: MetricsModalProps) => {
data-testid={metricsModaltestIds.showAdditionalSettings}
className={styles.noBorder}
>
Additional Settings
<Trans i18nKey="querybuilder.metrics-modal.additional-settings">Additional Settings</Trans>
</Button>
<Button
className={styles.noBorder}
@ -252,12 +253,21 @@ export const MetricsModal = (props: MetricsModalProps) => {
</div>
</div>
<div className={styles.resultsData}>
{query.metric && <i className={styles.currentlySelected}>Currently selected: {query.metric}</i>}
{query.metric && (
<i className={styles.currentlySelected}>
<Trans i18nKey="querybuilder.metrics-modal.currently-selected" values={{ selected: query.metric }}>
Currently selected: {'{{selected}}'}
</Trans>
</i>
)}
{query.labels.length > 0 && (
<div className={styles.resultsDataFiltered}>
<Icon name="info-circle" size="sm" />
<div className={styles.resultsDataFilteredText}>
&nbsp;These metrics have been pre-filtered by labels chosen in the label filters.
&nbsp;
<Trans i18nKey="querybuilder.metrics-modal.metrics-pre-filtered">
These metrics have been pre-filtered by labels chosen in the label filters.
</Trans>
</div>
</div>
)}
@ -276,7 +286,13 @@ export const MetricsModal = (props: MetricsModalProps) => {
</div>
<div className={styles.resultsFooter}>
<div className={styles.resultsAmount}>
Showing {state.filteredMetricCount} of {state.totalMetricCount} results
<Trans
i18nKey="querybuilder.metrics-modal.results-amount"
values={{ num: state.filteredMetricCount }}
count={state.totalMetricCount}
>
Showing {'{{num}}'} of {'{{count}}'} results
</Trans>
</div>
<Pagination
currentPage={state.pageNum ?? 1}
@ -287,11 +303,13 @@ export const MetricsModal = (props: MetricsModalProps) => {
}}
/>
<div className={styles.resultsPerPageWrapper}>
<p className={styles.resultsPerPageLabel}># Results per page&nbsp;</p>
<p className={styles.resultsPerPageLabel}>
<Trans i18nKey="querybuilder.metrics-modal.results-per-page">Results per page</Trans>
</p>
<Input
data-testid={metricsModaltestIds.resultsPerPage}
value={calculateResultsPerPage(state.resultsPerPage, DEFAULT_RESULTS_PER_PAGE, MAXIMUM_RESULTS_PER_PAGE)}
placeholder="results per page"
placeholder={t('querybuilder.metrics-modal.placeholder-results-per-page', 'results per page')}
width={10}
title={'The maximum results per page is ' + MAXIMUM_RESULTS_PER_PAGE}
type="number"

View file

@ -4,6 +4,7 @@ import { ReactElement } from 'react';
import Highlighter from 'react-highlight-words';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { Button, Icon, Tooltip, useTheme2 } from '@grafana/ui';
import { docsTip } from '../../../configuration/ConfigEditor';
@ -69,7 +70,9 @@ export function ResultsTable(props: ResultsTableProps) {
<Tooltip
content={
<>
When creating a {descriptiveType}, Prometheus exposes multiple series with the type counter.{' '}
<Trans i18nKey="querybuilder.results-table.content-descriptive-type">
When creating a {{ descriptiveType }}, Prometheus exposes multiple series with the type counter.{' '}
</Trans>
{docsTip(link)}
</>
}
@ -140,11 +143,17 @@ export function ResultsTable(props: ResultsTableProps) {
<table className={styles.table}>
<thead className={styles.stickyHeader}>
<tr>
<th className={`${styles.nameWidth} ${styles.tableHeaderPadding}`}>Name</th>
<th className={`${styles.nameWidth} ${styles.tableHeaderPadding}`}>
<Trans i18nKey="querybuilder.results-table.name">Name</Trans>
</th>
{state.hasMetadata && (
<>
<th className={`${styles.typeWidth} ${styles.tableHeaderPadding}`}>Type</th>
<th className={`${styles.descriptionWidth} ${styles.tableHeaderPadding}`}>Description</th>
<th className={`${styles.typeWidth} ${styles.tableHeaderPadding}`}>
<Trans i18nKey="querybuilder.results-table.type">Type</Trans>
</th>
<th className={`${styles.descriptionWidth} ${styles.tableHeaderPadding}`}>
<Trans i18nKey="querybuilder.results-table.description">Description</Trans>
</th>
</>
)}
<th className={styles.selectButtonWidth}> </th>
@ -172,7 +181,7 @@ export function ResultsTable(props: ResultsTableProps) {
onClick={() => selectMetric(metric)}
className={styles.centerButton}
>
Select
<Trans i18nKey="querybuilder.results-table.select">Select</Trans>
</Button>
</td>
</tr>

View file

@ -125,6 +125,7 @@ export function getOperationParamId(operationId: string, paramIndex: number) {
}
export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOperationParamDef {
/* eslint-disable @grafana/i18n/no-untranslated-strings */
const options: Array<SelectableValue<string>> = [
{
label: '$__interval',
@ -145,6 +146,7 @@ export function getRangeVectorParamDef(withRateInterval = false): QueryBuilderOp
// tooltip: 'Always above 4x scrape interval',
});
}
/* eslint-enable @grafana/i18n/no-untranslated-strings */
const param: QueryBuilderOperationParamDef = {
name: 'Range',

View file

@ -26,6 +26,8 @@ import {
Without,
} from '@prometheus-io/lezer-promql';
import { t } from '@grafana/i18n';
import { binaryScalarOperatorToOperatorName } from './binaryScalarOperations';
import {
ErrorId,
@ -225,7 +227,7 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) {
// Visual query builder doesn't support nested queries and so info function.
if (funcName === 'info') {
context.errors.push({
text: 'Query parsing is ambiguous.',
text: t('querybuilder.handle-function.text.query-parsing-is-ambiguous', 'Query parsing is ambiguous.'),
from: node.from,
to: node.to,
});
@ -329,7 +331,7 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont
if (binaryExpressionWithinFunctionArgs) {
context.errors.push({
text: 'Query parsing is ambiguous.',
text: t('querybuilder.update-function-args.text.query-parsing-is-ambiguous', 'Query parsing is ambiguous.'),
from: binaryExpressionWithinFunctionArgs.from,
to: binaryExpressionWithinFunctionArgs.to,
});

View file

@ -5,6 +5,7 @@ import { useEffect, useId, useState } from 'react';
import * as React from 'react';
import { DataSourceApi, GrafanaTheme2, TimeRange } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Button, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { getOperationParamId } from '../operationUtils';
@ -52,7 +53,13 @@ export function OperationEditor({
const id = useId();
if (!def) {
return <span>Operation {operation.id} not found</span>;
return (
<span>
<Trans i18nKey="querybuilder.operation-editor.not-found" values={{ id: operation.id }}>
Operation {'{{id}}'} not found
</Trans>
</span>
);
}
const onParamValueChanged = (paramIdx: number, value: QueryBuilderOperationParamValue) => {
@ -113,7 +120,7 @@ export function OperationEditor({
fill="text"
icon="times"
variant="secondary"
title={`Remove ${paramDef.name}`}
title={t('querybuilder.operation-editor.title-remove', 'Remove {{name}}', { name: paramDef.name })}
onClick={() => onRemoveRestParam(paramIndex)}
/>
)}

View file

@ -4,6 +4,7 @@ import { DraggableProvided } from '@hello-pangea/dnd';
import { memo, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { t } from '@grafana/i18n';
import { FlexItem } from '@grafana/plugin-ui';
import { Button, Select, useStyles2 } from '@grafana/ui';
@ -54,7 +55,10 @@ export const OperationHeader = memo<Props>(
onClick={onToggleSwitcher}
fill="text"
variant="secondary"
title="Click to view alternative operations"
title={t(
'querybuilder.operation-header.title-click-to-view-alternative-operations',
'Click to view alternative operations'
)}
/>
<OperationInfoButton def={def} operation={operation} />
<Button
@ -63,7 +67,7 @@ export const OperationHeader = memo<Props>(
onClick={() => onRemove(index)}
fill="text"
variant="secondary"
title="Remove operation"
title={t('querybuilder.operation-header.title-remove-operation', 'Remove operation')}
/>
</div>
</>
@ -73,7 +77,7 @@ export const OperationHeader = memo<Props>(
<Select
autoFocus
openMenuOnFocus
placeholder="Replace with"
placeholder={t('querybuilder.operation-header.placeholder-replace-with', 'Replace with')}
options={state.alternatives}
isOpen={true}
onCloseMenu={onToggleSwitcher}

View file

@ -13,6 +13,7 @@ import {
import { memo, useState } from 'react';
import { GrafanaTheme2, renderMarkdown } from '@grafana/data';
import { t } from '@grafana/i18n';
import { FlexItem } from '@grafana/plugin-ui';
import { Button, Portal, useStyles2 } from '@grafana/ui';
@ -55,7 +56,7 @@ export const OperationInfoButton = memo<Props>(({ def, operation }) => {
return (
<>
<Button
title="Click to show description"
title={t('querybuilder.operation-info-button.title-click-to-show-description', 'Click to show description')}
ref={refs.setReference}
icon="info-circle"
size="sm"
@ -74,7 +75,7 @@ export const OperationInfoButton = memo<Props>(({ def, operation }) => {
onClick={() => setShow(false)}
fill="text"
variant="secondary"
title="Remove operation"
title={t('querybuilder.operation-info-button.title-remove-operation', 'Remove operation')}
/>
</div>
<div

View file

@ -5,6 +5,7 @@ import { useState } from 'react';
import { useMountedState, usePrevious } from 'react-use';
import { DataSourceApi, GrafanaTheme2, TimeRange } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { Button, Cascader, CascaderOption, useStyles2, Stack } from '@grafana/ui';
import { OperationEditor } from './OperationEditor';
@ -126,11 +127,16 @@ export function OperationList<T extends QueryWithOperations>({
autoFocus={true}
alwaysOpen={true}
hideActiveLevelLabel={true}
placeholder={'Search'}
placeholder={t('querybuilder.operation-list.placeholder-search', 'Search')}
/>
) : (
<Button icon={'plus'} variant={'secondary'} onClick={() => setCascaderOpen(true)} title={'Add operation'}>
Operations
<Button
icon={'plus'}
variant={'secondary'}
onClick={() => setCascaderOpen(true)}
title={t('querybuilder.operation-list.title-add-operation', 'Add operation')}
>
<Trans i18nKey="querybuilder.operation-list.operations">Operations</Trans>
</Button>
)}
</div>

View file

@ -3,6 +3,7 @@ import { css } from '@emotion/css';
import { ComponentType } from 'react';
import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data';
import { t } from '@grafana/i18n';
import { AutoSizeInput, Button, Checkbox, Select, useStyles2, Stack } from '@grafana/ui';
import { getOperationParamId } from '../operationUtils';
@ -86,7 +87,7 @@ function SelectInputParamEditor({
<Button
size="sm"
variant="secondary"
title={`Add ${paramDef.name}`}
title={t('querybuilder.operation-param-editor.title-add', 'Add {{name}}', { name: paramDef.name })}
icon="plus"
onClick={() => onChange(index, selectOptions[0].value)}
>
@ -114,7 +115,7 @@ function SelectInputParamEditor({
fill="text"
icon="times"
variant="secondary"
title={`Remove ${paramDef.name}`}
title={t('querybuilder.operation-param-editor.title-remove', 'Remove {{name}}', { name: paramDef.name })}
onClick={() => onChange(index, '')}
/>
)}

View file

@ -3,6 +3,7 @@ import { css } from '@emotion/css';
import { useEffect, useState } from 'react';
import { GrafanaTheme2, PanelData, QueryHint } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { Button, Tooltip, useStyles2 } from '@grafana/ui';
@ -43,6 +44,7 @@ export const QueryBuilderHints = <T extends PromLokiVisualQuery>({
<div className={styles.container}>
{hints.map((hint) => {
return (
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
<Tooltip content={`${hint.label} ${hint.fix?.label}`} key={hint.type}>
<Button
onClick={() => {
@ -62,7 +64,12 @@ export const QueryBuilderHints = <T extends PromLokiVisualQuery>({
size="sm"
className={styles.hint}
>
hint: {hint.fix?.title || hint.fix?.action?.type.toLowerCase().replace('_', ' ')}
<Trans
i18nKey="querybuilder.query-builder-hints.hint-details"
values={{ hintDetails: hint.fix?.title || hint.fix?.action?.type.toLowerCase().replace('_', ' ') }}
>
hint: {'{{hintDetails}}'}
</Trans>
</Button>
</Tooltip>
);

View file

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { useEffect, useState } from 'react';
import { GrafanaTheme2, QueryHint } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { Button, Tooltip, useStyles2 } from '@grafana/ui';
@ -24,9 +25,15 @@ export function QueryEditorHints(props: PromQueryEditorProps) {
<div className={styles.container}>
{hints.map((hint) => {
return (
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
<Tooltip content={`${hint.label} ${hint.fix?.label}`} key={hint.type}>
<Button onClick={() => onHintButtonClick(hint, props)} fill="outline" size="sm" className={styles.hint}>
hint: {hint.fix?.title || hint.fix?.action?.type.toLowerCase().replace('_', ' ')}
<Trans
i18nKey="querybuilder.query-editor-hints.hint-details"
values={{ hintDetails: hint.fix?.title || hint.fix?.action?.type.toLowerCase().replace('_', ' ') }}
>
hint: {'{{hintDetails}}'}
</Trans>
</Button>
</Tooltip>
);

View file

@ -3,6 +3,7 @@ import { css, cx } from '@emotion/css';
import Prism, { Grammar } from 'prismjs';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { useTheme2 } from '@grafana/ui';
export interface Props {
@ -22,7 +23,7 @@ export function RawQuery({ query, lang, className }: Props) {
return (
<div
className={cx(styles.editorField, 'prism-syntax-highlight', className)}
aria-label="selector"
aria-label={t('querybuilder.raw-query.aria-label-selector', 'selector')}
dangerouslySetInnerHTML={{ __html: highlighted }}
/>
);

View file

@ -7,7 +7,8 @@
"isolatedModules": true,
"allowJs": true,
"rootDirs": ["."],
"resolveJsonModule": true
"resolveJsonModule": true,
"moduleResolution": "bundler"
},
"exclude": ["dist/**/*"],
"extends": "@grafana/tsconfig",

View file

@ -3400,6 +3400,7 @@ __metadata:
"@floating-ui/react": "npm:0.27.12"
"@grafana/data": "npm:12.1.0-pre"
"@grafana/e2e-selectors": "npm:12.1.0-pre"
"@grafana/i18n": "npm:12.1.0-pre"
"@grafana/plugin-ui": "npm:0.10.6"
"@grafana/runtime": "npm:12.1.0-pre"
"@grafana/schema": "npm:12.1.0-pre"
@ -3431,6 +3432,7 @@ __metadata:
"@types/uuid": "npm:10.0.0"
debounce-promise: "npm:3.1.2"
esbuild: "npm:0.25.0"
i18next-parser: "npm:9.3.0"
jest: "npm:29.7.0"
jest-environment-jsdom: "npm:29.7.0"
lodash: "npm:4.17.21"