From e75fbe10ca3f64eff4bc9f3168ed3a95c93b312e Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:54:15 +0200 Subject: [PATCH] Alerting: Central alert history (part1) (#88593) * WIP * Add barchart panel with scenes * Fix timerange in barchart panel * Refactor: component names * Remove not used css styles and rename panel title * Remove unnecessary HistoryEventsListObject class and update text in labels filter * add padding top for filter * Add translations * update limit labels constant * Update showing state reason * Fix scene object * Address review comments * Update icons * use endpoints instead of the autogenerated hook * Address review comments * Add tooltip for alert name * use private polling interval * fix autogenerated translations * Address pr rewview comments * Address review comments * Update text in placeholder * Rename variable and remove spaces in Trans children --- .betterer.results | 3 +- pkg/services/navtree/navtreeimpl/navtree.go | 10 + public/app/features/alerting/routes.tsx | 13 + .../alerting/unified/api/stateHistoryApi.ts | 2 +- .../unified/components/AlertLabels.tsx | 6 +- .../CentralAlertHistory.tsx | 383 ++++++++++++++++++ .../CentralAlertHistoryPage.tsx | 16 + .../CentralAlertHistoryScene.tsx | 124 ++++++ .../rules/state-history/LokiStateHistory.tsx | 2 +- .../components/rules/state-history/common.ts | 1 + .../alerting/unified/utils/rule-id.ts | 2 +- .../__mocks__/store.navIndex.mock.ts | 7 + public/app/types/unified-alerting-dto.ts | 5 + public/locales/en-US/grafana.json | 30 +- public/locales/pseudo-LOCALE/grafana.json | 30 +- 15 files changed, 615 insertions(+), 19 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistory.tsx create mode 100644 public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx create mode 100644 public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx diff --git a/.betterer.results b/.betterer.results index 33fec39f716..fd64c63eea8 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1646,8 +1646,7 @@ exports[`better eslint`] = { ], "public/app/features/alerting/unified/components/AlertLabels.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/alerting/unified/components/AnnotationDetailsField.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 102e4fe953e..7ed6f2164c0 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -408,6 +408,16 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"}) } + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingCentralAlertHistory) { + alertChildNavs = append(alertChildNavs, &navtree.NavLink{ + Text: "History", + SubTitle: "History of events that were generated by your Grafana-managed alert rules. Silences and Mute timings are ignored.", + Id: "alerts-history", + Url: s.cfg.AppSubURL + "/alerting/history", + Icon: "history", + }) + } + if c.SignedInUser.GetOrgRole() == org.RoleAdmin { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "Settings", Id: "alerting-admin", Url: s.cfg.AppSubURL + "/alerting/admin", diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index a481cade4ab..23031c03dc4 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -166,6 +166,19 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] { () => import(/* webpackChunkName: "AlertGroups" */ 'app/features/alerting/unified/AlertGroups') ), }, + { + path: '/alerting/history/', + roles: evaluateAccess([ + AccessControlAction.AlertingInstanceRead, + AccessControlAction.AlertingInstancesExternalRead, + ]), + component: importAlertingComponent( + () => + import( + /* webpackChunkName: "HistoryPage" */ 'app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage' + ) + ), + }, { path: '/alerting/new/:type?', pageClass: 'page-alerting', diff --git a/public/app/features/alerting/unified/api/stateHistoryApi.ts b/public/app/features/alerting/unified/api/stateHistoryApi.ts index 3a78bcd8425..2f82fe6ef2d 100644 --- a/public/app/features/alerting/unified/api/stateHistoryApi.ts +++ b/public/app/features/alerting/unified/api/stateHistoryApi.ts @@ -4,7 +4,7 @@ import { alertingApi } from './alertingApi'; export const stateHistoryApi = alertingApi.injectEndpoints({ endpoints: (build) => ({ - getRuleHistory: build.query({ + getRuleHistory: build.query({ query: ({ ruleUid, from, to, limit = 100 }) => ({ url: '/api/v1/rules/history', params: { ruleUID: ruleUid, from, to, limit }, diff --git a/public/app/features/alerting/unified/components/AlertLabels.tsx b/public/app/features/alerting/unified/components/AlertLabels.tsx index 1e01be64cf4..40801357372 100644 --- a/public/app/features/alerting/unified/components/AlertLabels.tsx +++ b/public/app/features/alerting/unified/components/AlertLabels.tsx @@ -5,6 +5,7 @@ import React, { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, getTagColorsFromName, useStyles2 } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { isPrivateLabel } from '../utils/labels'; @@ -28,6 +29,7 @@ export const AlertLabels = ({ labels, commonLabels = {}, size }: Props) => { const commonLabelsCount = Object.keys(commonLabels).length; const hasCommonLabels = commonLabelsCount > 0; + const tooltip = t('alert-labels.button.show.tooltip', 'Show common labels'); return (
@@ -39,7 +41,7 @@ export const AlertLabels = ({ labels, commonLabels = {}, size }: Props) => { variant="secondary" fill="text" onClick={() => setShowCommonLabels(true)} - tooltip="Show common labels" + tooltip={tooltip} tooltipPlacement="top" size="sm" > @@ -54,7 +56,7 @@ export const AlertLabels = ({ labels, commonLabels = {}, size }: Props) => { tooltipPlacement="top" size="sm" > - Hide common labels + Hide common labels )}
diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistory.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistory.tsx new file mode 100644 index 00000000000..e01bb4f6a02 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistory.tsx @@ -0,0 +1,383 @@ +import { css } from '@emotion/css'; +import React, { useCallback, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useMeasure } from 'react-use'; + +import { GrafanaTheme2, TimeRange } from '@grafana/data'; +import { isFetchError } from '@grafana/runtime'; +import { SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes'; +import { + Alert, + Button, + Field, + Icon, + Input, + Label, + LoadingBar, + Stack, + Text, + Tooltip, + useStyles2, + withErrorBoundary, +} from '@grafana/ui'; +import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; +import { Trans, t } from 'app/core/internationalization'; +import { + GrafanaAlertStateWithReason, + isAlertStateWithReason, + isGrafanaAlertState, + mapStateWithReasonToBaseState, + mapStateWithReasonToReason, +} from 'app/types/unified-alerting-dto'; + +import { stateHistoryApi } from '../../../api/stateHistoryApi'; +import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; +import { stringifyErrorLike } from '../../../utils/misc'; +import { hashLabelsOrAnnotations } from '../../../utils/rule-id'; +import { AlertLabels } from '../../AlertLabels'; +import { CollapseToggle } from '../../CollapseToggle'; +import { LogRecord } from '../state-history/common'; +import { useRuleHistoryRecords } from '../state-history/useRuleHistoryRecords'; + +const LIMIT_EVENTS = 250; + +const HistoryEventsList = ({ timeRange }: { timeRange?: TimeRange }) => { + const styles = useStyles2(getStyles); + + // Filter state + const [eventsFilter, setEventsFilter] = useState(''); + // form for filter fields + const { register, handleSubmit, reset } = useForm({ defaultValues: { query: '' } }); // form for search field + const from = timeRange?.from.unix(); + const to = timeRange?.to.unix(); + const onFilterCleared = useCallback(() => { + setEventsFilter(''); + reset(); + }, [setEventsFilter, reset]); + + const { + data: stateHistory, + isLoading, + isError, + error, + } = stateHistoryApi.endpoints.getRuleHistory.useQuery( + { + from: from, + to: to, + limit: LIMIT_EVENTS, + }, + { + refetchOnFocus: true, + refetchOnReconnect: true, + } + ); + + const { historyRecords } = useRuleHistoryRecords(stateHistory, eventsFilter); + + if (isError) { + return ; + } + + return ( + +
+
setEventsFilter(data.query))}> + + + +
+ + +
+ ); +}; + +// todo: this function has been copied from RuleList.v2.tsx, should be moved to a shared location +const LoadingIndicator = ({ visible = false }) => { + const [measureRef, { width }] = useMeasure(); + return
{visible && }
; +}; + +interface HistoryLogEventsProps { + logRecords: LogRecord[]; +} +function HistoryLogEvents({ logRecords }: HistoryLogEventsProps) { + // display log records + return ( +
    + {logRecords.map((record) => { + return ; + })} +
+ ); +} + +interface HistoryErrorMessageProps { + error: unknown; +} + +function HistoryErrorMessage({ error }: HistoryErrorMessageProps) { + if (isFetchError(error) && error.status === 404) { + return ; + } + const title = t('central-alert-history.error', 'Something went wrong loading the alert state history'); + + return {stringifyErrorLike(error)}; +} + +interface SearchFieldInputProps { + showClearFilterSuffix: boolean; + onClearFilterClick: () => void; +} +const SearchFieldInput = React.forwardRef( + ({ showClearFilterSuffix, onClearFilterClick, ...rest }: SearchFieldInputProps, ref) => { + const placeholder = t('central-alert-history.filter.placeholder', 'Filter events in the list with labels'); + return ( + + + + Filter events + + + + } + > + } + suffix={ + showClearFilterSuffix && ( + + ) + } + placeholder={placeholder} + ref={ref} + {...rest} + /> + + ); + } +); + +SearchFieldInput.displayName = 'SearchFieldInput'; + +function EventRow({ record }: { record: LogRecord }) { + const styles = useStyles2(getStyles); + const [isCollapsed, setIsCollapsed] = useState(true); + return ( +
+
+ + +
+ +
+
+ +
+
+ {record.line.labels ? : null} +
+
+ +
+
+
+
+ ); +} + +function AlertRuleName({ labels, ruleUID }: { labels: Record; ruleUID?: string }) { + const styles = useStyles2(getStyles); + const alertRuleName = labels['alertname']; + if (!ruleUID) { + return {alertRuleName}; + } + return ( + + + {alertRuleName} + + + ); +} + +interface EventTransitionProps { + previous: GrafanaAlertStateWithReason; + current: GrafanaAlertStateWithReason; +} +function EventTransition({ previous, current }: EventTransitionProps) { + return ( + + + + + + ); +} + +function EventState({ state }: { state: GrafanaAlertStateWithReason }) { + const styles = useStyles2(getStyles); + + if (!isGrafanaAlertState(state) && !isAlertStateWithReason(state)) { + return ( + + + + ); + } + const baseState = mapStateWithReasonToBaseState(state); + const reason = mapStateWithReasonToReason(state); + + switch (baseState) { + case 'Normal': + return ( + + + + ); + case 'Alerting': + return ( + + + + ); + case 'NoData': //todo:change icon + return ( + + + {/* no idea which icon to use */} + + ); + case 'Error': + return ( + + + + ); + + case 'Pending': + return ( + + + + ); + default: + return ; + } +} + +interface TimestampProps { + time: number; // epoch timestamp +} + +const Timestamp = ({ time }: TimestampProps) => { + const dateTime = new Date(time); + const formattedDate = dateTime.toLocaleString('en-US', { + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }); + + return ( + + {formattedDate} + + ); +}; + +export default withErrorBoundary(HistoryEventsList, { style: 'page' }); + +export const getStyles = (theme: GrafanaTheme2) => { + return { + header: css({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + padding: `${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)} 0`, + flexWrap: 'nowrap', + borderBottom: `1px solid ${theme.colors.border.weak}`, + + '&:hover': { + backgroundColor: theme.components.table.rowHoverBackground, + }, + }), + + collapseToggle: css({ + background: 'none', + border: 'none', + marginTop: `-${theme.spacing(1)}`, + marginBottom: `-${theme.spacing(1)}`, + + svg: { + marginBottom: 0, + }, + }), + normalColor: css({ + fill: theme.colors.success.text, + }), + warningColor: css({ + fill: theme.colors.warning.text, + }), + alertingColor: css({ + fill: theme.colors.error.text, + }), + timeCol: css({ + width: '150px', + }), + transitionCol: css({ + width: '80px', + }), + alertNameCol: css({ + width: '300px', + }), + labelsCol: css({ + display: 'flex', + overflow: 'hidden', + alignItems: 'center', + paddingRight: theme.spacing(2), + flex: 1, + }), + alertName: css({ + whiteSpace: 'nowrap', + cursor: 'pointer', + overflow: 'hidden', + textOverflow: 'ellipsis', + display: 'block', + color: theme.colors.text.link, + }), + labelsFilter: css({ + width: '100%', + paddingTop: theme.spacing(4), + }), + }; +}; + +export class HistoryEventsListObject extends SceneObjectBase { + public static Component = HistoryEventsListObjectRenderer; +} + +export function HistoryEventsListObjectRenderer({ model }: SceneComponentProps) { + const { value: timeRange } = sceneGraph.getTimeRange(model).useState(); // get time range from scene graph + + return ; +} diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx new file mode 100644 index 00000000000..0ab57f508d4 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { withErrorBoundary } from '@grafana/ui'; + +import { AlertingPageWrapper } from '../../AlertingPageWrapper'; + +import { CentralAlertHistoryScene } from './CentralAlertHistoryScene'; + +const HistoryPage = () => { + return ( + + + + ); +}; +export default withErrorBoundary(HistoryPage, { style: 'page' }); diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx new file mode 100644 index 00000000000..0cd75c05a4b --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryScene.tsx @@ -0,0 +1,124 @@ +import React from 'react'; + +import { getDataSourceSrv } from '@grafana/runtime'; +import { + EmbeddedScene, + PanelBuilders, + SceneControlsSpacer, + SceneFlexItem, + SceneFlexLayout, + SceneQueryRunner, + SceneReactObject, + SceneRefreshPicker, + SceneTimePicker, +} from '@grafana/scenes'; +import { + GraphDrawStyle, + GraphGradientMode, + LegendDisplayMode, + LineInterpolation, + ScaleDistribution, + StackingMode, + TooltipDisplayMode, + VisibilityMode, +} from '@grafana/schema/dist/esm/index'; + +import { DataSourceInformation, PANEL_STYLES } from '../../../home/Insights'; +import { SectionSubheader } from '../../../insights/SectionSubheader'; + +import { HistoryEventsListObjectRenderer } from './CentralAlertHistory'; + +export const CentralAlertHistoryScene = () => { + const dataSourceSrv = getDataSourceSrv(); + const alertStateHistoryDatasource: DataSourceInformation = { + type: 'loki', + uid: 'grafanacloud-alert-state-history', + settings: undefined, + }; + + alertStateHistoryDatasource.settings = dataSourceSrv.getInstanceSettings(alertStateHistoryDatasource.uid); + + const scene = new EmbeddedScene({ + controls: [new SceneControlsSpacer(), new SceneTimePicker({}), new SceneRefreshPicker({})], + body: new SceneFlexLayout({ + direction: 'column', + children: [ + new SceneFlexItem({ + ySizing: 'content', + body: getEventsSceneObject(alertStateHistoryDatasource), + }), + new SceneFlexItem({ + body: new SceneReactObject({ + component: HistoryEventsListObjectRenderer, + }), + }), + ], + }), + }); + + return ; +}; + +function getEventsSceneObject(ashDs: DataSourceInformation) { + return new EmbeddedScene({ + controls: [ + new SceneReactObject({ + component: SectionSubheader, + }), + ], + body: new SceneFlexLayout({ + direction: 'column', + children: [ + new SceneFlexItem({ + ySizing: 'content', + body: new SceneFlexLayout({ + children: [getEventsScenesFlexItem(ashDs)], + }), + }), + ], + }), + }); +} + +function getSceneQuery(datasource: DataSourceInformation) { + const query = new SceneQueryRunner({ + datasource, + queries: [ + { + refId: 'A', + expr: 'count_over_time({from="state-history"} |= `` [$__auto])', + queryType: 'range', + step: '10s', + }, + ], + }); + return query; +} + +export function getEventsScenesFlexItem(datasource: DataSourceInformation) { + return new SceneFlexItem({ + ...PANEL_STYLES, + body: PanelBuilders.timeseries() + .setTitle('Events') + .setDescription('Alert events during the period of time.') + .setData(getSceneQuery(datasource)) + .setColor({ mode: 'continuous-BlPu' }) + .setCustomFieldConfig('fillOpacity', 100) + .setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars) + .setCustomFieldConfig('lineInterpolation', LineInterpolation.Linear) + .setCustomFieldConfig('lineWidth', 1) + .setCustomFieldConfig('barAlignment', 0) + .setCustomFieldConfig('spanNulls', false) + .setCustomFieldConfig('insertNulls', false) + .setCustomFieldConfig('showPoints', VisibilityMode.Auto) + .setCustomFieldConfig('pointSize', 5) + .setCustomFieldConfig('stacking', { mode: StackingMode.None, group: 'A' }) + .setCustomFieldConfig('gradientMode', GraphGradientMode.Hue) + .setCustomFieldConfig('scaleDistribution', { type: ScaleDistribution.Linear }) + .setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden }) + .setOption('tooltip', { mode: TooltipDisplayMode.Single }) + + .setNoValue('No events found') + .build(), + }); +} diff --git a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx index 062ff5b1820..9c9ea58eabe 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { DataFrame, dateTime, GrafanaTheme2, TimeRange } from '@grafana/data'; -import { Alert, Button, Field, Icon, Input, Label, Tooltip, useStyles2, Stack } from '@grafana/ui'; +import { Alert, Button, Field, Icon, Input, Label, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { stateHistoryApi } from '../../../api/stateHistoryApi'; import { combineMatcherStrings } from '../../../utils/alertmanager'; diff --git a/public/app/features/alerting/unified/components/rules/state-history/common.ts b/public/app/features/alerting/unified/components/rules/state-history/common.ts index f797fa1f8a5..d6d924dccdf 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/common.ts +++ b/public/app/features/alerting/unified/components/rules/state-history/common.ts @@ -7,6 +7,7 @@ export interface Line { current: GrafanaAlertStateWithReason; values?: Record; labels?: Record; + ruleUID?: string; } export interface LogRecord { diff --git a/public/app/features/alerting/unified/utils/rule-id.ts b/public/app/features/alerting/unified/utils/rule-id.ts index 3e6ebad8c64..b3785ffcaf5 100644 --- a/public/app/features/alerting/unified/utils/rule-id.ts +++ b/public/app/features/alerting/unified/utils/rule-id.ts @@ -240,7 +240,7 @@ export function hashRule(rule: Rule): string { throw new Error('only recording and alerting rules can be hashed'); } -function hashLabelsOrAnnotations(item: Labels | Annotations | undefined): string { +export function hashLabelsOrAnnotations(item: Labels | Annotations | undefined): string { return JSON.stringify(Object.entries(item || {}).sort((a, b) => a[0].localeCompare(b[0]))); } diff --git a/public/app/features/connections/__mocks__/store.navIndex.mock.ts b/public/app/features/connections/__mocks__/store.navIndex.mock.ts index 217f9891655..f135bf3cf83 100644 --- a/public/app/features/connections/__mocks__/store.navIndex.mock.ts +++ b/public/app/features/connections/__mocks__/store.navIndex.mock.ts @@ -149,6 +149,13 @@ export const navIndex: NavIndex = { icon: 'layer-group', url: '/alerting/groups', }, + { + id: 'history', + text: 'History', + subTitle: 'Alert state history', + icon: 'history', + url: '/alerting/history', + }, { id: 'alerting-admin', text: 'Settings', diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index b226871d30b..c47780b31c3 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -42,6 +42,11 @@ export function isAlertStateWithReason( return state !== null && state !== undefined && !propAlertingRuleStateValues.includes(state); } +export function mapStateWithReasonToReason(state: GrafanaAlertStateWithReason): string { + const match = state.match(/\((.*?)\)/); + return match ? match[1] : ''; +} + export function mapStateWithReasonToBaseState( state: GrafanaAlertStateWithReason | PromAlertingRuleState ): GrafanaAlertState | PromAlertingRuleState { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index c83965e3c5a..36fb784122a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -25,6 +25,14 @@ "user": "User" } }, + "alert-labels": { + "button": { + "hide": "Hide common labels", + "show": { + "tooltip": "Show common labels" + } + } + }, "alert-rule-form": { "evaluation-behaviour": { "description": { @@ -84,15 +92,15 @@ }, "counts": { "alertRule_one": "{{count}} alert rule", - "alertRule_other": "{{count}} alert rules", + "alertRule_other": "{{count}} alert rule", "dashboard_one": "{{count}} dashboard", - "dashboard_other": "{{count}} dashboards", + "dashboard_other": "{{count}} dashboard", "folder_one": "{{count}} folder", - "folder_other": "{{count}} folders", + "folder_other": "{{count}} folder", "libraryPanel_one": "{{count}} library panel", - "libraryPanel_other": "{{count}} library panels", + "libraryPanel_other": "{{count}} library panel", "total_one": "{{count}} item", - "total_other": "{{count}} items" + "total_other": "{{count}} item" }, "dashboards-tree": { "collapse-folder-button": "Collapse folder {{title}}", @@ -138,6 +146,16 @@ "text": "No results found for your query" } }, + "central-alert-history": { + "error": "Something went wrong loading the alert state history", + "filter": { + "button": { + "clear": "Clear" + }, + "label": "Filter events", + "placeholder": "Filter events in the list with labels" + } + }, "clipboard-button": { "inline-toast": { "success": "Copied" @@ -758,7 +776,7 @@ }, "modal": { "body_one": "This panel is being used in {{count}} dashboard. Please choose which dashboard to view the panel in:", - "body_other": "This panel is being used in {{count}} dashboards. Please choose which dashboard to view the panel in:", + "body_other": "This panel is being used in {{count}} dashboard. Please choose which dashboard to view the panel in:", "button-cancel": "Cancel", "button-view-panel1": "View panel in {{label}}...", "button-view-panel2": "View panel in dashboard...", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index f99ab49211d..bfe2a139e08 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -25,6 +25,14 @@ "user": "Ůşęř" } }, + "alert-labels": { + "button": { + "hide": "Ħįđę čőmmőʼn ľäþęľş", + "show": { + "tooltip": "Ŝĥőŵ čőmmőʼn ľäþęľş" + } + } + }, "alert-rule-form": { "evaluation-behaviour": { "description": { @@ -84,15 +92,15 @@ }, "counts": { "alertRule_one": "{{count}} äľęřŧ řūľę", - "alertRule_other": "{{count}} äľęřŧ řūľęş", + "alertRule_other": "{{count}} äľęřŧ řūľę", "dashboard_one": "{{count}} đäşĥþőäřđ", - "dashboard_other": "{{count}} đäşĥþőäřđş", + "dashboard_other": "{{count}} đäşĥþőäřđ", "folder_one": "{{count}} ƒőľđęř", - "folder_other": "{{count}} ƒőľđęřş", + "folder_other": "{{count}} ƒőľđęř", "libraryPanel_one": "{{count}} ľįþřäřy päʼnęľ", - "libraryPanel_other": "{{count}} ľįþřäřy päʼnęľş", + "libraryPanel_other": "{{count}} ľįþřäřy päʼnęľ", "total_one": "{{count}} įŧęm", - "total_other": "{{count}} įŧęmş" + "total_other": "{{count}} įŧęm" }, "dashboards-tree": { "collapse-folder-button": "Cőľľäpşę ƒőľđęř {{title}}", @@ -138,6 +146,16 @@ "text": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy" } }, + "central-alert-history": { + "error": "Ŝőmęŧĥįʼnģ ŵęʼnŧ ŵřőʼnģ ľőäđįʼnģ ŧĥę äľęřŧ şŧäŧę ĥįşŧőřy", + "filter": { + "button": { + "clear": "Cľęäř" + }, + "label": "Fįľŧęř ęvęʼnŧş", + "placeholder": "Fįľŧęř ęvęʼnŧş įʼn ŧĥę ľįşŧ ŵįŧĥ ľäþęľş" + } + }, "clipboard-button": { "inline-toast": { "success": "Cőpįęđ" @@ -758,7 +776,7 @@ }, "modal": { "body_one": "Ŧĥįş päʼnęľ įş þęįʼnģ ūşęđ įʼn {{count}} đäşĥþőäřđ. Pľęäşę čĥőőşę ŵĥįčĥ đäşĥþőäřđ ŧő vįęŵ ŧĥę päʼnęľ įʼn:", - "body_other": "Ŧĥįş päʼnęľ įş þęįʼnģ ūşęđ įʼn {{count}} đäşĥþőäřđş. Pľęäşę čĥőőşę ŵĥįčĥ đäşĥþőäřđ ŧő vįęŵ ŧĥę päʼnęľ įʼn:", + "body_other": "Ŧĥįş päʼnęľ įş þęįʼnģ ūşęđ įʼn {{count}} đäşĥþőäřđ. Pľęäşę čĥőőşę ŵĥįčĥ đäşĥþőäřđ ŧő vįęŵ ŧĥę päʼnęľ įʼn:", "button-cancel": "Cäʼnčęľ", "button-view-panel1": "Vįęŵ päʼnęľ įʼn {{label}}...", "button-view-panel2": "Vįęŵ päʼnęľ įʼn đäşĥþőäřđ...",