Internationalisation: More mark up following manual review (#107709)

* mark up variable types

* mark up annotation stuff

* mark up TagsInput placeholder

* mark up validation messages

* mark up required error

* Mark up metrics explorer modal
pull/107910/head^2
Ashley Harrison 1 week ago committed by GitHub
parent b6580ccb10
commit 453a791db1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 26
      packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json
  2. 4
      packages/grafana-prometheus/src/querybuilder/components/metrics-modal/AdditionalSettings.tsx
  3. 8
      packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.tsx
  4. 17
      packages/grafana-prometheus/src/querybuilder/components/metrics-modal/ResultsTable.tsx
  5. 69
      packages/grafana-prometheus/src/querybuilder/components/metrics-modal/state/helpers.ts
  6. 1
      packages/grafana-prometheus/src/querybuilder/components/metrics-modal/types.ts
  7. 5
      packages/grafana-ui/src/components/TagsInput/TagsInput.tsx
  8. 4
      public/app/features/annotations/components/AnnotationResultMapper.tsx
  9. 35
      public/app/features/annotations/standardAnnotationSupport.ts
  10. 5
      public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx
  11. 25
      public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx
  12. 24
      public/app/features/dashboard-scene/settings/variables/utils.test.ts
  13. 69
      public/app/features/dashboard-scene/settings/variables/utils.ts
  14. 25
      public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx
  15. 28
      public/app/features/manage-dashboards/services/ValidationSrv.ts
  16. 66
      public/locales/en-US/grafana.json

@ -225,6 +225,29 @@
"step": "Step: {{value}}",
"type": "Type: {{value}}"
},
"get-placeholders": {
"browse": "Search metrics by name",
"include-null-metadata": "Include results with no metadata",
"metadata-search-switch": "Include description in search",
"set-use-backend": "Enable regex search",
"type": "Filter by type"
},
"get-prom-types": {
"description-counter": "A cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart.",
"description-gauge": "A metric that represents a single numerical value that can arbitrarily go up and down.",
"description-histogram": "A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets.",
"description-native-histogram": "Native histograms are different from classic Prometheus histograms in a number of ways: Native histogram bucket boundaries are calculated by a formula that depends on the scale (resolution) of the native histogram, and are not user defined.",
"description-no-type": "These metrics have no defined type in the metadata.",
"description-summary": "A summary samples observations (usually things like request durations and response sizes) and can calculate configurable quantiles over a sliding time window.",
"description-unknown": "These metrics have been given the type unknown in the metadata.",
"label-counter": "Counter",
"label-gauge": "Gauge",
"label-histogram": "Histogram",
"label-native-histogram": "Native histogram",
"label-no-type": "No type",
"label-summary": "Summary",
"label-unknown": "Unknown"
},
"handle-function": {
"text": {
"query-parsing-is-ambiguous": "Query parsing is ambiguous."
@ -368,6 +391,9 @@
"results-table": {
"content-descriptive-type": "When creating a {{descriptiveType}}, Prometheus exposes multiple series with the type counter. ",
"description": "Description",
"message-expand-label-filters": "There are no metrics found. Try to expand your label filters.",
"message-expand-search": "There are no metrics found. Try to expand your search and filters.",
"message-no-metrics-found": "There are no metrics found in the data source.",
"name": "Name",
"select": "Select",
"type": "Type"

@ -7,7 +7,7 @@ import { Icon, Switch, Tooltip, useTheme2 } from '@grafana/ui';
import { metricsModaltestIds } from './shared/testIds';
import { AdditionalSettingsProps } from './shared/types';
import { placeholders } from './state/helpers';
import { getPlaceholders } from './state/helpers';
export function AdditionalSettings(props: AdditionalSettingsProps) {
const { state, onChangeFullMetaSearch, onChangeIncludeNullMetadata, onChangeDisableTextWrap, onChangeUseBackend } =
@ -16,6 +16,8 @@ export function AdditionalSettings(props: AdditionalSettingsProps) {
const theme = useTheme2();
const styles = getStyles(theme);
const placeholders = getPlaceholders();
return (
<>
<div className={styles.selectItem}>

@ -32,8 +32,8 @@ import {
calculatePageList,
calculateResultsPerPage,
displayedMetrics,
placeholders,
promTypes,
getPlaceholders,
getPromTypes,
setMetrics,
tracking,
} from './state/helpers';
@ -55,6 +55,8 @@ export const MetricsModal = (props: MetricsModalProps) => {
const theme = useTheme2();
const styles = getStyles(theme, state.disableTextWrap);
const placeholders = getPlaceholders();
const promTypes = getPromTypes();
/**
* loads metrics and metadata on opening modal and switching off useBackend
@ -88,7 +90,7 @@ export const MetricsModal = (props: MetricsModalProps) => {
const typeOptions: SelectableValue[] = promTypes.map((t: PromFilterOption) => {
return {
value: t.value,
label: t.value,
label: t.label,
description: t.description,
};
});

@ -4,7 +4,7 @@ import { ReactElement } from 'react';
import Highlighter from 'react-highlight-words';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import { Button, Icon, Tooltip, useTheme2 } from '@grafana/ui';
import { docsTip } from '../../../configuration/shared/utils';
@ -106,15 +106,24 @@ export function ResultsTable(props: ResultsTableProps) {
let message;
if (!state.fuzzySearchQuery) {
message = 'There are no metrics found in the data source.';
message = t(
'grafana-prometheus.querybuilder.results-table.message-no-metrics-found',
'There are no metrics found in the data source.'
);
}
if (query.labels.length > 0) {
message = 'There are no metrics found. Try to expand your label filters.';
message = t(
'grafana-prometheus.querybuilder.results-table.message-expand-label-filters',
'There are no metrics found. Try to expand your label filters.'
);
}
if (state.fuzzySearchQuery || state.selectedTypes.length > 0) {
message = 'There are no metrics found. Try to expand your search and filters.';
message = t(
'grafana-prometheus.querybuilder.results-table.message-expand-search',
'There are no metrics found. Try to expand your search and filters.'
);
}
return (

@ -1,6 +1,7 @@
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/metrics-modal/state/helpers.ts
import { AnyAction } from '@reduxjs/toolkit';
import { t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { PrometheusDatasource } from '../../../../datasource';
@ -232,45 +233,75 @@ export function tracking(event: string, state?: MetricsModalState | null, metric
}
}
export const promTypes: PromFilterOption[] = [
export const getPromTypes: () => PromFilterOption[] = () => [
{
value: 'counter',
description:
'A cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-counter', 'Counter'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-counter',
'A cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart.'
),
},
{
value: 'gauge',
description: 'A metric that represents a single numerical value that can arbitrarily go up and down.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-gauge', 'Gauge'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-gauge',
'A metric that represents a single numerical value that can arbitrarily go up and down.'
),
},
{
value: 'histogram',
description:
'A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-histogram', 'Histogram'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-histogram',
'A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets.'
),
},
{
value: 'native histogram',
description:
'Native histograms are different from classic Prometheus histograms in a number of ways: Native histogram bucket boundaries are calculated by a formula that depends on the scale (resolution) of the native histogram, and are not user defined.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-native-histogram', 'Native histogram'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-native-histogram',
'Native histograms are different from classic Prometheus histograms in a number of ways: Native histogram bucket boundaries are calculated by a formula that depends on the scale (resolution) of the native histogram, and are not user defined.'
),
},
{
value: 'summary',
description:
'A summary samples observations (usually things like request durations and response sizes) and can calculate configurable quantiles over a sliding time window.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-summary', 'Summary'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-summary',
'A summary samples observations (usually things like request durations and response sizes) and can calculate configurable quantiles over a sliding time window.'
),
},
{
value: 'unknown',
description: 'These metrics have been given the type unknown in the metadata.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-unknown', 'Unknown'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-unknown',
'These metrics have been given the type unknown in the metadata.'
),
},
{
value: 'no type',
description: 'These metrics have no defined type in the metadata.',
label: t('grafana-prometheus.querybuilder.get-prom-types.label-no-type', 'No type'),
description: t(
'grafana-prometheus.querybuilder.get-prom-types.description-no-type',
'These metrics have no defined type in the metadata.'
),
},
];
export const placeholders = {
browse: 'Search metrics by name',
metadataSearchSwitch: 'Include description in search',
type: 'Filter by type',
includeNullMetadata: 'Include results with no metadata',
setUseBackend: 'Enable regex search',
};
export const getPlaceholders = () => ({
browse: t('grafana-prometheus.querybuilder.get-placeholders.browse', 'Search metrics by name'),
metadataSearchSwitch: t(
'grafana-prometheus.querybuilder.get-placeholders.metadata-search-switch',
'Include description in search'
),
type: t('grafana-prometheus.querybuilder.get-placeholders.type', 'Filter by type'),
includeNullMetadata: t(
'grafana-prometheus.querybuilder.get-placeholders.include-null-metadata',
'Include results with no metadata'
),
setUseBackend: t('grafana-prometheus.querybuilder.get-placeholders.set-use-backend', 'Enable regex search'),
});

@ -9,6 +9,7 @@ export type MetricData = {
export type PromFilterOption = {
value: string;
label: string;
description: string;
};

@ -3,7 +3,7 @@ import { useCallback, useState, forwardRef } from 'react';
import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { Button } from '../Button/Button';
@ -32,7 +32,7 @@ export interface Props {
export const TagsInput = forwardRef<HTMLInputElement, Props>(
(
{
placeholder = 'New tag (enter key to add)',
placeholder: placeholderProp,
tags = [],
onChange,
width,
@ -45,6 +45,7 @@ export const TagsInput = forwardRef<HTMLInputElement, Props>(
},
ref
) => {
const placeholder = placeholderProp ?? t('grafana-ui.tags-input.placeholder-new-tag', 'New tag (enter key to add)');
const [newTagName, setNewTagName] = useState('');
const styles = useStyles2(getStyles);
const theme = useTheme2();

@ -13,7 +13,7 @@ import {
import { Trans, t } from '@grafana/i18n';
import { Select, Tooltip, Icon } from '@grafana/ui';
import { annotationEventNames, AnnotationFieldInfo } from '../standardAnnotationSupport';
import { getAnnotationEventNames, AnnotationFieldInfo } from '../standardAnnotationSupport';
import { AnnotationQueryResponse } from '../types';
// const valueOptions: Array<SelectableValue<AnnotationEventFieldSource>> = [
@ -204,7 +204,7 @@ export class AnnotationFieldMapper extends PureComponent<Props, State> {
</tr>
</thead>
<tbody>
{annotationEventNames.map((row) => {
{getAnnotationEventNames().map((row) => {
return this.renderRow(row, mappings[row.key] || {}, first);
})}
</tbody>

@ -17,6 +17,7 @@ import {
KeyValue,
standardTransformers,
} from '@grafana/data';
import { t } from '@grafana/i18n';
import { config } from 'app/core/config';
export const standardAnnotationSupport: AnnotationSupport = {
@ -97,22 +98,44 @@ export interface AnnotationFieldInfo {
}
// These fields get added to the standard UI
export const annotationEventNames: AnnotationFieldInfo[] = [
export const getAnnotationEventNames: () => AnnotationFieldInfo[] = () => [
{
key: 'time',
field: (frame: DataFrame) => frame.fields.find((f) => f.type === FieldType.time),
placeholder: 'time, or the first time field',
placeholder: t(
'annotations.get-annotation-event-names.placeholder.time-or-the-first-field',
'{{defaultField}}, or the first time field',
{ defaultField: 'time' }
),
},
{
key: 'timeEnd',
// label: 'end time',
help: t(
'annotations.get-annotation-event-names.help.annotation-treated-as-range',
'When this field is defined, the annotation will be treated as a range'
),
},
{ key: 'timeEnd', label: 'end time', help: 'When this field is defined, the annotation will be treated as a range' },
{
key: 'title',
},
{
key: 'text',
field: (frame: DataFrame) => frame.fields.find((f) => f.type === FieldType.string),
placeholder: 'text, or the first text field',
placeholder: t(
'annotations.get-annotation-event-names.placeholder.text-or-the-first-field',
'{{defaultField}}, or the first text field',
{ defaultField: 'text' }
),
},
{
key: 'tags',
split: ',',
help: t(
'annotations.get-annotation-event-names.help.results-split-on-comma',
'The results will be split on comma (,)'
),
},
{ key: 'tags', split: ',', help: 'The results will be split on comma (,)' },
{
key: 'id',
},
@ -134,7 +157,7 @@ export const publicDashboardEventNames: AnnotationFieldInfo[] = [
// pipeline, but include fields that should not be exposed generally
const alertEventAndAnnotationFields: AnnotationFieldInfo[] = [
...(config.publicDashboardAccessToken ? publicDashboardEventNames : []),
...annotationEventNames,
...getAnnotationEventNames(),
{ key: 'userId' },
{ key: 'login' },
{ key: 'email' },

@ -118,7 +118,10 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
<form onSubmit={handleSubmit(() => onSave(false))}>
<Field label={<TitleFieldLabel onChange={setValue} />} invalid={!!errors.title} error={errors.title?.message}>
<Input
{...register('title', { required: 'Required', validate: validateDashboardName })}
{...register('title', {
required: t('dashboard-scene.save-dashboard-as-form.required', 'Required'),
validate: validateDashboardName,
})}
aria-label={t(
'dashboard-scene.save-dashboard-as-form.aria-label-save-dashboard-title-field',
'Save dashboard title field'

@ -245,7 +245,7 @@ export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate
>
<>
<Select
options={panelFilters}
options={getPanelFilters()}
value={panelFilter}
onChange={onFilterTypeChange}
data-testid={selectors.components.Annotations.annotationsTypeInput}
@ -320,20 +320,29 @@ enum PanelFilterType {
ExcludePanels,
}
const panelFilters = [
const getPanelFilters = () => [
{
label: 'All panels',
label: t('dashboard-scene.get-panel-filters.label.all-panels', 'All panels'),
value: PanelFilterType.AllPanels,
description: 'Send the annotation data to all panels that support annotations',
description: t(
'dashboard-scene.get-panel-filters.description.annotation-panels-support-annotations',
'Send the annotation data to all panels that support annotations'
),
},
{
label: 'Selected panels',
label: t('dashboard-scene.get-panel-filters.label.selected-panels', 'Selected panels'),
value: PanelFilterType.IncludePanels,
description: 'Send the annotations to the explicitly listed panels',
description: t(
'dashboard-scene.get-panel-filters.description.annotations-explicitly-listed-panels',
'Send the annotations to the explicitly listed panels'
),
},
{
label: 'All panels except',
label: t('dashboard-scene.get-panel-filters.label.all-panels-except', 'All panels except'),
value: PanelFilterType.ExcludePanels,
description: 'Do not send annotation data to the following panels',
description: t(
'dashboard-scene.get-panel-filters.description.annotation-following-panels',
'Do not send annotation data to the following panels'
),
},
];

@ -25,8 +25,8 @@ import { QueryVariableEditor } from './editors/QueryVariableEditor';
import { TextBoxVariableEditor } from './editors/TextBoxVariableEditor';
import {
isEditableVariableType,
EDITABLE_VARIABLES,
EDITABLE_VARIABLES_SELECT_ORDER,
getEditableVariables,
getVariableTypeSelectOptions,
getVariableEditor,
getVariableScene,
@ -136,20 +136,22 @@ describe('getVariableTypeSelectOptions', () => {
it('should contain all editable variable types', () => {
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length);
const editableVariables = getEditableVariables();
expect(options).toHaveLength(Object.keys(editableVariables).length);
EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => {
expect(EDITABLE_VARIABLES).toHaveProperty(type);
expect(editableVariables).toHaveProperty(type);
});
});
it('should return an array of selectable values for editable variable types', () => {
const editableVariables = getEditableVariables();
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(8);
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
const variableTypeConfig = EDITABLE_VARIABLES[editableType];
const variableTypeConfig = editableVariables[editableType];
expect(option.value).toBe(editableType);
expect(option.label).toBe(variableTypeConfig.name);
@ -160,21 +162,23 @@ describe('getVariableTypeSelectOptions', () => {
describe('when groupByVariable is disabled', () => {
it('should contain all editable variable types except groupby', () => {
const editableVariables = getEditableVariables();
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length - 1);
expect(options).toHaveLength(Object.keys(editableVariables).length - 1);
EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => {
expect(EDITABLE_VARIABLES).toHaveProperty(type);
expect(editableVariables).toHaveProperty(type);
});
});
it('should return an array of selectable values for editable variable types', () => {
const editableVariables = getEditableVariables();
const options = getVariableTypeSelectOptions();
expect(options).toHaveLength(7);
options.forEach((option, index) => {
const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index];
const variableTypeConfig = EDITABLE_VARIABLES[editableType];
const variableTypeConfig = editableVariables[editableType];
expect(option.value).toBe(editableType);
expect(option.label).toBe(variableTypeConfig.name);
@ -185,11 +189,12 @@ describe('getVariableTypeSelectOptions', () => {
});
describe('getVariableEditor', () => {
const editableVariables = getEditableVariables();
beforeEach(() => {
jest.clearAllMocks();
});
it.each(Object.keys(EDITABLE_VARIABLES) as EditableVariableType[])(
it.each(Object.keys(editableVariables) as EditableVariableType[])(
'should define an editor for variable type "%s"',
(type) => {
const editor = getVariableEditor(type);
@ -212,11 +217,12 @@ describe('getVariableEditor', () => {
});
describe('getVariableScene', () => {
const editableVariables = getEditableVariables();
beforeAll(() => {
setTemplateSrv(templateSrv);
});
it.each(Object.keys(EDITABLE_VARIABLES) as EditableVariableType[])(
it.each(Object.keys(editableVariables) as EditableVariableType[])(
'should define a scene object for every variable type',
(type) => {
const variable = getVariableScene(type, { name: 'foo' });

@ -1,6 +1,7 @@
import { chain } from 'lodash';
import { DataSourceInstanceSettings, SelectableValue } from '@grafana/data';
import { t } from '@grafana/i18n';
import { config, getDataSourceSrv } from '@grafana/runtime';
import {
ConstantVariable,
@ -46,59 +47,81 @@ export function isEditableVariableType(type: VariableType): type is EditableVari
return type !== 'system';
}
export const EDITABLE_VARIABLES: Record<EditableVariableType, EditableVariableConfig> = {
export const getEditableVariables: () => Record<EditableVariableType, EditableVariableConfig> = () => ({
custom: {
name: 'Custom',
description: 'Values are static and defined manually',
name: t('dashboard-scene.get-editable-variables.name.custom', 'Custom'),
description: t(
'dashboard-scene.get-editable-variables.description.values-are-static-and-defined-manually',
'Values are static and defined manually'
),
editor: CustomVariableEditor,
getOptions: getCustomVariableOptions,
},
query: {
name: 'Query',
description: 'Values are fetched from a data source query',
name: t('dashboard-scene.get-editable-variables.name.query', 'Query'),
description: t(
'dashboard-scene.get-editable-variables.description.values-fetched-source-query',
'Values are fetched from a data source query'
),
editor: QueryVariableEditor,
getOptions: getQueryVariableOptions,
},
constant: {
name: 'Constant',
description: 'A hidden constant variable, useful for metric prefixes in dashboards you want to share',
name: t('dashboard-scene.get-editable-variables.name.constant', 'Constant'),
description: t(
'dashboard-scene.get-editable-variables.description.hidden-constant-variable',
'A hidden constant variable, useful for metric prefixes in dashboards you want to share'
),
editor: ConstantVariableEditor,
getOptions: getConstantVariableOptions,
},
interval: {
name: 'Interval',
description: 'Values are timespans, ex 1m, 1h, 1d',
name: t('dashboard-scene.get-editable-variables.name.interval', 'Interval'),
description: t(
'dashboard-scene.get-editable-variables.description.values-timespans',
'Values are timespans, ex 1m, 1h, 1d'
),
editor: IntervalVariableEditor,
getOptions: getIntervalVariableOptions,
},
datasource: {
name: 'Data source',
description: 'Dynamically switch the data source for multiple panels',
name: t('dashboard-scene.get-editable-variables.name.data-source', 'Data source'),
description: t(
'dashboard-scene.get-editable-variables.description.dynamically-switch-source-multiple-panels',
'Dynamically switch the data source for multiple panels'
),
editor: DataSourceVariableEditor,
getOptions: getDataSourceVariableOptions,
},
adhoc: {
name: 'Ad hoc filters',
description: 'Add key/value filters on the fly',
name: t('dashboard-scene.get-editable-variables.name.ad-hoc-filters', 'Ad hoc filters'),
description: t(
'dashboard-scene.get-editable-variables.description.add-keyvalue-filters-on-the-fly',
'Add key/value filters on the fly'
),
editor: AdHocFiltersVariableEditor,
getOptions: getAdHocFilterOptions,
},
groupby: {
name: 'Group by',
description: 'Add keys to group by on the fly',
name: t('dashboard-scene.get-editable-variables.name.group-by', 'Group by'),
description: t('dashboard-scene.get-editable-variables.description.group', 'Add keys to group by on the fly'),
editor: GroupByVariableEditor,
getOptions: getGroupByVariableOptions,
},
textbox: {
name: 'Textbox',
description: 'Users can enter any arbitrary strings in a textbox',
name: t('dashboard-scene.get-editable-variables.name.textbox', 'Textbox'),
description: t(
'dashboard-scene.get-editable-variables.description.users-enter-arbitrary-strings-textbox',
'Users can enter any arbitrary strings in a textbox'
),
editor: TextBoxVariableEditor,
getOptions: getTextBoxVariableOptions,
},
};
});
export function getEditableVariableDefinition(type: string): EditableVariableConfig {
const editableVariable = EDITABLE_VARIABLES[type as EditableVariableType];
const editableVariables = getEditableVariables();
const editableVariable = editableVariables[type as EditableVariableType];
if (!editableVariable) {
throw new Error(`Variable type ${type} not found`);
}
@ -118,10 +141,11 @@ export const EDITABLE_VARIABLES_SELECT_ORDER: EditableVariableType[] = [
];
export function getVariableTypeSelectOptions(): Array<SelectableValue<EditableVariableType>> {
const editableVariables = getEditableVariables();
const results = EDITABLE_VARIABLES_SELECT_ORDER.map((variableType) => ({
label: EDITABLE_VARIABLES[variableType].name,
label: editableVariables[variableType].name,
value: variableType,
description: EDITABLE_VARIABLES[variableType].description,
description: editableVariables[variableType].description,
}));
if (!config.featureToggles.groupByVariable) {
@ -133,7 +157,8 @@ export function getVariableTypeSelectOptions(): Array<SelectableValue<EditableVa
}
export function getVariableEditor(type: EditableVariableType) {
return EDITABLE_VARIABLES[type].editor;
const editableVariables = getEditableVariables();
return editableVariables[type].editor;
}
interface CommonVariableProperties {

@ -241,7 +241,7 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => {
>
<>
<Select
options={panelFilters}
options={getPanelFilters()}
value={panelFilter}
onChange={onFilterTypeChange}
data-testid={selectors.components.Annotations.annotationsTypeInput}
@ -319,20 +319,29 @@ enum PanelFilterType {
ExcludePanels,
}
const panelFilters = [
const getPanelFilters = () => [
{
label: 'All panels',
label: t('dashboard.get-panel-filters.label.all-panels', 'All panels'),
value: PanelFilterType.AllPanels,
description: 'Send the annotation data to all panels that support annotations',
description: t(
'dashboard.get-panel-filters.description.annotation-panels-support-annotations',
'Send the annotation data to all panels that support annotations'
),
},
{
label: 'Selected panels',
label: t('dashboard.get-panel-filters.label.selected-panels', 'Selected panels'),
value: PanelFilterType.IncludePanels,
description: 'Send the annotations to the explicitly listed panels',
description: t(
'dashboard.get-panel-filters.description.annotations-explicitly-listed-panels',
'Send the annotations to the explicitly listed panels'
),
},
{
label: 'All panels except',
label: t('dashboard.get-panel-filters.label.all-panels-except', 'All panels except'),
value: PanelFilterType.ExcludePanels,
description: 'Do not send annotation data to the following panels',
description: t(
'dashboard.get-panel-filters.description.annotation-following-panels',
'Do not send annotation data to the following panels'
),
},
];

@ -1,3 +1,4 @@
import { t } from '@grafana/i18n';
import { getGrafanaSearcher } from 'app/features/search/service/searcher';
class ValidationError extends Error {
@ -13,14 +14,24 @@ export class ValidationSrv {
rootName = 'general';
validateNewDashboardName(folderUID: string, name: string) {
return this.validate(folderUID, name, 'A dashboard or a folder with the same name already exists');
return this.validate(
folderUID,
name,
t(
'manage-dashboards.validation-srv.message-same-name',
'A dashboard or a folder with the same name already exists'
)
);
}
validateNewFolderName(name?: string) {
return this.validate(
this.rootName,
name,
'A folder or dashboard in the general folder with the same name already exists'
t(
'manage-dashboards.validation-srv.message-same-name-general',
'A folder or dashboard in the general folder with the same name already exists'
)
);
}
@ -29,11 +40,20 @@ export class ValidationSrv {
const nameLowerCased = name.toLowerCase();
if (name.length === 0) {
throw new ValidationError('REQUIRED', 'Name is required');
throw new ValidationError(
'REQUIRED',
t('manage-dashboards.validation-srv.message-name-required', 'Name is required')
);
}
if (nameLowerCased === this.rootName) {
throw new ValidationError('EXISTING', 'This is a reserved name and cannot be used for a folder.');
throw new ValidationError(
'EXISTING',
t(
'manage-dashboards.validation-srv.message-reserved-name',
'This is a reserved name and cannot be used for a folder.'
)
);
}
const searcher = getGrafanaSearcher();

@ -3143,6 +3143,16 @@
"info-box-content-2": "Checkout the <2>Annotations documentation</2> for more information.",
"title": "There are no custom annotation queries added yet"
},
"get-annotation-event-names": {
"help": {
"annotation-treated-as-range": "When this field is defined, the annotation will be treated as a range",
"results-split-on-comma": "The results will be split on comma (,)"
},
"placeholder": {
"text-or-the-first-field": "{{defaultField}}, or the first text field",
"time-or-the-first-field": "{{defaultField}}, or the first time field"
}
},
"standard-annotation-query-editor": {
"events-found": "{{numEvents}} events (from {{numFields}} fields)",
"no-events-found": "No events found",
@ -4676,6 +4686,18 @@
"transform-data": "Transform data"
}
},
"get-panel-filters": {
"description": {
"annotation-following-panels": "Do not send annotation data to the following panels",
"annotation-panels-support-annotations": "Send the annotation data to all panels that support annotations",
"annotations-explicitly-listed-panels": "Send the annotations to the explicitly listed panels"
},
"label": {
"all-panels": "All panels",
"all-panels-except": "All panels except",
"selected-panels": "Selected panels"
}
},
"get-panel-frame-category": {
"descriptor": {
"title": {
@ -5581,6 +5603,40 @@
"row-height-options": "Row height options"
}
},
"get-editable-variables": {
"description": {
"add-keyvalue-filters-on-the-fly": "Add key/value filters on the fly",
"dynamically-switch-source-multiple-panels": "Dynamically switch the data source for multiple panels",
"group": "Add keys to group by on the fly",
"hidden-constant-variable": "A hidden constant variable, useful for metric prefixes in dashboards you want to share",
"users-enter-arbitrary-strings-textbox": "Users can enter any arbitrary strings in a textbox",
"values-are-static-and-defined-manually": "Values are static and defined manually",
"values-fetched-source-query": "Values are fetched from a data source query",
"values-timespans": "Values are timespans, ex 1m, 1h, 1d"
},
"name": {
"ad-hoc-filters": "Ad hoc filters",
"constant": "Constant",
"custom": "Custom",
"data-source": "Data source",
"group-by": "Group by",
"interval": "Interval",
"query": "Query",
"textbox": "Textbox"
}
},
"get-panel-filters": {
"description": {
"annotation-following-panels": "Do not send annotation data to the following panels",
"annotation-panels-support-annotations": "Send the annotation data to all panels that support annotations",
"annotations-explicitly-listed-panels": "Send the annotations to the explicitly listed panels"
},
"label": {
"all-panels": "All panels",
"all-panels-except": "All panels except",
"selected-panels": "Selected panels"
}
},
"get-panel-frame-options": {
"descriptor": {
"title": {
@ -5814,7 +5870,8 @@
"label-folder": "Folder",
"render-footer": {
"title-failed-to-save-dashboard": "Failed to save dashboard"
}
},
"required": "Required"
},
"save-dashboard-drawer": {
"tabs": {
@ -8022,6 +8079,7 @@
},
"tags-input": {
"add": "Add",
"placeholder-new-tag": "New tag (enter key to add)",
"remove": "Remove tag: {{name}}"
},
"time-sync-button": {
@ -8968,6 +9026,12 @@
"text": {
"import-dashboard": "Import dashboard"
}
},
"validation-srv": {
"message-name-required": "Name is required",
"message-reserved-name": "This is a reserved name and cannot be used for a folder.",
"message-same-name": "A dashboard or a folder with the same name already exists",
"message-same-name-general": "A folder or dashboard in the general folder with the same name already exists"
}
},
"metric-select": {

Loading…
Cancel
Save