diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
index 54d22db90ac..3d46e0873e7 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
-import { useEffect, useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { FormProvider, SubmitErrorHandler, UseFormWatch, useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom-v5-compat';
@@ -22,7 +22,6 @@ import {
isGrafanaRulerRulePaused,
isRecordingRuleByType,
} from 'app/features/alerting/unified/utils/rules';
-import { isExpressionQuery } from 'app/features/expressions/guards';
import { RuleGroupIdentifier, RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting';
import { PostableRuleGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto';
@@ -40,24 +39,21 @@ import { shouldUsePrometheusRulesPrimary } from '../../../featureToggles';
import { useDeleteRuleFromGroup } from '../../../hooks/ruleGroup/useDeleteRuleFromGroup';
import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup';
import { useReturnTo } from '../../../hooks/useReturnTo';
-import { useURLSearchParams } from '../../../hooks/useURLSearchParams';
-import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
-import { DataSourceType } from '../../../utils/datasource';
import {
- DEFAULT_GROUP_EVALUATION_INTERVAL,
+ defaultFormValuesForRuleType,
+ formValuesFromExistingRule,
+ formValuesFromPrefill,
+ translateRouteParamToRuleType,
+} from '../../../rule-editor/formDefaults';
+import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
+import {
MANUAL_ROUTING_KEY,
SIMPLIFIED_QUERY_EDITOR_KEY,
- formValuesFromExistingRule,
formValuesToRulerGrafanaRuleDTO,
formValuesToRulerRuleDTO,
- getDefaultFormValues,
- getDefaultQueries,
- ignoreHiddenQueries,
- normalizeDefaultAnnotations,
} from '../../../utils/rule-form';
import * as ruleId from '../../../utils/rule-id';
import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id';
-import { isGrafanaRecordingRuleByType } from '../../../utils/rules';
import { createRelativeUrl } from '../../../utils/url';
import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter';
import { AlertRuleNameAndMetric } from '../AlertRuleNameInput';
@@ -68,12 +64,7 @@ import { GrafanaFolderAndLabelsStep } from '../GrafanaFolderAndLabelsStep';
import { NotificationsStep } from '../NotificationsStep';
import { RecordingRulesNameSpaceAndGroupStep } from '../RecordingRulesNameSpaceAndGroupStep';
import { RuleInspector } from '../RuleInspector';
-import {
- QueryAndExpressionsStep,
- areQueriesTransformableToSimpleCondition,
- isExpressionQueryInAlert,
-} from '../query-and-alert-condition/QueryAndExpressionsStep';
-import { translateRouteParamToRuleType } from '../util';
+import { QueryAndExpressionsStep } from '../query-and-alert-condition/QueryAndExpressionsStep';
type Props = {
existing?: RuleWithLocation;
@@ -85,9 +76,7 @@ const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary();
export const AlertRuleForm = ({ existing, prefill }: Props) => {
const styles = useStyles2(getStyles);
const notifyApp = useAppNotification();
- const [queryParams] = useURLSearchParams();
const [showEditYaml, setShowEditYaml] = useState(false);
- const [evaluateEvery, setEvaluateEvery] = useState(existing?.group.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL);
const [deleteRuleFromGroup] = useDeleteRuleFromGroup();
const [addRuleToRuleGroup] = useAddRuleToRuleGroup();
@@ -110,19 +99,10 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
return formValuesFromPrefill(prefill);
}
- if (queryParams.has('defaults')) {
- return formValuesFromQueryParams(queryParams.get('defaults') ?? '', ruleType);
- }
const defaultRuleType = ruleType || RuleFormType.grafana;
- return {
- ...getDefaultFormValues(),
- condition: 'C',
- queries: getDefaultQueries(isGrafanaRecordingRuleByType(defaultRuleType)),
- type: defaultRuleType,
- evaluateEvery: evaluateEvery,
- };
- }, [existing, prefill, queryParams, evaluateEvery, ruleType]);
+ return defaultFormValuesForRuleType(defaultRuleType);
+ }, [existing, prefill, ruleType]);
const formAPI = useForm
({
mode: 'onSubmit',
@@ -151,6 +131,8 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
// @todo why is error not propagated to form?
const submit = async (values: RuleFormValues, exitOnSave: boolean) => {
+ const { type, evaluateEvery } = values;
+
if (conditionErrorMsg !== '') {
notifyApp.error(conditionErrorMsg);
if (!existing && grafanaTypeRule) {
@@ -160,7 +142,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
return;
}
- trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type });
+ trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: type });
const ruleDefinition = grafanaTypeRule ? formValuesToRulerGrafanaRuleDTO(values) : formValuesToRulerRuleDTO(values);
@@ -206,12 +188,11 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
const deleteRule = async () => {
if (existing) {
- const returnTo = queryParams.get('returnTo') || '/alerting/list';
const ruleGroupIdentifier = getRuleGroupLocationFromRuleWithLocation(existing);
const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule);
await deleteRuleFromGroup.execute(ruleGroupIdentifier, ruleIdentifier);
- locationService.replace(returnTo);
+ locationService.replace(returnTo ?? '/alerting/list');
}
};
@@ -236,9 +217,6 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
locationService.getHistory().goBack();
};
- const evaluateEveryInForm = watch('evaluateEvery');
- useEffect(() => setEvaluateEvery(evaluateEveryInForm), [evaluateEveryInForm]);
-
const actionButtons = (
{existing && (
@@ -314,12 +292,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
{/* Step 4 & 5 & 6*/}
{isGrafanaManagedRuleByType(type) && (
-
+
)}
{/* Notifications step*/}
@@ -379,101 +352,17 @@ const isCortexLokiOrRecordingRule = (watch: UseFormWatch) => {
return (ruleType === RuleFormType.cloudAlerting || ruleType === RuleFormType.cloudRecording) && dataSourceName !== '';
};
-function formValuesFromQueryParams(ruleDefinition: string, type: RuleFormType): RuleFormValues {
- let ruleFromQueryParams: Partial;
-
- try {
- ruleFromQueryParams = JSON.parse(ruleDefinition);
- } catch (err) {
- return {
- ...getDefaultFormValues(),
- queries: getDefaultQueries(),
- };
- }
-
- return setQueryEditorSettings(
- setInstantOrRange(
- ignoreHiddenQueries({
- ...getDefaultFormValues(),
- ...ruleFromQueryParams,
- annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []),
- queries: ruleFromQueryParams.queries ?? getDefaultQueries(),
- type: type || RuleFormType.grafana,
- evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
- })
- )
- );
-}
-
-function formValuesFromPrefill(rule: Partial): RuleFormValues {
- return ignoreHiddenQueries({
- ...getDefaultFormValues(),
- ...rule,
- });
-}
-
-function setQueryEditorSettings(values: RuleFormValues): RuleFormValues {
- const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false;
-
- if (!isQuerySwitchModeEnabled) {
- return {
- ...values,
- editorSettings: {
- simplifiedQueryEditor: false,
- simplifiedNotificationEditor: true, // actually it doesn't matter in this case
- },
- };
- }
-
- // data queries only
- const dataQueries = values.queries.filter((query) => !isExpressionQuery(query.model));
-
- // expression queries only
- const expressionQueries = values.queries.filter((query) => isExpressionQueryInAlert(query));
-
- const queryParamsAreTransformable = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries);
- return {
- ...values,
- editorSettings: {
- simplifiedQueryEditor: queryParamsAreTransformable,
- simplifiedNotificationEditor: true,
- },
- };
-}
-
-function setInstantOrRange(values: RuleFormValues): RuleFormValues {
- return {
- ...values,
- queries: values.queries?.map((query) => {
- if (isExpressionQuery(query.model)) {
- return query;
- }
- // data query
- const defaultToInstant =
- query.model.datasource?.type === DataSourceType.Loki ||
- query.model.datasource?.type === DataSourceType.Prometheus;
- const isInstant =
- 'instant' in query.model && query.model.instant !== undefined ? query.model.instant : defaultToInstant;
- return {
- ...query,
- model: {
- ...query.model,
- instant: isInstant,
- range: !isInstant, // we cannot have both instant and range queries in alerting
- },
- };
- }),
- };
-}
-
function storeInLocalStorageValues(values: RuleFormValues) {
- if (values.manualRouting) {
+ const { manualRouting, editorSettings } = values;
+
+ if (manualRouting) {
localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
} else {
localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
}
- if (values.editorSettings) {
- if (values.editorSettings.simplifiedQueryEditor) {
+
+ if (editorSettings) {
+ if (editorSettings.simplifiedQueryEditor) {
localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'true');
} else {
localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'false');
diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx
index 38598f08784..72093ecc490 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx
@@ -16,14 +16,10 @@ import { alertRuleApi } from '../../../api/alertRuleApi';
import { fetchRulerRulesGroup } from '../../../api/ruler';
import { useDataSourceFeatures } from '../../../hooks/useCombinedRule';
import { useReturnTo } from '../../../hooks/useReturnTo';
+import { DEFAULT_GROUP_EVALUATION_INTERVAL, getDefaultFormValues } from '../../../rule-editor/formDefaults';
import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource';
-import {
- DEFAULT_GROUP_EVALUATION_INTERVAL,
- formValuesToRulerGrafanaRuleDTO,
- getDefaultFormValues,
- getDefaultQueries,
-} from '../../../utils/rule-form';
+import { formValuesToRulerGrafanaRuleDTO, getDefaultQueries } from '../../../utils/rule-form';
import { isGrafanaRulerRule } from '../../../utils/rules';
import { FileExportPreview } from '../../export/FileExportPreview';
import { GrafanaExportDrawer } from '../../export/GrafanaExportDrawer';
@@ -64,9 +60,7 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor
const { returnTo } = useReturnTo('/alerting/list');
const [exportData, setExportData] = useState(undefined);
-
const [conditionErrorMsg, setConditionErrorMsg] = useState('');
- const [evaluateEvery, setEvaluateEvery] = useState(ruleForm?.evaluateEvery ?? DEFAULT_GROUP_EVALUATION_INTERVAL);
const onInvalid = (): void => {
notifyApp.error('There are errors in the form. Please correct them and try again!');
@@ -112,12 +106,7 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor
{/* Step 4 & 5 */}
-
+
{/* Notifications step*/}
{/* Annotations only for cloud and Grafana */}
diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/getPayloadToExport.test.ts b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/getPayloadToExport.test.ts
index 6fc5ba37a35..2a1e67855e3 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/getPayloadToExport.test.ts
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/getPayloadToExport.test.ts
@@ -1,9 +1,9 @@
import { RulerRuleDTO, RulerRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { mockRulerGrafanaRecordingRule, mockRulerGrafanaRule } from '../../../mocks';
+import { getDefaultFormValues } from '../../../rule-editor/formDefaults';
import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
import { Annotation } from '../../../utils/constants';
-import { getDefaultFormValues } from '../../../utils/rule-form';
import { getPayloadToExport } from './ModifyExportRuleForm';
diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx
index 11b3a0f5dfc..116374475da 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx
@@ -106,8 +106,10 @@ describe('Can create a new grafana managed alert using simplified routing', () =
it('simplified routing is not available when Grafana AM is not enabled', async () => {
setAlertmanagerChoices(AlertmanagerChoice.External, 1);
- renderRuleEditor();
+ const { user } = renderRuleEditor();
+ // Just to make sure all dropdowns have been loaded
+ await selectFolderAndGroup(user);
await waitFor(() => expect(ui.inputs.simplifiedRouting.contactPointRouting.query()).not.toBeInTheDocument());
});
@@ -147,6 +149,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
expect(await screen.findByText('Email')).toBeInTheDocument();
});
});
+
describe('switch modes enabled', () => {
testWithFeatureToggles(['alertingQueryAndExpressionsStepMode', 'alertingNotificationsStepMode']);
@@ -168,6 +171,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
it('can create the new grafana-managed rule with advanced modes', async () => {
const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/'));
@@ -185,6 +189,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
it('can create the new grafana-managed rule with only notifications step advanced mode', async () => {
const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/'));
@@ -202,6 +207,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
it('can create the new grafana-managed rule with only query step advanced mode', async () => {
const contactPointName = 'lotsa-emails';
const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/'));
@@ -221,6 +227,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
it('switch modes are intiallized depending on the local storage - 1', async () => {
localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'false');
localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
@@ -231,6 +238,7 @@ describe('Can create a new grafana managed alert using simplified routing', () =
expect(ui.inputs.switchModeAdvanced(GrafanaRuleFormStep.Query).get()).toBeInTheDocument();
expect(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Notification).get()).toBeInTheDocument();
});
+
it('switch modes are intiallized depending on the local storage - 2', async () => {
localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'true');
localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx
index 752d9e71101..a0983cba07f 100644
--- a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx
@@ -136,6 +136,7 @@ describe('LabelsField with suggestions', () => {
expect(screen.getByTestId('labelsInSubform-key-2')).toHaveTextContent('key3');
expect(screen.getByTestId('labelsInSubform-value-2')).toHaveTextContent('value3');
});
+
it('Should be able to write new keys and values using the dropdowns, case sensitive', async () => {
const { user } = await renderLabelsWithSuggestions();
diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx
index f43e3a14b67..2ecd8131b3b 100644
--- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx
@@ -23,16 +23,14 @@ import {
import { Text } from '@grafana/ui/src/components/Text/Text';
import { Trans, t } from 'app/core/internationalization';
import { isExpressionQuery } from 'app/features/expressions/guards';
-import {
- ExpressionDatasourceUID,
- ExpressionQuery,
- ExpressionQueryType,
- ReducerMode,
- expressionTypes,
-} from 'app/features/expressions/types';
-import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
+import { ExpressionDatasourceUID, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types';
+import { AlertQuery } from 'app/types/unified-alerting-dto';
import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler';
+import {
+ areQueriesTransformableToSimpleCondition,
+ isExpressionQueryInAlert,
+} from '../../../rule-editor/formProcessing';
import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource';
import { PromOrLokiQuery, isPromOrLokiQuery } from '../../../utils/rule-form';
@@ -76,49 +74,6 @@ import {
import { useAdvancedMode } from './useAdvancedMode';
import { useAlertQueryRunner } from './useAlertQueryRunner';
-export function areQueriesTransformableToSimpleCondition(
- dataQueries: Array>,
- expressionQueries: Array>
-) {
- if (dataQueries.length !== 1) {
- return false;
- }
- const singleReduceExpressionInInstantQuery =
- 'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1;
-
- if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) {
- return false;
- }
-
- const query = dataQueries[0];
-
- if (query.refId !== SimpleConditionIdentifier.queryId) {
- return false;
- }
-
- const reduceExpressionIndex = expressionQueries.findIndex(
- (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId
- );
- const reduceExpression = expressionQueries.at(reduceExpressionIndex);
- const reduceOk =
- reduceExpression &&
- reduceExpressionIndex === 0 &&
- (reduceExpression.model.settings?.mode === ReducerMode.Strict ||
- reduceExpression.model.settings?.mode === undefined);
-
- const thresholdExpressionIndex = expressionQueries.findIndex(
- (query) =>
- query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId
- );
- const thresholdExpression = expressionQueries.at(thresholdExpressionIndex);
- const conditions = thresholdExpression?.model.conditions ?? [];
- const thresholdIndexOk = singleReduceExpressionInInstantQuery
- ? thresholdExpressionIndex === 0
- : thresholdExpressionIndex === 1;
- const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined;
- return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk);
-}
-
interface Props {
editingExistingRule: boolean;
onDataChange: (error: string) => void;
@@ -777,9 +732,3 @@ const useSetExpressionAndDataSource = () => {
}
};
};
-
-export function isExpressionQueryInAlert(
- query: AlertQuery
-): query is AlertQuery {
- return isExpressionQuery(query.model);
-}
diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts
index 208342735e6..2a9294ada7d 100644
--- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts
+++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/areQueriesTransformableToSimpleCondition.test.ts
@@ -2,11 +2,10 @@ import { produce } from 'immer';
import { EvalFunction } from 'app/features/alerting/state/alertDef';
import { dataQuery, reduceExpression, thresholdExpression } from 'app/features/alerting/unified/mocks';
+import { areQueriesTransformableToSimpleCondition } from 'app/features/alerting/unified/rule-editor/formProcessing';
import { ExpressionQuery, ReducerMode } from 'app/features/expressions/types';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
-import { areQueriesTransformableToSimpleCondition } from '../QueryAndExpressionsStep';
-
const expressionQueries: Array> = [reduceExpression, thresholdExpression];
describe('areQueriesTransformableToSimpleCondition', () => {
diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts
index 0c793dfdaac..08e9d889bbf 100644
--- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts
+++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/useAdvancedMode.ts
@@ -5,7 +5,8 @@ import { EvalFunction } from 'app/features/alerting/state/alertDef';
import { ExpressionQuery } from 'app/features/expressions/types';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
-import { areQueriesTransformableToSimpleCondition } from './QueryAndExpressionsStep';
+import { areQueriesTransformableToSimpleCondition } from '../../../rule-editor/formProcessing';
+
import { SimpleCondition, getSimpleConditionFromExpressions } from './SimpleCondition';
function initializeSimpleCondition(
@@ -30,7 +31,7 @@ export function determineAdvancedMode(simplifiedQueryEditor: boolean | undefined
}
/*
- This hook is used mantain the state of the advanced mode, and the simple condition,
+ This hook is used mantain the state of the advanced mode, and the simple condition,
depending on the editor settings, the alert type, and the queries.
*/
export const useAdvancedMode = (
diff --git a/public/app/features/alerting/unified/components/rule-editor/util.ts b/public/app/features/alerting/unified/components/rule-editor/util.ts
index 1c432344b1c..3933de4f48e 100644
--- a/public/app/features/alerting/unified/components/rule-editor/util.ts
+++ b/public/app/features/alerting/unified/components/rule-editor/util.ts
@@ -15,8 +15,6 @@ import { isExpressionQuery } from 'app/features/expressions/guards';
import { ClassicCondition, ExpressionQueryType } from 'app/features/expressions/types';
import { AlertQuery } from 'app/types/unified-alerting-dto';
-import { RuleFormType } from '../../types/rule-form';
-
import { createDagFromQueries, getOriginOfRefId } from './dag';
export function queriesWithUpdatedReferences(
@@ -312,18 +310,6 @@ export function getStatusMessage(data: PanelData): string | undefined {
return data.error?.message ?? genericErrorMessage;
}
-export function translateRouteParamToRuleType(param = ''): RuleFormType {
- if (param === 'recording') {
- return RuleFormType.cloudRecording;
- }
-
- if (param === 'grafana-recording') {
- return RuleFormType.grafanaRecording;
- }
-
- return RuleFormType.grafana;
-}
-
/**
* This function finds what refIds have been updated given the previous Array of queries and an Array of updated data queries.
* All expression queries are discarded from the arrays, since we have separate handlers for those (see "onUpdateRefId") of the ExpressionEditor
diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx
index be7153bc131..7f41a873797 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx
@@ -4,7 +4,6 @@ import { byRole, byText } from 'testing-library-selector';
import { setPluginLinksHook } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
-import { setFolderAccessControl } from 'app/features/alerting/unified/mocks/server/configure';
import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
import { CombinedRule, RuleIdentifier } from 'app/types/unified-alerting';
@@ -20,6 +19,7 @@ import {
mockPromAlertingRule,
} from '../../mocks';
import { grafanaRulerRule } from '../../mocks/grafanaRulerApi';
+import { grantPermissionsHelper } from '../../test/test-utils';
import { setupDataSources } from '../../testSetup/datasources';
import { Annotation } from '../../utils/constants';
import { DataSourceType } from '../../utils/datasource';
@@ -76,16 +76,6 @@ setPluginLinksHook(() => ({
isLoading: false,
}));
-/**
- * "Grants" permissions via contextSrv mock, and additionally sets folder access control
- * API response to match
- */
-const grantPermissionsHelper = (permissions: AccessControlAction[]) => {
- const permissionsHash = permissions.reduce((hash, permission) => ({ ...hash, [permission]: true }), {});
- grantUserPermissions(permissions);
- setFolderAccessControl(permissionsHash);
-};
-
const openSilenceDrawer = async () => {
const user = userEvent.setup();
await user.click(ELEMENTS.actions.more.button.get());
diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
index bb5bd689b32..f1d3064fadb 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
@@ -5,9 +5,9 @@ import { useMeasure } from 'react-use';
import { NavModelItem, UrlQueryValue } from '@grafana/data';
import { Alert, LinkButton, LoadingBar, Stack, TabContent, Text, TextLink, useStyles2 } from '@grafana/ui';
-import { Trans, t } from '@grafana/ui/src/utils/i18n';
import { PageInfoItem } from 'app/core/components/Page/types';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
+import { Trans, t } from 'app/core/internationalization';
import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule';
import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import { AlertInstanceTotalState, CombinedRule, RuleHealth, RuleIdentifier } from 'app/types/unified-alerting';
diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx
index dcbc646ae03..f755d6ff9e3 100644
--- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx
+++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx
@@ -1,17 +1,17 @@
+import { HttpResponse } from 'msw';
import { render } from 'test/test-utils';
import { byLabelText, byTestId, byText, byTitle } from 'testing-library-selector';
-import { CombinedRuleNamespace } from 'app/types/unified-alerting';
+import { AccessControlAction } from 'app/types';
+import { RuleGroupIdentifier } from 'app/types/unified-alerting';
-import {
- mockCombinedRule,
- mockCombinedRuleNamespace,
- mockDataSource,
- mockPromAlertingRule,
- mockPromRecordingRule,
- mockRulerAlertingRule,
- mockRulerRecordingRule,
-} from '../../mocks';
+import { GRAFANA_RULER_CONFIG } from '../../api/featureDiscoveryApi';
+import server, { setupMswServer } from '../../mockApi';
+import { mimirDataSource } from '../../mocks/server/configure';
+import { alertingFactory } from '../../mocks/server/db';
+import { rulerRuleGroupHandler as grafanaRulerRuleGroupHandler } from '../../mocks/server/handlers/grafanaRuler';
+import { rulerRuleGroupHandler } from '../../mocks/server/handlers/mimirRuler';
+import { grantPermissionsHelper } from '../../test/test-utils';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { EditRuleGroupModal } from './EditRuleGroupModal';
@@ -29,133 +29,165 @@ const ui = {
};
const noop = () => jest.fn();
+setupMswServer();
-describe('EditGroupModal', () => {
+jest.mock('@grafana/runtime', () => ({
+ ...jest.requireActual('@grafana/runtime'),
+ useReturnToPrevious: jest.fn(),
+}));
+
+describe('EditGroupModal component on cloud alert rules', () => {
it('Should disable all inputs but interval when intervalEditOnly is set', async () => {
- const namespace = mockCombinedRuleNamespace({
- name: 'my-alerts',
- rulesSource: mockDataSource(),
- groups: [{ name: 'default-group', interval: '90s', rules: [], totals: {} }],
+ const { rulerConfig } = mimirDataSource();
+
+ const group = alertingFactory.ruler.group.build({
+ rules: [alertingFactory.ruler.alertingRule.build(), alertingFactory.ruler.recordingRule.build()],
});
- const group = namespace.groups[0];
+ // @TODO need to simplify this a bit I think, ideally there would be a higher-level function that simply sets up a few rules
+ // and attaches the ruler and prometheus endpoint(s) – including the namespaces and group endpoints.
+ server.use(
+ rulerRuleGroupHandler({
+ response: HttpResponse.json(group),
+ })
+ );
- render();
+ const rulerGroupIdentifier: RuleGroupIdentifier = {
+ dataSourceName: rulerConfig.dataSourceName,
+ groupName: 'default-group',
+ namespaceName: 'my-namespace',
+ };
+
+ render(
+
+ );
expect(await ui.input.namespace.find()).toHaveAttribute('readonly');
expect(ui.input.group.get()).toHaveAttribute('readonly');
expect(ui.input.interval.get()).not.toHaveAttribute('readonly');
});
-});
-
-describe('EditGroupModal component on cloud alert rules', () => {
- const promDsSettings = mockDataSource({ name: 'Prometheus-1', uid: 'Prometheus-1' });
-
- const alertingRule = mockCombinedRule({
- namespace: undefined,
- promRule: mockPromAlertingRule({ name: 'alerting-rule-cpu' }),
- rulerRule: mockRulerAlertingRule({ alert: 'alerting-rule-cpu' }),
- });
-
- const recordingRule1 = mockCombinedRule({
- namespace: undefined,
- promRule: mockPromRecordingRule({ name: 'recording-rule-memory' }),
- rulerRule: mockRulerRecordingRule({ record: 'recording-rule-memory' }),
- });
-
- const recordingRule2 = mockCombinedRule({
- namespace: undefined,
- promRule: mockPromRecordingRule({ name: 'recording-rule-cpu' }),
- rulerRule: mockRulerRecordingRule({ record: 'recording-rule-cpu' }),
- });
it('Should show alert table in case of having some non-recording rules in the group', async () => {
- const promNs = mockCombinedRuleNamespace({
- name: 'prometheus-ns',
- rulesSource: promDsSettings,
- groups: [
- { name: 'default-group', interval: '90s', rules: [alertingRule, recordingRule1, recordingRule2], totals: {} },
- ],
+ const { dataSource, rulerConfig } = mimirDataSource();
+
+ const group = alertingFactory.ruler.group.build({
+ rules: [alertingFactory.ruler.alertingRule.build(), alertingFactory.ruler.recordingRule.build()],
});
- const group = promNs.groups[0];
+ // @TODO need to simplify this a bit I think, ideally there would be a higher-level function that simply sets up a few rules
+ // and attaches the ruler and prometheus endpoint(s) – including the namespaces and group endpoints.
+ server.use(
+ rulerRuleGroupHandler({
+ response: HttpResponse.json(group),
+ })
+ );
- render();
+ const ruleGroupIdentifier: RuleGroupIdentifier = {
+ dataSourceName: dataSource.name,
+ groupName: group.name,
+ namespaceName: 'ns1',
+ };
- expect(await ui.input.namespace.find()).toHaveValue('prometheus-ns');
+ render();
+
+ expect(await ui.input.namespace.find()).toHaveValue('ns1');
expect(ui.input.namespace.get()).not.toHaveAttribute('readonly');
- expect(ui.input.group.get()).toHaveValue('default-group');
+ expect(ui.input.group.get()).toHaveValue(group.name);
+
+ // @ts-ignore
+ const ruleName = group.rules.at(0).alert;
expect(ui.tableRows.getAll()).toHaveLength(1); // Only one rule is non-recording
- expect(ui.tableRows.getAll()[0]).toHaveTextContent('alerting-rule-cpu');
+ expect(ui.tableRows.getAll().at(0)).toHaveTextContent(ruleName);
});
it('Should not show alert table in case of having exclusively recording rules in the group', async () => {
- const promNs = mockCombinedRuleNamespace({
- name: 'prometheus-ns',
- rulesSource: promDsSettings,
- groups: [{ name: 'default-group', interval: '90s', rules: [recordingRule1, recordingRule2], totals: {} }],
+ const { dataSource, rulerConfig } = mimirDataSource();
+
+ const group = alertingFactory.ruler.group.build({
+ rules: [alertingFactory.ruler.recordingRule.build(), alertingFactory.ruler.recordingRule.build()],
});
- const group = promNs.groups[0];
+ // @TODO need to simplify this a bit I think
+ server.use(
+ rulerRuleGroupHandler({
+ response: HttpResponse.json(group),
+ })
+ );
- render();
+ const ruleGroupIdentifier: RuleGroupIdentifier = {
+ dataSourceName: dataSource.name,
+ groupName: group.name,
+ namespaceName: 'ns1',
+ };
+
+ render();
expect(ui.table.query()).not.toBeInTheDocument();
expect(await ui.noRulesText.find()).toBeInTheDocument();
});
});
describe('EditGroupModal component on grafana-managed alert rules', () => {
- const grafanaNamespace: CombinedRuleNamespace = {
- name: 'namespace1',
- rulesSource: GRAFANA_RULES_SOURCE_NAME,
- groups: [
- {
- name: 'grafanaGroup1',
- interval: '30s',
- rules: [
- mockCombinedRule({
- namespace: undefined,
- promRule: mockPromAlertingRule({ name: 'high-cpu-1' }),
- rulerRule: mockRulerAlertingRule({ alert: 'high-cpu-1' }),
- }),
- mockCombinedRule({
- namespace: undefined,
- promRule: mockPromAlertingRule({ name: 'high-memory' }),
- rulerRule: mockRulerAlertingRule({ alert: 'high-memory' }),
- }),
- ],
- totals: {},
- },
- ],
+ // @TODO simplify folder stuff, should also have a higher-level function to set these up
+ const folder = alertingFactory.folder.build();
+ const NAMESPACE_UID = folder.uid;
+
+ const group = alertingFactory.ruler.group.build({
+ rules: [alertingFactory.ruler.alertingRule.build(), alertingFactory.ruler.alertingRule.build()],
+ });
+
+ const ruleGroupIdentifier: RuleGroupIdentifier = {
+ dataSourceName: GRAFANA_RULES_SOURCE_NAME,
+ groupName: group.name,
+ namespaceName: NAMESPACE_UID,
};
- const grafanaGroup1 = grafanaNamespace.groups[0];
+ beforeEach(() => {
+ grantPermissionsHelper([
+ AccessControlAction.AlertingRuleCreate,
+ AccessControlAction.AlertingRuleRead,
+ AccessControlAction.AlertingRuleUpdate,
+ ]);
+
+ server.use(
+ grafanaRulerRuleGroupHandler({
+ response: HttpResponse.json(group),
+ })
+ );
+ });
const renderWithGrafanaGroup = () =>
- render();
+ render(
+
+ );
it('Should show alert table', async () => {
renderWithGrafanaGroup();
- expect(await ui.input.namespace.find()).toHaveValue('namespace1');
- expect(ui.input.group.get()).toHaveValue('grafanaGroup1');
- expect(ui.input.interval.get()).toHaveValue('30s');
+ expect(await ui.input.namespace.find()).toHaveValue(NAMESPACE_UID);
+ expect(ui.input.group.get()).toHaveValue(group.name);
+ expect(ui.input.interval.get()).toHaveValue(group.interval);
expect(ui.tableRows.getAll()).toHaveLength(2);
- expect(ui.tableRows.getAll()[0]).toHaveTextContent('high-cpu-1');
- expect(ui.tableRows.getAll()[1]).toHaveTextContent('high-memory');
+ // @ts-ignore
+ expect(ui.tableRows.getAll().at(0)).toHaveTextContent(group.rules.at(0).alert);
+ // @ts-ignore
+ expect(ui.tableRows.getAll().at(1)).toHaveTextContent(group.rules.at(1).alert);
});
it('Should have folder input in readonly mode', async () => {
renderWithGrafanaGroup();
-
expect(await ui.input.namespace.find()).toHaveAttribute('readonly');
});
it('Should not display folder link if no folderUrl provided', async () => {
renderWithGrafanaGroup();
- expect(await ui.input.namespace.find()).toHaveValue('namespace1');
+ expect(await ui.input.namespace.find()).toHaveValue(NAMESPACE_UID);
expect(ui.folderLink.query()).not.toBeInTheDocument();
});
});
diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx
index 76427e7d5fa..828920a7e52 100644
--- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx
+++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx
@@ -4,32 +4,46 @@ import { useMemo } from 'react';
import { FieldValues, FormProvider, RegisterOptions, useForm, useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
-import { Alert, Badge, Button, Field, Input, Label, LinkButton, Modal, Stack, useStyles2 } from '@grafana/ui';
+import {
+ Alert,
+ Badge,
+ Button,
+ Field,
+ Input,
+ Label,
+ LinkButton,
+ LoadingPlaceholder,
+ Modal,
+ Stack,
+ useStyles2,
+} from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
-import { Trans } from 'app/core/internationalization';
+import { Trans, t } from 'app/core/internationalization';
import { dispatch } from 'app/store/store';
-import { CombinedRuleGroup, CombinedRuleNamespace, RuleGroupIdentifier } from 'app/types/unified-alerting';
-import { RulerRuleDTO } from 'app/types/unified-alerting-dto';
+import { RuleGroupIdentifier, RulerDataSourceConfig } from 'app/types/unified-alerting';
+import { RulerRuleDTO, RulerRuleGroupDTO } from 'app/types/unified-alerting-dto';
+import { alertRuleApi } from '../../api/alertRuleApi';
import {
useMoveRuleGroup,
useRenameRuleGroup,
useUpdateRuleGroupConfiguration,
} from '../../hooks/ruleGroup/useUpdateRuleGroup';
import { anyOfRequestState } from '../../hooks/useAsync';
+import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../rule-editor/formDefaults';
import { fetchRulerRulesAction, rulesInSameGroupHaveInvalidFor } from '../../state/actions';
import { checkEvaluationIntervalGlobalLimit } from '../../utils/config';
-import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName } from '../../utils/datasource';
+import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { stringifyErrorLike } from '../../utils/misc';
-import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../utils/rule-form';
import { AlertInfo, getAlertInfo, isGrafanaOrDataSourceRecordingRule } from '../../utils/rules';
import { formatPrometheusDuration, parsePrometheusDuration, safeParsePrometheusDuration } from '../../utils/time';
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning';
-import { decodeGrafanaNamespace, encodeGrafanaNamespace } from '../expressions/util';
import { EvaluationGroupQuickPick } from '../rule-editor/EvaluationGroupQuickPick';
import { MIN_TIME_RANGE_STEP_S } from '../rule-editor/GrafanaEvaluationBehavior';
+const useRuleGroupDefinition = alertRuleApi.endpoints.getRuleGroupForNamespace.useQuery;
+
const ITEMS_PER_PAGE = 10;
function ForBadge({ message, error }: { message: string; error?: boolean }) {
@@ -170,17 +184,59 @@ export const evaluateEveryValidationOptions = (rules: Rul
});
export interface ModalProps {
- namespace: CombinedRuleNamespace;
- group: CombinedRuleGroup;
+ ruleGroupIdentifier: RuleGroupIdentifier;
+ folderTitle?: string;
+ rulerConfig: RulerDataSourceConfig;
onClose: (saved?: boolean) => void;
intervalEditOnly?: boolean;
folderUrl?: string;
- folderUid?: string;
hideFolder?: boolean;
}
-export function EditRuleGroupModal(props: ModalProps): React.ReactElement {
- const { namespace, group, onClose, intervalEditOnly, folderUid } = props;
+export interface ModalFormProps {
+ ruleGroupIdentifier: RuleGroupIdentifier;
+ folderTitle?: string; // used to display the GMA folder title
+ ruleGroup: RulerRuleGroupDTO;
+ onClose: (saved?: boolean) => void;
+ intervalEditOnly?: boolean;
+ folderUrl?: string;
+ hideFolder?: boolean;
+}
+
+// this component just wraps the modal with some loading state for grabbing rules and such
+export function EditRuleGroupModal(props: ModalProps) {
+ const { ruleGroupIdentifier, rulerConfig, intervalEditOnly, onClose } = props;
+ const rulesSourceName = ruleGroupIdentifier.dataSourceName;
+ const isGrafanaManagedGroup = rulesSourceName === GRAFANA_RULES_SOURCE_NAME;
+
+ const modalTitle =
+ intervalEditOnly || isGrafanaManagedGroup ? 'Edit evaluation group' : 'Edit namespace or evaluation group';
+
+ const styles = useStyles2(getStyles);
+
+ const {
+ data: ruleGroup,
+ error,
+ isLoading,
+ } = useRuleGroupDefinition({
+ group: ruleGroupIdentifier.groupName,
+ namespace: ruleGroupIdentifier.namespaceName,
+ rulerConfig,
+ });
+
+ const loadingText = t('alerting.common.loading', 'Loading...');
+
+ return (
+
+ {isLoading && }
+ {error ? stringifyErrorLike(error) : null}
+ {ruleGroup && }
+
+ );
+}
+
+export function EditRuleGroupModalForm(props: ModalFormProps): React.ReactElement {
+ const { ruleGroup, ruleGroupIdentifier, folderTitle, onClose, intervalEditOnly } = props;
const styles = useStyles2(getStyles);
const notifyApp = useAppNotification();
@@ -200,32 +256,21 @@ export function EditRuleGroupModal(props: ModalProps): React.ReactElement {
const defaultValues = useMemo(
(): FormValues => ({
- namespaceName: decodeGrafanaNamespace(namespace).name,
- groupName: group.name,
- groupInterval: group.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL,
+ namespaceName: ruleGroupIdentifier.namespaceName,
+ groupName: ruleGroupIdentifier.groupName,
+ groupInterval: ruleGroup?.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL,
}),
- [namespace, group.name, group.interval]
+ [ruleGroup?.interval, ruleGroupIdentifier.groupName, ruleGroupIdentifier.namespaceName]
);
- const rulesSourceName = getRulesSourceName(namespace.rulesSource);
+ const rulesSourceName = ruleGroupIdentifier.dataSourceName;
const isGrafanaManagedGroup = rulesSourceName === GRAFANA_RULES_SOURCE_NAME;
- // parse any parent folders the alert rule might be stored in
- const nestedFolderParents = decodeGrafanaNamespace(namespace).parents;
-
const nameSpaceLabel = isGrafanaManagedGroup ? 'Folder' : 'Namespace';
const onSubmit = async (values: FormValues) => {
- const ruleGroupIdentifier: RuleGroupIdentifier = {
- dataSourceName: rulesSourceName,
- groupName: group.name,
- namespaceName: isGrafanaManagedGroup ? folderUid! : namespace.name,
- };
-
// make sure that when dealing with a nested folder for Grafana managed rules we encode the folder properly
- const updatedNamespaceName = isGrafanaManagedGroup
- ? encodeGrafanaNamespace(values.namespaceName, nestedFolderParents)
- : values.namespaceName;
+ const updatedNamespaceName = values.namespaceName;
const updatedGroupName = values.groupName;
const updatedInterval = values.groupInterval;
@@ -266,136 +311,133 @@ export function EditRuleGroupModal(props: ModalProps): React.ReactElement {
};
const rulesWithoutRecordingRules = compact(
- group.rules.map((r) => r.rulerRule).filter((rule) => !isGrafanaOrDataSourceRecordingRule(rule))
+ ruleGroup?.rules.filter((rule) => !isGrafanaOrDataSourceRecordingRule(rule))
);
const hasSomeNoRecordingRules = rulesWithoutRecordingRules.length > 0;
- const modalTitle =
- intervalEditOnly || isGrafanaManagedGroup ? 'Edit evaluation group' : 'Edit namespace or evaluation group';
return (
-
-
-
-
-
+ icon="folder-open"
+ target="_blank"
+ />
+ )}
+
+ )}
+
+ Evaluation group
+
+ }
+ invalid={!!errors.groupName}
+ error={errors.groupName?.message}
+ >
+
+
+
+ Evaluation interval
+
+ }
+ invalid={Boolean(errors.groupInterval) ? true : undefined}
+ error={errors.groupInterval?.message}
+ >
+
+
+ setValue('groupInterval', value, { shouldValidate: true, shouldDirty: true })}
+ />
+
+
+
+ {/* if we're dealing with a Grafana-managed group, check if the evaluation interval is valid / permitted */}
+ {isGrafanaManagedGroup && checkEvaluationIntervalGlobalLimit(watch('groupInterval')).exceedsLimit && (
+
+ )}
+
+ {!hasSomeNoRecordingRules && This group does not contain alert rules.
}
+ {hasSomeNoRecordingRules && (
+ <>
+ List of rules that belong to this group
+
+ #Eval column represents the number of evaluations needed before alert starts firing.
+
+
+ >
+ )}
+ {error && {stringifyErrorLike(error)}}
+
+
+
+
+
+
+ >
+
+
);
}
diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx
index d79e630ffb5..9baab7600cd 100644
--- a/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx
@@ -7,12 +7,14 @@ import { byRole, byTestId, byText } from 'testing-library-selector';
import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
-import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
+import { CombinedRuleGroup, CombinedRuleNamespace, RulerDataSourceConfig } from 'app/types/unified-alerting';
import * as analytics from '../../Analytics';
+import { GRAFANA_RULER_CONFIG } from '../../api/featureDiscoveryApi';
import { useHasRuler } from '../../hooks/useHasRuler';
import { mockExportApi, mockFolderApi, setupMswServer } from '../../mockApi';
import { grantUserPermissions, mockCombinedRule, mockDataSource, mockFolder, mockGrafanaRulerRule } from '../../mocks';
+import { mimirDataSource } from '../../mocks/server/configure';
import { RulesGroup } from './RulesGroup';
@@ -38,10 +40,10 @@ const mocks = {
useHasRuler: jest.mocked(useHasRuler),
};
-function mockUseHasRuler(hasRuler: boolean, rulerRulesLoaded: boolean) {
+function mockUseHasRuler(hasRuler: boolean, rulerConfig: RulerDataSourceConfig) {
mocks.useHasRuler.mockReturnValue({
hasRuler,
- rulerRulesLoaded,
+ rulerConfig,
});
}
@@ -107,7 +109,7 @@ describe('Rules group tests', () => {
it('Should hide delete and edit group buttons', async () => {
// Act
- mockUseHasRuler(true, true);
+ mockUseHasRuler(true, GRAFANA_RULER_CONFIG);
mockFolderApi(server).folder('cpu-usage', mockFolder({ uid: 'cpu-usage', canSave: false }));
renderRulesGroup(namespace, group);
expect(await screen.findByTestId('rule-group')).toBeInTheDocument();
@@ -119,7 +121,7 @@ describe('Rules group tests', () => {
it('Should allow exporting rules group', async () => {
// Arrange
- mockUseHasRuler(true, true);
+ mockUseHasRuler(true, GRAFANA_RULER_CONFIG);
mockFolderApi(server).folder('cpu-usage', mockFolder({ uid: 'cpu-usage' }));
mockExportApi(server).exportRulesGroup('cpu-usage', 'TestGroup', {
yaml: 'Yaml Export Content',
@@ -151,6 +153,8 @@ describe('Rules group tests', () => {
});
describe('Cloud rules', () => {
+ const { rulerConfig } = mimirDataSource();
+
beforeEach(() => {
contextSrv.isEditor = true;
});
@@ -169,7 +173,7 @@ describe('Rules group tests', () => {
it('When ruler enabled should display delete and edit group buttons', () => {
// Arrange
- mockUseHasRuler(true, true);
+ mockUseHasRuler(true, rulerConfig);
// Act
renderRulesGroup(namespace, group);
@@ -182,7 +186,7 @@ describe('Rules group tests', () => {
it('When ruler disabled should hide delete and edit group buttons', () => {
// Arrange
- mockUseHasRuler(false, false);
+ mockUseHasRuler(false, rulerConfig);
// Act
renderRulesGroup(namespace, group);
@@ -195,7 +199,7 @@ describe('Rules group tests', () => {
it('Delete button click should display confirmation modal', async () => {
// Arrange
- mockUseHasRuler(true, true);
+ mockUseHasRuler(true, rulerConfig);
// Act
renderRulesGroup(namespace, group);
@@ -206,36 +210,4 @@ describe('Rules group tests', () => {
expect(ui.confirmDeleteModal.confirmButton.get()).toBeInTheDocument();
});
});
-
- describe('Analytics', () => {
- beforeEach(() => {
- contextSrv.isEditor = true;
- });
-
- const group: CombinedRuleGroup = {
- name: 'TestGroup',
- rules: [mockCombinedRule()],
- totals: {},
- };
-
- const namespace: CombinedRuleNamespace = {
- name: 'TestNamespace',
- rulesSource: mockDataSource(),
- groups: [group],
- };
-
- it('Should log info when closing the edit group rule modal without saving', async () => {
- mockUseHasRuler(true, true);
- renderRulesGroup(namespace, group);
-
- await userEvent.click(ui.editGroupButton.get());
-
- expect(screen.getByText('Cancel')).toBeInTheDocument();
-
- await userEvent.click(screen.getByText('Cancel'));
-
- expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
- expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.leavingRuleGroupEdit);
- });
- });
});
diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx
index b1a5bddd43c..ccf725b5f99 100644
--- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import pluralize from 'pluralize';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -26,7 +26,7 @@ import { ActionIcon } from './ActionIcon';
import { EditRuleGroupModal } from './EditRuleGroupModal';
import { ReorderCloudGroupModal } from './ReorderRuleGroupModal';
import { RuleGroupStats } from './RuleStats';
-import { RulesTable } from './RulesTable';
+import { RulesTable, useIsRulesLoading } from './RulesTable';
type ViewMode = 'grouped' | 'list';
@@ -42,6 +42,7 @@ const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi;
export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: Props) => {
const { rulesSource } = namespace;
const rulesSourceName = getRulesSourceName(rulesSource);
+ const rulerRulesLoaded = useIsRulesLoading(rulesSource);
const [deleteRuleGroup] = useDeleteRuleGroup();
const styles = useStyles2(getStyles);
@@ -58,7 +59,7 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
setIsCollapsed(!expandAll);
}, [expandAll]);
- const { hasRuler, rulerRulesLoaded } = useHasRuler(namespace.rulesSource);
+ const { hasRuler, rulerConfig } = useHasRuler(namespace.rulesSource);
const { currentData: dsFeatures } = useDiscoverDsFeaturesQuery({ rulesSourceName });
const rulerRule = group.rules[0]?.rulerRule;
@@ -78,12 +79,15 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
const isListView = viewMode === 'list';
const isGroupView = viewMode === 'grouped';
- const deleteGroup = async () => {
- const namespaceName = decodeGrafanaNamespace(namespace).name;
+ const ruleGroupIdentifier = useMemo(() => {
+ const namespaceName = namespace.uid ?? namespace.name;
const groupName = group.name;
const dataSourceName = getRulesSourceName(namespace.rulesSource);
- const ruleGroupIdentifier: RuleGroupIdentifier = { namespaceName, groupName, dataSourceName };
+ return { namespaceName, groupName, dataSourceName };
+ }, [namespace, group.name]);
+
+ const deleteGroup = async () => {
await deleteRuleGroup.execute(ruleGroupIdentifier);
setIsDeletingGroup(false);
};
@@ -274,13 +278,13 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }:
rules={group.rules}
/>
)}
- {isEditingGroup && (
+ {isEditingGroup && rulerConfig && (
closeEditModal()}
folderUrl={folder?.canEdit ? makeFolderSettingsLink(folder.uid) : undefined}
- folderUid={folderUID}
/>
)}
{isReorderingGroup && dsFeatures?.rulerConfig && (
diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
index 38eb1045e40..1f7c6c44b11 100644
--- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
@@ -4,7 +4,7 @@ import Skeleton from 'react-loading-skeleton';
import { GrafanaTheme2 } from '@grafana/data';
import { Pagination, Tooltip, useStyles2 } from '@grafana/ui';
-import { CombinedRule } from 'app/types/unified-alerting';
+import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
import { alertRuleApi } from '../../api/alertRuleApi';
@@ -14,6 +14,7 @@ import { useAsync } from '../../hooks/useAsync';
import { attachRulerRuleToCombinedRule } from '../../hooks/useCombinedRuleNamespaces';
import { useHasRuler } from '../../hooks/useHasRuler';
import { usePagination } from '../../hooks/usePagination';
+import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
import { calculateNextEvaluationEstimate } from '../../rule-list/components/util';
import { Annotation } from '../../utils/constants';
@@ -328,8 +329,20 @@ function RuleActionsCell({ rule, isLoadingRuler }: { rule: CombinedRule; isLoadi
);
}
+export function useIsRulesLoading(rulesSource: RulesSource) {
+ const rulerRules = useUnifiedAlertingSelector((state) => state.rulerRules);
+ const rulesSourceName = getRulesSourceName(rulesSource);
+
+ const rulerRulesLoaded = Boolean(rulerRules[rulesSourceName]?.result);
+ return rulerRulesLoaded;
+}
+
function useRuleStatus(rule: CombinedRule) {
- const { hasRuler, rulerRulesLoaded } = useHasRuler(rule.namespace.rulesSource);
+ const rulesSource = rule.namespace.rulesSource;
+
+ const rulerRulesLoaded = useIsRulesLoading(rulesSource);
+ const { hasRuler } = useHasRuler(rulesSource);
+
const { promRule, rulerRule } = rule;
// If prometheusRulesPrimary is enabled, we don't fetch rules from the Ruler API (except for Grafana managed rules)
diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts b/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
index f7c54c1121b..6d8d890e737 100644
--- a/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
+++ b/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
@@ -7,7 +7,7 @@ import { alertRuleApi } from '../../api/alertRuleApi';
import { featureDiscoveryApi } from '../../api/featureDiscoveryApi';
import { notFoundToNullOrThrow } from '../../api/util';
import { ruleGroupReducer } from '../../reducers/ruler/ruleGroups';
-import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../utils/rule-form';
+import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../rule-editor/formDefaults';
const { useLazyGetRuleGroupForNamespaceQuery } = alertRuleApi;
const { useLazyDiscoverDsFeaturesQuery } = featureDiscoveryApi;
diff --git a/public/app/features/alerting/unified/hooks/useHasRuler.ts b/public/app/features/alerting/unified/hooks/useHasRuler.ts
index db2e85f09ee..399aca6081f 100644
--- a/public/app/features/alerting/unified/hooks/useHasRuler.ts
+++ b/public/app/features/alerting/unified/hooks/useHasRuler.ts
@@ -3,19 +3,14 @@ import { RulesSource } from 'app/types/unified-alerting';
import { featureDiscoveryApi } from '../api/featureDiscoveryApi';
import { getRulesSourceName } from '../utils/datasource';
-import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';
-
const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi;
// datasource has ruler if the discovery api returns a rulerConfig
export function useHasRuler(rulesSource: RulesSource) {
- const rulerRules = useUnifiedAlertingSelector((state) => state.rulerRules);
const rulesSourceName = getRulesSourceName(rulesSource);
const { currentData: dsFeatures } = useDiscoverDsFeaturesQuery({ rulesSourceName });
-
const hasRuler = Boolean(dsFeatures?.rulerConfig);
- const rulerRulesLoaded = Boolean(rulerRules[rulesSourceName]?.result);
- return { hasRuler, rulerRulesLoaded };
+ return { hasRuler, rulerConfig: dsFeatures?.rulerConfig };
}
diff --git a/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts b/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts
index 7b50599934c..5e6daa81e8b 100644
--- a/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts
+++ b/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts
@@ -4,6 +4,9 @@ import { StoreState, useSelector } from 'app/types';
import { UnifiedAlertingState } from '../state/reducers';
+/**
+ * @deprecated: DO NOT USE THIS; when using this you are INCORRECTLY assuming that we already have dispatched an action to populate the redux store values
+ */
export function useUnifiedAlertingSelector(
selector: (state: UnifiedAlertingState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 5676dbb9a04..278178b395e 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -196,15 +196,12 @@ export const mockRulerAlertingRule = (partial: Partial = {
...partial,
});
-export const mockRulerRecordingRule = (partial: Partial = {}): RulerAlertingRuleDTO => ({
- alert: 'alert1',
+export const mockRulerRecordingRule = (partial: Partial = {}): RulerRecordingRuleDTO => ({
+ record: 'alert1',
expr: 'up = 1',
labels: {
severity: 'warning',
},
- annotations: {
- summary: 'test alert',
- },
...partial,
});
@@ -735,7 +732,7 @@ export function mockStore(recipe: (state: StoreState) => void) {
return configureStore(produce(defaultState, recipe));
}
-export function mockAlertQuery(query: Partial): AlertQuery {
+export function mockAlertQuery(query: Partial = {}): AlertQuery {
return {
datasourceUid: '--uid--',
refId: 'A',
diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts
index 063a070a10f..bc638438e14 100644
--- a/public/app/features/alerting/unified/mocks/server/configure.ts
+++ b/public/app/features/alerting/unified/mocks/server/configure.ts
@@ -20,6 +20,7 @@ import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridg
import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings';
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { FolderDTO } from 'app/types';
+import { RulerDataSourceConfig } from 'app/types/unified-alerting';
import { PromRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { setupDataSources } from '../../testSetup/datasources';
@@ -108,9 +109,15 @@ export function mimirDataSource() {
{ alerting: true, module: 'core:plugin/prometheus' }
);
+ const rulerConfig: RulerDataSourceConfig = {
+ apiVersion: 'config',
+ dataSourceUid: dataSource.uid,
+ dataSourceName: dataSource.name,
+ };
+
setupDataSources(dataSource);
- return { dataSource };
+ return { dataSource, rulerConfig };
}
export function setPrometheusRules(ds: DataSourceInstanceSettings, groups: PromRuleGroupDTO[]) {
diff --git a/public/app/features/alerting/unified/mocks/server/db.ts b/public/app/features/alerting/unified/mocks/server/db.ts
index 55e76f78208..ba78b0be31a 100644
--- a/public/app/features/alerting/unified/mocks/server/db.ts
+++ b/public/app/features/alerting/unified/mocks/server/db.ts
@@ -1,18 +1,23 @@
import { Factory } from 'fishery';
+import { uniqueId } from 'lodash';
import { DataSourceInstanceSettings, PluginType } from '@grafana/data';
import { config, setDataSourceSrv } from '@grafana/runtime';
+import { FolderDTO } from 'app/types';
import {
PromAlertingRuleDTO,
PromAlertingRuleState,
PromRuleGroupDTO,
PromRuleType,
+ RulerAlertingRuleDTO,
+ RulerRecordingRuleDTO,
+ RulerRuleGroupDTO,
} from 'app/types/unified-alerting-dto';
import { MockDataSourceSrv } from '../../mocks';
import { DataSourceType } from '../../utils/datasource';
-const ruleFactory = Factory.define(({ sequence }) => ({
+const prometheusRuleFactory = Factory.define(({ sequence }) => ({
name: `test-rule-${sequence}`,
query: 'test-query',
state: PromAlertingRuleState.Inactive,
@@ -21,15 +26,35 @@ const ruleFactory = Factory.define(({ sequence }) => ({
labels: { team: 'infra' },
}));
-const groupFactory = Factory.define(({ sequence }) => {
+const rulerAlertingRuleFactory = Factory.define(({ sequence }) => ({
+ alert: `ruler-alerting-rule-${sequence}`,
+ expr: 'vector(0)',
+ annotations: { 'annotation-key-1': 'annotation-value-1' },
+ labels: { 'label-key-1': 'label-value-1' },
+ for: '5m',
+}));
+
+const rulerRecordingRuleFactory = Factory.define(({ sequence }) => ({
+ record: `ruler-recording-rule-${sequence}`,
+ expr: 'vector(0)',
+ labels: { 'label-key-1': 'label-value-1' },
+}));
+
+const rulerRuleGroupFactory = Factory.define(({ sequence }) => ({
+ name: `ruler-rule-group-${sequence}`,
+ rules: [],
+ interval: '1m',
+}));
+
+const prometheusRuleGroupFactory = Factory.define(({ sequence }) => {
const group = {
name: `test-group-${sequence}`,
file: `test-namespace`,
interval: 10,
- rules: ruleFactory.buildList(10),
+ rules: prometheusRuleFactory.buildList(10),
};
- ruleFactory.rewindSequence();
+ prometheusRuleFactory.rewindSequence();
return group;
});
@@ -72,8 +97,33 @@ const dataSourceFactory = Factory.define(({ sequence
};
});
+const grafanaFolderFactory = Factory.define(({ sequence }) => ({
+ id: sequence,
+ uid: uniqueId(),
+ title: `Mock Folder ${sequence}`,
+ version: 1,
+ url: '',
+ canAdmin: true,
+ canDelete: true,
+ canEdit: true,
+ canSave: true,
+ created: '',
+ createdBy: '',
+ hasAcl: false,
+ updated: '',
+ updatedBy: '',
+}));
+
export const alertingFactory = {
- group: groupFactory,
- rule: ruleFactory,
+ folder: grafanaFolderFactory,
+ prometheus: {
+ group: prometheusRuleGroupFactory,
+ rule: prometheusRuleFactory,
+ },
+ ruler: {
+ group: rulerRuleGroupFactory,
+ alertingRule: rulerAlertingRuleFactory,
+ recordingRule: rulerRecordingRuleFactory,
+ },
dataSource: dataSourceFactory,
};
diff --git a/public/app/features/alerting/unified/reducers/ruler/__snapshots__/ruleGroups.test.ts.snap b/public/app/features/alerting/unified/reducers/ruler/__snapshots__/ruleGroups.test.ts.snap
index 95329f16627..42aac7884e8 100644
--- a/public/app/features/alerting/unified/reducers/ruler/__snapshots__/ruleGroups.test.ts.snap
+++ b/public/app/features/alerting/unified/reducers/ruler/__snapshots__/ruleGroups.test.ts.snap
@@ -104,10 +104,6 @@ exports[`removing a rule should remove a Data source managed ruler rule without
},
},
{
- "alert": "alert1",
- "annotations": {
- "summary": "test alert",
- },
"expr": "up = 1",
"labels": {
"severity": "warning",
diff --git a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx
similarity index 93%
rename from public/app/features/alerting/unified/CloneRuleEditor.test.tsx
rename to public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx
index 7399bcae26b..298be9b1529 100644
--- a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx
@@ -4,36 +4,36 @@ import { getWrapper, render, waitFor, waitForElementToBeRemoved, within } from '
import { byRole, byTestId, byText } from 'testing-library-selector';
import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants';
+import { AccessControlAction } from 'app/types';
import { RuleWithLocation } from 'app/types/unified-alerting';
-
-import { AccessControlAction } from '../../../types';
import {
RulerAlertingRuleDTO,
RulerGrafanaRuleDTO,
RulerRecordingRuleDTO,
RulerRuleDTO,
-} from '../../../types/unified-alerting-dto';
+} from 'app/types/unified-alerting-dto';
-import { CloneRuleEditor, cloneRuleDefinition } from './CloneRuleEditor';
-import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
-import { setupMswServer } from './mockApi';
+import { ExpressionEditorProps } from '../components/rule-editor/ExpressionEditor';
+import { setupMswServer } from '../mockApi';
import {
grantUserPermissions,
mockDataSource,
mockRulerAlertingRule,
mockRulerGrafanaRule,
mockRulerRuleGroup,
-} from './mocks';
-import { grafanaRulerRule } from './mocks/grafanaRulerApi';
-import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from './mocks/rulerApi';
-import { AlertingQueryRunner } from './state/AlertingQueryRunner';
-import { setupDataSources } from './testSetup/datasources';
-import { RuleFormValues } from './types/rule-form';
-import { Annotation } from './utils/constants';
-import { getDefaultFormValues } from './utils/rule-form';
-import { hashRulerRule } from './utils/rule-id';
+} from '../mocks';
+import { grafanaRulerRule } from '../mocks/grafanaRulerApi';
+import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from '../mocks/rulerApi';
+import { AlertingQueryRunner } from '../state/AlertingQueryRunner';
+import { setupDataSources } from '../testSetup/datasources';
+import { RuleFormValues } from '../types/rule-form';
+import { Annotation } from '../utils/constants';
+import { hashRulerRule } from '../utils/rule-id';
-jest.mock('./components/rule-editor/ExpressionEditor', () => ({
+import { CloneRuleEditor, cloneRuleDefinition } from './CloneRuleEditor';
+import { getDefaultFormValues } from './formDefaults';
+
+jest.mock('../components/rule-editor/ExpressionEditor', () => ({
// eslint-disable-next-line react/display-name
ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => (
onChange(e.target.value)} />
diff --git a/public/app/features/alerting/unified/CloneRuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx
similarity index 72%
rename from public/app/features/alerting/unified/CloneRuleEditor.tsx
rename to public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx
index 92745f20bde..e92ad4b46e6 100644
--- a/public/app/features/alerting/unified/CloneRuleEditor.tsx
+++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.tsx
@@ -1,18 +1,17 @@
import { cloneDeep } from 'lodash';
-import { locationService } from '@grafana/runtime/src';
-import { Alert, LoadingPlaceholder } from '@grafana/ui/src';
+import { locationService } from '@grafana/runtime';
+import { Alert, LoadingPlaceholder } from '@grafana/ui';
+import { RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting';
+import { RulerRuleDTO } from 'app/types/unified-alerting-dto';
-import { RuleIdentifier, RuleWithLocation } from '../../../types/unified-alerting';
-import { RulerRuleDTO } from '../../../types/unified-alerting-dto';
-
-import { AlertRuleForm } from './components/rule-editor/alert-rule-form/AlertRuleForm';
-import { useRuleWithLocation } from './hooks/useCombinedRule';
-import { generateCopiedName } from './utils/duplicate';
-import { stringifyErrorLike } from './utils/misc';
-import { rulerRuleToFormValues } from './utils/rule-form';
-import { getRuleName, isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './utils/rules';
-import { createRelativeUrl } from './utils/url';
+import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRuleForm';
+import { useRuleWithLocation } from '../hooks/useCombinedRule';
+import { generateCopiedName } from '../utils/duplicate';
+import { stringifyErrorLike } from '../utils/misc';
+import { rulerRuleToFormValues } from '../utils/rule-form';
+import { getRuleName, isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from '../utils/rules';
+import { createRelativeUrl } from '../utils/url';
export function CloneRuleEditor({ sourceRuleId }: { sourceRuleId: RuleIdentifier }) {
const { loading, result: rule, error } = useRuleWithLocation({ ruleIdentifier: sourceRuleId });
diff --git a/public/app/features/alerting/unified/ExistingRuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
similarity index 71%
rename from public/app/features/alerting/unified/ExistingRuleEditor.tsx
rename to public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
index 17dc0d74753..7d1b9d92fcf 100644
--- a/public/app/features/alerting/unified/ExistingRuleEditor.tsx
+++ b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
@@ -1,19 +1,18 @@
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { RuleIdentifier } from 'app/types/unified-alerting';
-import { AlertWarning } from './AlertWarning';
-import { AlertRuleForm } from './components/rule-editor/alert-rule-form/AlertRuleForm';
-import { useRuleWithLocation } from './hooks/useCombinedRule';
-import { useIsRuleEditable } from './hooks/useIsRuleEditable';
-import { stringifyErrorLike } from './utils/misc';
-import * as ruleId from './utils/rule-id';
+import { AlertWarning } from '../AlertWarning';
+import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRuleForm';
+import { useRuleWithLocation } from '../hooks/useCombinedRule';
+import { useIsRuleEditable } from '../hooks/useIsRuleEditable';
+import { stringifyErrorLike } from '../utils/misc';
+import * as ruleId from '../utils/rule-id';
interface ExistingRuleEditorProps {
identifier: RuleIdentifier;
- id?: string;
}
-export function ExistingRuleEditor({ identifier, id }: ExistingRuleEditorProps) {
+export function ExistingRuleEditor({ identifier }: ExistingRuleEditorProps) {
const {
loading: loadingAlertRule,
result: ruleWithLocation,
diff --git a/public/app/features/alerting/unified/RuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
similarity index 69%
rename from public/app/features/alerting/unified/RuleEditor.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
index bdda3e5d28b..b5adba57cf2 100644
--- a/public/app/features/alerting/unified/RuleEditor.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
@@ -5,14 +5,16 @@ import { NavModelItem } from '@grafana/data';
import { withErrorBoundary } from '@grafana/ui';
import { RuleIdentifier } from 'app/types/unified-alerting';
-import { AlertWarning } from './AlertWarning';
+import { AlertWarning } from '../AlertWarning';
+import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
+import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRuleForm';
+import { useURLSearchParams } from '../hooks/useURLSearchParams';
+import { useRulesAccess } from '../utils/accessControlHooks';
+import * as ruleId from '../utils/rule-id';
+
import { CloneRuleEditor } from './CloneRuleEditor';
import { ExistingRuleEditor } from './ExistingRuleEditor';
-import { AlertingPageWrapper } from './components/AlertingPageWrapper';
-import { AlertRuleForm } from './components/rule-editor/alert-rule-form/AlertRuleForm';
-import { useURLSearchParams } from './hooks/useURLSearchParams';
-import { useRulesAccess } from './utils/accessControlHooks';
-import * as ruleId from './utils/rule-id';
+import { formValuesFromQueryParams, translateRouteParamToRuleType } from './formDefaults';
type RuleEditorPathParams = {
id?: string;
@@ -44,14 +46,8 @@ const getPageNav = (identifier?: RuleIdentifier, type?: RuleEditorPathParams['ty
};
const RuleEditor = () => {
- const [searchParams] = useURLSearchParams();
- const params = useParams();
- const { type } = params;
- const id = ruleId.getRuleIdFromPathname(params);
- const identifier = ruleId.tryParse(id, true);
-
- const copyFromId = searchParams.get('copyFrom') ?? undefined;
- const copyFromIdentifier = ruleId.tryParse(copyFromId);
+ const { identifier, type } = useRuleEditorPathParams();
+ const { copyFromIdentifier, queryDefaults } = useRuleEditorQueryParams();
const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess();
@@ -65,15 +61,15 @@ const RuleEditor = () => {
}
if (identifier) {
- return ;
+ return ;
}
if (copyFromIdentifier) {
return ;
}
// new alert rule
- return ;
- }, [canCreateCloudRules, canCreateGrafanaRules, canEditRules, copyFromIdentifier, id, identifier]);
+ return ;
+ }, [canCreateCloudRules, canCreateGrafanaRules, canEditRules, copyFromIdentifier, identifier, queryDefaults]);
return (
@@ -83,3 +79,28 @@ const RuleEditor = () => {
};
export default withErrorBoundary(RuleEditor, { style: 'page' });
+
+function useRuleEditorPathParams() {
+ const params = useParams();
+ const { type } = params;
+ const id = ruleId.getRuleIdFromPathname(params);
+ const identifier = ruleId.tryParse(id, true);
+
+ return { identifier, type };
+}
+
+function useRuleEditorQueryParams() {
+ const { type } = useParams();
+
+ const [searchParams] = useURLSearchParams();
+ const copyFromId = searchParams.get('copyFrom') ?? undefined;
+ const copyFromIdentifier = ruleId.tryParse(copyFromId);
+
+ const ruleType = translateRouteParamToRuleType(type);
+
+ const queryDefaults = searchParams.has('defaults')
+ ? formValuesFromQueryParams(searchParams.get('defaults') ?? '', ruleType)
+ : undefined;
+
+ return { copyFromIdentifier, queryDefaults };
+}
diff --git a/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudOnlyAllowed.test.tsx
similarity index 88%
rename from public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditorCloudOnlyAllowed.test.tsx
index e99c56856d2..3671d13d232 100644
--- a/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudOnlyAllowed.test.tsx
@@ -6,24 +6,24 @@ import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction } from 'app/types';
import { PromApiFeatures, PromApplication } from 'app/types/unified-alerting-dto';
-import { discoverFeaturesByUid } from './api/buildInfo';
-import { fetchRulerRulesGroup } from './api/ruler';
-import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
-import { setupMswServer } from './mockApi';
-import { grantUserPermissions, mockDataSource } from './mocks';
-import { setupDataSources } from './testSetup/datasources';
-import { DataSourceType, GRAFANA_DATASOURCE_NAME, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
+import { discoverFeaturesByUid } from '../api/buildInfo';
+import { fetchRulerRulesGroup } from '../api/ruler';
+import { ExpressionEditorProps } from '../components/rule-editor/ExpressionEditor';
+import { setupMswServer } from '../mockApi';
+import { grantUserPermissions, mockDataSource } from '../mocks';
+import { setupDataSources } from '../testSetup/datasources';
+import { DataSourceType, GRAFANA_DATASOURCE_NAME, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
-jest.mock('./components/rule-editor/ExpressionEditor', () => ({
+jest.mock('../components/rule-editor/ExpressionEditor', () => ({
// eslint-disable-next-line react/display-name
ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => (
onChange(e.target.value)} />
),
}));
-jest.mock('./api/buildInfo');
-jest.mock('./api/ruler', () => ({
- rulerUrlBuilder: jest.requireActual('./api/ruler').rulerUrlBuilder,
+jest.mock('../api/buildInfo');
+jest.mock('../api/ruler', () => ({
+ rulerUrlBuilder: jest.requireActual('../api/ruler').rulerUrlBuilder,
fetchRulerRules: jest.fn(),
fetchRulerRulesGroup: jest.fn(),
fetchRulerRulesNamespace: jest.fn(),
@@ -36,8 +36,8 @@ jest.mock('app/features/query/components/QueryEditorRow', () => ({
QueryEditorRow: () => hi
,
}));
-jest.mock('./components/rule-editor/util', () => {
- const originalModule = jest.requireActual('./components/rule-editor/util');
+jest.mock('../components/rule-editor/util', () => {
+ const originalModule = jest.requireActual('../components/rule-editor/util');
return {
...originalModule,
getThresholdsForQueries: jest.fn(() => ({})),
diff --git a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
similarity index 85%
rename from public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
index 21ba09cb12f..d70177fc7a9 100644
--- a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
@@ -5,15 +5,15 @@ import { screen } from 'test/test-utils';
import { selectors } from '@grafana/e2e-selectors';
import { AccessControlAction } from 'app/types';
-import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
-import { setupMswServer } from './mockApi';
-import { grantUserPermissions } from './mocks';
-import { GROUP_3, NAMESPACE_2 } from './mocks/mimirRulerApi';
-import { mimirDataSource } from './mocks/server/configure';
-import { MIMIR_DATASOURCE_UID } from './mocks/server/constants';
-import { captureRequests, serializeRequests } from './mocks/server/events';
+import { ExpressionEditorProps } from '../components/rule-editor/ExpressionEditor';
+import { setupMswServer } from '../mockApi';
+import { grantUserPermissions } from '../mocks';
+import { GROUP_3, NAMESPACE_2 } from '../mocks/mimirRulerApi';
+import { mimirDataSource } from '../mocks/server/configure';
+import { MIMIR_DATASOURCE_UID } from '../mocks/server/constants';
+import { captureRequests, serializeRequests } from '../mocks/server/events';
-jest.mock('./components/rule-editor/ExpressionEditor', () => ({
+jest.mock('../components/rule-editor/ExpressionEditor', () => ({
// eslint-disable-next-line react/display-name
ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => (
onChange(e.target.value)} />
diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx
similarity index 95%
rename from public/app/features/alerting/unified/RuleEditorExisting.test.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx
index 15ecae4e3ec..1781ab75d2b 100644
--- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx
@@ -7,15 +7,15 @@ import { setFolderResponse } from 'app/features/alerting/unified/mocks/server/co
import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants';
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
import { DashboardSearchItemType } from 'app/features/search/types';
+import { AccessControlAction } from 'app/types';
-import { AccessControlAction } from '../../../types';
+import { setupMswServer } from '../mockApi';
+import { grantUserPermissions, mockDataSource, mockFolder } from '../mocks';
+import { grafanaRulerRule } from '../mocks/grafanaRulerApi';
+import { setupDataSources } from '../testSetup/datasources';
+import { Annotation } from '../utils/constants';
import RuleEditor from './RuleEditor';
-import { setupMswServer } from './mockApi';
-import { grantUserPermissions, mockDataSource, mockFolder } from './mocks';
-import { grafanaRulerRule } from './mocks/grafanaRulerApi';
-import { setupDataSources } from './testSetup/datasources';
-import { Annotation } from './utils/constants';
jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({
AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) => {actions}
,
diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
similarity index 94%
rename from public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
index e07f62cd7c1..94888d4c9b1 100644
--- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
@@ -9,10 +9,10 @@ import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { PROMETHEUS_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants';
import { AccessControlAction } from 'app/types';
-import { grantUserPermissions, mockDataSource } from './mocks';
-import { grafanaRulerGroup } from './mocks/grafanaRulerApi';
-import { captureRequests, serializeRequests } from './mocks/server/events';
-import { setupDataSources } from './testSetup/datasources';
+import { grantUserPermissions, mockDataSource } from '../mocks';
+import { grafanaRulerGroup } from '../mocks/grafanaRulerApi';
+import { captureRequests, serializeRequests } from '../mocks/server/events';
+import { setupDataSources } from '../testSetup/datasources';
jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({
AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) => {actions}
,
diff --git a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx
similarity index 88%
rename from public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx
rename to public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx
index 766253febf1..8e979828aa9 100644
--- a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorRecordingRule.test.tsx
@@ -7,14 +7,14 @@ import { byText } from 'testing-library-selector';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { AccessControlAction } from 'app/types';
-import { RecordingRuleEditorProps } from './components/rule-editor/RecordingRuleEditor';
-import { grantUserPermissions } from './mocks';
-import { GROUP_3, NAMESPACE_2 } from './mocks/mimirRulerApi';
-import { mimirDataSource } from './mocks/server/configure';
-import { MIMIR_DATASOURCE_UID } from './mocks/server/constants';
-import { captureRequests, serializeRequests } from './mocks/server/events';
+import { RecordingRuleEditorProps } from '../components/rule-editor/RecordingRuleEditor';
+import { grantUserPermissions } from '../mocks';
+import { GROUP_3, NAMESPACE_2 } from '../mocks/mimirRulerApi';
+import { mimirDataSource } from '../mocks/server/configure';
+import { MIMIR_DATASOURCE_UID } from '../mocks/server/constants';
+import { captureRequests, serializeRequests } from '../mocks/server/events';
-jest.mock('./components/rule-editor/RecordingRuleEditor', () => ({
+jest.mock('../components/rule-editor/RecordingRuleEditor', () => ({
RecordingRuleEditor: ({ queries, onChangeQuery }: Pick) => {
const onChange = (expr: string) => {
const query = queries[0];
diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap b/public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorCloudRules.test.tsx.snap
similarity index 100%
rename from public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap
rename to public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorCloudRules.test.tsx.snap
diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap b/public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap
similarity index 100%
rename from public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap
rename to public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap
diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap b/public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorRecordingRule.test.tsx.snap
similarity index 100%
rename from public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap
rename to public/app/features/alerting/unified/rule-editor/__snapshots__/RuleEditorRecordingRule.test.tsx.snap
diff --git a/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts b/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts
new file mode 100644
index 00000000000..800b95a6193
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-editor/formDefaults.test.ts
@@ -0,0 +1,196 @@
+import { config } from '@grafana/runtime';
+
+import { mockAlertQuery, mockDataSource, reduceExpression, thresholdExpression } from '../mocks';
+import { testWithFeatureToggles } from '../test/test-utils';
+import { RuleFormType } from '../types/rule-form';
+import { Annotation } from '../utils/constants';
+import { DataSourceType, getDefaultOrFirstCompatibleDataSource } from '../utils/datasource';
+import { MANUAL_ROUTING_KEY, getDefaultQueries } from '../utils/rule-form';
+
+import { formValuesFromQueryParams, getDefaultFormValues, getDefautManualRouting } from './formDefaults';
+import { isAlertQueryOfAlertData } from './formProcessing';
+
+jest.mock('../utils/datasource');
+
+const mocks = {
+ getDefaultOrFirstCompatibleDataSource: jest.mocked(getDefaultOrFirstCompatibleDataSource),
+};
+
+// Setup mock implementation
+mocks.getDefaultOrFirstCompatibleDataSource.mockReturnValue(
+ mockDataSource({
+ type: DataSourceType.Prometheus,
+ })
+);
+
+// TODO Not sure why queries are an empty array in the default form values
+const defaultFormValues = {
+ ...getDefaultFormValues(),
+ queries: getDefaultQueries(),
+};
+
+describe('formValuesFromQueryParams', () => {
+ it('should return default values when given invalid JSON', () => {
+ const result = formValuesFromQueryParams('invalid json', RuleFormType.grafana);
+
+ expect(result).toEqual(defaultFormValues);
+ });
+
+ it('should normalize annotations', () => {
+ const ruleDefinition = JSON.stringify({
+ annotations: [
+ { key: 'custom', value: 'my custom annotation' },
+ { key: Annotation.runbookURL, value: 'runbook annotation' },
+ { key: 'custom-2', value: 'custom annotation v2' },
+ { key: Annotation.summary, value: 'summary annotation' },
+ { key: 'custom-3', value: 'custom annotation v3' },
+ { key: Annotation.description, value: 'description annotation' },
+ ],
+ });
+
+ const result = formValuesFromQueryParams(ruleDefinition, RuleFormType.grafana);
+
+ const [summary, description, runbookURL, ...rest] = result.annotations;
+
+ expect(summary).toEqual({ key: Annotation.summary, value: 'summary annotation' });
+ expect(description).toEqual({ key: Annotation.description, value: 'description annotation' });
+ expect(runbookURL).toEqual({ key: Annotation.runbookURL, value: 'runbook annotation' });
+ expect(rest).toContainEqual({ key: 'custom', value: 'my custom annotation' });
+ expect(rest).toContainEqual({ key: 'custom-2', value: 'custom annotation v2' });
+ expect(rest).toContainEqual({ key: 'custom-3', value: 'custom annotation v3' });
+ });
+
+ it('should disable simplified query editor when query switch mode is disabled', () => {
+ const result = formValuesFromQueryParams(JSON.stringify({}), RuleFormType.grafana);
+
+ expect(result.editorSettings).toBeDefined();
+ expect(result.editorSettings!.simplifiedQueryEditor).toBe(false);
+ });
+
+ describe('when simplified query editor is enabled', () => {
+ testWithFeatureToggles(['alertingQueryAndExpressionsStepMode']);
+
+ it('should enable simplified query editor if queries are transformable to simple condition', () => {
+ const result = formValuesFromQueryParams(
+ JSON.stringify({
+ queries: [mockAlertQuery(), reduceExpression, thresholdExpression],
+ }),
+ RuleFormType.grafana
+ );
+
+ expect(result.editorSettings).toBeDefined();
+ expect(result.editorSettings!.simplifiedQueryEditor).toBe(true);
+ });
+
+ it('should disable simplified query editor if queries are not transformable to simple condition', () => {
+ const result = formValuesFromQueryParams(
+ JSON.stringify({
+ queries: [mockAlertQuery(), mockAlertQuery(), thresholdExpression],
+ }),
+ RuleFormType.grafana
+ );
+
+ expect(result.editorSettings).toBeDefined();
+ expect(result.editorSettings!.simplifiedQueryEditor).toBe(false);
+ });
+ });
+
+ it('should default to instant queries for loki and prometheus if not specified', () => {
+ const result = formValuesFromQueryParams(
+ JSON.stringify({
+ queries: [
+ mockAlertQuery({ datasourceUid: 'loki', model: { refId: 'A', datasource: { type: DataSourceType.Loki } } }),
+ mockAlertQuery({
+ datasourceUid: 'prometheus',
+ model: { refId: 'B', datasource: { type: DataSourceType.Prometheus } },
+ }),
+ ],
+ }),
+ RuleFormType.grafana
+ );
+
+ const [lokiQuery, prometheusQuery] = result.queries.filter(isAlertQueryOfAlertData);
+
+ expect(lokiQuery.model.instant).toBe(true);
+ expect(lokiQuery.model.range).toBe(false);
+ expect(prometheusQuery.model.instant).toBe(true);
+ expect(prometheusQuery.model.range).toBe(false);
+ });
+
+ it('should preserver instant and range values if specified', () => {
+ const result = formValuesFromQueryParams(
+ JSON.stringify({
+ queries: [
+ mockAlertQuery({
+ datasourceUid: 'loki',
+ model: { refId: 'A', datasource: { type: DataSourceType.Loki }, instant: true, range: false },
+ }),
+ mockAlertQuery({
+ datasourceUid: 'prometheus',
+ model: { refId: 'B', datasource: { type: DataSourceType.Prometheus }, instant: false, range: true },
+ }),
+ ],
+ }),
+ RuleFormType.grafana
+ );
+
+ const [lokiQuery, prometheusQuery] = result.queries.filter(isAlertQueryOfAlertData);
+
+ expect(lokiQuery.model.instant).toBe(true);
+ expect(lokiQuery.model.range).toBe(false);
+ expect(prometheusQuery.model.range).toBe(true);
+ expect(prometheusQuery.model.instant).toBe(false);
+ });
+
+ it('should reveal hidden queries', () => {
+ const ruleDefinition = JSON.stringify({
+ queries: [
+ { refId: 'A', model: { refId: 'A', hide: true } },
+ { refId: 'B', model: { refId: 'B', hide: false } },
+ { refId: 'C', model: { refId: 'C' } },
+ ],
+ });
+
+ const result = formValuesFromQueryParams(ruleDefinition, RuleFormType.grafana);
+
+ expect(result.queries.length).toBe(3);
+
+ const [q1, q2, q3] = result.queries;
+ expect(q1.refId).toBe('A');
+ expect(q2.refId).toBe('B');
+ expect(q3.refId).toBe('C');
+ expect(q1.model).not.toHaveProperty('hide');
+ expect(q2.model).not.toHaveProperty('hide');
+ expect(q3.model).not.toHaveProperty('hide');
+ });
+});
+
+describe('getDefaultManualRouting', () => {
+ afterEach(() => {
+ window.localStorage.clear();
+ });
+
+ it('returns false if the feature toggle is not enabled', () => {
+ config.featureToggles.alertingSimplifiedRouting = false;
+ expect(getDefautManualRouting()).toBe(false);
+ });
+
+ it('returns true if the feature toggle is enabled and localStorage is not set', () => {
+ config.featureToggles.alertingSimplifiedRouting = true;
+ expect(getDefautManualRouting()).toBe(true);
+ });
+
+ it('returns false if the feature toggle is enabled and localStorage is set to "false"', () => {
+ config.featureToggles.alertingSimplifiedRouting = true;
+ localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
+ expect(getDefautManualRouting()).toBe(false);
+ });
+
+ it('returns true if the feature toggle is enabled and localStorage is set to any value other than "false"', () => {
+ config.featureToggles.alertingSimplifiedRouting = true;
+ localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
+ expect(getDefautManualRouting()).toBe(true);
+ localStorage.removeItem(MANUAL_ROUTING_KEY);
+ expect(getDefautManualRouting()).toBe(true);
+ });
+});
diff --git a/public/app/features/alerting/unified/rule-editor/formDefaults.ts b/public/app/features/alerting/unified/rule-editor/formDefaults.ts
new file mode 100644
index 00000000000..7c6a0c824c0
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-editor/formDefaults.ts
@@ -0,0 +1,157 @@
+import { clamp } from 'lodash';
+
+import { config } from '@grafana/runtime';
+import { RuleWithLocation } from 'app/types/unified-alerting';
+import { GrafanaAlertStateDecision, RulerRuleDTO } from 'app/types/unified-alerting-dto';
+
+import { RuleFormType, RuleFormValues } from '../types/rule-form';
+// TODO Ideally all of these should be moved here
+import { getRulesAccess } from '../utils/access-control';
+import { defaultAnnotations } from '../utils/constants';
+import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
+import {
+ MANUAL_ROUTING_KEY,
+ SIMPLIFIED_QUERY_EDITOR_KEY,
+ getDefaultQueries,
+ rulerRuleToFormValues,
+} from '../utils/rule-form';
+import { isGrafanaRecordingRuleByType } from '../utils/rules';
+import { formatPrometheusDuration, safeParsePrometheusDuration } from '../utils/time';
+
+import {
+ normalizeDefaultAnnotations,
+ revealHiddenQueries,
+ setInstantOrRange,
+ setQueryEditorSettings,
+} from './formProcessing';
+
+// even if the min interval is < 1m we should default to 1m, but allow arbitrary values for minInterval > 1m
+const GROUP_EVALUATION_MIN_INTERVAL_MS = safeParsePrometheusDuration(config.unifiedAlerting?.minInterval ?? '10s');
+const GROUP_EVALUATION_INTERVAL_LOWER_BOUND = safeParsePrometheusDuration('1m');
+const GROUP_EVALUATION_INTERVAL_UPPER_BOUND = Infinity;
+
+export const DEFAULT_GROUP_EVALUATION_INTERVAL = formatPrometheusDuration(
+ clamp(GROUP_EVALUATION_MIN_INTERVAL_MS, GROUP_EVALUATION_INTERVAL_LOWER_BOUND, GROUP_EVALUATION_INTERVAL_UPPER_BOUND)
+);
+export const getDefaultFormValues = (): RuleFormValues => {
+ const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess();
+
+ return Object.freeze({
+ name: '',
+ uid: '',
+ labels: [{ key: '', value: '' }],
+ annotations: defaultAnnotations,
+ dataSourceName: GRAFANA_RULES_SOURCE_NAME, // let's use Grafana-managed alert rule by default
+ type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts
+ group: '',
+
+ // grafana
+ folder: undefined,
+ queries: [],
+ recordingRulesQueries: [],
+ condition: '',
+ noDataState: GrafanaAlertStateDecision.NoData,
+ execErrState: GrafanaAlertStateDecision.Error,
+ evaluateFor: DEFAULT_GROUP_EVALUATION_INTERVAL,
+ evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
+ manualRouting: getDefautManualRouting(), // we default to true if the feature toggle is enabled and the user hasn't set local storage to false
+ contactPoints: {},
+ overrideGrouping: false,
+ overrideTimings: false,
+ muteTimeIntervals: [],
+ editorSettings: getDefaultEditorSettings(),
+
+ // cortex / loki
+ namespace: '',
+ expression: '',
+ forTime: 1,
+ forTimeUnit: 'm',
+ });
+};
+
+export const getDefautManualRouting = () => {
+ // first check if feature toggle for simplified routing is enabled
+ const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false;
+ if (!simplifiedRoutingToggleEnabled) {
+ return false;
+ }
+ //then, check in local storage if the user has enabled simplified routing
+ // if it's not set, we'll default to true
+ const manualRouting = localStorage.getItem(MANUAL_ROUTING_KEY);
+ return manualRouting !== 'false';
+};
+
+function getDefaultEditorSettings() {
+ const editorSettingsEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false;
+ if (!editorSettingsEnabled) {
+ return undefined;
+ }
+ //then, check in local storage if the user has saved last rule with sections simplified
+ const queryEditorSettings = localStorage.getItem(SIMPLIFIED_QUERY_EDITOR_KEY);
+ const notificationStepSettings = localStorage.getItem(MANUAL_ROUTING_KEY);
+ return {
+ simplifiedQueryEditor: queryEditorSettings !== 'false',
+ simplifiedNotificationEditor: notificationStepSettings !== 'false',
+ };
+}
+
+export function formValuesFromQueryParams(ruleDefinition: string, type: RuleFormType): RuleFormValues {
+ let ruleFromQueryParams: Partial;
+
+ try {
+ ruleFromQueryParams = JSON.parse(ruleDefinition);
+ } catch (err) {
+ return {
+ ...getDefaultFormValues(),
+ queries: getDefaultQueries(),
+ };
+ }
+
+ return setQueryEditorSettings(
+ setInstantOrRange(
+ revealHiddenQueries({
+ ...getDefaultFormValues(),
+ ...ruleFromQueryParams,
+ annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []),
+ queries: ruleFromQueryParams.queries ?? getDefaultQueries(),
+ type: type || RuleFormType.grafana,
+ evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
+ })
+ )
+ );
+}
+
+export function formValuesFromPrefill(rule: Partial): RuleFormValues {
+ return revealHiddenQueries({
+ ...getDefaultFormValues(),
+ ...rule,
+ });
+}
+
+export function formValuesFromExistingRule(rule: RuleWithLocation) {
+ return revealHiddenQueries(rulerRuleToFormValues(rule));
+}
+
+export function defaultFormValuesForRuleType(ruleType: RuleFormType): RuleFormValues {
+ return {
+ ...getDefaultFormValues(),
+ condition: 'C',
+ queries: getDefaultQueries(isGrafanaRecordingRuleByType(ruleType)),
+ type: ruleType,
+ evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
+ };
+}
+
+// TODO This function is not 100% valid. There is no support for cloud form type because
+// it's not valid from the path param point of view.
+export function translateRouteParamToRuleType(param = ''): RuleFormType {
+ if (param === 'recording') {
+ return RuleFormType.cloudRecording;
+ }
+
+ if (param === 'grafana-recording') {
+ return RuleFormType.grafanaRecording;
+ }
+
+ return RuleFormType.grafana;
+}
diff --git a/public/app/features/alerting/unified/rule-editor/formProcessing.ts b/public/app/features/alerting/unified/rule-editor/formProcessing.ts
new file mode 100644
index 00000000000..c9fa1951417
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-editor/formProcessing.ts
@@ -0,0 +1,150 @@
+import { omit } from 'lodash';
+
+import { config } from '@grafana/runtime';
+import { isExpressionQuery } from 'app/features/expressions/guards';
+import { ExpressionQuery, ExpressionQueryType, ReducerMode } from 'app/features/expressions/types';
+import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
+
+import { SimpleConditionIdentifier } from '../components/rule-editor/query-and-alert-condition/SimpleCondition';
+import { KVObject, RuleFormValues } from '../types/rule-form';
+import { defaultAnnotations } from '../utils/constants';
+import { DataSourceType } from '../utils/datasource';
+
+export function setQueryEditorSettings(values: RuleFormValues): RuleFormValues {
+ const isQuerySwitchModeEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false;
+
+ if (!isQuerySwitchModeEnabled) {
+ return {
+ ...values,
+ editorSettings: {
+ simplifiedQueryEditor: false,
+ simplifiedNotificationEditor: true, // actually it doesn't matter in this case
+ },
+ };
+ }
+
+ // data queries only
+ const dataQueries = values.queries.filter((query) => !isExpressionQuery(query.model));
+
+ // expression queries only
+ const expressionQueries = values.queries.filter((query) => isExpressionQueryInAlert(query));
+
+ const queryParamsAreTransformable = areQueriesTransformableToSimpleCondition(dataQueries, expressionQueries);
+ return {
+ ...values,
+ editorSettings: {
+ simplifiedQueryEditor: queryParamsAreTransformable,
+ simplifiedNotificationEditor: true,
+ },
+ };
+}
+
+export function setInstantOrRange(values: RuleFormValues): RuleFormValues {
+ return {
+ ...values,
+ queries: values.queries?.map((query) => {
+ if (isExpressionQuery(query.model)) {
+ return query;
+ }
+ // data query
+ const defaultToInstant =
+ query.model.datasource?.type === DataSourceType.Loki ||
+ query.model.datasource?.type === DataSourceType.Prometheus;
+ const isInstant =
+ 'instant' in query.model && query.model.instant !== undefined ? query.model.instant : defaultToInstant;
+ return {
+ ...query,
+ model: {
+ ...query.model,
+ instant: isInstant,
+ range: !isInstant, // we cannot have both instant and range queries in alerting
+ },
+ };
+ }),
+ };
+}
+
+export function areQueriesTransformableToSimpleCondition(
+ dataQueries: Array>,
+ expressionQueries: Array>
+) {
+ if (dataQueries.length !== 1) {
+ return false;
+ }
+ const singleReduceExpressionInInstantQuery =
+ 'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1;
+
+ if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) {
+ return false;
+ }
+
+ const query = dataQueries[0];
+
+ if (query.refId !== SimpleConditionIdentifier.queryId) {
+ return false;
+ }
+
+ const reduceExpressionIndex = expressionQueries.findIndex(
+ (query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId
+ );
+ const reduceExpression = expressionQueries.at(reduceExpressionIndex);
+ const reduceOk =
+ reduceExpression &&
+ reduceExpressionIndex === 0 &&
+ (reduceExpression.model.settings?.mode === ReducerMode.Strict ||
+ reduceExpression.model.settings?.mode === undefined);
+
+ const thresholdExpressionIndex = expressionQueries.findIndex(
+ (query) =>
+ query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId
+ );
+ const thresholdExpression = expressionQueries.at(thresholdExpressionIndex);
+ const conditions = thresholdExpression?.model.conditions ?? [];
+ const thresholdIndexOk = singleReduceExpressionInInstantQuery
+ ? thresholdExpressionIndex === 0
+ : thresholdExpressionIndex === 1;
+ const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined;
+ return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk);
+}
+
+export function isExpressionQueryInAlert(
+ query: AlertQuery
+): query is AlertQuery {
+ return isExpressionQuery(query.model);
+}
+
+export function isAlertQueryOfAlertData(
+ query: AlertQuery
+): query is AlertQuery {
+ return !isExpressionQuery(query.model);
+}
+
+// the backend will always execute "hidden" queries, so we have no choice but to remove the property in the front-end
+// to avoid confusion. The query editor shows them as "disabled" and that's a different semantic meaning.
+// furthermore the "AlertingQueryRunner" calls `filterQuery` on each data source and those will skip running queries that are "hidden"."
+// It seems like we have no choice but to act like "hidden" queries don't exist in alerting.
+export const revealHiddenQueries = (ruleDefinition: RuleFormValues): RuleFormValues => {
+ return {
+ ...ruleDefinition,
+ queries: ruleDefinition.queries?.map((query) => omit(query, 'model.hide')),
+ };
+};
+
+export function normalizeDefaultAnnotations(annotations: KVObject[]) {
+ const orderedAnnotations = [...annotations];
+ const defaultAnnotationKeys = defaultAnnotations.map((annotation) => annotation.key);
+
+ defaultAnnotationKeys.forEach((defaultAnnotationKey, index) => {
+ const fieldIndex = orderedAnnotations.findIndex((field) => field.key === defaultAnnotationKey);
+
+ if (fieldIndex === -1) {
+ //add the default annotation if abstent
+ const emptyValue = { key: defaultAnnotationKey, value: '' };
+ orderedAnnotations.splice(index, 0, emptyValue);
+ } else if (fieldIndex !== index) {
+ //move it to the correct position if present
+ orderedAnnotations.splice(index, 0, orderedAnnotations.splice(fieldIndex, 1)[0]);
+ }
+ });
+ return orderedAnnotations;
+}
diff --git a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx
index b436e84941a..de51e6dde8b 100644
--- a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx
+++ b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx
@@ -19,9 +19,9 @@ grantUserPermissions([AccessControlAction.AlertingRuleExternalRead]);
setupMswServer();
-const mimirGroups = alertingFactory.group.buildList(5000, { file: 'test-mimir-namespace' });
-alertingFactory.group.rewindSequence();
-const prometheusGroups = alertingFactory.group.buildList(200, { file: 'test-prometheus-namespace' });
+const mimirGroups = alertingFactory.prometheus.group.buildList(5000, { file: 'test-mimir-namespace' });
+alertingFactory.prometheus.group.rewindSequence();
+const prometheusGroups = alertingFactory.prometheus.group.buildList(200, { file: 'test-prometheus-namespace' });
const mimirDs = alertingFactory.dataSource.build({ name: 'Mimir', uid: 'mimir' });
const prometheusDs = alertingFactory.dataSource.build({ name: 'Prometheus', uid: 'prometheus' });
diff --git a/public/app/features/alerting/unified/rule-list/GroupedView.test.tsx b/public/app/features/alerting/unified/rule-list/GroupedView.test.tsx
index adb8b83c077..953bce50ead 100644
--- a/public/app/features/alerting/unified/rule-list/GroupedView.test.tsx
+++ b/public/app/features/alerting/unified/rule-list/GroupedView.test.tsx
@@ -19,9 +19,9 @@ grantUserPermissions([AccessControlAction.AlertingRuleExternalRead]);
setupMswServer();
-const mimirGroups = alertingFactory.group.buildList(500, { file: 'test-mimir-namespace' });
-alertingFactory.group.rewindSequence();
-const prometheusGroups = alertingFactory.group.buildList(130, { file: 'test-prometheus-namespace' });
+const mimirGroups = alertingFactory.prometheus.group.buildList(500, { file: 'test-mimir-namespace' });
+alertingFactory.prometheus.group.rewindSequence();
+const prometheusGroups = alertingFactory.prometheus.group.buildList(130, { file: 'test-prometheus-namespace' });
const mimirDs = alertingFactory.dataSource.build({ name: 'Mimir', uid: 'mimir' });
const prometheusDs = alertingFactory.dataSource.build({ name: 'Prometheus', uid: 'prometheus' });
diff --git a/public/app/features/alerting/unified/test/test-utils.ts b/public/app/features/alerting/unified/test/test-utils.ts
index 88de7e27984..66598dea083 100644
--- a/public/app/features/alerting/unified/test/test-utils.ts
+++ b/public/app/features/alerting/unified/test/test-utils.ts
@@ -2,6 +2,10 @@ import { act } from '@testing-library/react';
import { FeatureToggles } from '@grafana/data';
import { config } from '@grafana/runtime';
+import { AccessControlAction } from 'app/types';
+
+import { grantUserPermissions } from '../mocks';
+import { setFolderAccessControl } from '../mocks/server/configure';
/**
* Flushes out microtasks so we don't get warnings from `@floating-ui/react`
@@ -47,3 +51,13 @@ export const testWithLicenseFeatures = (features: string[]) => {
config.licenseInfo.enabledFeatures = originalFeatures;
});
};
+
+/**
+ * "Grants" permissions via contextSrv mock, and additionally sets folder access control
+ * API response to match
+ */
+export const grantPermissionsHelper = (permissions: AccessControlAction[]) => {
+ const permissionsHash = permissions.reduce((hash, permission) => ({ ...hash, [permission]: true }), {});
+ grantUserPermissions(permissions);
+ setFolderAccessControl(permissionsHash);
+};
diff --git a/public/app/features/alerting/unified/utils/rule-form.test.ts b/public/app/features/alerting/unified/utils/rule-form.test.ts
index bfc7f86ea5c..80442e87984 100644
--- a/public/app/features/alerting/unified/utils/rule-form.test.ts
+++ b/public/app/features/alerting/unified/utils/rule-form.test.ts
@@ -1,20 +1,17 @@
import { PromQuery } from '@grafana/prometheus';
-import { config } from '@grafana/runtime';
import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto';
+import { getDefaultFormValues } from '../rule-editor/formDefaults';
import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form';
import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
import {
- MANUAL_ROUTING_KEY,
alertingRulerRuleToRuleForm,
cleanAnnotations,
cleanLabels,
formValuesToRulerGrafanaRuleDTO,
formValuesToRulerRuleDTO,
getContactPointsFromDTO,
- getDefaultFormValues,
- getDefautManualRouting,
getNotificationSettingsForDTO,
} from './rule-form';
@@ -227,36 +224,6 @@ describe('getNotificationSettingsForDTO', () => {
});
});
-describe('getDefautManualRouting', () => {
- afterEach(() => {
- window.localStorage.clear();
- });
-
- it('returns false if the feature toggle is not enabled', () => {
- config.featureToggles.alertingSimplifiedRouting = false;
- expect(getDefautManualRouting()).toBe(false);
- });
-
- it('returns true if the feature toggle is enabled and localStorage is not set', () => {
- config.featureToggles.alertingSimplifiedRouting = true;
- expect(getDefautManualRouting()).toBe(true);
- });
-
- it('returns false if the feature toggle is enabled and localStorage is set to "false"', () => {
- config.featureToggles.alertingSimplifiedRouting = true;
- localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
- expect(getDefautManualRouting()).toBe(false);
- });
-
- it('returns true if the feature toggle is enabled and localStorage is set to any value other than "false"', () => {
- config.featureToggles.alertingSimplifiedRouting = true;
- localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
- expect(getDefautManualRouting()).toBe(true);
- localStorage.removeItem(MANUAL_ROUTING_KEY);
- expect(getDefautManualRouting()).toBe(true);
- });
-});
-
describe('cleanAnnotations', () => {
it('should remove falsy KVs', () => {
const output = cleanAnnotations([{ key: '', value: '' }]);
diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts
index de5ee071296..ecea384f9e7 100644
--- a/public/app/features/alerting/unified/utils/rule-form.ts
+++ b/public/app/features/alerting/unified/utils/rule-form.ts
@@ -1,5 +1,3 @@
-import { clamp, omit } from 'lodash';
-
import {
DataQuery,
DataSourceInstanceSettings,
@@ -31,7 +29,6 @@ import {
AlertDataQuery,
AlertQuery,
Annotations,
- GrafanaAlertStateDecision,
GrafanaNotificationSettings,
GrafanaRuleDefinition,
Labels,
@@ -42,6 +39,8 @@ import {
} from 'app/types/unified-alerting-dto';
import { EvalFunction } from '../../state/alertDef';
+import { getDefaultFormValues } from '../rule-editor/formDefaults';
+import { normalizeDefaultAnnotations } from '../rule-editor/formProcessing';
import {
AlertManagerManualRouting,
ContactPoint,
@@ -51,8 +50,7 @@ import {
SimplifiedEditor,
} from '../types/rule-form';
-import { getRulesAccess } from './access-control';
-import { Annotation, defaultAnnotations } from './constants';
+import { Annotation } from './constants';
import {
DataSourceType,
GRAFANA_RULES_SOURCE_NAME,
@@ -68,84 +66,13 @@ import {
isGrafanaRulerRule,
isRecordingRulerRule,
} from './rules';
-import { formatPrometheusDuration, parseInterval, safeParsePrometheusDuration } from './time';
+import { parseInterval } from './time';
export type PromOrLokiQuery = PromQuery | LokiQuery;
export const MANUAL_ROUTING_KEY = 'grafana.alerting.manualRouting';
export const SIMPLIFIED_QUERY_EDITOR_KEY = 'grafana.alerting.simplifiedQueryEditor';
-// even if the min interval is < 1m we should default to 1m, but allow arbitrary values for minInterval > 1m
-const GROUP_EVALUATION_MIN_INTERVAL_MS = safeParsePrometheusDuration(config.unifiedAlerting?.minInterval ?? '10s');
-const GROUP_EVALUATION_INTERVAL_LOWER_BOUND = safeParsePrometheusDuration('1m');
-const GROUP_EVALUATION_INTERVAL_UPPER_BOUND = Infinity;
-
-export const DEFAULT_GROUP_EVALUATION_INTERVAL = formatPrometheusDuration(
- clamp(GROUP_EVALUATION_MIN_INTERVAL_MS, GROUP_EVALUATION_INTERVAL_LOWER_BOUND, GROUP_EVALUATION_INTERVAL_UPPER_BOUND)
-);
-
-export const getDefaultFormValues = (): RuleFormValues => {
- const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess();
-
- return Object.freeze({
- name: '',
- uid: '',
- labels: [{ key: '', value: '' }],
- annotations: defaultAnnotations,
- dataSourceName: GRAFANA_RULES_SOURCE_NAME, // let's use Grafana-managed alert rule by default
- type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts
- group: '',
-
- // grafana
- folder: undefined,
- queries: [],
- recordingRulesQueries: [],
- condition: '',
- noDataState: GrafanaAlertStateDecision.NoData,
- execErrState: GrafanaAlertStateDecision.Error,
- evaluateFor: DEFAULT_GROUP_EVALUATION_INTERVAL,
- evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
- manualRouting: getDefautManualRouting(), // we default to true if the feature toggle is enabled and the user hasn't set local storage to false
- contactPoints: {},
- overrideGrouping: false,
- overrideTimings: false,
- muteTimeIntervals: [],
- editorSettings: getDefaultEditorSettings(),
-
- // cortex / loki
- namespace: '',
- expression: '',
- forTime: 1,
- forTimeUnit: 'm',
- });
-};
-
-export const getDefautManualRouting = () => {
- // first check if feature toggle for simplified routing is enabled
- const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false;
- if (!simplifiedRoutingToggleEnabled) {
- return false;
- }
- //then, check in local storage if the user has enabled simplified routing
- // if it's not set, we'll default to true
- const manualRouting = localStorage.getItem(MANUAL_ROUTING_KEY);
- return manualRouting !== 'false';
-};
-
-function getDefaultEditorSettings() {
- const editorSettingsEnabled = config.featureToggles.alertingQueryAndExpressionsStepMode ?? false;
- if (!editorSettingsEnabled) {
- return undefined;
- }
- //then, check in local storage if the user has saved last rule with sections simplified
- const queryEditorSettings = localStorage.getItem(SIMPLIFIED_QUERY_EDITOR_KEY);
- const notificationStepSettings = localStorage.getItem(MANUAL_ROUTING_KEY);
- return {
- simplifiedQueryEditor: queryEditorSettings !== 'false',
- simplifiedNotificationEditor: notificationStepSettings !== 'false',
- };
-}
-
export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO {
const { name, expression, forTime, forTimeUnit, keepFiringForTime, keepFiringForTimeUnit, type } = values;
@@ -184,26 +111,6 @@ export function listifyLabelsOrAnnotations(item: Labels | Annotations | undefine
return list;
}
-//make sure default annotations are always shown in order even if empty
-export function normalizeDefaultAnnotations(annotations: KVObject[]) {
- const orderedAnnotations = [...annotations];
- const defaultAnnotationKeys = defaultAnnotations.map((annotation) => annotation.key);
-
- defaultAnnotationKeys.forEach((defaultAnnotationKey, index) => {
- const fieldIndex = orderedAnnotations.findIndex((field) => field.key === defaultAnnotationKey);
-
- if (fieldIndex === -1) {
- //add the default annotation if abstent
- const emptyValue = { key: defaultAnnotationKey, value: '' };
- orderedAnnotations.splice(index, 0, emptyValue);
- } else if (fieldIndex !== index) {
- //move it to the correct position if present
- orderedAnnotations.splice(index, 0, orderedAnnotations.splice(fieldIndex, 1)[0]);
- }
- });
- return orderedAnnotations;
-}
-
export function getNotificationSettingsForDTO(
manualRouting: boolean,
contactPoints?: AlertManagerManualRouting
@@ -902,21 +809,6 @@ export function isPromOrLokiQuery(model: AlertDataQuery): model is PromOrLokiQue
return 'expr' in model;
}
-// the backend will always execute "hidden" queries, so we have no choice but to remove the property in the front-end
-// to avoid confusion. The query editor shows them as "disabled" and that's a different semantic meaning.
-// furthermore the "AlertingQueryRunner" calls `filterQuery` on each data source and those will skip running queries that are "hidden"."
-// It seems like we have no choice but to act like "hidden" queries don't exist in alerting.
-export const ignoreHiddenQueries = (ruleDefinition: RuleFormValues): RuleFormValues => {
- return {
- ...ruleDefinition,
- queries: ruleDefinition.queries?.map((query) => omit(query, 'model.hide')),
- };
-};
-
-export function formValuesFromExistingRule(rule: RuleWithLocation) {
- return ignoreHiddenQueries(rulerRuleToFormValues(rule));
-}
-
export function getInstantFromDataQuery(model: AlertDataQuery, type: string): boolean | undefined {
// if the datasource is not prometheus or loki, instant is defined in the model or defaults to undefined
if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) {
diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
index 7d93518167e..582e0855008 100644
--- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
+++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
@@ -223,7 +223,7 @@ async function buildTestSceneWithLibraryPanel() {
title: 'Panel A',
pluginId: 'table',
key: 'panel-12',
- $behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: '111' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: '111' })],
titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })],
$data: new SceneDataTransformer({
transformations: [
diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
index 70c468e9247..22b527b5bdc 100644
--- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
+++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
@@ -2,6 +2,7 @@ import { isEqual } from 'lodash';
import { locationUtil, UrlQueryMap } from '@grafana/data';
import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
+import { sceneGraph } from '@grafana/scenes';
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import { StateManagerBase } from 'app/core/services/StateManagerBase';
import { getMessageFromError, getMessageIdFromError, getStatusFromError } from 'app/core/utils/errors';
@@ -136,7 +137,10 @@ abstract class DashboardScenePageStateManagerBase
this.setState({ dashboard: dashboard, isLoading: false, options });
const measure = stopMeasure(LOAD_SCENE_MEASUREMENT);
+ const queryController = sceneGraph.getQueryController(dashboard);
+
trackDashboardSceneLoaded(dashboard, measure?.duration);
+ queryController?.startProfile(dashboard);
if (options.route !== DashboardRoutes.New) {
emitDashboardViewEvent({
diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
index fa309c8bd21..f204eae7138 100644
--- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
+++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
@@ -200,7 +200,6 @@ describe('PanelEditor', () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: true,
- title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,
@@ -239,7 +238,7 @@ describe('PanelEditor', () => {
// Wait for mock api to return and update the library panel
expect(libPanelBehavior.state._loadedPanel?.version).toBe(2);
expect(libPanelBehavior.state.name).toBe('changed name');
- expect(libPanelBehavior.state.title).toBe('changed title');
+ expect(panel.state.title).toBe('changed title');
expect((gridItem.state.body as VizPanel).state.title).toBe('changed title');
});
@@ -258,7 +257,6 @@ describe('PanelEditor', () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: true,
- title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,
diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx
index 264c053de17..3f54a519b70 100644
--- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx
+++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx
@@ -177,7 +177,6 @@ describe('PanelOptions', () => {
const libraryPanel = new LibraryPanelBehavior({
isLoaded: true,
- title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,
diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
index f298e7b19f9..cba0d0f9e01 100644
--- a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
+++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
@@ -32,6 +32,7 @@ describe('AddLibraryPanelWidget', () => {
const panelInfo: LibraryPanel = {
uid: 'uid',
model: {
+ title: 'model title',
type: 'timeseries',
},
name: 'name',
@@ -47,6 +48,8 @@ describe('AddLibraryPanelWidget', () => {
expect(panels.length).toBe(1);
expect(panel.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
expect(panel.state.key).toBe('panel-1');
+ expect(panel.state.title).toBe('model title');
+ expect(panel.state.hoverHeader).toBe(false);
});
it('should add library panel from menu and enter edit mode in a dashboard that is not already in edit mode', async () => {
@@ -69,6 +72,7 @@ describe('AddLibraryPanelWidget', () => {
const panelInfo: LibraryPanel = {
uid: 'uid',
model: {
+ title: 'model title',
type: 'timeseries',
},
name: 'name',
@@ -88,15 +92,16 @@ describe('AddLibraryPanelWidget', () => {
expect(panels.length).toBe(1);
expect(panel.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
expect(panel.state.key).toBe('panel-1');
+ expect(panel.state.title).toBe('model title');
expect(dashboard.state.isEditing).toBe(true);
});
it('should replace grid item when grid item state is passed', async () => {
const libPanel = new VizPanel({
- title: 'Panel Title',
+ title: 'Some panel title',
pluginId: 'table',
key: 'panel-1',
- $behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: 'uid' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: 'uid' })],
});
addLibPanelDrawer = new AddLibraryPanelDrawer({ panelToReplaceRef: libPanel.getRef() });
@@ -115,6 +120,7 @@ describe('AddLibraryPanelWidget', () => {
const panelInfo: LibraryPanel = {
uid: 'new_uid',
model: {
+ title: 'model title',
type: 'timeseries',
},
name: 'new_name',
@@ -132,6 +138,27 @@ describe('AddLibraryPanelWidget', () => {
expect(behavior).toBeInstanceOf(LibraryPanelBehavior);
expect(behavior.state.uid).toBe('new_uid');
expect(behavior.state.name).toBe('new_name');
+ expect(panels[0].state.title).toBe('model title');
+ });
+
+ it('should set hoverHeader to true if the library panel title is empty', () => {
+ const panelInfo: LibraryPanel = {
+ uid: 'uid',
+ model: {
+ title: '',
+ type: 'timeseries',
+ },
+ name: 'name',
+ version: 1,
+ type: 'timeseries',
+ };
+
+ addLibPanelDrawer.onAddLibraryPanel(panelInfo);
+
+ const panels = dashboard.state.body.getVizPanels();
+ const panel = panels[0];
+ expect(panel.state.title).toBe('');
+ expect(panel.state.hoverHeader).toBe(true);
});
});
diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
index e8e462005eb..1d4c4b198a5 100644
--- a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
+++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
@@ -26,6 +26,9 @@ export class AddLibraryPanelDrawer extends SceneObjectBase {
it('should re-run queries when library panel re-runs query', async () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: false,
- title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,
@@ -469,7 +468,6 @@ describe('DashboardDatasourceBehaviour', () => {
jest.spyOn(console, 'error').mockImplementation();
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: false,
- title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,
@@ -519,7 +517,6 @@ describe('DashboardDatasourceBehaviour', () => {
// Simulate library panel being loaded
libPanelBehavior.setState({
isLoaded: true,
- title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,
diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
index 2c54e174bea..80d4b19fa3a 100644
--- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
@@ -468,7 +468,7 @@ describe('DashboardScene', () => {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-4',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
});
scene.copyPanel(libVizPanel);
@@ -523,7 +523,7 @@ describe('DashboardScene', () => {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-4',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
})
);
@@ -542,7 +542,7 @@ describe('DashboardScene', () => {
const libPanel = new VizPanel({
title: 'Panel B',
pluginId: 'table',
- $behaviors: [new LibraryPanelBehavior({ title: 'title', name: 'lib panel', uid: 'abc', isLoaded: true })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'lib panel', uid: 'abc', isLoaded: true })],
});
const scene = buildTestScene({
@@ -907,7 +907,7 @@ function buildTestScene(overrides?: Partial) {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-5',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
}),
],
@@ -925,7 +925,7 @@ function buildTestScene(overrides?: Partial) {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-6',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ $behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
}),
],
diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx
index a3c358b10a4..b285610b5a0 100644
--- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx
@@ -374,7 +374,13 @@ export class DashboardScene extends SceneObjectBase {
}
public onRestore = async (version: DecoratedRevisionModel): Promise => {
- const versionRsp = await historySrv.restoreDashboard(version.uid, version.version);
+ let versionRsp;
+ if (config.featureToggles.kubernetesCliDashboards) {
+ // the id here is the resource version in k8s, use this instead to get the specific version
+ versionRsp = await historySrv.restoreDashboard(version.uid, version.id);
+ } else {
+ versionRsp = await historySrv.restoreDashboard(version.uid, version.version);
+ }
if (!Number.isInteger(versionRsp.version)) {
return false;
diff --git a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
index 02a363d5d3a..b6eb6872566 100644
--- a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
+++ b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
@@ -167,7 +167,7 @@ describe('LibraryPanelBehavior', () => {
});
async function buildTestSceneWithLibraryPanel() {
- const behavior = new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: '111' });
+ const behavior = new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: '111' });
const vizPanel = new VizPanel({
title: 'Panel A',
diff --git a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx
index 423cc2c34ee..44a1c0de5a8 100644
--- a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx
+++ b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx
@@ -17,8 +17,6 @@ import { AngularDeprecation } from './angular/AngularDeprecation';
import { DashboardGridItem } from './layout-default/DashboardGridItem';
export interface LibraryPanelBehaviorState extends SceneObjectState {
- // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
- title?: string;
uid: string;
name: string;
isLoaded?: boolean;
@@ -66,7 +64,7 @@ export class LibraryPanelBehavior extends SceneObjectBase uid === '*' || uid === oldModel.uid) !== -1,
+ onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title),
+ }),
registerDashboardMacro,
registerPanelInteractionsReporter,
new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }),
@@ -434,3 +439,40 @@ function trackIfEmpty(grid: SceneGridLayout) {
sub.unsubscribe();
};
}
+
+function getDashboardInteractionCallback(uid: string, title: string) {
+ return (e: SceneInteractionProfileEvent) => {
+ let interactionType = '';
+
+ if (e.origin === 'SceneTimeRange') {
+ interactionType = 'time-range-change';
+ } else if (e.origin === 'SceneRefreshPicker') {
+ interactionType = 'refresh';
+ } else if (e.origin === 'DashboardScene') {
+ interactionType = 'view';
+ } else if (e.origin.indexOf('Variable') > -1) {
+ interactionType = 'variable-change';
+ }
+ reportInteraction('dashboard-render', {
+ interactionType,
+ duration: e.duration,
+ networkDuration: e.networkDuration,
+ totalJSHeapSize: e.totalJSHeapSize,
+ usedJSHeapSize: e.usedJSHeapSize,
+ jsHeapSizeLimit: e.jsHeapSizeLimit,
+ });
+
+ logMeasurement(
+ `dashboard.${interactionType}`,
+ {
+ duration: e.duration,
+ networkDuration: e.networkDuration,
+ totalJSHeapSize: e.totalJSHeapSize,
+ usedJSHeapSize: e.usedJSHeapSize,
+ jsHeapSizeLimit: e.jsHeapSizeLimit,
+ timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration,
+ },
+ { dashboard: uid, title: title }
+ );
+ };
+}
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
index 03f9d93fd94..a0fc2a828ec 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
@@ -355,7 +355,6 @@ describe('transformSceneToSaveModel', () => {
$behaviors: [
new LibraryPanelBehavior({
name: 'Some lib panel panel',
- title: 'A panel',
uid: 'lib-panel-uid',
}),
],
@@ -399,7 +398,7 @@ describe('transformSceneToSaveModel', () => {
x: 0,
y: 0,
});
- expect(result.title).toBe('A panel');
+ expect(result.title).toBe('Panel blahh blah');
expect(result.transformations).toBeUndefined();
expect(result.fieldConfig).toBeUndefined();
expect(result.options).toBeUndefined();
@@ -851,7 +850,6 @@ describe('transformSceneToSaveModel', () => {
$behaviors: [
new LibraryPanelBehavior({
name: 'Some lib panel panel',
- title: 'A panel',
uid: 'lib-panel-uid',
}),
],
@@ -865,7 +863,7 @@ describe('transformSceneToSaveModel', () => {
expect(result[0]).toMatchObject({
id: 4,
- title: 'A panel',
+ title: 'Panel blahh blah',
libraryPanel: {
name: 'Some lib panel panel',
uid: 'lib-panel-uid',
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
index 4c8368fc1fd..c1b6c96ec07 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
@@ -188,7 +188,7 @@ export function vizPanelToPanel(
panel = {
id: getPanelIdForVizPanel(vizPanel),
- title: libPanel!.state.title,
+ title: vizPanel.state.title,
gridPos: gridPos,
libraryPanel: {
name: libPanel!.state.name,
diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
index 6d2b37219f6..e6449d0d3f6 100644
--- a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
@@ -112,44 +112,47 @@ describe('VersionsEditView', () => {
});
function getVersions() {
- return [
- {
- id: 4,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 3,
- restoredFrom: 0,
- version: 4,
- created: '2017-02-22T17:43:01-08:00',
- createdBy: 'admin',
- message: '',
- checked: false,
- },
- {
- id: 3,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 1,
- restoredFrom: 1,
- version: 3,
- created: '2017-02-22T17:43:01-08:00',
- createdBy: 'admin',
- message: '',
- checked: false,
- },
- {
- id: 2,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 1,
- restoredFrom: 1,
- version: 2,
- created: '2017-02-23T17:43:01-08:00',
- createdBy: 'admin',
- message: '',
- checked: false,
- },
- ];
+ return {
+ continueToken: '',
+ versions: [
+ {
+ id: 4,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 3,
+ restoredFrom: 0,
+ version: 4,
+ created: '2017-02-22T17:43:01-08:00',
+ createdBy: 'admin',
+ message: '',
+ checked: false,
+ },
+ {
+ id: 3,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 1,
+ restoredFrom: 1,
+ version: 3,
+ created: '2017-02-22T17:43:01-08:00',
+ createdBy: 'admin',
+ message: '',
+ checked: false,
+ },
+ {
+ id: 2,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 1,
+ restoredFrom: 1,
+ version: 2,
+ created: '2017-02-23T17:43:01-08:00',
+ createdBy: 'admin',
+ message: '',
+ checked: false,
+ },
+ ],
+ };
}
async function buildTestScene() {
diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx
index c2acbb9b7af..17256382d0b 100644
--- a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx
+++ b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
import { PageLayoutType, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
+import { config } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes';
import { Spinner, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
@@ -41,6 +42,7 @@ export class VersionsEditView extends SceneObjectBase imp
public static Component = VersionsEditorSettingsListView;
private _limit: number = VERSIONS_FETCH_LIMIT;
private _start = 0;
+ private _continueToken = '';
constructor(state: VersionsEditViewState) {
super({
@@ -102,14 +104,20 @@ export class VersionsEditView extends SceneObjectBase imp
this.setState({ isAppending: append });
+ const requestOptions = this._continueToken
+ ? { limit: this._limit, start: this._start, continueToken: this._continueToken }
+ : { limit: this._limit, start: this._start };
+
historySrv
- .getHistoryList(uid, { limit: this._limit, start: this._start })
+ .getHistoryList(uid, requestOptions)
.then((result) => {
this.setState({
isLoading: false,
- versions: [...(this.state.versions ?? []), ...this.decorateVersions(result)],
+ versions: [...(this.state.versions ?? []), ...this.decorateVersions(result.versions)],
});
this._start += this._limit;
+ // Update the continueToken for the next request, if available
+ this._continueToken = result.continueToken ?? '';
})
.catch((err) => console.log(err))
.finally(() => this.setState({ isAppending: false }));
@@ -127,9 +135,15 @@ export class VersionsEditView extends SceneObjectBase imp
if (!this._dashboard.state.uid) {
return;
}
-
- const lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.version);
- const rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.version);
+ let lhs, rhs;
+ if (config.featureToggles.kubernetesCliDashboards) {
+ // the id here is the resource version in k8s, use this instead to get the specific version
+ lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.id);
+ rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.id);
+ } else {
+ lhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, baseInfo.version);
+ rhs = await historySrv.getDashboardVersion(this._dashboard.state.uid, newInfo.version);
+ }
this.setState({
baseInfo,
@@ -145,6 +159,7 @@ export class VersionsEditView extends SceneObjectBase imp
};
public reset = () => {
+ this._continueToken = '';
this.setState({
baseInfo: undefined,
diffData: {
diff --git a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts
index c2caee31a0e..84a737d5a1d 100644
--- a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts
+++ b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts
@@ -62,11 +62,11 @@ describe('historySrv', () => {
describe('getDashboardVersion', () => {
it('should return a version object for the given dashboard id and version', () => {
- getMock.mockImplementation(() => Promise.resolve(versionsResponse[0]));
+ getMock.mockImplementation(() => Promise.resolve(versionsResponse.versions[0]));
historySrv = new HistorySrv();
return historySrv.getDashboardVersion(dash.uid, 4).then((version) => {
- expect(version).toEqual(versionsResponse[0]);
+ expect(version).toEqual(versionsResponse.versions[0]);
});
});
diff --git a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.ts b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.ts
index 61312b8ad45..95cd468ec63 100644
--- a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.ts
+++ b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.ts
@@ -4,6 +4,7 @@ import { Dashboard } from '@grafana/schema';
export interface HistoryListOpts {
limit: number;
start: number;
+ continueToken?: string;
}
export interface RevisionsModel {
diff --git a/public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts b/public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts
index 0de97c47a95..3fbefb31c92 100644
--- a/public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts
+++ b/public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts
@@ -1,51 +1,54 @@
export function versions() {
- return [
- {
- id: 4,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 3,
- restoredFrom: 0,
- version: 4,
- created: '2017-02-22T17:43:01-08:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 3,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 1,
- restoredFrom: 1,
- version: 3,
- created: '2017-02-22T17:43:01-08:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 2,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 0,
- restoredFrom: -1,
- version: 2,
- created: '2017-02-22T17:29:52-08:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 1,
- dashboardId: 1,
- dashboardUID: '_U4zObQMz',
- parentVersion: 0,
- restoredFrom: -1,
- slug: 'history-dashboard',
- version: 1,
- created: '2017-02-22T17:06:37-08:00',
- createdBy: 'admin',
- message: '',
- },
- ];
+ return {
+ continueToken: '',
+ versions: [
+ {
+ id: 4,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 3,
+ restoredFrom: 0,
+ version: 4,
+ created: '2017-02-22T17:43:01-08:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 3,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 1,
+ restoredFrom: 1,
+ version: 3,
+ created: '2017-02-22T17:43:01-08:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 2,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 0,
+ restoredFrom: -1,
+ version: 2,
+ created: '2017-02-22T17:29:52-08:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 1,
+ dashboardId: 1,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 0,
+ restoredFrom: -1,
+ slug: 'history-dashboard',
+ version: 1,
+ created: '2017-02-22T17:06:37-08:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ ],
+ };
}
export function restore(version: number, restoredFrom?: number) {
diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/UpsertSnapshot.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/UpsertSnapshot.tsx
index 5458db42ec1..57cd584afb1 100644
--- a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/UpsertSnapshot.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/UpsertSnapshot.tsx
@@ -5,8 +5,7 @@ import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { SceneObjectRef, VizPanel } from '@grafana/scenes';
import { Alert, Button, Divider, Field, RadioButtonGroup, Stack, Text, useStyles2 } from '@grafana/ui';
import { Input } from '@grafana/ui/src/components/Input/Input';
-import { t } from '@grafana/ui/src/utils/i18n';
-import { Trans } from 'app/core/internationalization';
+import { t, Trans } from 'app/core/internationalization';
import { getExpireOptions } from '../../ShareSnapshotTab';
diff --git a/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.test.ts
index e6e44c7a583..02dcc897936 100644
--- a/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.test.ts
+++ b/public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.test.ts
@@ -17,7 +17,6 @@ describe('PanelModelCompatibilityWrapper', () => {
const libPanel = new LibraryPanelBehavior({
uid: 'a',
name: 'aa',
- title: 'a',
});
panel.setState({
diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx
index 4b0b91ef6e6..16f8d84f237 100644
--- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx
+++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx
@@ -66,7 +66,7 @@ describe('VersionSettings', () => {
await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument());
const tableBodyRows = within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row');
- expect(tableBodyRows.length).toBe(versions.length);
+ expect(tableBodyRows.length).toBe(versions.versions.length);
const firstRow = within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row')[0];
@@ -76,7 +76,11 @@ describe('VersionSettings', () => {
test('does not render buttons if versions === 1', async () => {
// @ts-ignore
- historySrv.getHistoryList.mockResolvedValue(versions.slice(0, 1));
+ historySrv.getHistoryList.mockResolvedValue({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(0, 1),
+ });
+
setup();
expect(screen.queryByRole('button', { name: /show more versions/i })).not.toBeInTheDocument();
@@ -90,7 +94,11 @@ describe('VersionSettings', () => {
test('does not render show more button if versions < VERSIONS_FETCH_LIMIT', async () => {
// @ts-ignore
- historySrv.getHistoryList.mockResolvedValue(versions.slice(0, VERSIONS_FETCH_LIMIT - 5));
+ historySrv.getHistoryList.mockResolvedValue({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT - 5),
+ });
+
setup();
expect(screen.queryByRole('button', { name: /show more versions/i })).not.toBeInTheDocument();
@@ -104,7 +112,11 @@ describe('VersionSettings', () => {
test('renders buttons if versions >= VERSIONS_FETCH_LIMIT', async () => {
// @ts-ignore
- historySrv.getHistoryList.mockResolvedValue(versions.slice(0, VERSIONS_FETCH_LIMIT));
+ historySrv.getHistoryList.mockResolvedValue({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT),
+ });
+
setup();
expect(screen.queryByRole('button', { name: /show more versions/i })).not.toBeInTheDocument();
@@ -124,9 +136,17 @@ describe('VersionSettings', () => {
test('clicking show more appends results to the table', async () => {
historySrv.getHistoryList
// @ts-ignore
- .mockImplementationOnce(() => Promise.resolve(versions.slice(0, VERSIONS_FETCH_LIMIT)))
- .mockImplementationOnce(
- () => new Promise((resolve) => setTimeout(() => resolve(versions.slice(VERSIONS_FETCH_LIMIT)), 1000))
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT),
+ })
+ )
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(VERSIONS_FETCH_LIMIT),
+ })
);
setup();
@@ -146,13 +166,16 @@ describe('VersionSettings', () => {
await waitFor(() => {
expect(screen.queryByText(/Fetching more entries/i)).not.toBeInTheDocument();
- expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(versions.length);
+ expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(versions.versions.length);
});
});
test('selecting two versions and clicking compare button should render compare view', async () => {
// @ts-ignore
- historySrv.getHistoryList.mockResolvedValue(versions.slice(0, VERSIONS_FETCH_LIMIT));
+ historySrv.getHistoryList.mockResolvedValue({
+ continueToken: versions.continueToken,
+ versions: versions.versions.slice(0, VERSIONS_FETCH_LIMIT),
+ });
historySrv.getDashboardVersion
// @ts-ignore
.mockImplementationOnce(() => Promise.resolve(diffs.lhs))
diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx
index c22f31c9437..30ccbe39708 100644
--- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx
+++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx
@@ -1,6 +1,7 @@
import { PureComponent } from 'react';
import * as React from 'react';
+import { config } from '@grafana/runtime';
import { Spinner, HorizontalGroup } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import {
@@ -38,11 +39,13 @@ export const VERSIONS_FETCH_LIMIT = 10;
export class VersionsSettings extends PureComponent {
limit: number;
start: number;
+ continueToken: string;
constructor(props: Props) {
super(props);
this.limit = VERSIONS_FETCH_LIMIT;
this.start = 0;
+ this.continueToken = '';
this.state = {
isAppending: true,
isLoading: true,
@@ -62,14 +65,20 @@ export class VersionsSettings extends PureComponent {
getVersions = (append = false) => {
this.setState({ isAppending: append });
+ const requestOptions = this.continueToken
+ ? { limit: this.limit, start: this.start, continueToken: this.continueToken }
+ : { limit: this.limit, start: this.start };
+
historySrv
- .getHistoryList(this.props.dashboard.uid, { limit: this.limit, start: this.start })
+ .getHistoryList(this.props.dashboard.uid, requestOptions)
.then((res) => {
this.setState({
isLoading: false,
- versions: [...this.state.versions, ...this.decorateVersions(res)],
+ versions: [...(this.state.versions ?? []), ...this.decorateVersions(res.versions)],
});
this.start += this.limit;
+ // Update the continueToken for the next request, if available
+ this.continueToken = res.continueToken ?? '';
})
.catch((err) => console.log(err))
.finally(() => this.setState({ isAppending: false }));
@@ -84,8 +93,15 @@ export class VersionsSettings extends PureComponent {
isLoading: true,
});
- const lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.version);
- const rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.version);
+ let lhs, rhs;
+ if (config.featureToggles.kubernetesCliDashboards) {
+ // the id here is the resource version in k8s, use this instead to get the specific version
+ lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.id);
+ rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.id);
+ } else {
+ lhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, baseInfo.version);
+ rhs = await historySrv.getDashboardVersion(this.props.dashboard.uid, newInfo.version);
+ }
this.setState({
baseInfo,
@@ -121,6 +137,7 @@ export class VersionsSettings extends PureComponent {
};
reset = () => {
+ this.continueToken = '';
this.setState({
baseInfo: undefined,
diffData: {
diff --git a/public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts b/public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts
index 62f3cf4d876..3b3dc5629d8 100644
--- a/public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts
+++ b/public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts
@@ -1,126 +1,129 @@
-export const versions = [
- {
- id: 249,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 10,
- restoredFrom: 0,
- version: 11,
- created: '2021-01-15T14:44:44+01:00',
- createdBy: 'admin',
- message: 'testing changes...',
- },
- {
- id: 247,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 9,
- restoredFrom: 0,
- version: 10,
- created: '2021-01-15T10:19:17+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 246,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 8,
- restoredFrom: 0,
- version: 9,
- created: '2021-01-15T10:18:12+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 245,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 7,
- restoredFrom: 0,
- version: 8,
- created: '2021-01-15T10:11:16+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 239,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 6,
- restoredFrom: 0,
- version: 7,
- created: '2021-01-14T15:14:25+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 237,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 5,
- restoredFrom: 0,
- version: 6,
- created: '2021-01-14T14:55:29+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 236,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 4,
- restoredFrom: 0,
- version: 5,
- created: '2021-01-14T14:28:01+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 218,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 3,
- restoredFrom: 0,
- version: 4,
- created: '2021-01-08T10:45:33+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 217,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 2,
- restoredFrom: 0,
- version: 3,
- created: '2021-01-05T15:41:33+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 216,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 1,
- restoredFrom: 0,
- version: 2,
- created: '2021-01-05T15:01:50+01:00',
- createdBy: 'admin',
- message: '',
- },
- {
- id: 215,
- dashboardId: 74,
- dashboardUID: '_U4zObQMz',
- parentVersion: 1,
- restoredFrom: 0,
- version: 1,
- created: '2021-01-05T14:59:15+01:00',
- createdBy: 'admin',
- message: '',
- },
-];
+export const versions = {
+ continueToken: '',
+ versions: [
+ {
+ id: 249,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 10,
+ restoredFrom: 0,
+ version: 11,
+ created: '2021-01-15T14:44:44+01:00',
+ createdBy: 'admin',
+ message: 'testing changes...',
+ },
+ {
+ id: 247,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 9,
+ restoredFrom: 0,
+ version: 10,
+ created: '2021-01-15T10:19:17+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 246,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 8,
+ restoredFrom: 0,
+ version: 9,
+ created: '2021-01-15T10:18:12+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 245,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 7,
+ restoredFrom: 0,
+ version: 8,
+ created: '2021-01-15T10:11:16+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 239,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 6,
+ restoredFrom: 0,
+ version: 7,
+ created: '2021-01-14T15:14:25+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 237,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 5,
+ restoredFrom: 0,
+ version: 6,
+ created: '2021-01-14T14:55:29+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 236,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 4,
+ restoredFrom: 0,
+ version: 5,
+ created: '2021-01-14T14:28:01+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 218,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 3,
+ restoredFrom: 0,
+ version: 4,
+ created: '2021-01-08T10:45:33+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 217,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 2,
+ restoredFrom: 0,
+ version: 3,
+ created: '2021-01-05T15:41:33+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 216,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 1,
+ restoredFrom: 0,
+ version: 2,
+ created: '2021-01-05T15:01:50+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ {
+ id: 215,
+ dashboardId: 74,
+ dashboardUID: '_U4zObQMz',
+ parentVersion: 1,
+ restoredFrom: 0,
+ version: 1,
+ created: '2021-01-05T14:59:15+01:00',
+ createdBy: 'admin',
+ message: '',
+ },
+ ],
+};
export const diffs = {
lhs: {
diff --git a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx
index 5a4451b4596..955d45388c4 100644
--- a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx
+++ b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx
@@ -5,12 +5,13 @@ import { ConfirmModal } from '@grafana/ui';
import { useDashboardRestore } from './useDashboardRestore';
export interface RevertDashboardModalProps {
hideModal: () => void;
+ id: number;
version: number;
}
-export const RevertDashboardModal = ({ hideModal, version }: RevertDashboardModalProps) => {
+export const RevertDashboardModal = ({ hideModal, id, version }: RevertDashboardModalProps) => {
// TODO: how should state.error be handled?
- const { state, onRestoreDashboard } = useDashboardRestore(version);
+ const { state, onRestoreDashboard } = useDashboardRestore(id, version);
useEffect(() => {
if (!state.loading && state.value) {
diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx
index 2f23cd32e17..72294e80941 100644
--- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx
+++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx
@@ -43,6 +43,7 @@ export const VersionHistoryComparison = ({ baseInfo, newInfo, diffData, isNewLat
icon="history"
onClick={() => {
showModal(RevertDashboardModal, {
+ id: baseInfo.id,
version: baseInfo.version,
hideModal,
});
diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx
index 2cd8514992a..5edd119e490 100644
--- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx
+++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx
@@ -60,6 +60,7 @@ export const VersionHistoryTable = ({ versions, canCompare, onCheck }: VersionsT
icon="history"
onClick={() => {
showModal(RevertDashboardModal, {
+ id: version.id,
version: version.version,
hideModal,
});
diff --git a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx
index 4ee340a8110..91fd0964aca 100644
--- a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx
+++ b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useAsyncFn } from 'react-use';
import { locationUtil } from '@grafana/data';
-import { locationService } from '@grafana/runtime';
+import { config, locationService } from '@grafana/runtime';
import { useAppNotification } from 'app/core/copy/appNotification';
import { historySrv } from 'app/features/dashboard-scene/settings/version-history';
import { useSelector } from 'app/types';
@@ -16,9 +16,12 @@ const restoreDashboard = async (version: number, dashboard: DashboardModel) => {
return await historySrv.restoreDashboard(dashboard.uid, version);
};
-export const useDashboardRestore = (version: number) => {
+export const useDashboardRestore = (id: number, version: number) => {
const dashboard = useSelector((state) => state.dashboard.getModel());
- const [state, onRestoreDashboard] = useAsyncFn(async () => await restoreDashboard(version, dashboard!), []);
+ const [state, onRestoreDashboard] = useAsyncFn(
+ async () => await restoreDashboard(config.featureToggles.kubernetesCliDashboards ? id : version, dashboard!),
+ []
+ );
const notifyApp = useAppNotification();
useEffect(() => {
diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts
index 7761ed37fbc..b8261ec1d32 100644
--- a/public/app/features/dashboard/services/TimeSrv.ts
+++ b/public/app/features/dashboard/services/TimeSrv.ts
@@ -15,9 +15,9 @@ import {
} from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { sceneGraph } from '@grafana/scenes';
-import { t } from '@grafana/ui/src/utils/i18n';
import appEvents from 'app/core/app_events';
import { config } from 'app/core/config';
+import { t } from 'app/core/internationalization';
import { AutoRefreshInterval, contextSrv, ContextSrv } from 'app/core/services/context_srv';
import { getCopiedTimeRange, getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker';
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
diff --git a/public/app/features/explore/ExploreRunQueryButton.tsx b/public/app/features/explore/ExploreRunQueryButton.tsx
index 7dd87dbd6c3..7cb37a62a45 100644
--- a/public/app/features/explore/ExploreRunQueryButton.tsx
+++ b/public/app/features/explore/ExploreRunQueryButton.tsx
@@ -4,7 +4,7 @@ import { ConnectedProps, connect } from 'react-redux';
import { config, reportInteraction } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button, ButtonVariant, Dropdown, Menu, ToolbarButton } from '@grafana/ui';
-import { t } from '@grafana/ui/src/utils/i18n';
+import { t } from 'app/core/internationalization';
import { useSelector } from 'app/types';
import { changeDatasource } from './state/datasource';
@@ -28,7 +28,7 @@ interface ExploreRunQueryButtonProps {
export type Props = ConnectedProps & ExploreRunQueryButtonProps;
-/*
+/*
This component does not validate datasources before running them. Root datasource validation should happen outside this component and can pass in an undefined if invalid
If query level validation is done and a query datasource is invalid, pass in disabled = true
*/
diff --git a/public/app/features/explore/state/time.ts b/public/app/features/explore/state/time.ts
index 38683f45bf4..f794042c113 100644
--- a/public/app/features/explore/state/time.ts
+++ b/public/app/features/explore/state/time.ts
@@ -10,8 +10,8 @@ import {
} from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { RefreshPicker } from '@grafana/ui';
-import { t } from '@grafana/ui/src/utils/i18n';
import appEvents from 'app/core/app_events';
+import { t } from 'app/core/internationalization';
import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
import { getCopiedTimeRange, getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
diff --git a/public/app/features/search/service/unified.test.ts b/public/app/features/search/service/unified.test.ts
new file mode 100644
index 00000000000..4d50e25193b
--- /dev/null
+++ b/public/app/features/search/service/unified.test.ts
@@ -0,0 +1,65 @@
+import { toDashboardResults, SearchHit, SearchAPIResponse } from './unified';
+
+describe('Unified Storage Searcher', () => {
+ it('can create dashboard search results and set meta sortBy so column is added for sprinkles sort field', () => {
+ const mockHits: SearchHit[] = [
+ {
+ resource: 'dashboard',
+ name: 'Main Dashboard',
+ title: 'Main Dashboard Title',
+ location: '/dashboards/1',
+ folder: 'General',
+ tags: ['monitoring', 'performance'],
+ field: { errors_today: 1 },
+ url: '/dashboards/1',
+ },
+ {
+ resource: 'dashboard',
+ name: 'Main Dashboard',
+ title: 'Main Dashboard Title',
+ location: '/dashboards/1',
+ folder: 'General',
+ tags: ['monitoring', 'performance'],
+ field: { errors_today: 2 },
+ url: '/dashboards/1',
+ },
+ ];
+
+ const mockResponse: SearchAPIResponse = {
+ totalHits: 2,
+ hits: mockHits,
+ facets: {},
+ };
+ const results = toDashboardResults(mockResponse, 'errors_today');
+
+ expect(results.length).toBe(2);
+ const sprinklesField = results.fields[10];
+ expect(sprinklesField.name).toBe('errors_today');
+ expect(sprinklesField.values).toEqual([1, 2]); // this also tests the hits original order is preserved
+ expect(results.meta?.custom?.sortBy).toBe('errors_today');
+ });
+
+ it('will trim "-" from the sort field name', () => {
+ const mockHits: SearchHit[] = [
+ {
+ resource: 'dashboard',
+ name: 'Main Dashboard',
+ title: 'Main Dashboard Title',
+ location: '/dashboards/1',
+ folder: 'General',
+ tags: ['monitoring', 'performance'],
+ field: { errors_today: 1 },
+ url: '/dashboards/1',
+ },
+ ];
+
+ const mockResponse: SearchAPIResponse = {
+ totalHits: 0,
+ hits: mockHits,
+ facets: {},
+ };
+ const results = toDashboardResults(mockResponse, '-errors_today');
+
+ expect(results.meta?.custom?.sortBy).toBe('errors_today');
+ });
+});
diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts
index 9e379873cc3..3ed44146c35 100644
--- a/public/app/features/search/service/unified.ts
+++ b/public/app/features/search/service/unified.ts
@@ -20,7 +20,7 @@ const loadingFrameName = 'Loading';
const searchURI = `apis/dashboard.grafana.app/v0alpha1/namespaces/${config.namespace}/search`;
-type SearchHit = {
+export type SearchHit = {
resource: string; // dashboards | folders
name: string;
title: string;
@@ -28,11 +28,13 @@ type SearchHit = {
folder: string;
tags: string[];
+ field: Record; // extra fields from the backend - sort fields included here as well
+
// calculated in the frontend
url: string;
};
-type SearchAPIResponse = {
+export type SearchAPIResponse = {
totalHits: number;
hits: SearchHit[];
facets?: {
@@ -110,7 +112,7 @@ export class UnifiedSearcher implements GrafanaSearcher {
const uri = await this.newRequest(query);
const rsp = await getBackendSrv().get(uri);
- const first = toDashboardResults(rsp);
+ const first = toDashboardResults(rsp, query.sort ?? '');
if (first.name === loadingFrameName) {
return this.fallbackSearcher.search(query);
}
@@ -145,7 +147,7 @@ export class UnifiedSearcher implements GrafanaSearcher {
}
const nextPageUrl = `${uri}&offset=${offset}`;
const resp = await getBackendSrv().get(nextPageUrl);
- const frame = toDashboardResults(resp);
+ const frame = toDashboardResults(resp, query.sort ?? '');
if (!frame) {
console.log('no results', frame);
return;
@@ -217,6 +219,9 @@ export class UnifiedSearcher implements GrafanaSearcher {
if (query.sort) {
const sort = query.sort.replace('_sort', '').replace('name', 'title');
uri += `&sort=${sort}`;
+ const sortField = sort.startsWith('-') ? sort.substring(1) : sort;
+
+ uri += `&field=${sortField}`; // we want to the sort field to be included in the response
}
if (query.name?.length) {
@@ -279,7 +284,7 @@ function getSortFieldDisplayName(name: string) {
return name;
}
-function toDashboardResults(rsp: SearchAPIResponse): DataFrame {
+export function toDashboardResults(rsp: SearchAPIResponse, sort: string): DataFrame {
const hits = rsp.hits;
if (hits.length < 1) {
return { fields: [], length: 0 };
@@ -290,6 +295,11 @@ function toDashboardResults(rsp: SearchAPIResponse): DataFrame {
location = 'general';
}
+ // display null field values as "-"
+ const field = Object.fromEntries(
+ Object.entries(hit.field ?? {}).map(([key, value]) => [key, value == null ? '-' : value])
+ );
+
return {
...hit,
uid: hit.name,
@@ -299,6 +309,7 @@ function toDashboardResults(rsp: SearchAPIResponse): DataFrame {
location,
name: hit.title, // 🤯 FIXME hit.name is k8s name, eg grafana dashboards UID
kind: hit.resource.substring(0, hit.resource.length - 1), // dashboard "kind" is not plural
+ ...field,
};
});
const frame = toDataFrame(dashboardHits);
@@ -308,6 +319,11 @@ function toDashboardResults(rsp: SearchAPIResponse): DataFrame {
max_score: 1,
},
};
+ if (sort && frame.meta.custom) {
+ // trim the "-" from sort if it exists
+ frame.meta.custom.sortBy = sort.startsWith('-') ? sort.substring(1) : sort;
+ }
+
for (const field of frame.fields) {
field.display = getDisplayProcessor({ field, theme: config.theme2 });
}
diff --git a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx
index fa24596b328..c9816d0fb1a 100644
--- a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx
+++ b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx
@@ -3,13 +3,13 @@ import { FormProvider, useForm } from 'react-hook-form';
import { config, getBackendSrv, locationService } from '@grafana/runtime';
import { Button, Input, Field, FieldSet } from '@grafana/ui';
-import { t, Trans } from '@grafana/ui/src/utils/i18n';
import { Form } from 'app/core/components/Form/Form';
import { Page } from 'app/core/components/Page/Page';
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
import { fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicker/api';
import { RolePickerSelect } from 'app/core/components/RolePickerDrawer/RolePickerSelect';
import { contextSrv } from 'app/core/core';
+import { t, Trans } from 'app/core/internationalization';
import { AccessControlAction, OrgRole, Role, ServiceAccountCreateApiResponse, ServiceAccountDTO } from 'app/types';
import { OrgRolePicker } from '../admin/OrgRolePicker';
diff --git a/public/app/features/trails/DataTrailSettings.tsx b/public/app/features/trails/DataTrailSettings.tsx
index 2584b7bb930..7007b511ad0 100644
--- a/public/app/features/trails/DataTrailSettings.tsx
+++ b/public/app/features/trails/DataTrailSettings.tsx
@@ -3,7 +3,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { Dropdown, Switch, ToolbarButton, useStyles2 } from '@grafana/ui';
-import { Trans } from '@grafana/ui/src/utils/i18n';
+import { Trans } from 'app/core/internationalization';
import { MetricScene } from './MetricScene';
import { MetricSelectScene } from './MetricSelect/MetricSelectScene';
diff --git a/public/app/features/trails/MetricSelect/NativeHistogramBadge.tsx b/public/app/features/trails/MetricSelect/NativeHistogramBadge.tsx
index f2c551f3d70..21fed5c0ce7 100644
--- a/public/app/features/trails/MetricSelect/NativeHistogramBadge.tsx
+++ b/public/app/features/trails/MetricSelect/NativeHistogramBadge.tsx
@@ -3,7 +3,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneObjectBase } from '@grafana/scenes';
import { Badge, useStyles2 } from '@grafana/ui';
-import { Trans } from '@grafana/ui/src/utils/i18n';
+import { Trans } from 'app/core/internationalization';
export class NativeHistogramBadge extends SceneObjectBase {
public static Component = () => {
diff --git a/public/app/features/trails/banners/NativeHistogramBanner.tsx b/public/app/features/trails/banners/NativeHistogramBanner.tsx
index 5813c06d30f..68ca35c0c67 100644
--- a/public/app/features/trails/banners/NativeHistogramBanner.tsx
+++ b/public/app/features/trails/banners/NativeHistogramBanner.tsx
@@ -3,7 +3,7 @@ import { useState, type Dispatch, type SetStateAction } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, useTheme2, Alert, Button } from '@grafana/ui';
-import { t, Trans } from '@grafana/ui/src/utils/i18n';
+import { t, Trans } from 'app/core/internationalization';
import { DataTrail } from '../DataTrail';
import { reportExploreMetrics } from '../interactions';
diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx
index a06b3739072..4c326a44fb3 100644
--- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.tsx
@@ -1,7 +1,7 @@
import { intersection } from 'lodash';
import { useState, useMemo } from 'react';
-import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
+import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/plugin-ui';
import Datasource from '../../datasource';
import { selectors } from '../../e2e/selectors';
diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx
index fa80caadc82..6ab8bc69a18 100644
--- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/AzureCredentialsForm.tsx
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { AzureAuthType, AzureCredentials, getAzureClouds } from '@grafana/azure-sdk';
import { SelectableValue } from '@grafana/data';
-import { ConfigSection } from '@grafana/experimental';
+import { ConfigSection } from '@grafana/plugin-ui';
import { Select, Field } from '@grafana/ui';
import { selectors } from '../../e2e/selectors';
diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx
index e4246af0996..bdf2129e11d 100644
--- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx
@@ -1,7 +1,7 @@
import { PureComponent } from 'react';
import { DataSourcePluginOptionsEditorProps, SelectableValue, updateDatasourcePluginOption } from '@grafana/data';
-import { ConfigSection, DataSourceDescription } from '@grafana/experimental';
+import { ConfigSection, DataSourceDescription } from '@grafana/plugin-ui';
import { getBackendSrv, getTemplateSrv, isFetchError, TemplateSrv, config } from '@grafana/runtime';
import { Alert, Divider, SecureSocksProxySettings } from '@grafana/ui';
diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx
index 951afa88344..d13296dc1c5 100644
--- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { AadCurrentUserCredentials, AzureCredentials, instanceOfAzureCredential } from '@grafana/azure-sdk';
import { SelectableValue } from '@grafana/data';
-import { ConfigSection } from '@grafana/experimental';
+import { ConfigSection } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { Select, Field, RadioButtonGroup, Alert, Stack } from '@grafana/ui';
diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AdvancedResourcePicker.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AdvancedResourcePicker.tsx
index 26efe10eb20..640fdd1a6c7 100644
--- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AdvancedResourcePicker.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AdvancedResourcePicker.tsx
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import { Icon, Input, Tooltip, Label, Button, useStyles2 } from '@grafana/ui';
export interface ResourcePickerProps {
diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx
index b1b13313f7c..622d6b660c8 100644
--- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { PanelData, TimeRange } from '@grafana/data';
-import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
+import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/plugin-ui';
import { getTemplateSrv } from '@grafana/runtime';
import { Alert, LinkButton, Text, TextLink } from '@grafana/ui';
diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx
index b052df9448c..7ab12383dc5 100644
--- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AdvancedResourcePicker.tsx
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import { Input, Label, InlineField, Button, useStyles2 } from '@grafana/ui';
import { selectors } from '../../e2e/selectors';
diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx
index b7b7c2f13d6..9a4e1975a74 100644
--- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.tsx
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import { SelectableValue, DataFrame, PanelData, Labels } from '@grafana/data';
-import { EditorList, AccessoryButton } from '@grafana/experimental';
+import { EditorList, AccessoryButton } from '@grafana/plugin-ui';
import { Select, HorizontalGroup, MultiSelect } from '@grafana/ui';
import { AzureMetricDimension, AzureMonitorOption, AzureMonitorQuery, AzureQueryEditorFieldProps } from '../../types';
diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx
index 751392acf44..6048ebca21e 100644
--- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx
@@ -1,5 +1,5 @@
import { PanelData } from '@grafana/data';
-import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental';
+import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/plugin-ui';
import { multiResourceCompatibleTypes } from '../../azureMetadata';
import type Datasource from '../../datasource';
diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx
index 737053e81db..7fddd79ce0c 100644
--- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorHeader, InlineSelect } from '@grafana/experimental';
+import { EditorHeader, InlineSelect } from '@grafana/plugin-ui';
import { selectors } from '../../e2e/selectors';
import { AzureMonitorQuery, AzureQueryType } from '../../types';
diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx
index 12f985dbb92..15d4f7793ee 100644
--- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filter.tsx
@@ -5,7 +5,7 @@ import { lastValueFrom } from 'rxjs';
import { CoreApp, DataFrame, getDefaultTimeRange, SelectableValue, TimeRange } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import {
HorizontalGroup,
Select,
diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx
index 9c8c84d1bc6..8628766f6ba 100644
--- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.tsx
@@ -2,7 +2,7 @@ import { uniq } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorList } from '@grafana/experimental';
+import { EditorList } from '@grafana/plugin-ui';
import { Field } from '@grafana/ui';
import { AzureQueryEditorFieldProps, AzureTracesFilter } from '../../types';
diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx
index 7ed37f8fa21..b550ecf09a8 100644
--- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { usePrevious } from 'react-use';
import { TimeRange } from '@grafana/data';
-import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
+import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/plugin-ui';
import { Input } from '@grafana/ui';
import Datasource from '../../datasource';
diff --git a/public/app/plugins/datasource/azuremonitor/components/shared/Field.tsx b/public/app/plugins/datasource/azuremonitor/components/shared/Field.tsx
index 2657c066115..3c5a9cf1123 100644
--- a/public/app/plugins/datasource/azuremonitor/components/shared/Field.tsx
+++ b/public/app/plugins/datasource/azuremonitor/components/shared/Field.tsx
@@ -1,4 +1,4 @@
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { InlineField } from '@grafana/ui';
import { Props as InlineFieldProps } from '@grafana/ui/src/components/Forms/InlineField';
diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json
index 1a6ea15bb03..0f8b460420e 100644
--- a/public/app/plugins/datasource/azuremonitor/package.json
+++ b/public/app/plugins/datasource/azuremonitor/package.json
@@ -6,7 +6,7 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "11.5.0-pre",
- "@grafana/experimental": "2.1.6",
+ "@grafana/plugin-ui": "0.9.6",
"@grafana/runtime": "11.5.0-pre",
"@grafana/schema": "11.5.0-pre",
"@grafana/ui": "11.5.0-pre",
@@ -32,8 +32,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/prismjs": "1.26.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx
index 8d9c874bb38..2d5089f06fc 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import { getAggregationOptionsByMetric } from '../functions';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx
index a275b89e15f..6b92a061e66 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx
@@ -2,7 +2,7 @@ import { debounce } from 'lodash';
import { useState } from 'react';
import * as React from 'react';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Input } from '@grafana/ui';
export interface Props {
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx
index c9a313e48ed..1a3d21c3a9d 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField, EditorFieldGroup } from '@grafana/experimental';
+import { EditorField, EditorFieldGroup } from '@grafana/plugin-ui';
import { ALIGNMENT_PERIODS } from '../constants';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx
index 35f524cc556..6925e724d68 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { useDebounce } from 'react-use';
import { QueryEditorProps, getDefaultTimeRange, toOption } from '@grafana/data';
-import { EditorField, EditorRows } from '@grafana/experimental';
+import { EditorField, EditorRows } from '@grafana/plugin-ui';
import { Input } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx
index 02943e876ef..9889693efae 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx
@@ -1,8 +1,8 @@
import { PureComponent } from 'react';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
-import { ConfigSection, DataSourceDescription } from '@grafana/experimental';
import { ConnectionConfig } from '@grafana/google-sdk';
+import { ConfigSection, DataSourceDescription } from '@grafana/plugin-ui';
import { reportInteraction, config } from '@grafana/runtime';
import { Divider, SecureSocksProxySettings } from '@grafana/ui';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx
index 13dd8e3d201..9e6c7aed0c3 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/GraphPeriod.tsx
@@ -1,5 +1,5 @@
import { SelectableValue } from '@grafana/data';
-import { EditorField, EditorRow } from '@grafana/experimental';
+import { EditorField, EditorRow } from '@grafana/plugin-ui';
import { HorizontalGroup, Switch } from '@grafana/ui';
import { GRAPH_PERIODS } from '../constants';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx
index d9fd0e4c70c..356a81d5ade 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField, EditorFieldGroup } from '@grafana/experimental';
+import { EditorField, EditorFieldGroup } from '@grafana/plugin-ui';
import { MultiSelect } from '@grafana/ui';
import { SYSTEM_LABELS } from '../constants';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx b/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx
index 102bc22ef2f..dca57374c91 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { SelectableValue, toOption } from '@grafana/data';
-import { AccessoryButton, EditorField, EditorList, EditorRow } from '@grafana/experimental';
+import { AccessoryButton, EditorField, EditorList, EditorRow } from '@grafana/plugin-ui';
import { HorizontalGroup, Select } from '@grafana/ui';
import { labelsToGroupedOptions, stringArrayToFilters } from '../functions';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/LookbackPeriodSelect.tsx b/public/app/plugins/datasource/cloud-monitoring/components/LookbackPeriodSelect.tsx
index 0e7d991327a..92cce156c87 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/LookbackPeriodSelect.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/LookbackPeriodSelect.tsx
@@ -1,5 +1,5 @@
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import { LOOKBACK_PERIODS } from '../constants';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx
index 8a245c46503..7cd44f61ffb 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx
@@ -2,7 +2,8 @@ import { useCallback, useEffect } from 'react';
import * as React from 'react';
import { SelectableValue, TimeRange } from '@grafana/data';
-import { EditorRows, Stack } from '@grafana/experimental';
+import { EditorRows } from '@grafana/plugin-ui';
+import { Stack } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
import { AlignmentTypes, CloudMonitoringQuery, QueryType, TimeSeriesList, TimeSeriesQuery } from '../types/query';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx
index c7f6c66d355..3ca7d101942 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { RadioButtonGroup } from '@grafana/ui';
import { getAlignmentPickerData } from '../functions';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx
index de42fbdd788..289b4745ba0 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx
index 8e54fbe372c..deaa5e7eaf8 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField, EditorRow } from '@grafana/experimental';
+import { EditorField, EditorRow } from '@grafana/plugin-ui';
import { TextArea, Input } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx
index 9464e273ea2..7b701df34da 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx
@@ -3,7 +3,7 @@ import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
import { QueryEditorProps, getDefaultTimeRange, toOption } from '@grafana/data';
-import { EditorRows } from '@grafana/experimental';
+import { EditorRows } from '@grafana/plugin-ui';
import { ConfirmModal } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.tsx
index 150bb3c1f1f..71431f0fc20 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.tsx
@@ -1,4 +1,4 @@
-import { EditorHeader, FlexItem, InlineSelect } from '@grafana/experimental';
+import { EditorHeader, FlexItem, InlineSelect } from '@grafana/plugin-ui';
import { QUERY_TYPES } from '../constants';
import { CloudMonitoringQuery } from '../types/query';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx
index 2e0117cfe70..be4606155d7 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx
index 0d62b1cff1e..1a4ec805596 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import * as React from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
+import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/plugin-ui';
import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../constants';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx
index 690e03219b6..ea0e5903af5 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx
@@ -1,5 +1,5 @@
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import { SELECTORS } from '../constants';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx
index 35093b92a7b..9425437d7b3 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { EditorField } from '@grafana/experimental';
+import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import CloudMonitoringDatasource from '../datasource';
diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx
index d50b6011e8e..56824f89358 100644
--- a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx
+++ b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from 'react';
import * as React from 'react';
import { GrafanaTheme2, SelectableValue, TimeRange } from '@grafana/data';
-import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
+import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/plugin-ui';
import { reportInteraction } from '@grafana/runtime';
import { getSelectStyles, Select, AsyncSelect, useStyles2, useTheme2 } from '@grafana/ui';
diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json
index c278483c99f..b281d43e7a3 100644
--- a/public/app/plugins/datasource/cloud-monitoring/package.json
+++ b/public/app/plugins/datasource/cloud-monitoring/package.json
@@ -6,8 +6,8 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "11.5.0-pre",
- "@grafana/experimental": "2.1.6",
"@grafana/google-sdk": "0.1.2",
+ "@grafana/plugin-ui": "0.9.6",
"@grafana/runtime": "11.5.0-pre",
"@grafana/schema": "11.5.0-pre",
"@grafana/ui": "11.5.0-pre",
@@ -34,8 +34,8 @@
"@testing-library/user-event": "14.5.2",
"@types/debounce-promise": "3.1.9",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/prismjs": "1.26.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json
index 5ebdb88eb3e..2f85083d376 100644
--- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json
+++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json
@@ -22,8 +22,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"ts-node": "10.9.2",
"typescript": "5.7.3",
diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json
index 75c7b27c73d..ca1682f1758 100644
--- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json
+++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json
@@ -26,8 +26,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/prismjs": "1.26.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json
index 3726f299ff1..47acd088aeb 100644
--- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json
+++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json
@@ -29,8 +29,8 @@
"@testing-library/user-event": "14.5.2",
"@types/d3-random": "^3.0.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/uuid": "10.0.0",
diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/FromSection.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/FromSection.tsx
index 458f2eb9176..153ec2aa039 100644
--- a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/FromSection.tsx
+++ b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/FromSection.tsx
@@ -1,4 +1,4 @@
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import { DEFAULT_POLICY } from '../../../../../types';
import { toSelectableValue } from '../utils/toSelectableValue';
diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx
index 3bb380ee126..14f188bd683 100644
--- a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx
+++ b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx
@@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
import { Fragment, useMemo } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import { useTheme2 } from '@grafana/ui';
import { toSelectableValue } from '../utils/toSelectableValue';
diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx
index 9079bc056c7..3edc8320144 100644
--- a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx
+++ b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/TagsSection.tsx
@@ -1,5 +1,5 @@
import { SelectableValue } from '@grafana/data';
-import { AccessoryButton } from '@grafana/experimental';
+import { AccessoryButton } from '@grafana/plugin-ui';
import { InfluxQueryTag } from '../../../../../types';
import { adjustOperatorIfNeeded, getCondition, getOperator } from '../utils/tagUtils';
diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tags.test.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tags.test.tsx
index 7d6b19d637f..910bb67fca5 100644
--- a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tags.test.tsx
+++ b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.tags.test.tsx
@@ -38,13 +38,12 @@ jest.mock('../../../../../influxql_metadata_query', () => {
};
});
-jest.mock('@grafana/runtime', () => {
- return {
- getTemplateSrv: jest.fn().mockReturnValueOnce({
- getVariables: jest.fn().mockReturnValueOnce([]),
- }),
- };
-});
+jest.mock('@grafana/runtime', () => ({
+ ...jest.requireActual('@grafana/runtime'),
+ getTemplateSrv: jest.fn().mockReturnValueOnce({
+ getVariables: jest.fn().mockReturnValueOnce([]),
+ }),
+}));
beforeEach(() => {
(mockedMeta.getTagKeys as jest.Mock).mockClear();
diff --git a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts
index ce40ff3c53a..da0a3ed5f25 100644
--- a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts
+++ b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.ts
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { DataSourceInstanceSettings, TimeRange } from '@grafana/data';
-import { CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/experimental';
+import { CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/plugin-ui';
import { TemplateSrv, config, getTemplateSrv } from '@grafana/runtime';
import { COMMON_FNS, DB, FuncParameter, SQLQuery, SqlDatasource, formatSQL } from '@grafana/sql';
diff --git a/public/app/plugins/datasource/influxdb/fsql/sqlCompletionProvider.ts b/public/app/plugins/datasource/influxdb/fsql/sqlCompletionProvider.ts
index b44b2475027..19fca7d6f56 100644
--- a/public/app/plugins/datasource/influxdb/fsql/sqlCompletionProvider.ts
+++ b/public/app/plugins/datasource/influxdb/fsql/sqlCompletionProvider.ts
@@ -11,7 +11,7 @@ import {
TableDefinition,
TableIdentifier,
TokenType,
-} from '@grafana/experimental';
+} from '@grafana/plugin-ui';
interface CompletionProviderGetterArgs {
getMeta: (t?: TableIdentifier) => Promise;
diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json
index a0aefef0d6f..a882e2093de 100644
--- a/public/app/plugins/datasource/jaeger/package.json
+++ b/public/app/plugins/datasource/jaeger/package.json
@@ -29,9 +29,9 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
+ "@types/lodash": "4.17.15",
"@types/logfmt": "^1.2.3",
- "@types/node": "22.10.10",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/react-window": "1.8.8",
diff --git a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx
index 607dc32f114..287257b4eaf 100644
--- a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx
+++ b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx
@@ -10,7 +10,7 @@ import {
updateDatasourcePluginJsonDataOption,
updateDatasourcePluginResetOption,
} from '@grafana/data';
-import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/experimental';
+import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { ConnectionLimits, useMigrateDatabaseFields } from '@grafana/sql';
import { NumberInput } from '@grafana/sql/src/components/configuration/NumberInput';
diff --git a/public/app/plugins/datasource/mssql/configuration/Kerberos.tsx b/public/app/plugins/datasource/mssql/configuration/Kerberos.tsx
index f5dfc8759c3..d1166ba79e0 100644
--- a/public/app/plugins/datasource/mssql/configuration/Kerberos.tsx
+++ b/public/app/plugins/datasource/mssql/configuration/Kerberos.tsx
@@ -1,7 +1,7 @@
import { SyntheticEvent } from 'react';
import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data';
-import { ConfigSubSection } from '@grafana/experimental';
+import { ConfigSubSection } from '@grafana/plugin-ui';
import { FieldSet, Input, Field } from '@grafana/ui';
import { MSSQLAuthenticationType, MssqlOptions } from '../types';
diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts
index abe0ee75b13..b020d051975 100644
--- a/public/app/plugins/datasource/mssql/datasource.ts
+++ b/public/app/plugins/datasource/mssql/datasource.ts
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
-import { LanguageDefinition } from '@grafana/experimental';
+import { LanguageDefinition } from '@grafana/plugin-ui';
import { TemplateSrv, config } from '@grafana/runtime';
import {
COMMON_FNS,
diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json
index af7fab99453..008012c3d27 100644
--- a/public/app/plugins/datasource/mssql/package.json
+++ b/public/app/plugins/datasource/mssql/package.json
@@ -6,7 +6,7 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "11.5.0-pre",
- "@grafana/experimental": "2.1.6",
+ "@grafana/plugin-ui": "0.9.6",
"@grafana/runtime": "11.5.0-pre",
"@grafana/sql": "11.5.0-pre",
"@grafana/ui": "11.5.0-pre",
@@ -22,8 +22,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"ts-node": "10.9.2",
"typescript": "5.7.3",
diff --git a/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts b/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts
index 871c2172dfc..72cc15f421d 100644
--- a/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts
+++ b/public/app/plugins/datasource/mssql/sqlCompletionProvider.ts
@@ -6,7 +6,7 @@ import {
TableDefinition,
TableIdentifier,
TokenType,
-} from '@grafana/experimental';
+} from '@grafana/plugin-ui';
import { DB, SQLQuery } from '@grafana/sql';
interface CompletionProviderGetterArgs {
diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json
index b35ff254ad9..2cbed20190a 100644
--- a/public/app/plugins/datasource/mysql/package.json
+++ b/public/app/plugins/datasource/mysql/package.json
@@ -22,8 +22,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"ts-node": "10.9.2",
"typescript": "5.7.3",
diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json
index df78383ef64..cf4dcec1268 100644
--- a/public/app/plugins/datasource/parca/package.json
+++ b/public/app/plugins/datasource/parca/package.json
@@ -22,8 +22,8 @@
"@testing-library/dom": "10.4.0",
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"ts-node": "10.9.2",
diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json
index b42d506a96c..55a87c3c5f3 100644
--- a/public/app/plugins/datasource/tempo/package.json
+++ b/public/app/plugins/datasource/tempo/package.json
@@ -45,8 +45,8 @@
"@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/prismjs": "1.26.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
diff --git a/public/app/plugins/datasource/tempo/traceql/TempoQueryBuilderOptions.tsx b/public/app/plugins/datasource/tempo/traceql/TempoQueryBuilderOptions.tsx
index 25a13cc349a..f273bc788ca 100644
--- a/public/app/plugins/datasource/tempo/traceql/TempoQueryBuilderOptions.tsx
+++ b/public/app/plugins/datasource/tempo/traceql/TempoQueryBuilderOptions.tsx
@@ -1,7 +1,9 @@
+import { css } from '@emotion/css';
import * as React from 'react';
+import { GrafanaTheme2 } from '@grafana/data';
import { EditorField, EditorRow } from '@grafana/experimental';
-import { AutoSizeInput, RadioButtonGroup } from '@grafana/ui';
+import { AutoSizeInput, RadioButtonGroup, useStyles2 } from '@grafana/ui';
import { QueryOptionGroup } from '../_importedDependencies/datasources/prometheus/QueryOptionGroup';
import { SearchTableType } from '../dataquery.gen';
@@ -28,6 +30,8 @@ const parseIntWithFallback = (val: string, fallback: number) => {
};
export const TempoQueryBuilderOptions = React.memo(({ onChange, query, isStreaming }) => {
+ const styles = useStyles2(getStyles);
+
if (!query.hasOwnProperty('limit')) {
query.limit = DEFAULT_LIMIT;
}
@@ -60,21 +64,23 @@ export const TempoQueryBuilderOptions = React.memo(({ onChange, query, is
// }
// };
- const collapsedInfoList = [
+ const collapsedSearchOptions = [
`Limit: ${query.limit || DEFAULT_LIMIT}`,
`Spans Limit: ${query.spss || DEFAULT_SPSS}`,
`Table Format: ${query.tableType === SearchTableType.Traces ? 'Traces' : 'Spans'}`,
'|',
- `Step: ${query.step || 'auto'}`,
- // `Exemplars: ${query.exemplars !== undefined ? query.exemplars : 'auto'}`,
- '|',
`Streaming: ${isStreaming ? 'Enabled' : 'Disabled'}`,
];
+ const collapsedMetricsOptions = [
+ `Step: ${query.step || 'auto'}`,
+ // `Exemplars: ${query.exemplars !== undefined ? query.exemplars : 'auto'}`,
+ ];
+
return (
- <>
-
-
+
+
+
(({ onChange, query, is
onChange={onTableTypeChange}
/>
+ } tooltipInteractive>
+ {isStreaming ? 'Enabled' : 'Disabled'}
+
+
+
+
(({ onChange, query, is
{/* value={query.exemplars}*/}
{/* />*/}
{/**/}
- } tooltipInteractive>
- {isStreaming ? 'Enabled' : 'Disabled'}
-
-
- >
+
+
);
});
@@ -163,3 +172,17 @@ const StreamingTooltip = () => {
};
TempoQueryBuilderOptions.displayName = 'TempoQueryBuilderOptions';
+
+const getStyles = (theme: GrafanaTheme2) => {
+ return {
+ options: css({
+ display: 'flex',
+ width: '-webkit-fill-available',
+ gap: theme.spacing(1),
+
+ '> div': {
+ width: 'auto',
+ },
+ }),
+ };
+};
diff --git a/public/app/plugins/datasource/zipkin/datasource.test.ts b/public/app/plugins/datasource/zipkin/datasource.test.ts
index 4123de7960d..5b1ab000004 100644
--- a/public/app/plugins/datasource/zipkin/datasource.test.ts
+++ b/public/app/plugins/datasource/zipkin/datasource.test.ts
@@ -1,5 +1,4 @@
import { lastValueFrom, of } from 'rxjs';
-import { createFetchResponse } from 'test/helpers/createFetchResponse';
import {
DataFrameView,
@@ -8,52 +7,49 @@ import {
DataSourcePluginMeta,
FieldType,
} from '@grafana/data';
-import { BackendSrv, TemplateSrv } from '@grafana/runtime';
+import { BackendSrv, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runtime';
import { addNodeGraphFramesToResponse, ZipkinDatasource } from './datasource';
import mockJson from './mocks/mockJsonResponse.json';
import { mockTraceDataFrame } from './mocks/mockTraceDataFrame';
-import { ZipkinQuery, ZipkinSpan } from './types';
-import { traceFrameFields, zipkinResponse } from './utils/testData';
+import { ZipkinQuery } from './types';
-export const backendSrv = { fetch: jest.fn() } as unknown as BackendSrv;
-
-jest.mock('@grafana/runtime', () => ({
- ...jest.requireActual('@grafana/runtime'),
- getBackendSrv: () => backendSrv,
-}));
+const templateSrv: TemplateSrv = {
+ replace: jest.fn().mockImplementation((value) => value),
+ getVariables: jest.fn(),
+ containsTemplate: jest.fn(),
+ updateTimeRange: jest.fn(),
+};
describe('ZipkinDatasource', () => {
+ let origBackendSrv: BackendSrv;
+ let ds: ZipkinDatasource;
+ beforeEach(() => {
+ origBackendSrv = getBackendSrv();
+ setBackendSrv({ ...origBackendSrv, fetch: jest.fn().mockReturnValue(of({ data: {} })) });
+ ds = new ZipkinDatasource(defaultSettings, templateSrv);
+ });
+
+ afterEach(() => {
+ setBackendSrv(origBackendSrv);
+ jest.clearAllMocks();
+ });
describe('query', () => {
- const templateSrv: TemplateSrv = {
- replace: jest.fn(),
- getVariables: jest.fn(),
- containsTemplate: jest.fn(),
- updateTimeRange: jest.fn(),
- };
-
it('runs query', async () => {
- setupBackendSrv(zipkinResponse);
- const ds = new ZipkinDatasource(defaultSettings, templateSrv);
- await expect(ds.query({ targets: [{ query: '12345' }] } as DataQueryRequest)).toEmitValuesWith(
- (val) => {
- expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
- }
- );
- });
+ const origBackendSrv = getBackendSrv();
+ setBackendSrv({
+ ...origBackendSrv,
+ fetch: jest.fn().mockReturnValue(of({ data: [] })),
+ });
- it('runs query with traceId that includes special characters', async () => {
- setupBackendSrv(zipkinResponse);
- const ds = new ZipkinDatasource(defaultSettings, templateSrv);
- await expect(ds.query({ targets: [{ query: 'a/b' }] } as DataQueryRequest)).toEmitValuesWith(
- (val) => {
- expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
- }
- );
+ const response = await lastValueFrom(ds.query({ targets: [{ query: 'test' }] } as DataQueryRequest));
+ expect(response).toEqual({
+ state: 'Done',
+ data: [],
+ });
});
it('should handle json file upload', async () => {
- const ds = new ZipkinDatasource(defaultSettings);
ds.uploadedJson = JSON.stringify(mockJson);
const response = await lastValueFrom(
ds.query({
@@ -67,7 +63,6 @@ describe('ZipkinDatasource', () => {
});
it('should fail on invalid json file upload', async () => {
- const ds = new ZipkinDatasource(defaultSettings);
ds.uploadedJson = JSON.stringify({ key: 'value', arr: [] });
const response = await lastValueFrom(
ds.query({
@@ -81,7 +76,10 @@ describe('ZipkinDatasource', () => {
describe('metadataRequest', () => {
it('runs query', async () => {
- setupBackendSrv(['service 1', 'service 2'] as unknown as ZipkinSpan[]);
+ setBackendSrv({
+ ...origBackendSrv,
+ fetch: jest.fn().mockReturnValue(of({ data: ['service 1', 'service 2'] })),
+ });
const ds = new ZipkinDatasource(defaultSettings);
const response = await ds.metadataRequest('services');
expect(response).toEqual(['service 1', 'service 2']);
@@ -118,13 +116,6 @@ describe('addNodeGraphFramesToResponse', () => {
});
});
-function setupBackendSrv(response: ZipkinSpan[]) {
- const defaultMock = () => of(createFetchResponse(response));
-
- const fetchMock = jest.spyOn(backendSrv, 'fetch');
- fetchMock.mockImplementation(defaultMock);
-}
-
const defaultSettings: DataSourceInstanceSettings = {
id: 1,
uid: '1',
diff --git a/public/app/plugins/datasource/zipkin/datasource.ts b/public/app/plugins/datasource/zipkin/datasource.ts
index 54ff15dafb1..7b49e7a0c1c 100644
--- a/public/app/plugins/datasource/zipkin/datasource.ts
+++ b/public/app/plugins/datasource/zipkin/datasource.ts
@@ -1,4 +1,4 @@
-import { lastValueFrom, Observable, of } from 'rxjs';
+import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import {
@@ -9,26 +9,15 @@ import {
FieldType,
createDataFrame,
ScopedVars,
- urlUtil,
toDataFrame,
} from '@grafana/data';
import { createNodeGraphFrames, NodeGraphOptions, SpanBarOptions } from '@grafana/o11y-ds-frontend';
-import {
- BackendSrvRequest,
- config,
- DataSourceWithBackend,
- FetchResponse,
- getBackendSrv,
- getTemplateSrv,
- TemplateSrv,
-} from '@grafana/runtime';
+import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { ZipkinQuery, ZipkinSpan } from './types';
import { createGraphFrames } from './utils/graphTransform';
import { transformResponse } from './utils/transforms';
-const apiPrefix = '/api/v2';
-
export interface ZipkinJsonData extends DataSourceJsonData {
nodeGraph?: NodeGraphOptions;
}
@@ -38,7 +27,7 @@ export class ZipkinDatasource extends DataSourceWithBackend,
+ instanceSettings: DataSourceInstanceSettings,
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super(instanceSettings);
@@ -61,40 +50,24 @@ export class ZipkinDatasource extends DataSourceWithBackend {
- if (this.nodeGraph?.enabled) {
- return addNodeGraphFramesToResponse(response);
- }
- return response;
- })
- );
- }
- const query = this.applyTemplateVariables(target, options.scopedVars);
- return this.request(`${apiPrefix}/trace/${encodeURIComponent(query.query)}`).pipe(
- map((res) => responseToDataQueryResponse(res, this.nodeGraph?.enabled))
+ return super.query(options).pipe(
+ map((response) => {
+ if (this.nodeGraph?.enabled) {
+ return addNodeGraphFramesToResponse(response);
+ }
+ return response;
+ })
);
}
return of(emptyDataQueryResponse);
}
async metadataRequest(url: string, params?: Record) {
- if (config.featureToggles.zipkinBackendMigration) {
- return await this.getResource(url, params);
- }
- const urlWithPrefix = `${apiPrefix}/${url}`;
- const res = await lastValueFrom(this.request(urlWithPrefix, params, { hideFromInspector: true }));
- return res.data;
+ return await this.getResource(url, params);
}
async testDatasource(): Promise<{ status: string; message: string }> {
- if (config.featureToggles.zipkinBackendMigration) {
- return await super.testDatasource();
- }
-
- await this.metadataRequest('services');
- return { status: 'success', message: 'Data source is working' };
+ return await super.testDatasource();
}
getQueryDisplayText(query: ZipkinQuery): string {
@@ -123,21 +96,6 @@ export class ZipkinDatasource extends DataSourceWithBackend(
- apiUrl: string,
- data?: unknown,
- options?: Partial
- ): Observable> {
- const params = data ? urlUtil.serializeParams(data) : '';
- const url = `${this.instanceSettings.url}${apiUrl}${params.length ? `?${params}` : ''}`;
- const req = {
- ...options,
- url,
- };
-
- return getBackendSrv().fetch(req);
- }
}
function responseToDataQueryResponse(response: { data: ZipkinSpan[] }, nodeGraph = false): DataQueryResponse {
diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json
index b52961af94a..efddda4e217 100644
--- a/public/app/plugins/datasource/zipkin/package.json
+++ b/public/app/plugins/datasource/zipkin/package.json
@@ -25,8 +25,8 @@
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.1.0",
"@types/jest": "29.5.14",
- "@types/lodash": "4.17.14",
- "@types/node": "22.10.10",
+ "@types/lodash": "4.17.15",
+ "@types/node": "22.12.0",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"ts-node": "10.9.2",
diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx
index 1f1dc3552f2..4b50dce768e 100644
--- a/public/test/helpers/alertingRuleEditor.tsx
+++ b/public/test/helpers/alertingRuleEditor.tsx
@@ -4,7 +4,8 @@ import { byRole, byTestId, byText } from 'testing-library-selector';
import { selectors } from '@grafana/e2e-selectors';
import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList';
-import RuleEditor from 'app/features/alerting/unified/RuleEditor';
+import RuleEditor from 'app/features/alerting/unified/rule-editor/RuleEditor';
+
export enum GrafanaRuleFormStep {
Query = 2,
Notification = 5,
diff --git a/yarn.lock b/yarn.lock
index 4c42d41e41d..53483ac0007 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2706,8 +2706,8 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.5.0-pre"
"@grafana/e2e-selectors": "npm:11.5.0-pre"
- "@grafana/experimental": "npm:2.1.6"
"@grafana/plugin-configs": "npm:11.5.0-pre"
+ "@grafana/plugin-ui": "npm:0.9.6"
"@grafana/runtime": "npm:11.5.0-pre"
"@grafana/schema": "npm:11.5.0-pre"
"@grafana/ui": "npm:11.5.0-pre"
@@ -2717,8 +2717,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
@@ -2759,8 +2759,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
lodash: "npm:4.17.21"
react: "npm:18.3.1"
@@ -2789,8 +2789,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
@@ -2831,8 +2831,8 @@ __metadata:
"@testing-library/user-event": "npm:14.5.2"
"@types/d3-random": "npm:^3.0.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
"@types/uuid": "npm:10.0.0"
@@ -2871,9 +2871,9 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
+ "@types/lodash": "npm:4.17.15"
"@types/logfmt": "npm:^1.2.3"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
"@types/react-window": "npm:1.8.8"
@@ -2903,8 +2903,8 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.5.0-pre"
"@grafana/e2e-selectors": "npm:11.5.0-pre"
- "@grafana/experimental": "npm:2.1.6"
"@grafana/plugin-configs": "npm:11.5.0-pre"
+ "@grafana/plugin-ui": "npm:0.9.6"
"@grafana/runtime": "npm:11.5.0-pre"
"@grafana/sql": "npm:11.5.0-pre"
"@grafana/ui": "npm:11.5.0-pre"
@@ -2912,8 +2912,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
lodash: "npm:4.17.21"
react: "npm:18.3.1"
@@ -2943,8 +2943,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
lodash: "npm:4.17.21"
react: "npm:18.3.1"
@@ -2971,8 +2971,8 @@ __metadata:
"@testing-library/dom": "npm:10.4.0"
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
lodash: "npm:4.17.21"
@@ -2997,9 +2997,9 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.5.0-pre"
"@grafana/e2e-selectors": "npm:11.5.0-pre"
- "@grafana/experimental": "npm:2.1.6"
"@grafana/google-sdk": "npm:0.1.2"
"@grafana/plugin-configs": "npm:11.5.0-pre"
+ "@grafana/plugin-ui": "npm:0.9.6"
"@grafana/runtime": "npm:11.5.0-pre"
"@grafana/schema": "npm:11.5.0-pre"
"@grafana/ui": "npm:11.5.0-pre"
@@ -3009,8 +3009,8 @@ __metadata:
"@testing-library/user-event": "npm:14.5.2"
"@types/debounce-promise": "npm:3.1.9"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
@@ -3062,8 +3062,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
@@ -3112,8 +3112,8 @@ __metadata:
"@testing-library/jest-dom": "npm:6.6.3"
"@testing-library/react": "npm:16.1.0"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
lodash: "npm:4.17.21"
@@ -3205,8 +3205,8 @@ __metadata:
"@rollup/plugin-node-resolve": "npm:16.0.0"
"@types/d3-interpolate": "npm:^3.0.0"
"@types/history": "npm:4.7.11"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/papaparse": "npm:5.3.15"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
@@ -3223,9 +3223,9 @@ __metadata:
marked: "npm:15.0.6"
marked-mangle: "npm:1.1.10"
moment: "npm:2.30.1"
- moment-timezone: "npm:0.5.46"
+ moment-timezone: "npm:0.5.47"
ol: "npm:7.4.0"
- papaparse: "npm:5.5.1"
+ papaparse: "npm:5.5.2"
react: "npm:18.3.1"
react-dom: "npm:18.3.1"
react-use: "npm:17.6.0"
@@ -3264,7 +3264,7 @@ __metadata:
dependencies:
"@grafana/tsconfig": "npm:^2.0.0"
"@rollup/plugin-node-resolve": "npm:16.0.0"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/semver": "npm:7.5.8"
esbuild: "npm:0.24.2"
rimraf: "npm:6.0.1"
@@ -3398,8 +3398,8 @@ __metadata:
"@testing-library/user-event": "npm:14.5.2"
"@types/d3": "npm:^7"
"@types/jest": "npm:^29.5.4"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-virtualized-auto-sizer": "npm:1.0.4"
"@types/tinycolor2": "npm:1.4.6"
@@ -3497,7 +3497,7 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.5.0-pre"
"@grafana/e2e-selectors": "npm:11.5.0-pre"
- "@grafana/experimental": "npm:2.1.6"
+ "@grafana/plugin-ui": "npm:0.9.6"
"@grafana/runtime": "npm:11.5.0-pre"
"@grafana/schema": "npm:11.5.0-pre"
"@grafana/tsconfig": "npm:^2.0.0"
@@ -3507,7 +3507,7 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:^29.5.4"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/systemjs": "npm:6.15.1"
jest: "npm:^29.6.4"
@@ -3627,8 +3627,8 @@ __metadata:
"@types/eslint": "npm:9.6.1"
"@types/jest": "npm:29.5.14"
"@types/jquery": "npm:3.5.32"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/pluralize": "npm:^0.0.33"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
@@ -3666,7 +3666,7 @@ __metadata:
marked: "npm:15.0.6"
marked-mangle: "npm:1.1.10"
moment: "npm:2.30.1"
- moment-timezone: "npm:0.5.46"
+ moment-timezone: "npm:0.5.47"
monaco-promql: "npm:1.7.4"
pluralize: "npm:8.0.0"
prettier: "npm:3.4.2"
@@ -3719,7 +3719,7 @@ __metadata:
"@types/angular": "npm:1.8.9"
"@types/history": "npm:4.7.11"
"@types/jest": "npm:29.5.14"
- "@types/lodash": "npm:4.17.14"
+ "@types/lodash": "npm:4.17.15"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
"@types/systemjs": "npm:6.15.1"
@@ -3779,7 +3779,7 @@ __metadata:
"@svgr/plugin-prettier": "npm:^8.1.0"
"@svgr/plugin-svgo": "npm:^8.1.0"
"@types/babel__core": "npm:^7"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
esbuild: "npm:0.24.2"
@@ -3799,11 +3799,11 @@ __metadata:
languageName: unknown
linkType: soft
-"@grafana/scenes-react@npm:5.38.0":
- version: 5.38.0
- resolution: "@grafana/scenes-react@npm:5.38.0"
+"@grafana/scenes-react@npm:5.41.0":
+ version: 5.41.0
+ resolution: "@grafana/scenes-react@npm:5.41.0"
dependencies:
- "@grafana/scenes": "npm:5.38.0"
+ "@grafana/scenes": "npm:5.41.0"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3814,13 +3814,13 @@ __metadata:
"@grafana/ui": ^11.0.0
react: ^18.0.0
react-dom: ^18.0.0
- checksum: 10/f130dabfd82abeb232f5b9d485102d03d19a7b0a91924899f6df25ee79e093d216738c9c9b12cd0ae0378eeae91dc1a0b06dadc3c9bda80ab7a2f855fb1cdac9
+ checksum: 10/80d5b51a190ed962e17468c0646e9b48a52133c3af026f8bc599c5e8e1150bcd0210f7973dd13e28f96a300798a1c2a1ec41c1bcedc580d8da393591d1e69eb4
languageName: node
linkType: hard
-"@grafana/scenes@npm:5.38.0":
- version: 5.38.0
- resolution: "@grafana/scenes@npm:5.38.0"
+"@grafana/scenes@npm:5.41.0":
+ version: 5.41.0
+ resolution: "@grafana/scenes@npm:5.41.0"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
@@ -3837,7 +3837,7 @@ __metadata:
"@grafana/ui": ">=10.4"
react: ^18.0.0
react-dom: ^18.0.0
- checksum: 10/ed6f67e1d54ec49df77cf5fce55e13402c84755dc2f2ef09650fa44c3783dd7f18d08de26721dd4ba5f51ac02187f2c35d27023d75deb9d71c8599490f05eaf8
+ checksum: 10/a77b57688b5ac518fa1f3842a871ad21e2daf840d670abda6b69facce74bb6761195c43f25507a3dff87c8551625a979c4240debe62832834473b67145b5d589
languageName: node
linkType: hard
@@ -3875,7 +3875,7 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.5.0-pre"
"@grafana/e2e-selectors": "npm:11.5.0-pre"
- "@grafana/experimental": "npm:2.1.6"
+ "@grafana/plugin-ui": "npm:0.9.6"
"@grafana/runtime": "npm:11.5.0-pre"
"@grafana/tsconfig": "npm:^2.0.0"
"@grafana/ui": "npm:11.5.0-pre"
@@ -3885,8 +3885,8 @@ __metadata:
"@testing-library/react": "npm:16.1.0"
"@testing-library/user-event": "npm:14.5.2"
"@types/jest": "npm:^29.5.4"
- "@types/lodash": "npm:4.17.14"
- "@types/node": "npm:22.10.10"
+ "@types/lodash": "npm:4.17.15"
+ "@types/node": "npm:22.12.0"
"@types/react": "npm:18.3.18"
"@types/react-dom": "npm:18.3.5"
"@types/react-virtualized-auto-sizer": "npm:1.0.4"
@@ -4048,9 +4048,9 @@ __metadata:
"@types/is-hotkey": "npm:0.1.10"
"@types/jest": "npm:29.5.14"
"@types/jquery": "npm:3.5.32"
- "@types/lodash": "npm:4.17.14"
+ "@types/lodash": "npm:4.17.15"
"@types/mock-raf": "npm:1.0.6"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/prismjs": "npm:1.26.5"
"@types/react": "npm:18.3.18"
"@types/react-color": "npm:3.0.13"
@@ -7977,57 +7977,57 @@ __metadata:
languageName: node
linkType: hard
-"@swagger-api/apidom-ast@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ast@npm:^1.0.0-beta.11, @swagger-api/apidom-ast@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
unraw: "npm:^3.0.0"
- checksum: 10/b2ea32b8ed589a3aff122e9209d5f0c873364bb34b234d13796422d4fce6b9f52fab599ef47956f655316256cf6af821c233117c0ca96a677b867e075b70cb5d
+ checksum: 10/efe7caf37735f6b1a9b56ca780ce59134540c00c50ef00606607a458905142b7794466cacbffdcd9afe11ece9f5a99651f2f62d24b05f71727d86718e2dea91c
languageName: node
linkType: hard
-"@swagger-api/apidom-core@npm:>=1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.5"
+"@swagger-api/apidom-core@npm:>=1.0.0-beta.11 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.11, @swagger-api/apidom-core@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-ast": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
minim: "npm:~0.23.8"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
short-unique-id: "npm:^5.0.2"
ts-mixer: "npm:^6.0.3"
- checksum: 10/c034ef286738b2b5aab525b068fd22e1b54145e3024477abcafde926f3783c280c69e4de23cc28d9cc568a62fb02719a0a89e6fd2011136cf447f42ed66fca55
+ checksum: 10/bb81c1ef603b80985c8e7ad8808a12eaaf2daa3de2a41ac2f9f8c041c7a7f26bcbfa9713738248db198af1a6b424127b10abd5ac7e6fcad95d852cea8fb8a526
languageName: node
linkType: hard
-"@swagger-api/apidom-error@npm:>=1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.5"
+"@swagger-api/apidom-error@npm:>=1.0.0-beta.11 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.11, @swagger-api/apidom-error@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- checksum: 10/defb3ba3775be8a511ff01be4ed7d2eca66faf5ab478f65a39845c8981510a0286e622268240de56613fd3ee37de906a7c6947a82aceb214fdd89d0988b972bb
+ checksum: 10/d63ec68067a17c10fa51b2929c18d18f397d91ab45961e398ad99341b45e87d60a0ec6f634ac3883733ea7f4a5155e9929207dd44572f65754fa0f5b7d004466
languageName: node
linkType: hard
-"@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.5"
+"@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.11 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.11, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.3 <1.0.0-rc.0":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
- checksum: 10/68b3b196de1d2cb86663350ba70f885599a12c21804cd9bc1be1777ee5e99e9f570623599d476d241bda298e0890c34d2c9a516f7a47cf060992107c31e9d9f5
+ checksum: 10/dee858ee4b4a0f93ab082451a703c8b9e0250f7b486d8d9ef243b77f92acc49741cfdd1e10a9cc44d946b64e469aa4016a1380c7a28d660fe048a2897098afc7
languageName: node
linkType: hard
@@ -8062,50 +8062,82 @@ __metadata:
languageName: node
linkType: hard
-"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.0.0-beta.11":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
ts-mixer: "npm:^6.0.4"
- checksum: 10/2518b3aa9b7387a90553565132101616dc24c6d1f8aee581f3562bac87403f87887217fdb3cf17b113f685e051980fc66ec25d9cab7e4a0bf4922b4bcd3bc502
+ checksum: 10/b91f90e7376922f1112752520bcac265c20245c1d2d682cd5ba9344a3eb7287962f18e3430f501af4d4db43397a2977267b15ef02a3bcd6e8ba384901e195672
languageName: node
linkType: hard
-"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.0.0-beta.11":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
ts-mixer: "npm:^6.0.4"
- checksum: 10/c439c3679dc6ea1807affa0a2913deea52ff8d63b48722c741458da147e849be9acc0c13955757a7b6904a4e7148cdb7845291da758a085083c61610a96fc36f
+ checksum: 10/847103b0923fa7a0fcff124f5359f735f56e38a88666051e5959403e0060e33bea63b30098dec99d36bcda6619b2a3d13ce61c26e6ca03c508ea8518e871c123
languageName: node
linkType: hard
-"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-beta.11, @swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-ast": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
ts-mixer: "npm:^6.0.4"
- checksum: 10/28c8f989e26a453f2ca6c655d6822d63f7bc57137375c460c707caa964a5097566969031f322d772b49de0ce2924aeb69e2c5a6ff66191b457844e68a40f013e
+ checksum: 10/9e0ba9e29849915c0067227a82d8d9a741221b3ac73592b73e23d3cfe0c57548b21cbbb66cdeba914218c5f675f4ec497232d465880013f8aec8f486e4c3d91c
+ languageName: node
+ linkType: hard
+
+"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.0.0-beta.11":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.0.0-beta.11"
+ dependencies:
+ "@babel/runtime-corejs3": "npm:^7.20.7"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.11"
+ "@types/ramda": "npm:~0.30.0"
+ ramda: "npm:~0.30.0"
+ ramda-adjunct: "npm:^5.0.0"
+ ts-mixer: "npm:^6.0.4"
+ checksum: 10/0df53880202cdb8dbe1e534314842c2c10fe4cd03f6eecd56d67174492807df03134f360e1c0724610fc4531520c49bb41d81e6fd7373118031ad883e0101338
+ languageName: node
+ linkType: hard
+
+"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-beta.11, @swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.0.0-beta.11"
+ dependencies:
+ "@babel/runtime-corejs3": "npm:^7.20.7"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.0.0-beta.11"
+ "@types/ramda": "npm:~0.30.0"
+ ramda: "npm:~0.30.0"
+ ramda-adjunct: "npm:^5.0.0"
+ ts-mixer: "npm:^6.0.4"
+ checksum: 10/9ef5261e58b2e6798a3c22ad01940c613475a6515d2cd79d2f150b02152118036ef472bb75a271c0d1c3b617d51ccb2262680469824335f27150600f695eb6d8
languageName: node
linkType: hard
@@ -8125,36 +8157,37 @@ __metadata:
languageName: node
linkType: hard
-"@swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.11, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-error": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-error": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
ts-mixer: "npm:^6.0.3"
- checksum: 10/5bb9c191a3d79d9a69aa04deb5dd6ce2eaa09700210f5a89fdeb0668ec76eb3f2d4a26950405f001459e59ad199ddd4632251992b9fc1b9fa03955e1ad600810
+ checksum: 10/44eabda02fb8ad965b7756931bba7ac2ae9f49ef720f401179efb9eafbf94ff6b3c8cb8fd260c9ddb1affe35c3876f780cef4cb090b755736847adb7eba699c8
languageName: node
linkType: hard
-"@swagger-api/apidom-ns-openapi-3-1@npm:>=1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.5":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.0.0-beta.5"
+"@swagger-api/apidom-ns-openapi-3-1@npm:>=1.0.0-beta.11 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.5":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.5"
- "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-ast": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-beta.11"
+ "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.11"
"@types/ramda": "npm:~0.30.0"
ramda: "npm:~0.30.0"
ramda-adjunct: "npm:^5.0.0"
ts-mixer: "npm:^6.0.3"
- checksum: 10/69018147465c78a25efc5e7bc4439561c2d620c65c9cf86bdc503dbad9b40e52db77bb0cb11800a4c34bdc88d462126e3dc355bad290cdfe7c9fbeda391405c8
+ checksum: 10/6ed3a3d7bc231c8f3273c60dcd459cdb117246e372ed9d0d2483601a966a3109b9617a4d8f327f494a31013d5bcf2bc372c5f3297bbf953c61ff4770c87414a5
languageName: node
linkType: hard
@@ -8391,12 +8424,12 @@ __metadata:
languageName: node
linkType: hard
-"@swagger-api/apidom-reference@npm:>=1.0.0-beta.3 <1.0.0-rc.0":
- version: 1.0.0-beta.5
- resolution: "@swagger-api/apidom-reference@npm:1.0.0-beta.5"
+"@swagger-api/apidom-reference@npm:>=1.0.0-beta.11 <1.0.0-rc.0":
+ version: 1.0.0-beta.11
+ resolution: "@swagger-api/apidom-reference@npm:1.0.0-beta.11"
dependencies:
"@babel/runtime-corejs3": "npm:^7.20.7"
- "@swagger-api/apidom-core": "npm:^1.0.0-beta.5"
+ "@swagger-api/apidom-core": "npm:^1.0.0-beta.11"
"@swagger-api/apidom-error": "npm:^1.0.0-beta.3 <1.0.0-rc.0"
"@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.3 <1.0.0-rc.0"
"@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.3 <1.0.0-rc.0"
@@ -8467,7 +8500,16 @@ __metadata:
optional: true
"@swagger-api/apidom-parser-adapter-yaml-1-2":
optional: true
- checksum: 10/e118278a4a460545de4e393aecaef16f92828cc0af02f4b9b3e8773b9f61c495e0afbce97e68ebd901858dc4b73a178aec2ec18fc1cec2a54d3ccbd3f6bc5331
+ checksum: 10/1ae856fd2d13884f8f063421c010f166da1602075ae3b08c041c98199c724615b389110672e2c1b129b7b551a21b8738d32df87c066cc105669a03b6b21897e6
+ languageName: node
+ linkType: hard
+
+"@swaggerexpert/cookie@npm:^1.4.1":
+ version: 1.4.1
+ resolution: "@swaggerexpert/cookie@npm:1.4.1"
+ dependencies:
+ apg-lite: "npm:^1.0.3"
+ checksum: 10/936590cb70fb7af4ec988e7ee7c5e965b66d09046d3fd2b52c140f9777cda1452f2f24d80eb64ff9678b79f566dd0ef51699050aea9c9a3fd5345dcc73a3fa7a
languageName: node
linkType: hard
@@ -8892,9 +8934,9 @@ __metadata:
linkType: hard
"@types/babel__preset-env@npm:^7":
- version: 7.9.7
- resolution: "@types/babel__preset-env@npm:7.9.7"
- checksum: 10/624425a84d9149aec04795fed6b1ac2f27dfd5d7976fde479bb1a4d754de34c92cdc28a1a373a5826382a68127b536420a0e090aa5fae522cb62724b7a571cb5
+ version: 7.10.0
+ resolution: "@types/babel__preset-env@npm:7.10.0"
+ checksum: 10/7d4d12758d89708afe327079d7d7580e8af3292295f087b8a9a48e12ac1d90aadc18ac3bc00f9b0cbc8778f3ce9fe778801d4d49b7691a75e3f13a901b69fd07
languageName: node
linkType: hard
@@ -9647,10 +9689,10 @@ __metadata:
languageName: node
linkType: hard
-"@types/lodash@npm:*, @types/lodash@npm:4.17.14, @types/lodash@npm:^4.14.172":
- version: 4.17.14
- resolution: "@types/lodash@npm:4.17.14"
- checksum: 10/6ee40725f3e192f5ef1f493caca19210aa7acd7adc3136b8dba84d418a35be0abea0668105aed9f696ad62a54310a9c0d328971ad4b157f5bcda700424ed5aae
+"@types/lodash@npm:*, @types/lodash@npm:4.17.15, @types/lodash@npm:^4.14.172":
+ version: 4.17.15
+ resolution: "@types/lodash@npm:4.17.15"
+ checksum: 10/27b348b5971b9c670215331b52448a13d7d65bf1fbd320a7049c9c153c1186ff5d116ba75f05f07d32d7ece8a992b26a30c7bdc9be22a3d1e4e3e6068aa04603
languageName: node
linkType: hard
@@ -9745,12 +9787,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:*, @types/node@npm:22.10.10, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4, @types/node@npm:^22.0.0":
- version: 22.10.10
- resolution: "@types/node@npm:22.10.10"
+"@types/node@npm:*, @types/node@npm:22.12.0, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4, @types/node@npm:^22.0.0":
+ version: 22.12.0
+ resolution: "@types/node@npm:22.12.0"
dependencies:
undici-types: "npm:~6.20.0"
- checksum: 10/61559b62bc7e62b947876e097c99472dd01317dd3b5916b538e2c40db8d68a1fc23c8de48149ef04cd2790a97bf2c005ea60cbb067e55ddbefd013d73da7a147
+ checksum: 10/aac2b6f6a845ec3540c3d979b3150efe3162165bfda953af10b579df2d1cc4f5c48506922bf6bf661a2e5a7ebb571c5729bf1f9f12488a810bb1a5fa9522ef9d
languageName: node
linkType: hard
@@ -10162,12 +10204,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/swagger-ui-react@npm:4.18.3":
- version: 4.18.3
- resolution: "@types/swagger-ui-react@npm:4.18.3"
+"@types/swagger-ui-react@npm:4.19.0":
+ version: 4.19.0
+ resolution: "@types/swagger-ui-react@npm:4.19.0"
dependencies:
"@types/react": "npm:*"
- checksum: 10/4927314f1b0d68edf200ef15bca7555f12ec1bb8cc699fa397ef2f4e1e1873d17880b663bbc6d70e30149e50c3e7055178c1e0aad110520dfb31d781ab326dcb
+ checksum: 10/c4a7ae85ca081bf079164b3e11ecc0c91dec1ed10da0f44fc67f8cf93a7d92d5bfa681035eb8b9d3327a0361d201861c02571b4b18f361253510f3ce19ce073f
languageName: node
linkType: hard
@@ -10280,9 +10322,9 @@ __metadata:
linkType: hard
"@types/webpack-env@npm:^1.18.4":
- version: 1.18.6
- resolution: "@types/webpack-env@npm:1.18.6"
- checksum: 10/463e545441d206e52bf24b4577a503bf9a96198a69b3ad1234d15bf96e9a6d5ccb25bb1197e520541cb8da968232305fe8d8400595d5745e16abfdc4089e73d0
+ version: 1.18.7
+ resolution: "@types/webpack-env@npm:1.18.7"
+ checksum: 10/b07ca300b8e8af9ffad2bfdd6a662adffe49f86710a72cec01e4d10bb99444ed1ce45efbedda66ccd399b26ee22e7eadefd7c118a64047f5ea465b556ba86cf3
languageName: node
linkType: hard
@@ -11269,7 +11311,7 @@ __metadata:
languageName: node
linkType: hard
-"apg-lite@npm:^1.0.3":
+"apg-lite@npm:^1.0.3, apg-lite@npm:^1.0.4":
version: 1.0.4
resolution: "apg-lite@npm:1.0.4"
checksum: 10/9c5eb431497415b738e332e5805f836c64ac9b75a399afaec0354859f3f44c95e203fd5c7b8fee9f28fe1e184dd30f6073ed9df062849c3df03c51624901a8a6
@@ -17752,8 +17794,8 @@ __metadata:
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"
- "@grafana/scenes": "npm:5.38.0"
- "@grafana/scenes-react": "npm:5.38.0"
+ "@grafana/scenes": "npm:5.41.0"
+ "@grafana/scenes-react": "npm:5.41.0"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^2.0.0"
@@ -17819,10 +17861,10 @@ __metadata:
"@types/jquery": "npm:3.5.32"
"@types/js-yaml": "npm:^4.0.5"
"@types/jsurl": "npm:^1.2.28"
- "@types/lodash": "npm:4.17.14"
+ "@types/lodash": "npm:4.17.15"
"@types/logfmt": "npm:^1.2.3"
"@types/lucene": "npm:^2"
- "@types/node": "npm:22.10.10"
+ "@types/node": "npm:22.12.0"
"@types/node-forge": "npm:^1"
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4"
"@types/pluralize": "npm:^0.0.33"
@@ -17844,7 +17886,7 @@ __metadata:
"@types/slate": "npm:0.47.11"
"@types/slate-plain-serializer": "npm:0.7.5"
"@types/slate-react": "npm:0.22.9"
- "@types/swagger-ui-react": "npm:4.18.3"
+ "@types/swagger-ui-react": "npm:4.19.0"
"@types/systemjs": "npm:6.15.1"
"@types/tinycolor2": "npm:1.4.6"
"@types/uuid": "npm:10.0.0"
@@ -17959,7 +18001,7 @@ __metadata:
ml-regression-polynomial: "npm:^3.0.0"
ml-regression-simple-linear: "npm:^3.0.0"
moment: "npm:2.30.1"
- moment-timezone: "npm:0.5.46"
+ moment-timezone: "npm:0.5.47"
monaco-editor: "npm:0.34.1"
moveable: "npm:0.53.0"
msw: "npm:2.7.0"
@@ -18032,7 +18074,7 @@ __metadata:
style-loader: "npm:4.0.0"
stylelint: "npm:16.13.2"
stylelint-config-sass-guidelines: "npm:12.1.0"
- swagger-ui-react: "npm:5.18.2"
+ swagger-ui-react: "npm:5.18.3"
symbol-observable: "npm:4.0.0"
systemjs: "npm:6.15.1"
systemjs-cjs-extra: "npm:0.2.1"
@@ -22433,6 +22475,15 @@ __metadata:
languageName: node
linkType: hard
+"moment-timezone@npm:0.5.47":
+ version: 0.5.47
+ resolution: "moment-timezone@npm:0.5.47"
+ dependencies:
+ moment: "npm:^2.29.4"
+ checksum: 10/b2ad32e6b7ea4ce99756c9f571f8eb1bdd6592ecdc2bf3f7fb988ae980a49a7e87b986776090571b9608c8576205f839b736dc7589c180ce36c82fad4b8416c6
+ languageName: node
+ linkType: hard
+
"moment@npm:2.30.1, moment@npm:2.x, moment@npm:^2.20.1, moment@npm:^2.29.4, moment@npm:^2.30.1":
version: 2.30.1
resolution: "moment@npm:2.30.1"
@@ -23434,21 +23485,21 @@ __metadata:
languageName: node
linkType: hard
-"openapi-path-templating@npm:^1.5.1":
- version: 1.6.0
- resolution: "openapi-path-templating@npm:1.6.0"
+"openapi-path-templating@npm:^2.0.1":
+ version: 2.1.0
+ resolution: "openapi-path-templating@npm:2.1.0"
dependencies:
- apg-lite: "npm:^1.0.3"
- checksum: 10/35353a0ce712dd79b7a60beca75d6a67fb6ad4bbdac4ceef87a6ab78d916f88dd6b426bd81e8ecf12c51798221fb15e63da8f9cadb1bcab4bc7aace8e8a266c1
+ apg-lite: "npm:^1.0.4"
+ checksum: 10/de3ba30a19cc4bed5ace5dad0314bea66e09689001bd3510224a441f7ced53d854655daea846f884ed024d90a8f2af2d4bd0f28256dbda0c49b337684a802da1
languageName: node
linkType: hard
-"openapi-server-url-templating@npm:^1.0.0":
- version: 1.1.0
- resolution: "openapi-server-url-templating@npm:1.1.0"
+"openapi-server-url-templating@npm:^1.2.0":
+ version: 1.3.0
+ resolution: "openapi-server-url-templating@npm:1.3.0"
dependencies:
- apg-lite: "npm:^1.0.3"
- checksum: 10/932f08f390269506e1ea1d24208319f0f2a90023368562f1ce167a30d4199f0e39236e17f250749359b4c7b64d86c510e68e8eb9c7f6168da35f0475534ef3ed
+ apg-lite: "npm:^1.0.4"
+ checksum: 10/4a98f67cedc0958d3a30cc6db91f35970b0aeae113d0403639adbe3f5e123ff02abebc0468c10639bd0d6db165a9bb0f21c4e367836e984c2345a5a4213620a9
languageName: node
linkType: hard
@@ -23759,10 +23810,10 @@ __metadata:
languageName: node
linkType: hard
-"papaparse@npm:5.5.1":
- version: 5.5.1
- resolution: "papaparse@npm:5.5.1"
- checksum: 10/6c3b47bd316cf11936641abe5590c6fb1e5b04297d1c594965a44dba89cc0f26b4e4d61eb1933352c538a30c4915d110628d14cf4d60cef0b8245c9a26477602
+"papaparse@npm:5.5.2":
+ version: 5.5.2
+ resolution: "papaparse@npm:5.5.2"
+ checksum: 10/f5d25ccb10b1b9e87a0fe2d2965c7f8a83223cd79ec8963dd7e8766e8e97a6228dc6c20738a78148dfc74f32b2372003de732a7d50593f38b6a2bb5092710bc7
languageName: node
linkType: hard
@@ -29364,35 +29415,35 @@ __metadata:
languageName: node
linkType: hard
-"swagger-client@npm:^3.31.0":
- version: 3.32.2
- resolution: "swagger-client@npm:3.32.2"
+"swagger-client@npm:^3.34.0":
+ version: 3.34.0
+ resolution: "swagger-client@npm:3.34.0"
dependencies:
"@babel/runtime-corejs3": "npm:^7.22.15"
"@scarf/scarf": "npm:=1.4.0"
- "@swagger-api/apidom-core": "npm:>=1.0.0-beta.3 <1.0.0-rc.0"
- "@swagger-api/apidom-error": "npm:>=1.0.0-beta.3 <1.0.0-rc.0"
- "@swagger-api/apidom-json-pointer": "npm:>=1.0.0-beta.3 <1.0.0-rc.0"
- "@swagger-api/apidom-ns-openapi-3-1": "npm:>=1.0.0-beta.3 <1.0.0-rc.0"
- "@swagger-api/apidom-reference": "npm:>=1.0.0-beta.3 <1.0.0-rc.0"
- cookie: "npm:~0.7.2"
+ "@swagger-api/apidom-core": "npm:>=1.0.0-beta.11 <1.0.0-rc.0"
+ "@swagger-api/apidom-error": "npm:>=1.0.0-beta.11 <1.0.0-rc.0"
+ "@swagger-api/apidom-json-pointer": "npm:>=1.0.0-beta.11 <1.0.0-rc.0"
+ "@swagger-api/apidom-ns-openapi-3-1": "npm:>=1.0.0-beta.11 <1.0.0-rc.0"
+ "@swagger-api/apidom-reference": "npm:>=1.0.0-beta.11 <1.0.0-rc.0"
+ "@swaggerexpert/cookie": "npm:^1.4.1"
deepmerge: "npm:~4.3.0"
fast-json-patch: "npm:^3.0.0-1"
js-yaml: "npm:^4.1.0"
neotraverse: "npm:=0.6.18"
node-abort-controller: "npm:^3.1.1"
node-fetch-commonjs: "npm:^3.3.2"
- openapi-path-templating: "npm:^1.5.1"
- openapi-server-url-templating: "npm:^1.0.0"
+ openapi-path-templating: "npm:^2.0.1"
+ openapi-server-url-templating: "npm:^1.2.0"
ramda: "npm:^0.30.1"
ramda-adjunct: "npm:^5.0.0"
- checksum: 10/651f8e6446b57c42de61684440e854f146c9e6a0d9eab28892d07a00bc0dd9e742205c285d4db2dea85d657c62da3a81a44cf417db2383753a75d69377877253
+ checksum: 10/ed89c44ba172abb9cd6a36a189cf2810ef9b1983cb71657772ede94ea804105b07549b90ba36bbead87e4ac71e609b51f0977d9f9c90112952756c7f4f8bde82
languageName: node
linkType: hard
-"swagger-ui-react@npm:5.18.2":
- version: 5.18.2
- resolution: "swagger-ui-react@npm:5.18.2"
+"swagger-ui-react@npm:5.18.3":
+ version: 5.18.3
+ resolution: "swagger-ui-react@npm:5.18.3"
dependencies:
"@babel/runtime-corejs3": "npm:^7.24.7"
"@braintree/sanitize-url": "npm:=7.0.4"
@@ -29423,7 +29474,7 @@ __metadata:
reselect: "npm:^5.1.1"
serialize-error: "npm:^8.1.0"
sha.js: "npm:^2.4.11"
- swagger-client: "npm:^3.31.0"
+ swagger-client: "npm:^3.34.0"
url-parse: "npm:^1.5.10"
xml: "npm:=1.0.1"
xml-but-prettier: "npm:^1.0.1"
@@ -29431,7 +29482,7 @@ __metadata:
peerDependencies:
react: ">=16.8.0 <19"
react-dom: ">=16.8.0 <19"
- checksum: 10/04d4bd8e67ac19472d7915ed779933108b660babd14740c74efa33c76a6bd17e94ebcabd4449e365da3527937f3c60f53c1c392c3e13e5ac5f2e984e6cdcee42
+ checksum: 10/4437fc3fd9a869ab3ef357c78137e20fc9d96dbc71332e9ac53b45decd41382c9467d610ebf0c15a628485c67475a8f89c37882ca548305d633e2dd4b632db5f
languageName: node
linkType: hard