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
This commit is contained in:
@@ -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"
|
||||
|
||||
+3
-1
@@ -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}>
|
||||
|
||||
+5
-3
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
+13
-4
@@ -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 (
|
||||
|
||||
+50
-19
@@ -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'
|
||||
|
||||
+17
-8
@@ -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 {
|
||||
|
||||
+17
-8
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user