+ {isManualRestore && (
+
+
+ You are manually restoring an old version of this alert rule. Please review the changes carefully before
+ saving the rule definition.
+
+
+ )}
{isPaused &&
}
{/* Step 1 */}
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 72093ecc490..71c049e661b 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
@@ -168,7 +168,7 @@ export const getPayloadToExport = (
} else {
// we have to create a new group with the updated rule
return {
- name: existingGroup?.name ?? '',
+ name: existingGroup?.name ?? formValues.group,
rules: [updatedRule],
};
}
diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
index c3e9883f923..4c0cf9ea5aa 100644
--- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx
@@ -15,10 +15,20 @@ export interface ContactPointSelectorProps {
}
export function ContactPointSelector({ alertManager, onSelectContactPoint }: ContactPointSelectorProps) {
- const { control, watch, trigger } = useFormContext();
+ const { control, watch, trigger, setError } = useFormContext();
const contactPointInForm = watch(`contactPoints.${alertManager}.selectedContactPoint`);
+ // Wrap in useCallback to avoid infinite render loop
+ const handleError = useCallback(
+ (err: Error) => {
+ setError(`contactPoints.${alertManager}.selectedContactPoint`, {
+ message: err.message,
+ });
+ },
+ [alertManager, setError]
+ );
+
// if we have a contact point selected, check if it still exists in the event that someone has deleted it
const validateContactPoint = useCallback(() => {
if (contactPointInForm) {
@@ -49,6 +59,7 @@ export function ContactPointSelector({ alertManager, onSelectContactPoint }: Con
}}
showRefreshButton
selectedContactPointName={contactPointInForm}
+ onError={handleError}
/>
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 2ecd8131b3b..20d3359a03e 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
@@ -17,10 +17,10 @@ import {
Menu,
MenuItem,
Stack,
+ Text,
Tooltip,
useStyles2,
} from '@grafana/ui';
-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, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types';
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 5faad744839..e714277c490 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
@@ -130,7 +130,7 @@ const RuleViewer = () => {
{activeTab === ActiveTab.Routing &&
}
{activeTab === ActiveTab.Details &&
}
{activeTab === ActiveTab.VersionHistory && isGrafanaRulerRule(rule.rulerRule) && (
-
+
)}
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/AlertVersionHistory.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/AlertVersionHistory.tsx
index abd042bc5b5..1018b04b5c9 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/tabs/AlertVersionHistory.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/AlertVersionHistory.tsx
@@ -1,22 +1,31 @@
-import { useMemo, useState } from 'react';
+import { useCallback, useMemo, useState } from 'react';
import { config } from '@grafana/runtime';
-import { Alert, Box, Button, Drawer, EmptyState, LoadingPlaceholder, Stack, Text, Tooltip } from '@grafana/ui';
-import { RevisionModel, VersionHistoryComparison } from 'app/core/components/VersionHistory/VersionHistoryComparison';
+import { Alert, Button, EmptyState, LoadingPlaceholder, Stack, Text, Tooltip } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
+import { RuleGroupIdentifierV2, RuleIdentifier } from 'app/types/unified-alerting';
import { GrafanaRuleDefinition, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
-import { LogMessages, logInfo, trackRuleVersionsComparisonClick } from '../../../Analytics';
+import {
+ LogMessages,
+ logInfo,
+ trackRuleVersionsComparisonClick,
+ trackRuleVersionsRestoreFail,
+ trackRuleVersionsRestoreSuccess,
+} from '../../../Analytics';
import { alertRuleApi } from '../../../api/alertRuleApi';
+import { AlertRuleAction, useRulerRuleAbility } from '../../../hooks/useAbilities';
+import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource';
import { stringifyErrorLike } from '../../../utils/misc';
-import { VersionHistoryTable } from './components/VersionHistoryTable';
-import { getSpecialUidsDisplayMap, preprocessRuleForDiffDisplay } from './versions-utils';
+import { ComparisonDrawer } from './version-history/ComparisonDrawer';
+import { Origin } from './version-history/ConfirmVersionRestoreModal';
+import { VersionHistoryTable } from './version-history/VersionHistoryTable';
const { useGetAlertVersionHistoryQuery } = alertRuleApi;
interface AlertVersionHistoryProps {
- ruleUid: string;
+ rule: RulerGrafanaRuleDTO;
}
/** List of (top level) properties to exclude from being shown in human readable summary of version changes */
@@ -32,9 +41,15 @@ export const grafanaAlertPropertiesToIgnore: Array
* Render the version history of a given Grafana managed alert rule, showing different edits
* and allowing to restore to a previous version.
*/
-export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
+export function AlertVersionHistory({ rule }: AlertVersionHistoryProps) {
+ const ruleUid = rule.grafana_alert.uid;
const { isLoading, currentData: ruleVersions = [], error } = useGetAlertVersionHistoryQuery({ uid: ruleUid });
+ const ruleIdentifier: RuleIdentifier = useMemo(
+ () => ({ ruleSourceName: GRAFANA_RULES_SOURCE_NAME, uid: ruleUid }),
+ [ruleUid]
+ );
+
const [oldVersion, setOldVersion] = useState>();
const [newVersion, setNewVersion] = useState>();
const [showDrawer, setShowDrawer] = useState(false);
@@ -42,6 +57,42 @@ export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
const [checkedVersions, setCheckedVersions] = useState(new Set());
const canCompare = useMemo(() => checkedVersions.size > 1, [checkedVersions]);
+ // check if restoring is allowed/enabled
+ const groupIdentifier: RuleGroupIdentifierV2 = {
+ namespace: { uid: rule.grafana_alert.namespace_uid },
+ groupName: rule.grafana_alert.rule_group,
+ groupOrigin: 'grafana',
+ };
+ const [restoreSupported, restoreAllowed] = useRulerRuleAbility(rule, groupIdentifier, AlertRuleAction.Restore);
+ const canRestore =
+ restoreAllowed && restoreSupported && Boolean(config.featureToggles.alertingRuleVersionHistoryRestore);
+
+ //tracking functions for restore action
+ const onRestoreSuccess = useCallback(
+ (origin: Origin) => {
+ trackRuleVersionsRestoreSuccess({
+ origin,
+ latest: newVersion === ruleVersions[0],
+ oldVersion: oldVersion?.grafana_alert.version || 0,
+ newVersion: newVersion?.grafana_alert.version || 0,
+ });
+ },
+ [oldVersion, newVersion, ruleVersions]
+ );
+
+ const onRestoreFail = useCallback(
+ (origin: Origin, error: Error) => {
+ trackRuleVersionsRestoreFail({
+ origin,
+ latest: newVersion === ruleVersions[0],
+ oldVersion: oldVersion?.grafana_alert.version || 0,
+ newVersion: newVersion?.grafana_alert.version || 0,
+ error,
+ });
+ },
+ [oldVersion, newVersion, ruleVersions]
+ );
+
if (error) {
return (
@@ -65,7 +116,8 @@ export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
);
}
- const compareVersions = () => {
+ const compareSelectedVersions = () => {
+ // precondition: we have only two versions selected in checkedVersions
const [older, newer] = ruleVersions
.filter((rule) => {
const version = rule.grafana_alert.version;
@@ -89,8 +141,16 @@ export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
newVersion: newer?.grafana_alert.version || 0,
});
- setOldVersion(older);
- setNewVersion(newer);
+ // setting the versions to compare
+ compareVersions(older, newer);
+ };
+
+ const compareVersions = (
+ oldRule: RulerGrafanaRuleDTO,
+ newRule: RulerGrafanaRuleDTO
+ ) => {
+ setOldVersion(oldRule);
+ setNewVersion(newRule);
setShowDrawer(true);
};
@@ -104,6 +164,8 @@ export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
setNewVersion(undefined);
}
+ const isNewLatest = ruleVersions[0].grafana_alert.version === newVersion?.grafana_alert.version;
+
return (
@@ -117,74 +179,33 @@ export function AlertVersionHistory({ ruleUid }: AlertVersionHistoryProps) {
content={t('core.versionHistory.comparison.select', 'Select two versions to start comparing')}
placement="bottom"
>
-
{showDrawer && oldVersion && newVersion && (
- setShowDrawer(false)}
- title={t('alerting.alertVersionHistory.comparing-versions', 'Comparing versions')}
- >
-
- {config.featureToggles.alertingRuleVersionHistoryRestore && (
-
-
- {}}>
-
- Reset to version {{ version: oldVersion.grafana_alert.version }}
-
-
-
-
- )}
-
+ onRestoreSuccess('comparison-drawer')}
+ onRestoreError={(err: Error) => onRestoreFail('comparison-drawer', err)}
+ canRestore={canRestore}
+ />
)}
-
compareVersions(rule, ruleVersions[0])}
onVersionsChecked={handleCheckedVersionChange}
ruleVersions={ruleVersions}
disableSelection={canCompare}
checkedVersions={checkedVersions}
+ onRestoreSuccess={() => onRestoreSuccess('version-list')}
+ onRestoreError={(err: Error) => onRestoreFail('version-list', err)}
+ canRestore={canRestore}
/>
);
}
-
-/**
- * Turns a version of a Grafana rule definition into data structure
- * used to display the version summary when comparing versions
- */
-function parseVersionInfoToSummary(version: RulerGrafanaRuleDTO): RevisionModel {
- const unknown = t('alerting.alertVersionHistory.unknown', 'Unknown');
- const SPECIAL_UID_MAP = getSpecialUidsDisplayMap();
- const createdBy = (() => {
- const updatedBy = version?.grafana_alert.updated_by;
- const uid = updatedBy?.uid;
- const name = updatedBy?.name;
-
- if (!updatedBy) {
- return unknown;
- }
- if (uid && SPECIAL_UID_MAP[uid]) {
- return SPECIAL_UID_MAP[uid].name;
- }
- if (name) {
- return name;
- }
- return uid ? t('alerting.alertVersionHistory.user-id', 'User ID {{uid}}', { uid }) : unknown;
- })();
-
- return {
- created: version.grafana_alert.updated || unknown,
- createdBy,
- version: version.grafana_alert.version || unknown,
- };
-}
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx
index 3faaa60744b..218e6cf0420 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx
@@ -18,7 +18,7 @@ import { isNullDate } from '../../../utils/time';
import { Tokenize } from '../../Tokenize';
import { DetailText } from '../../common/DetailText';
-import { UpdatedByUser } from './components/UpdatedBy';
+import { UpdatedByUser } from './version-history/UpdatedBy';
enum RuleType {
GrafanaManagedAlertRule = 'Grafana-managed alert rule',
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ComparisonDrawer.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ComparisonDrawer.tsx
new file mode 100644
index 00000000000..940b107659d
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ComparisonDrawer.tsx
@@ -0,0 +1,67 @@
+import { useCallback, useState } from 'react';
+
+import { Drawer } from '@grafana/ui';
+import { VersionHistoryComparison } from 'app/core/components/VersionHistory/VersionHistoryComparison';
+import { t } from 'app/core/internationalization';
+import { GrafanaRuleIdentifier } from 'app/types/unified-alerting';
+import { GrafanaRuleDefinition, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
+
+import { ConfirmVersionRestoreModal } from './ConfirmVersionRestoreModal';
+import { parseVersionInfoToSummary, preprocessRuleForDiffDisplay } from './versions-utils';
+
+interface ComparisonDrawerProps {
+ oldVersion: RulerGrafanaRuleDTO;
+ newVersion: RulerGrafanaRuleDTO;
+ ruleIdentifier: GrafanaRuleIdentifier;
+ isNewLatest: boolean;
+ setShowDrawer: (show: boolean) => void;
+ onRestoreSuccess: () => void;
+ onRestoreError: (error: Error) => void;
+ canRestore: boolean;
+}
+
+export const ComparisonDrawer = ({
+ oldVersion,
+ newVersion,
+ ruleIdentifier,
+ isNewLatest,
+ setShowDrawer,
+ onRestoreSuccess,
+ onRestoreError,
+ canRestore,
+}: ComparisonDrawerProps) => {
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
+ const onDismiss = useCallback(() => setShowDrawer(false), [setShowDrawer]);
+
+ const oldVersionSummary = parseVersionInfoToSummary(oldVersion);
+ const newVersionSummary = parseVersionInfoToSummary(newVersion);
+ return (
+ <>
+ setShowDrawer(false)}
+ title={t('alerting.alertVersionHistory.comparing-versions', 'Comparing versions')}
+ >
+ setShowConfirmModal(true)}
+ />
+
+ {showConfirmModal && (
+
+ )}
+ >
+ );
+};
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ConfirmVersionRestoreModal.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ConfirmVersionRestoreModal.tsx
new file mode 100644
index 00000000000..95336c85016
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/ConfirmVersionRestoreModal.tsx
@@ -0,0 +1,121 @@
+import { ComponentProps } from 'react';
+import { useNavigate } from 'react-router-dom-v5-compat';
+
+import { urlUtil } from '@grafana/data';
+import { Alert, ConfirmModal, Stack, Text } from '@grafana/ui';
+import { Trans, t } from 'app/core/internationalization';
+import { useRuleWithLocation } from 'app/features/alerting/unified/hooks/useCombinedRule';
+import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
+import { rulerRuleToFormValues } from 'app/features/alerting/unified/utils/rule-form';
+import { DiffGroup } from 'app/features/dashboard-scene/settings/version-history/DiffGroup';
+import { jsonDiff } from 'app/features/dashboard-scene/settings/version-history/utils';
+import { GrafanaRuleIdentifier } from 'app/types/unified-alerting';
+import { GrafanaRuleDefinition, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
+
+import { useRestoreVersion } from './useRestoreVersion';
+import { preprocessRuleForDiffDisplay } from './versions-utils';
+
+export type Origin = 'version-list' | 'comparison-drawer';
+
+type ModalProps = Pick, 'isOpen' | 'onDismiss'> & {
+ isOpen: boolean;
+ baseVersion?: RulerGrafanaRuleDTO;
+ versionToRestore?: RulerGrafanaRuleDTO;
+ ruleIdentifier: GrafanaRuleIdentifier;
+ onRestoreSucess: () => void;
+ onRestoreError: (error: Error) => void;
+};
+
+export const ConfirmVersionRestoreModal = ({
+ isOpen,
+ baseVersion,
+ versionToRestore,
+ ruleIdentifier,
+ onDismiss,
+ onRestoreSucess,
+ onRestoreError,
+}: ModalProps) => {
+ const { result: ruleWithLocation } = useRuleWithLocation({ ruleIdentifier });
+ const navigate = useNavigate();
+ const [restoreMethod, { error }] = useRestoreVersion();
+
+ const title = t('alerting.alertVersionHistory.restore-modal.title', 'Restore version');
+ const errorTitle = t('alerting.alertVersionHistory.restore-modal.error', 'Could not restore alert rule version ');
+ const confirmText = !error
+ ? t('alerting.alertVersionHistory.restore-modal.confirm', 'Yes, restore configuration')
+ : 'Manually restore rule';
+
+ const diff =
+ baseVersion && versionToRestore
+ ? jsonDiff(preprocessRuleForDiffDisplay(baseVersion), preprocessRuleForDiffDisplay(versionToRestore))
+ : undefined;
+
+ async function onRestoreConfirm() {
+ if (!versionToRestore || !ruleWithLocation) {
+ return;
+ }
+ return restoreMethod
+ .execute(versionToRestore, ruleWithLocation)
+ .then(() => {
+ onDismiss();
+ onRestoreSucess();
+ })
+ .catch((err) => {
+ onRestoreError(err);
+ });
+ }
+
+ async function onManualRestore() {
+ if (!versionToRestore || !ruleWithLocation) {
+ return;
+ }
+ const payload = rulerRuleToFormValues({ ...ruleWithLocation, rule: versionToRestore });
+ const ruleFormUrl = urlUtil.renderUrl(`/alerting/${ruleIdentifier.uid}/edit`, {
+ isManualRestore: true,
+ defaults: JSON.stringify(payload),
+ returnTo: location.pathname + location.search,
+ });
+
+ navigate(ruleFormUrl);
+ }
+
+ return (
+
+ {!error && (
+
+ Are you sure you want to restore the alert rule definition to this version? All unsaved changes will be
+ lost.
+
+ )}
+
+
+ Summary of changes to be applied:
+
+
+
+ {diff && Object.entries(diff).map(([key, diffs]) => )}
+
+ {error && (
+
+
+ Your alert rule could not be restored. This may be due to changes to other entities such as contact
+ points, data sources etc. Please manually restore the rule version
+
+
+ {stringifyErrorLike(error)}
+
+
+ )}
+
+ }
+ onConfirm={!error ? onRestoreConfirm : onManualRestore}
+ onDismiss={onDismiss}
+ />
+ );
+};
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/components/UpdatedBy.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/UpdatedBy.tsx
similarity index 96%
rename from public/app/features/alerting/unified/components/rule-viewer/tabs/components/UpdatedBy.tsx
rename to public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/UpdatedBy.tsx
index c391f13af83..095e7912ddb 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/tabs/components/UpdatedBy.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/UpdatedBy.tsx
@@ -4,7 +4,7 @@ import { Badge, Icon, Tooltip, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { UpdatedBy } from 'app/types/unified-alerting-dto';
-import { getSpecialUidsDisplayMap } from '../versions-utils';
+import { getSpecialUidsDisplayMap } from './versions-utils';
export const UpdatedByUser = ({ user }: { user: UpdatedBy | null | undefined }) => {
const unknown = t('alerting.alertVersionHistory.unknown', 'Unknown');
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/components/VersionHistoryTable.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx
similarity index 54%
rename from public/app/features/alerting/unified/components/rule-viewer/tabs/components/VersionHistoryTable.tsx
rename to public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx
index 29edbfcda20..e3f9e518e18 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/tabs/components/VersionHistoryTable.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx
@@ -1,51 +1,58 @@
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
-import { config } from '@grafana/runtime';
-import { Badge, Button, Checkbox, Column, ConfirmModal, InteractiveTable, Stack, Text } from '@grafana/ui';
+import { Badge, Button, Checkbox, Column, InteractiveTable, Stack, Text } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { computeVersionDiff } from 'app/features/alerting/unified/utils/diff';
-import { DiffGroup } from 'app/features/dashboard-scene/settings/version-history/DiffGroup';
-import { Diffs, jsonDiff } from 'app/features/dashboard-scene/settings/version-history/utils';
+import { RuleIdentifier } from 'app/types/unified-alerting';
import { GrafanaRuleDefinition, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
+import { ConfirmVersionRestoreModal } from './ConfirmVersionRestoreModal';
import { UpdatedByUser } from './UpdatedBy';
const VERSIONS_PAGE_SIZE = 20;
-export function VersionHistoryTable({
- onVersionsChecked,
- ruleVersions,
- disableSelection,
- checkedVersions,
-}: {
+export interface VersionHistoryTableProps {
onVersionsChecked(id: string): void;
+ onCompareSingleVersion(rule: RulerGrafanaRuleDTO): void;
ruleVersions: Array>;
disableSelection: boolean;
checkedVersions: Set;
-}) {
- //----> restore code : no need to review as it's behind a feature flag
- const [confirmRestore, setConfirmRestore] = useState(false);
- const [restoreDiff, setRestoreDiff] = useState();
+ onRestoreSuccess: () => void;
+ onRestoreError: (error: Error) => void;
+ canRestore: boolean;
+}
+export function VersionHistoryTable({
+ onVersionsChecked,
+ onCompareSingleVersion,
+ ruleVersions,
+ disableSelection,
+ checkedVersions,
+ onRestoreSuccess,
+ onRestoreError,
+ canRestore,
+}: VersionHistoryTableProps) {
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
+ const [ruleToRestore, setRuleToRestore] = useState>();
+ const ruleToRestoreUid = ruleToRestore?.grafana_alert?.uid ?? '';
+ const ruleIdentifier: RuleIdentifier = useMemo(
+ () => ({ ruleSourceName: GRAFANA_RULES_SOURCE_NAME, uid: ruleToRestoreUid }),
+ [ruleToRestoreUid]
+ );
- const showConfirmation = (id: string) => {
- const currentVersion = ruleVersions[0];
- const restoreVersion = ruleVersions.find((rule) => String(rule.grafana_alert.version) === id);
- if (!restoreVersion) {
- return;
- }
-
- setConfirmRestore(true);
- setRestoreDiff(jsonDiff(currentVersion, restoreVersion));
+ const showConfirmation = (ruleToRestore: RulerGrafanaRuleDTO) => {
+ setShowConfirmModal(true);
+ setRuleToRestore(ruleToRestore);
};
const hideConfirmation = () => {
- setConfirmRestore(false);
+ setShowConfirmModal(false);
};
- //----> end of restore code
+
const unknown = t('alerting.alertVersionHistory.unknown', 'Unknown');
- const columns: Array> = [
+ const columns: Array>> = [
{
disableGrow: true,
id: 'id',
@@ -116,22 +123,36 @@ export function VersionHistoryTable({
disableGrow: true,
cell: ({ row }) => {
const isFirstItem = row.index === 0;
+ const compareWithLatest = t('alerting.alertVersionHistory.compare-with-latest', 'Compare with latest version');
return (
{isFirstItem ? (
- ) : config.featureToggles.alertingRuleVersionHistoryRestore ? (
- {
- showConfirmation(row.values.id);
- }}
- >
- Restore
-
+ ) : canRestore ? (
+ <>
+ {
+ onCompareSingleVersion(row.original);
+ }}
+ tooltip={compareWithLatest}
+ >
+ Compare
+
+ {
+ row.original.grafana_alert.version && showConfirmation(row.original);
+ }}
+ >
+ Restore
+
+ >
) : null}
);
@@ -147,39 +168,15 @@ export function VersionHistoryTable({
data={ruleVersions}
getRowId={(row) => `${row.grafana_alert.version}`}
/>
- {/* ---------------------> restore code: no need to review for this pr as it's behind a feature flag */}
-
-
- Are you sure you want to restore the alert rule definition to this version? All unsaved changes will be
- lost.
-
-
-
- Summary of changes to be applied:
-
-
-
- {restoreDiff && (
- <>
- {Object.entries(restoreDiff).map(([key, diffs]) => (
-
- ))}
- >
- )}
-
-
- }
- confirmText={'Yes, restore configuration'}
- onConfirm={() => {
- hideConfirmation();
- }}
- onDismiss={() => hideConfirmation()}
+
- {/* ------------------------------------> END OF RESTORING CODE */}
>
);
}
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/useRestoreVersion.ts b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/useRestoreVersion.ts
new file mode 100644
index 00000000000..ec6d6fdf31d
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/useRestoreVersion.ts
@@ -0,0 +1,27 @@
+import { useUpdateRuleInRuleGroup } from 'app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup';
+import { useAsync } from 'app/features/alerting/unified/hooks/useAsync';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
+import { fromRulerRuleAndRuleGroupIdentifier } from 'app/features/alerting/unified/utils/rule-id';
+import { getRuleGroupLocationFromRuleWithLocation } from 'app/features/alerting/unified/utils/rules';
+import { RuleWithLocation } from 'app/types/unified-alerting';
+import { GrafanaRuleDefinition, RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto';
+
+export function useRestoreVersion() {
+ const [updateRuleInRuleGroup] = useUpdateRuleInRuleGroup();
+
+ return useAsync(
+ async (
+ newVersion: RulerGrafanaRuleDTO,
+ ruleWithLocation: RuleWithLocation
+ ) => {
+ const ruleGroupIdentifier = getRuleGroupLocationFromRuleWithLocation(ruleWithLocation);
+ const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, newVersion);
+ // restore version
+ return updateRuleInRuleGroup.execute(ruleGroupIdentifier, ruleIdentifier, newVersion, {
+ dataSourceName: GRAFANA_RULES_SOURCE_NAME,
+ namespaceName: newVersion.grafana_alert.namespace_uid,
+ groupName: newVersion.grafana_alert.rule_group,
+ });
+ }
+ );
+}
diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/versions-utils.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/versions-utils.tsx
similarity index 79%
rename from public/app/features/alerting/unified/components/rule-viewer/tabs/versions-utils.tsx
rename to public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/versions-utils.tsx
index b7ef95c8961..c1748cc1d04 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/tabs/versions-utils.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/versions-utils.tsx
@@ -1,5 +1,6 @@
import { IconName } from '@grafana/data';
import { BadgeColor } from '@grafana/ui';
+import { RevisionModel } from 'app/core/components/VersionHistory/VersionHistoryComparison';
import { t } from 'app/core/internationalization';
import {
GrafanaAlertRuleDTOField,
@@ -8,7 +9,7 @@ import {
TopLevelGrafanaRuleDTOField,
} from 'app/types/unified-alerting-dto';
-import { grafanaAlertPropertiesToIgnore } from './AlertVersionHistory';
+import { grafanaAlertPropertiesToIgnore } from '../AlertVersionHistory';
interface SpecialUidsDisplayMapEntry {
name: string;
@@ -104,3 +105,34 @@ export function preprocessRuleForDiffDisplay(rulerRule: RulerGrafanaRuleDTO): RevisionModel {
+ const unknown = t('alerting.alertVersionHistory.unknown', 'Unknown');
+ const SPECIAL_UID_MAP = getSpecialUidsDisplayMap();
+ const createdBy = (() => {
+ const updatedBy = version?.grafana_alert.updated_by;
+ const uid = updatedBy?.uid;
+ const name = updatedBy?.name;
+
+ if (!updatedBy) {
+ return unknown;
+ }
+ if (uid && SPECIAL_UID_MAP[uid]) {
+ return SPECIAL_UID_MAP[uid].name;
+ }
+ if (name) {
+ return name;
+ }
+ return uid ? t('alerting.alertVersionHistory.user-id', 'User ID {{uid}}', { uid }) : unknown;
+ })();
+
+ return {
+ createdAt: version.grafana_alert.updated || unknown,
+ createdBy,
+ version: version.grafana_alert.version || unknown,
+ };
+}
diff --git a/public/app/features/alerting/unified/components/rules/ActionButton.tsx b/public/app/features/alerting/unified/components/rules/ActionButton.tsx
index 2613be36cb1..8ccf3338f00 100644
--- a/public/app/features/alerting/unified/components/rules/ActionButton.tsx
+++ b/public/app/features/alerting/unified/components/rules/ActionButton.tsx
@@ -1,8 +1,7 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
-import { useStyles2 } from '@grafana/ui';
-import { Button, ButtonProps } from '@grafana/ui/src/components/Button';
+import { Button, ButtonProps, useStyles2 } from '@grafana/ui';
type Props = Omit;
diff --git a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx
index 8d198286ad8..5a3dbcc9870 100644
--- a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx
+++ b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx
@@ -1,11 +1,14 @@
+import { ComponentProps } from 'react';
+
import { Button, IconName, LinkButton, Tooltip } from '@grafana/ui';
-import { PopoverContent, TooltipPlacement } from '@grafana/ui/src/components/Tooltip';
+
+type TooltipProps = ComponentProps;
interface Props {
- tooltip: PopoverContent;
+ tooltip: TooltipProps['content'];
icon: IconName;
className?: string;
- tooltipPlacement?: TooltipPlacement;
+ tooltipPlacement?: TooltipProps['placement'];
to?: string;
target?: string;
onClick?: () => void;
diff --git a/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap b/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap
index 5a9da03ba29..45a27ea0e07 100644
--- a/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap
+++ b/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap
@@ -22,6 +22,10 @@ exports[`AlertRule abilities should report no permissions while we are loading d
false,
false,
],
+ "restore-alert-rule": [
+ false,
+ false,
+ ],
"silence-alert-rule": [
false,
false,
@@ -59,6 +63,10 @@ exports[`AlertRule abilities should report that all actions are supported for a
true,
false,
],
+ "restore-alert-rule": [
+ true,
+ false,
+ ],
"silence-alert-rule": [
true,
false,
diff --git a/public/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts
index f7a623a2c64..8bace1f93e9 100644
--- a/public/app/features/alerting/unified/hooks/useAbilities.ts
+++ b/public/app/features/alerting/unified/hooks/useAbilities.ts
@@ -87,6 +87,7 @@ export enum AlertRuleAction {
Silence = 'silence-alert-rule',
ModifyExport = 'modify-export-rule',
Pause = 'pause-alert-rule',
+ Restore = 'restore-alert-rule',
}
// this enum lists all of the actions we can perform within alerting in general, not linked to a specific
@@ -231,6 +232,7 @@ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities {
+ const validIdentifier = (() => {
+ if (isGrafanaRuleIdentifier(ruleIdentifier) && ruleIdentifier.uid !== '') {
+ return { uid: ruleIdentifier.uid };
+ }
+ return skipToken;
+ })();
+
const { isLoading, currentData, error, isUninitialized } = alertRuleApi.endpoints.getAlertRule.useQuery(
- { uid: isGrafanaRuleIdentifier(ruleIdentifier) ? ruleIdentifier.uid : '' },
- { skip: !isGrafanaRuleIdentifier(ruleIdentifier), refetchOnMountOrArgChange: true }
+ validIdentifier,
+ {
+ refetchOnMountOrArgChange: true,
+ }
);
return useMemo(() => {
diff --git a/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
index fe33ee855ec..00641db1eda 100644
--- a/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
+++ b/public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
@@ -1,23 +1,29 @@
import { Alert, LoadingPlaceholder } from '@grafana/ui';
+import { useQueryParams } from 'app/core/hooks/useQueryParams';
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 { RuleFormValues } from '../types/rule-form';
import { stringifyErrorLike } from '../utils/misc';
import * as ruleId from '../utils/rule-id';
interface ExistingRuleEditorProps {
identifier: RuleIdentifier;
+ /** Provide prefill if we are trying to restore an old version of an alert rule but we need the user to manually tweak the values */
+ prefill?: Partial;
}
-export function ExistingRuleEditor({ identifier }: ExistingRuleEditorProps) {
+export function ExistingRuleEditor({ identifier, prefill }: ExistingRuleEditorProps) {
const {
loading: loadingAlertRule,
result: ruleWithLocation,
error,
} = useRuleWithLocation({ ruleIdentifier: identifier });
+ const [queryParams] = useQueryParams();
+ const isManualRestore = Boolean(queryParams.isManualRestore);
const ruleSourceName = ruleId.ruleIdentifierToRuleSourceName(identifier);
@@ -45,5 +51,5 @@ export function ExistingRuleEditor({ identifier }: ExistingRuleEditorProps) {
return Sorry! You do not have permission to edit this rule.;
}
- return ;
+ return ;
}
diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
index 42585600052..5c1859b5865 100644
--- a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx
@@ -61,7 +61,7 @@ const RuleEditor = () => {
}
if (identifier) {
- return ;
+ return ;
}
if (copyFromIdentifier) {
diff --git a/public/app/features/alerting/unified/rule-list/FilterView.tsx b/public/app/features/alerting/unified/rule-list/FilterView.tsx
index 25358a57390..ea6b3ceec49 100644
--- a/public/app/features/alerting/unified/rule-list/FilterView.tsx
+++ b/public/app/features/alerting/unified/rule-list/FilterView.tsx
@@ -1,4 +1,5 @@
-import { take, tap, withAbort } from 'ix/asynciterable/operators';
+import { empty } from 'ix/asynciterable';
+import { catchError, take, tap, withAbort } from 'ix/asynciterable/operators';
import { useEffect, useRef, useState, useTransition } from 'react';
import { Card, EmptyState, Stack, Text } from '@grafana/ui';
@@ -75,7 +76,12 @@ function FilterViewResults({ filterState }: FilterViewProps) {
/* This function will fetch a page of results from the iterable */
const [{ execute: loadResultPage }, state] = useAsync(async () => {
- for await (const rule of rulesIterator.current.pipe(take(FRONTENT_PAGE_SIZE))) {
+ for await (const rule of rulesIterator.current.pipe(
+ // grab from the rules iterable
+ take(FRONTENT_PAGE_SIZE),
+ // if an error occurs trying to fetch a page, return an empty iterable so the front-end isn't caught in an infinite loop
+ catchError(() => empty())
+ )) {
startTransition(() => {
// Rule key could be computed on the fly, but we do it here to avoid recalculating it with each render
// It's a not trivial computation because it involves hashing the rule
diff --git a/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx b/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx
index cb543108054..29b714770d8 100644
--- a/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx
@@ -1,13 +1,14 @@
-import { memo } from 'react';
+import { ComponentProps, memo } from 'react';
import type { RequireAtLeastOne } from 'type-fest';
import { Icon, type IconName, Text, Tooltip } from '@grafana/ui';
-import type { TextProps } from '@grafana/ui/src/components/Text/Text';
import type { RuleHealth } from 'app/types/unified-alerting';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { isErrorHealth } from '../../components/rule-viewer/RuleViewer';
+type TextProps = ComponentProps;
+
interface RuleListIconProps {
recording?: boolean;
state?: PromAlertingRuleState;
diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts
index ddbfea8e216..6512ddcea18 100644
--- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts
+++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts
@@ -1,4 +1,4 @@
-import { AsyncIterableX, from } from 'ix/asynciterable/index';
+import { AsyncIterableX, empty, from } from 'ix/asynciterable';
import { merge } from 'ix/asynciterable/merge';
import { filter, flatMap, map } from 'ix/asynciterable/operators';
import { compact } from 'lodash';
@@ -68,11 +68,16 @@ export function useFilteredRulesIteratorProvider() {
map(([group, rule]) => mapGrafanaRuleToRuleWithOrigin(group, rule))
);
- const [source, ...iterables] = ruleSourcesToFetchFrom.map((ds) => {
- return from(prometheusGroupsGenerator(ds, groupLimit)).pipe(map((group) => [ds, group] as const));
+ const sourceIterables = ruleSourcesToFetchFrom.map((ds) => {
+ const generator = prometheusGroupsGenerator(ds, groupLimit);
+ return from(generator).pipe(map((group) => [ds, group] as const));
});
- const dataSourcesIterator = merge(source, ...iterables).pipe(
+ // if we have no prometheus data sources, use an empty async iterable
+ const source = sourceIterables.at(0) ?? empty();
+ const otherIterables = sourceIterables.slice(1);
+
+ const dataSourcesIterator = merge(source, ...otherIterables).pipe(
filter(([_, group]) => groupFilter(group, normalizedFilterState)),
flatMap(([rulesSource, group]) => group.rules.map((rule) => [rulesSource, group, rule] as const)),
filter(([_, __, rule]) => ruleFilter(rule, filterState)),
diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts
index 5b03e36fe6b..f45b7ff1dde 100644
--- a/public/app/features/alerting/unified/state/actions.ts
+++ b/public/app/features/alerting/unified/state/actions.ts
@@ -1,8 +1,7 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { isEmpty } from 'lodash';
-import { locationService } from '@grafana/runtime';
-import { logMeasurement } from '@grafana/runtime/src/utils/logging';
+import { locationService, logMeasurement } from '@grafana/runtime';
import {
AlertManagerCortexConfig,
AlertmanagerGroup,
diff --git a/public/app/features/alerting/unified/utils/matchers.test.ts b/public/app/features/alerting/unified/utils/matchers.test.ts
index f89e3fa8230..fdf9511c7fa 100644
--- a/public/app/features/alerting/unified/utils/matchers.test.ts
+++ b/public/app/features/alerting/unified/utils/matchers.test.ts
@@ -49,6 +49,14 @@ describe('Unified Alerting matchers', () => {
expect(matchers[0].name).toBe('alertname');
expect(matchers[0].value).toBe('TestData 1');
});
+
+ it('should not crash when matcher is not valid', () => {
+ expect(() => {
+ parseQueryParamMatchers(['alertname']);
+ }).not.toThrow();
+
+ expect(parseQueryParamMatchers(['alertname'])).toHaveLength(0);
+ });
});
describe('normalizeMatchers', () => {
diff --git a/public/app/features/alerting/unified/utils/matchers.ts b/public/app/features/alerting/unified/utils/matchers.ts
index f5f5476e208..32e0f08555a 100644
--- a/public/app/features/alerting/unified/utils/matchers.ts
+++ b/public/app/features/alerting/unified/utils/matchers.ts
@@ -5,7 +5,7 @@
* Please keep the references to other files here to a minimum, if we reference a file that uses GrafanaBootData from `window` the worker will fail to load.
*/
-import { compact, uniqBy } from 'lodash';
+import { chain, compact } from 'lodash';
import { parseFlags } from '@grafana/data';
import { Matcher, MatcherOperator, ObjectMatcher, Route } from 'app/plugins/datasource/alertmanager/types';
@@ -110,11 +110,16 @@ export function parsePromQLStyleMatcherLooseSafe(matcher: string): Matcher[] {
// Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[]
export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] {
- const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x));
-
- // Due to migration, old alert rules might have a duplicated alertname label
- // To handle that case want to filter out duplicates and make sure there are only unique labels
- return uniqBy(parsedMatchers, (matcher) => matcher.name);
+ return (
+ chain(matcherPairs)
+ .map((m) => m.trim()) // trim spaces
+ .compact() // remove empty strings
+ .flatMap(parsePromQLStyleMatcherLooseSafe)
+ // Due to migration, old alert rules might have a duplicated alertname label
+ // To handle that case want to filter out duplicates and make sure there are only unique labels
+ .uniqBy('name')
+ .value()
+ );
}
export const getMatcherQueryParams = (labels: Labels) => {
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx
index fbaf5aa5260..51cd262b24a 100644
--- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx
@@ -18,6 +18,7 @@ import { isInCloneChain } from '../utils/clone';
import { getDashboardSceneFor } from '../utils/utils';
import { DashboardAddPane } from './DashboardAddPane';
+import { DashboardOutline } from './DashboardOutline';
import { ElementEditPane } from './ElementEditPane';
import { ElementSelection } from './ElementSelection';
import { useEditableElement } from './useEditableElement';
@@ -181,6 +182,8 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
return null;
}
+ const { typeId } = editableElement.getEditableElementInfo();
+
if (isCollapsed) {
return (
<>
@@ -197,7 +200,7 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
{openOverlay && (
-
+
)}
>
@@ -225,8 +228,8 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
{tab === 'add' &&
}
- {tab === 'configure' &&
}
- {tab === 'outline' &&
}
+ {tab === 'configure' &&
}
+ {tab === 'outline' &&
}
);
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx
index b2fc279ccde..f51540115c5 100644
--- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx
@@ -73,38 +73,40 @@ export function DashboardEditPaneSplitter({ dashboard, isEditing, body, controls
return (
-
{
- if (evt.shiftKey) {
- return;
- }
+
+ {
+ if (evt.shiftKey) {
+ return;
+ }
- editPane.clearSelection();
- }}
- >
-
-
{controls}
-
-
-
{body}
+ editPane.clearSelection();
+ }}
+ >
+
+
{controls}
+
-
- {isEditing && (
- <>
-
-
- 0}
- />
-
- >
- )}
+ {isEditing && (
+ <>
+
+
+ 0}
+ />
+
+ >
+ )}
+
);
}
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
index 4699e8c1c3f..ae3085bc055 100644
--- a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
@@ -6,15 +6,18 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { DashboardScene } from '../scene/DashboardScene';
-import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
-import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
+import { DashboardLayoutSelector } from '../scene/layouts-shared/DashboardLayoutSelector';
+import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
export class DashboardEditableElement implements EditableDashboardElement {
public readonly isEditableDashboardElement = true;
- public readonly typeName = 'Dashboard';
public constructor(private dashboard: DashboardScene) {}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { typeId: 'dashboard', icon: 'apps', name: t('dashboard.edit-pane.elements.dashboard', 'Dashboard') };
+ }
+
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const dashboard = this.dashboard;
@@ -25,29 +28,29 @@ export class DashboardEditableElement implements EditableDashboardElement {
return new OptionsPaneCategoryDescriptor({
title: t('dashboard.options.title', 'Dashboard options'),
id: 'dashboard-options',
- isOpenDefault: true,
+ isOpenable: false,
})
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.title-option', 'Title'),
- render: function renderTitle() {
- return ;
- },
+ render: () => ,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.description', 'Description'),
- render: function renderTitle() {
- return ;
- },
+ render: () => ,
+ })
+ )
+ .addItem(
+ new OptionsPaneItemDescriptor({
+ title: t('dashboard.layout.common.layout', 'Layout'),
+ render: () => ,
})
);
- }, [dashboard]);
+ }, [body, dashboard]);
- const layoutCategory = useLayoutCategory(body);
-
- return [dashboardOptions, layoutCategory];
+ return [dashboardOptions];
}
}
diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx
new file mode 100644
index 00000000000..4f302a638fd
--- /dev/null
+++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx
@@ -0,0 +1,159 @@
+import { css, cx } from '@emotion/css';
+import { useMemo, useState } from 'react';
+
+import { GrafanaTheme2 } from '@grafana/data';
+import { SceneObject, VizPanel } from '@grafana/scenes';
+import { Box, Icon, IconButton, Stack, Text, useElementSelection, useStyles2 } from '@grafana/ui';
+import { t, Trans } from 'app/core/internationalization';
+
+import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
+import { isInCloneChain } from '../utils/clone';
+import { getDashboardSceneFor } from '../utils/utils';
+
+import { DashboardEditPane } from './DashboardEditPane';
+import { getEditableElementFor, hasEditableElement } from './shared';
+
+export interface Props {
+ editPane: DashboardEditPane;
+}
+
+export function DashboardOutline({ editPane }: Props) {
+ const dashboard = getDashboardSceneFor(editPane);
+
+ return (
+
+
+
+ );
+}
+
+function DashboardOutlineNode({ sceneObject, expandable }: { sceneObject: SceneObject; expandable: boolean }) {
+ const [isExpanded, setIsExpanded] = useState(true);
+ const { key } = sceneObject.useState();
+ const styles = useStyles2(getStyles);
+ const { isSelected, onSelect } = useElementSelection(key);
+ const isCloned = useMemo(() => isInCloneChain(key!), [key]);
+ const editableElement = useMemo(() => getEditableElementFor(sceneObject)!, [sceneObject]);
+
+ const children = collectEditableElementChildren(sceneObject);
+ const elementInfo = editableElement.getEditableElementInfo();
+
+ return (
+ <>
+
+ {expandable && (
+ setIsExpanded(!isExpanded)}
+ aria-label={
+ isExpanded
+ ? t('dashboard.outline.tree.item.collapse', 'Collapse item')
+ : t('dashboard.outline.tree.item.expand', 'Expand item')
+ }
+ />
+ )}
+ onSelect?.(evt)}
+ >
+
+ {elementInfo.name}
+
+
+ {expandable && isExpanded && (
+
+ {children.length > 0 ? (
+ children.map((child) => (
+
+ ))
+ ) : (
+
+ (empty)
+
+ )}
+
+ )}
+ >
+ );
+}
+
+function getStyles(theme: GrafanaTheme2) {
+ return {
+ container: css({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(1),
+ marginLeft: theme.spacing(1),
+ paddingLeft: theme.spacing(1.5),
+ borderLeft: `1px solid ${theme.colors.border.medium}`,
+ }),
+ nodeButton: css({
+ boxShadow: 'none',
+ border: 'none',
+ background: 'transparent',
+ padding: theme.spacing(0.25, 1),
+ borderRadius: theme.shape.radius.default,
+ display: 'flex',
+ alignItems: 'center',
+ gap: theme.spacing(1),
+ overflow: 'hidden',
+ '&:hover': {
+ backgroundColor: theme.colors.action.hover,
+ },
+ '> span': {
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
+ }),
+ nodeButtonSelected: css({
+ color: theme.colors.primary.text,
+ }),
+ nodeButtonClone: css({
+ color: theme.colors.text.secondary,
+ cursor: 'not-allowed',
+ }),
+ };
+}
+
+interface EditableElementConfig {
+ sceneObject: SceneObject;
+ expandable: boolean;
+}
+
+function collectEditableElementChildren(
+ sceneObject: SceneObject,
+ children: EditableElementConfig[] = []
+): EditableElementConfig[] {
+ sceneObject.forEachChild((child) => {
+ if (child instanceof DashboardGridItem) {
+ // DashboardGridItem is a special case as it can contain repeated panels
+ // In this case, we want to show the repeated panels as separate items, otherwise show the body panel
+ if (child.state.repeatedPanels?.length) {
+ children.push(...child.state.repeatedPanels.map((panel) => ({ sceneObject: panel, expandable: false })));
+ } else {
+ children.push({ sceneObject: child.state.body, expandable: false });
+ }
+ } else if (child instanceof VizPanel) {
+ children.push({ sceneObject: child, expandable: false });
+ } else if (hasEditableElement(child)) {
+ children.push({ sceneObject: child, expandable: true });
+ } else {
+ collectEditableElementChildren(child, children);
+ }
+ });
+
+ return children;
+}
diff --git a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx
new file mode 100644
index 00000000000..653daf384f2
--- /dev/null
+++ b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx
@@ -0,0 +1,57 @@
+import { Dropdown, Button, IconButton, Menu, Stack, Icon } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
+
+interface EditPaneHeaderProps {
+ title: string;
+ onDelete?: () => void;
+ onCopy?: () => void;
+ onDuplicate?: () => void;
+}
+
+export const EditPaneHeader = ({ title, onDelete, onCopy, onDuplicate }: EditPaneHeaderProps) => {
+ const addCopyOrDuplicate = onCopy || onDuplicate;
+ return (
+
+ {title}
+
+ {addCopyOrDuplicate ? (
+ }>
+
+
+
+
+ ) : null}
+
+
+
+
+ );
+};
+
+type MenuItemsProps = {
+ onCopy?: () => void;
+ onDuplicate?: () => void;
+};
+
+const MenuItems = ({ onCopy, onDuplicate }: MenuItemsProps) => {
+ return (
+
+ );
+};
diff --git a/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx
index 16adfe8004a..01b9a3f2398 100644
--- a/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/ElementEditPane.tsx
@@ -14,15 +14,18 @@ export interface Props {
export function ElementEditPane({ element }: Props) {
const categories = element.useEditPaneOptions ? element.useEditPaneOptions() : [];
const styles = useStyles2(getStyles);
+ const elementInfo = element.getEditableElementInfo();
return (
{element.renderActions && (
{element.renderActions()}
diff --git a/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts b/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts
index cecd28020cf..2141781408d 100644
--- a/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts
+++ b/public/app/features/dashboard-scene/edit-pane/ElementSelection.ts
@@ -1,15 +1,14 @@
import { SceneObject, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { ElementSelectionContextItem } from '@grafana/ui';
-import { DashboardScene } from '../scene/DashboardScene';
import { isBulkActionElement } from '../scene/types/BulkActionElement';
import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
-import { DashboardEditableElement } from './DashboardEditableElement';
import { MultiSelectedObjectsEditableElement } from './MultiSelectedObjectsEditableElement';
import { MultiSelectedVizPanelsEditableElement } from './MultiSelectedVizPanelsEditableElement';
import { VizPanelEditableElement } from './VizPanelEditableElement';
+import { getEditableElementFor } from './shared';
export class ElementSelection {
private selectedObjects?: Map>;
@@ -121,24 +120,7 @@ export class ElementSelection {
private createSingleSelectedElement(): EditableDashboardElement | undefined {
const sceneObj = this.selectedObjects?.values().next().value?.resolve();
-
- if (!sceneObj) {
- return undefined;
- }
-
- if (isEditableDashboardElement(sceneObj)) {
- return sceneObj;
- }
-
- if (sceneObj instanceof VizPanel) {
- return new VizPanelEditableElement(sceneObj);
- }
-
- if (sceneObj instanceof DashboardScene) {
- return new DashboardEditableElement(sceneObj);
- }
-
- return undefined;
+ return getEditableElementFor(sceneObj);
}
private createMultiSelectedElement(): MultiSelectedEditableDashboardElement | undefined {
@@ -161,6 +143,7 @@ export class ElementSelection {
}
const bulkActionElements = [];
+
for (const sceneObject of sceneObjects) {
if (sceneObject instanceof VizPanel) {
const editableElement = new VizPanelEditableElement(sceneObject);
diff --git a/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx
index 3278deb3a17..a55b5f0d8c9 100644
--- a/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/MultiSelectedObjectsEditableElement.tsx
@@ -2,20 +2,24 @@ import { ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { Stack, Text, Button } from '@grafana/ui';
-import { Trans } from 'app/core/internationalization';
+import { t, Trans } from 'app/core/internationalization';
import { BulkActionElement } from '../scene/types/BulkActionElement';
+import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
export class MultiSelectedObjectsEditableElement implements MultiSelectedEditableDashboardElement {
public readonly isMultiSelectedEditableDashboardElement = true;
- public readonly typeName = 'Objects';
public readonly key: string;
constructor(private _elements: BulkActionElement[]) {
this.key = uuidv4();
}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { name: t('dashboard.edit-pane.elements.objects', 'Objects'), typeId: 'objects', icon: 'folder' };
+ }
+
public renderActions(): ReactNode {
return (
diff --git a/public/app/features/dashboard-scene/edit-pane/MultiSelectedVizPanelsEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/MultiSelectedVizPanelsEditableElement.tsx
index 259ae9d91cc..3e6a7fa98ae 100644
--- a/public/app/features/dashboard-scene/edit-pane/MultiSelectedVizPanelsEditableElement.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/MultiSelectedVizPanelsEditableElement.tsx
@@ -1,39 +1,43 @@
-import { ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { VizPanel } from '@grafana/scenes';
-import { Button, Stack, Text } from '@grafana/ui';
-import { Trans } from 'app/core/internationalization';
+import { t } from 'app/core/internationalization';
+import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
+import { EditPaneHeader } from './EditPaneHeader';
+
export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEditableDashboardElement {
public readonly isMultiSelectedEditableDashboardElement = true;
- public readonly typeName = 'Panels';
public readonly key: string;
constructor(private _panels: VizPanel[]) {
this.key = uuidv4();
}
- renderActions(): ReactNode {
- return (
-
-
-
- No. of panels selected: {{ length }}
-
-
-
-
- this.onDelete()} icon="trash-alt" />
-
-
- );
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { name: t('dashboard.edit-pane.elements.panels', 'Panels'), typeId: 'panels', icon: 'folder' };
+ }
+
+ public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
+ const header = new OptionsPaneCategoryDescriptor({
+ title: ``,
+ id: 'panel-header',
+ isOpenable: false,
+ renderTitle: () => (
+ this.onDelete()}
+ />
+ ),
+ });
+
+ return [header];
}
public onDelete() {
diff --git a/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx
index 4d97803b8db..ec03d1d27f2 100644
--- a/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx
+++ b/public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx
@@ -1,11 +1,12 @@
-import { ReactNode, useMemo } from 'react';
+import { css, cx } from '@emotion/css';
+import { useMemo } from 'react';
+import { GrafanaTheme2, textUtil } from '@grafana/data';
import { sceneGraph, VizPanel } from '@grafana/scenes';
-import { Button } from '@grafana/ui';
-import { t, Trans } from 'app/core/internationalization';
+import { useStyles2, Text, Icon, Stack, Tooltip } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
-import { getVisualizationOptions2 } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions';
import {
PanelBackgroundSwitch,
@@ -14,14 +15,26 @@ import {
} from '../panel-edit/getPanelFrameOptions';
import { BulkActionElement } from '../scene/types/BulkActionElement';
import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
-import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
+import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
+import { getEditPanelUrl } from '../utils/urlBuilders';
+import { getPanelIdForVizPanel } from '../utils/utils';
+
+import { EditPaneHeader } from './EditPaneHeader';
export class VizPanelEditableElement implements EditableDashboardElement, BulkActionElement {
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Panel';
- public constructor(private panel: VizPanel) {}
+ public constructor(public panel: VizPanel) {}
+
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return {
+ typeId: 'panel',
+ icon: 'chart-line',
+ name: sceneGraph.interpolate(this.panel, this.panel.state.title, undefined, 'text'),
+ };
+ }
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const panel = this.panel;
@@ -29,35 +42,38 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
const panelOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
- title: t('dashboard.viz-panel.options.title', 'Panel options'),
- id: 'panel-options',
- isOpenDefault: true,
+ title: ``,
+ id: 'panel-header',
+ isOpenable: false,
+ renderTitle: () => (
+ this.onDelete()} />
+ ),
})
+ .addItem(
+ new OptionsPaneItemDescriptor({
+ title: '',
+ render: () => ,
+ })
+ )
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.title-option', 'Title'),
value: panel.state.title,
popularRank: 1,
- render: function renderTitle() {
- return ;
- },
+ render: () => ,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.description', 'Description'),
value: panel.state.description,
- render: function renderDescription() {
- return ;
- },
+ render: () => ,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.transparent-background', 'Transparent background'),
- render: function renderTransparent() {
- return ;
- },
+ render: () => ,
})
);
}, [panel]);
@@ -69,50 +85,73 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
return undefined;
}, [layoutElement]);
- const { options, fieldConfig, _pluginInstanceState } = panel.useState();
- const dataProvider = sceneGraph.getData(panel);
- const { data } = dataProvider.useState();
-
- const visualizationOptions = useMemo(() => {
- const plugin = panel.getPlugin();
- if (!plugin) {
- return [];
- }
-
- return getVisualizationOptions2({
- panel,
- data,
- plugin: plugin,
- eventBus: panel.getPanelContext().eventBus,
- instanceState: _pluginInstanceState,
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [data, panel, options, fieldConfig, _pluginInstanceState]);
-
const categories = [panelOptions];
if (layoutCategory) {
categories.push(layoutCategory);
}
- categories.push(...visualizationOptions);
-
return categories;
}
- public onDelete = () => {
+ public onDelete() {
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);
layout.removePanel?.(this.panel);
- };
-
- public renderActions(): ReactNode {
- return (
- <>
-
- Edit
-
-
-
- >
- );
}
}
+
+type OpenPanelEditVizProps = {
+ panel: VizPanel;
+};
+
+const OpenPanelEditViz = ({ panel }: OpenPanelEditVizProps) => {
+ const styles = useStyles2(getStyles);
+
+ const plugin = panel.getPlugin();
+ const imgSrc = plugin?.meta.info.logos.small;
+
+ return (
+
+ {plugin ? (
+
+
+
+ {plugin.meta.name}
+
+
+
+ ) : null}
+
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ pluginDescriptionWrapper: css({
+ display: 'flex',
+ flexWrap: 'nowrap',
+ alignItems: 'center',
+ columnGap: theme.spacing(1),
+ rowGap: theme.spacing(0.5),
+ minHeight: theme.spacing(4),
+ backgroundColor: theme.components.input.background,
+ border: `1px solid ${theme.colors.border.strong}`,
+ borderRadius: theme.shape.radius.default,
+ paddingInline: theme.spacing(1),
+ paddingBlock: theme.spacing(0.5),
+ flexGrow: 1,
+ }),
+ panelVizImg: css({
+ width: '16px',
+ height: '16px',
+ marginRight: theme.spacing(1),
+ }),
+ panelVizIcon: css({
+ marginLeft: 'auto',
+ }),
+});
diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts
index 59ee48dde75..72c44d5dc2b 100644
--- a/public/app/features/dashboard-scene/edit-pane/shared.ts
+++ b/public/app/features/dashboard-scene/edit-pane/shared.ts
@@ -1,5 +1,55 @@
import { useSessionStorage } from 'react-use';
+import { SceneGridRow, SceneObject, VizPanel } from '@grafana/scenes';
+
+import { DashboardScene } from '../scene/DashboardScene';
+import { SceneGridRowEditableElement } from '../scene/layout-default/SceneGridRowEditableElement';
+import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement';
+
+import { DashboardEditableElement } from './DashboardEditableElement';
+import { VizPanelEditableElement } from './VizPanelEditableElement';
+
export function useEditPaneCollapsed() {
return useSessionStorage('grafana.dashboards.edit-pane.isCollapsed', false);
}
+
+export function getEditableElementFor(sceneObj: SceneObject | undefined): EditableDashboardElement | undefined {
+ if (!sceneObj) {
+ return undefined;
+ }
+
+ if (isEditableDashboardElement(sceneObj)) {
+ return sceneObj;
+ }
+
+ if (sceneObj instanceof VizPanel) {
+ return new VizPanelEditableElement(sceneObj);
+ }
+
+ if (sceneObj instanceof SceneGridRow) {
+ return new SceneGridRowEditableElement(sceneObj);
+ }
+
+ if (sceneObj instanceof DashboardScene) {
+ return new DashboardEditableElement(sceneObj);
+ }
+
+ return undefined;
+}
+
+export function hasEditableElement(sceneObj: SceneObject | undefined): boolean {
+ if (!sceneObj) {
+ return false;
+ }
+
+ if (
+ isEditableDashboardElement(sceneObj) ||
+ sceneObj instanceof VizPanel ||
+ sceneObj instanceof SceneGridRow ||
+ sceneObj instanceof DashboardScene
+ ) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
index b9f37706bad..63da7075646 100644
--- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
+++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
@@ -151,7 +151,7 @@ abstract class DashboardScenePageStateManagerBase
const queryController = sceneGraph.getQueryController(dashboard);
trackDashboardSceneLoaded(dashboard, measure?.duration);
- queryController?.startProfile(dashboard);
+ queryController?.startProfile('DashboardScene');
if (options.route !== DashboardRoutes.New) {
emitDashboardViewEvent({
diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx
index e21fbeb4e8f..96e52c2346a 100644
--- a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx
+++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx
@@ -54,6 +54,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
const result = await onSaveDashboard(dashboard, {
overwrite,
folderUid: data.folder.uid,
+ rawDashboardJSON: changedSaveModel,
// save as config
saveAsCopy: true,
diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx
index ac621a91baf..7f77aac7f1b 100644
--- a/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx
+++ b/public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx
@@ -25,7 +25,7 @@ export interface Props {
}
export function SaveDashboardForm({ dashboard, drawer, changeInfo }: Props) {
- const { hasChanges } = changeInfo;
+ const { hasChanges, changedSaveModel } = changeInfo;
const { state, onSaveDashboard } = useSaveDashboard(false);
const [options, setOptions] = useState({
@@ -38,7 +38,7 @@ export function SaveDashboardForm({ dashboard, drawer, changeInfo }: Props) {
});
const onSave = async (overwrite: boolean) => {
- const result = await onSaveDashboard(dashboard, { ...options, overwrite });
+ const result = await onSaveDashboard(dashboard, { ...options, rawDashboardJSON: changedSaveModel, overwrite });
if (result.status === 'success') {
dashboard.closeModal();
drawer.state.onSaveSuccess?.();
diff --git a/public/app/features/dashboard-scene/saving/useSaveDashboard.ts b/public/app/features/dashboard-scene/saving/useSaveDashboard.ts
index a5d8352683e..93056eb6f8f 100644
--- a/public/app/features/dashboard-scene/saving/useSaveDashboard.ts
+++ b/public/app/features/dashboard-scene/saving/useSaveDashboard.ts
@@ -3,6 +3,7 @@ import { useAsyncFn } from 'react-use';
import { locationUtil } from '@grafana/data';
import { locationService, reportInteraction } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
+import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import appEvents from 'app/core/app_events';
import { useAppNotification } from 'app/core/copy/appNotification';
import { updateDashboardName } from 'app/core/reducers/navBarTree';
@@ -25,7 +26,7 @@ export function useSaveDashboard(isCopy = false) {
options: SaveDashboardOptions &
SaveDashboardAsOptions & {
// When provided, will take precedence over the scene's save model
- rawDashboardJSON?: Dashboard;
+ rawDashboardJSON?: Dashboard | DashboardV2Spec;
}
) => {
{
diff --git a/public/app/features/dashboard-scene/scene/DashboardScopesFacade.ts b/public/app/features/dashboard-scene/scene/DashboardScopesFacade.ts
index 93b81e735ce..ccde822ce13 100644
--- a/public/app/features/dashboard-scene/scene/DashboardScopesFacade.ts
+++ b/public/app/features/dashboard-scene/scene/DashboardScopesFacade.ts
@@ -1,6 +1,10 @@
-import { sceneGraph } from '@grafana/scenes';
+import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes';
import { ScopesFacade } from 'app/features/scopes';
+import { getDashboardSceneFor } from '../utils/utils';
+
+import { convertScopesToAdHocFilters } from './convertScopesToAdHocFilters';
+
export interface DashboardScopesFacadeState {
reloadOnParamsChange?: boolean;
uid?: string;
@@ -13,7 +17,32 @@ export class DashboardScopesFacade extends ScopesFacade {
if (!reloadOnParamsChange || !uid) {
sceneGraph.getTimeRange(facade).onRefresh();
}
+
+ // push filters as soon as they come
+ this.pushScopeFiltersToAdHocVariable();
},
});
+
+ this.addActivationHandler(() => {
+ // also try to push filters on activation, for
+ // when the dashboard is changed
+ this.pushScopeFiltersToAdHocVariable();
+ });
+ }
+
+ private pushScopeFiltersToAdHocVariable() {
+ const dashboard = getDashboardSceneFor(this);
+
+ const adhoc = dashboard.state.$variables?.state.variables.find((v) => v instanceof AdHocFiltersVariable);
+
+ if (!adhoc) {
+ return;
+ }
+
+ const filters = convertScopesToAdHocFilters(this.value);
+
+ adhoc.setState({
+ baseFilters: filters,
+ });
}
}
diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
index b3c4c75afe8..d5564aa2814 100644
--- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
+++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
@@ -20,7 +20,6 @@ import {
} from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
-import grafanaConfig from 'app/core/config';
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
import { contextSrv } from 'app/core/core';
import { Trans, t } from 'app/core/internationalization';
@@ -79,11 +78,6 @@ export function ToolbarActions({ dashboard }: Props) {
const showScopesSelector = config.featureToggles.scopeFilters && !isEditing;
const dashboardNewLayouts = config.featureToggles.dashboardNewLayouts;
- // Internal only;
- // allows viewer editing without ability to save
- // used for grafana play
- const canEdit = grafanaConfig.viewersCanEdit;
-
if (!isEditingPanel) {
// This adds the presence indicators in enterprise
addDynamicActions(toolbarActions, dynamicDashNavActions.left, 'left-actions');
@@ -360,7 +354,7 @@ export function ToolbarActions({ dashboard }: Props) {
toolbarActions.push({
group: 'main-buttons',
- condition: !isEditing && (dashboard.canEditDashboard() || canEdit) && !isViewingPanel && !isPlaying && editable,
+ condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isPlaying && editable,
render: () => (
{
diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts
new file mode 100644
index 00000000000..c6b84a4d39f
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.test.ts
@@ -0,0 +1,288 @@
+import { Scope, ScopeSpecFilter } from '@grafana/data';
+import { FilterOrigin } from '@grafana/scenes';
+
+import { convertScopesToAdHocFilters } from './convertScopesToAdHocFilters';
+
+describe('convertScopesToAdHocFilters', () => {
+ it('should return empty filters when no scopes are provided', () => {
+ let scopes = generateScopes([]);
+
+ expect(scopes).toEqual([]);
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([]);
+
+ scopes = generateScopes([[], []]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([]);
+ });
+
+ it('should return filters formatted for adHoc from a single scope', () => {
+ let scopes = generateScopes([
+ [
+ { key: 'key1', value: 'value1', operator: 'equals' },
+ { key: 'key2', value: 'value2', operator: 'not-equals' },
+ { key: 'key3', value: 'value3', operator: 'regex-not-match' },
+ ],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] },
+ { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] },
+ { key: 'key3', value: 'value3', operator: '!~', origin: FilterOrigin.Scopes, values: ['value3'] },
+ ]);
+
+ scopes = generateScopes([[{ key: 'key3', value: 'value3', operator: 'regex-match' }]]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] },
+ ]);
+ });
+
+ it('should return filters formatted for adHoc from multiple scopes with single values', () => {
+ let scopes = generateScopes([
+ [{ key: 'key1', value: 'value1', operator: 'equals' }],
+ [{ key: 'key2', value: 'value2', operator: 'regex-match' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] },
+ { key: 'key2', value: 'value2', operator: '=~', origin: FilterOrigin.Scopes, values: ['value2'] },
+ ]);
+ });
+
+ it('should return filters formatted for adHoc from multiple scopes with multiple values', () => {
+ let scopes = generateScopes([
+ [
+ { key: 'key1', value: 'value1', operator: 'equals' },
+ { key: 'key2', value: 'value2', operator: 'not-equals' },
+ ],
+ [
+ { key: 'key3', value: 'value3', operator: 'regex-match' },
+ { key: 'key4', value: 'value4', operator: 'regex-match' },
+ ],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ { key: 'key1', value: 'value1', operator: '=', origin: FilterOrigin.Scopes, values: ['value1'] },
+ { key: 'key2', value: 'value2', operator: '!=', origin: FilterOrigin.Scopes, values: ['value2'] },
+ { key: 'key3', value: 'value3', operator: '=~', origin: FilterOrigin.Scopes, values: ['value3'] },
+ { key: 'key4', value: 'value4', operator: '=~', origin: FilterOrigin.Scopes, values: ['value4'] },
+ ]);
+ });
+
+ it('should return formatted filters and concat values of the same key, coming from different scopes, if operator supports multi-value', () => {
+ let scopes = generateScopes([
+ [
+ { key: 'key1', value: 'value1', operator: 'equals' },
+ { key: 'key2', value: 'value2', operator: 'not-equals' },
+ ],
+ [
+ { key: 'key1', value: 'value3', operator: 'equals' },
+ { key: 'key2', value: 'value4', operator: 'not-equals' },
+ ],
+ [{ key: 'key1', value: 'value5', operator: 'equals' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=|',
+ origin: FilterOrigin.Scopes,
+ values: ['value1', 'value3', 'value5'],
+ },
+ { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] },
+ ]);
+ });
+
+ it('should ignore the rest of the duplicate filters, if they are a combination of equals and not-equals', () => {
+ let scopes = generateScopes([
+ [{ key: 'key1', value: 'value1', operator: 'equals' }],
+ [{ key: 'key1', value: 'value2', operator: 'not-equals' }],
+ [{ key: 'key1', value: 'value3', operator: 'equals' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=|',
+ origin: FilterOrigin.Scopes,
+ values: ['value1', 'value3'],
+ },
+ {
+ key: 'key1',
+ value: 'value2',
+ operator: '!=',
+ origin: FilterOrigin.Scopes,
+ values: ['value2'],
+ },
+ ]);
+ });
+
+ it('should return formatted filters and keep only the first filter of the same key if operator is not multi-value', () => {
+ let scopes = generateScopes([
+ [
+ { key: 'key1', value: 'value1', operator: 'regex-match' },
+ { key: 'key2', value: 'value2', operator: 'not-equals' },
+ ],
+ [
+ { key: 'key1', value: 'value3', operator: 'regex-match' },
+ { key: 'key2', value: 'value4', operator: 'not-equals' },
+ ],
+ [{ key: 'key1', value: 'value5', operator: 'equals' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value1'],
+ },
+ { key: 'key2', value: 'value2', operator: '!=|', origin: FilterOrigin.Scopes, values: ['value2', 'value4'] },
+ {
+ key: 'key1',
+ value: 'value3',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value3'],
+ },
+ {
+ key: 'key1',
+ value: 'value5',
+ operator: '=',
+ origin: FilterOrigin.Scopes,
+ values: ['value5'],
+ },
+ ]);
+
+ scopes = generateScopes([
+ [{ key: 'key1', value: 'value1', operator: 'regex-match' }],
+ [{ key: 'key1', value: 'value5', operator: 'equals' }],
+ [{ key: 'key1', value: 'value3', operator: 'regex-match' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value1'],
+ },
+ {
+ key: 'key1',
+ value: 'value5',
+ operator: '=',
+ origin: FilterOrigin.Scopes,
+ values: ['value5'],
+ },
+ {
+ key: 'key1',
+ value: 'value3',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value3'],
+ },
+ ]);
+ });
+
+ it('should return formatted filters and concat values that are multi-value and drop duplicates with non multi-value operator', () => {
+ let scopes = generateScopes([
+ [{ key: 'key1', value: 'value1', operator: 'equals' }],
+ [{ key: 'key1', value: 'value2', operator: 'regex-match' }],
+ [{ key: 'key1', value: 'value3', operator: 'equals' }],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=|',
+ origin: FilterOrigin.Scopes,
+ values: ['value1', 'value3'],
+ },
+ {
+ key: 'key1',
+ value: 'value2',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value2'],
+ },
+ ]);
+
+ scopes = generateScopes([
+ [
+ { key: 'key1', value: 'value1', operator: 'equals' },
+ { key: 'key2', value: 'value2', operator: 'equals' },
+ ],
+ [
+ { key: 'key1', value: 'value3', operator: 'equals' },
+ { key: 'key2', value: 'value4', operator: 'equals' },
+ ],
+ [
+ { key: 'key1', value: 'value5', operator: 'regex-match' },
+ { key: 'key2', value: 'value6', operator: 'equals' },
+ ],
+ [
+ { key: 'key1', value: 'value7', operator: 'equals' },
+ { key: 'key2', value: 'value8', operator: 'regex-match' },
+ ],
+ [
+ { key: 'key1', value: 'value9', operator: 'equals' },
+ { key: 'key2', value: 'value10', operator: 'equals' },
+ ],
+ ]);
+
+ expect(convertScopesToAdHocFilters(scopes)).toEqual([
+ {
+ key: 'key1',
+ value: 'value1',
+ operator: '=|',
+ origin: FilterOrigin.Scopes,
+ values: ['value1', 'value3', 'value7', 'value9'],
+ },
+ {
+ key: 'key2',
+ value: 'value2',
+ operator: '=|',
+ origin: FilterOrigin.Scopes,
+ values: ['value2', 'value4', 'value6', 'value10'],
+ },
+ {
+ key: 'key1',
+ value: 'value5',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value5'],
+ },
+ {
+ key: 'key2',
+ value: 'value8',
+ operator: '=~',
+ origin: FilterOrigin.Scopes,
+ values: ['value8'],
+ },
+ ]);
+ });
+});
+
+function generateScopes(filtersSpec: ScopeSpecFilter[][]) {
+ const scopes: Scope[] = [];
+
+ for (let i = 0; i < filtersSpec.length; i++) {
+ scopes.push({
+ metadata: { name: `name-${i}` },
+ spec: {
+ title: `scope-${i}`,
+ type: '',
+ description: 'desc',
+ category: '',
+ filters: filtersSpec[i],
+ },
+ });
+ }
+
+ return scopes;
+}
diff --git a/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts
new file mode 100644
index 00000000000..1066a8f7b15
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/convertScopesToAdHocFilters.ts
@@ -0,0 +1,92 @@
+import {
+ Scope,
+ ScopeSpecFilter,
+ isEqualityOrMultiOperator,
+ reverseScopeFilterOperatorMap,
+ scopeFilterOperatorMap,
+} from '@grafana/data';
+import { AdHocFilterWithLabels, FilterOrigin } from '@grafana/scenes';
+
+export function convertScopesToAdHocFilters(scopes: Scope[]): AdHocFilterWithLabels[] {
+ const formattedFilters: Map = new Map();
+ // duplicated filters that could not be processed in any way are just appended to the list
+ const duplicatedFilters: AdHocFilterWithLabels[] = [];
+ const allFilters = scopes.flatMap((scope) => scope.spec.filters);
+
+ for (const filter of allFilters) {
+ processFilter(formattedFilters, duplicatedFilters, filter);
+ }
+
+ return [...formattedFilters.values(), ...duplicatedFilters];
+}
+
+function processFilter(
+ formattedFilters: Map,
+ duplicatedFilters: AdHocFilterWithLabels[],
+ filter: ScopeSpecFilter
+) {
+ const existingFilter = formattedFilters.get(filter.key);
+
+ if (existingFilter && canValueBeMerged(existingFilter.operator, filter.operator)) {
+ mergeFilterValues(existingFilter, filter);
+ } else if (!existingFilter) {
+ // Add filter to map either only if it is new.
+ // Otherwise it is an existing filter that cannot be converted to multi-value
+ // and thus will be moved to the duplicatedFilters list
+ formattedFilters.set(filter.key, {
+ key: filter.key,
+ operator: reverseScopeFilterOperatorMap[filter.operator],
+ value: filter.value,
+ values: filter.values ?? [filter.value],
+ origin: FilterOrigin.Scopes,
+ });
+ } else {
+ duplicatedFilters.push({
+ key: filter.key,
+ operator: reverseScopeFilterOperatorMap[filter.operator],
+ value: filter.value,
+ values: filter.values ?? [filter.value],
+ origin: FilterOrigin.Scopes,
+ });
+ }
+}
+
+function mergeFilterValues(adHocFilter: AdHocFilterWithLabels, filter: ScopeSpecFilter) {
+ const values = filter.values ?? [filter.value];
+
+ for (const value of values) {
+ if (!adHocFilter.values?.includes(value)) {
+ adHocFilter.values?.push(value);
+ }
+ }
+
+ // If there's only one value, there's no need to update the
+ // operator to its multi-value equivalent
+ if (adHocFilter.values?.length === 1) {
+ return;
+ }
+
+ // Otherwise update it to the equivalent multi-value operator
+ if (filter.operator === 'equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['equals']) {
+ adHocFilter.operator = reverseScopeFilterOperatorMap['one-of'];
+ } else if (filter.operator === 'not-equals' && adHocFilter.operator === reverseScopeFilterOperatorMap['not-equals']) {
+ adHocFilter.operator = reverseScopeFilterOperatorMap['not-one-of'];
+ }
+}
+
+function canValueBeMerged(adHocFilterOperator: string, filterOperator: string) {
+ const scopeConvertedOperator = scopeFilterOperatorMap[adHocFilterOperator];
+
+ if (!isEqualityOrMultiOperator(scopeConvertedOperator) || !isEqualityOrMultiOperator(filterOperator)) {
+ return false;
+ }
+
+ if (
+ (scopeConvertedOperator.includes('not') && !filterOperator.includes('not')) ||
+ (!scopeConvertedOperator.includes('not') && filterOperator.includes('not'))
+ ) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx
index 4d8163724b5..58ff8dd2082 100644
--- a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItem.tsx
@@ -1,4 +1,5 @@
import { isEqual } from 'lodash';
+import { Unsubscribable } from 'rxjs';
import {
VizPanel,
@@ -49,17 +50,12 @@ export class DashboardGridItem
private _prevRepeatValues?: VariableValueSingle[];
+ private _gridSizeSub: Unsubscribable | undefined;
+
public constructor(state: DashboardGridItemState) {
super(state);
- this.addActivationHandler(() => this._activationHandler());
- }
-
- private _activationHandler() {
- if (this.state.variableName) {
- this._subs.add(this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState)));
- this.performRepeat();
- }
+ this.addActivationHandler(() => this.handleVariableName());
}
private _handleGridResize(newState: DashboardGridItemState, prevState: DashboardGridItemState) {
@@ -197,6 +193,23 @@ export class DashboardGridItem
this.publishEvent(new DashboardRepeatsProcessedEvent({ source: this }), true);
}
+ public handleVariableName() {
+ if (this.state.variableName) {
+ if (!this._gridSizeSub) {
+ this._gridSizeSub = this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState));
+ this._subs.add(this._gridSizeSub);
+ }
+ } else {
+ if (this._gridSizeSub) {
+ this._gridSizeSub.unsubscribe();
+ this._subs.remove(this._gridSizeSub);
+ this._gridSizeSub = undefined;
+ }
+ }
+
+ this.performRepeat();
+ }
+
public setRepeatByVariable(variableName: string | undefined) {
const stateUpdate: Partial = { variableName };
diff --git a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx
index 1b9e7c5fd2c..4a80df34331 100644
--- a/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx
@@ -1,4 +1,5 @@
import { SelectableValue } from '@grafana/data';
+import { sceneGraph, SceneGridLayout } from '@grafana/scenes';
import { RadioButtonGroup, Select } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
@@ -88,14 +89,24 @@ function MaxPerRowOption({ gridItem }: OptionComponentProps) {
}
function RepeatByOption({ gridItem }: OptionComponentProps) {
- const { variableName } = gridItem.useState();
+ const { variableName, width } = gridItem.useState();
return (
gridItem.setRepeatByVariable(value)}
+ onChange={(value?: string) => {
+ if (value !== variableName) {
+ gridItem.setRepeatByVariable(value);
+ gridItem.handleVariableName();
+
+ if (width !== 24) {
+ gridItem.setState({ width: 24 });
+ sceneGraph.getAncestor(gridItem, SceneGridLayout).forceRender();
+ }
+ }
+ }}
/>
);
}
diff --git a/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx b/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx
new file mode 100644
index 00000000000..497b4087da9
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx
@@ -0,0 +1,149 @@
+import { ReactNode, useMemo } from 'react';
+
+import { selectors } from '@grafana/e2e-selectors';
+import { sceneGraph, SceneGridRow, VizPanel } from '@grafana/scenes';
+import { Alert, Button, Input, TextLink } from '@grafana/ui';
+import { t, Trans } from 'app/core/internationalization';
+import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
+import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
+import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constants';
+import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
+
+import { getDashboardSceneFor, getLayoutManagerFor, getQueryRunnerFor } from '../../utils/utils';
+import { DashboardScene } from '../DashboardScene';
+import { BulkActionElement } from '../types/BulkActionElement';
+import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
+
+import { DefaultGridLayoutManager } from './DefaultGridLayoutManager';
+import { RowRepeaterBehavior } from './RowRepeaterBehavior';
+
+export class SceneGridRowEditableElement implements EditableDashboardElement, BulkActionElement {
+ public readonly isEditableDashboardElement = true;
+
+ public constructor(private _row: SceneGridRow) {}
+
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return {
+ typeId: 'panel',
+ icon: 'line-alt',
+ name: sceneGraph.interpolate(this._row, this._row.state.title, undefined, 'text'),
+ };
+ }
+
+ public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
+ const row = this._row;
+
+ const rowOptions = useMemo(() => {
+ return new OptionsPaneCategoryDescriptor({
+ title: t('dashboard.default-layout.row-options.title', 'Row options'),
+ id: 'row-options',
+ isOpenDefault: true,
+ }).addItem(
+ new OptionsPaneItemDescriptor({
+ title: t('dashboard.default-layout.row-options.form.title', 'Title'),
+ render: () => ,
+ })
+ );
+ }, [row]);
+
+ const rowRepeatOptions = useMemo(() => {
+ const dashboard = getDashboardSceneFor(row);
+
+ return new OptionsPaneCategoryDescriptor({
+ title: t('dashboard.default-layout.row-options.repeat.title', 'Repeat options'),
+ id: 'row-repeat-options',
+ isOpenDefault: true,
+ }).addItem(
+ new OptionsPaneItemDescriptor({
+ title: t('dashboard.default-layout.row-options.repeat.variable.title', 'Variable'),
+ render: () => ,
+ })
+ );
+ }, [row]);
+
+ return [rowOptions, rowRepeatOptions];
+ }
+
+ public onDelete() {
+ const layoutManager = getLayoutManagerFor(this._row);
+
+ if (layoutManager instanceof DefaultGridLayoutManager) {
+ layoutManager.removeRow(this._row);
+ }
+ }
+
+ public renderActions(): ReactNode {
+ return (
+ <>
+ this.onDelete()} icon="trash-alt" />
+ >
+ );
+ }
+}
+
+function RowTitleInput({ row }: { row: SceneGridRow }) {
+ const { title } = row.useState();
+
+ return row.setState({ title: e.currentTarget.value })} />;
+}
+
+function RowRepeatSelect({ row, dashboard }: { row: SceneGridRow; dashboard: DashboardScene }) {
+ const { $behaviors, children } = row.useState();
+ let repeatBehavior = $behaviors?.find((b) => b instanceof RowRepeaterBehavior);
+ const vizPanels = useMemo(
+ () => children.reduce((acc, child) => [...acc, ...sceneGraph.findDescendents(child, VizPanel)], []),
+ [children]
+ );
+
+ const isAnyPanelUsingDashboardDS = vizPanels.some((vizPanel) => {
+ const runner = getQueryRunnerFor(vizPanel);
+ return (
+ runner?.state.datasource?.uid === SHARED_DASHBOARD_QUERY ||
+ (runner?.state.datasource?.uid === MIXED_DATASOURCE_NAME &&
+ runner?.state.queries.some((query) => query.datasource?.uid === SHARED_DASHBOARD_QUERY))
+ );
+ });
+
+ return (
+ <>
+ {
+ if (repeat) {
+ repeatBehavior?.removeBehavior();
+ repeatBehavior = new RowRepeaterBehavior({ variableName: repeat });
+ row.setState({ $behaviors: [...(row.state.$behaviors ?? []), repeatBehavior] });
+ } else {
+ repeatBehavior?.removeBehavior();
+ }
+ }}
+ />
+ {isAnyPanelUsingDashboardDS ? (
+
+
+
+ Panels in this row use the {{ SHARED_DASHBOARD_QUERY }} data source. These panels will reference the panel
+ in the original row, not the ones in the repeated rows.
+
+
+
+ Learn more
+
+
+ ) : undefined}
+ >
+ );
+}
diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx
index a201a6a38f7..e7b9010a3d5 100644
--- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx
@@ -1,7 +1,24 @@
-import { SceneObjectState, VizPanel, SceneObjectBase } from '@grafana/scenes';
+import { isEqual } from 'lodash';
+
+import {
+ SceneObjectState,
+ VizPanel,
+ SceneObjectBase,
+ sceneGraph,
+ CustomVariable,
+ MultiValueVariable,
+ VariableValueSingle,
+ VizPanelState,
+ SceneVariableSet,
+ LocalValueVariable,
+ VariableDependencyConfig,
+} from '@grafana/scenes';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { getCloneKey } from '../../utils/clone';
+import { getMultiVariableValues } from '../../utils/utils';
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
+import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
import { getOptions } from './ResponsiveGridItemEditor';
import { ResponsiveGridItemRenderer } from './ResponsiveGridItemRenderer';
@@ -9,13 +26,30 @@ import { ResponsiveGridItemRenderer } from './ResponsiveGridItemRenderer';
export interface ResponsiveGridItemState extends SceneObjectState {
body: VizPanel;
hideWhenNoData?: boolean;
+ repeatedPanels?: VizPanel[];
+ variableName?: string;
}
export class ResponsiveGridItem extends SceneObjectBase implements DashboardLayoutItem {
public static Component = ResponsiveGridItemRenderer;
-
+ private _prevRepeatValues?: VariableValueSingle[];
+ protected _variableDependency = new VariableDependencyConfig(this, {
+ variableNames: this.state.variableName ? [this.state.variableName] : [],
+ onVariableUpdateCompleted: () => this.performRepeat(),
+ });
public readonly isDashboardLayoutItem = true;
+ public constructor(state: ResponsiveGridItemState) {
+ super(state);
+ this.addActivationHandler(() => this._activationHandler());
+ }
+
+ private _activationHandler() {
+ if (this.state.variableName) {
+ this.performRepeat();
+ }
+ }
+
public getOptions(): OptionsPaneCategoryDescriptor {
return getOptions(this);
}
@@ -23,4 +57,78 @@ export class ResponsiveGridItem extends SceneObjectBase
public toggleHideWhenNoData() {
this.setState({ hideWhenNoData: !this.state.hideWhenNoData });
}
+
+ public performRepeat() {
+ if (!this.state.variableName || sceneGraph.hasVariableDependencyInLoadingState(this)) {
+ return;
+ }
+
+ const variable =
+ sceneGraph.lookupVariable(this.state.variableName, this) ??
+ new CustomVariable({
+ name: '_____default_sys_repeat_var_____',
+ options: [],
+ value: '',
+ text: '',
+ query: 'A',
+ });
+
+ if (!(variable instanceof MultiValueVariable)) {
+ console.error('DashboardGridItem: Variable is not a MultiValueVariable');
+ return;
+ }
+
+ const { values, texts } = getMultiVariableValues(variable);
+
+ if (isEqual(this._prevRepeatValues, values)) {
+ return;
+ }
+
+ const panelToRepeat = this.state.body;
+ const repeatedPanels: VizPanel[] = [];
+
+ // when variable has no options (due to error or similar) it will not render any panels at all
+ // adding a placeholder in this case so that there is at least empty panel that can display error
+ const emptyVariablePlaceholderOption = {
+ values: [''],
+ texts: variable.hasAllValue() ? ['All'] : ['None'],
+ };
+
+ const variableValues = values.length ? values : emptyVariablePlaceholderOption.values;
+ const variableTexts = texts.length ? texts : emptyVariablePlaceholderOption.texts;
+ for (let index = 0; index < variableValues.length; index++) {
+ const cloneState: Partial = {
+ $variables: new SceneVariableSet({
+ variables: [
+ new LocalValueVariable({
+ name: variable.state.name,
+ value: variableValues[index],
+ text: String(variableTexts[index]),
+ }),
+ ],
+ }),
+ key: getCloneKey(panelToRepeat.state.key!, index),
+ };
+ const clone = panelToRepeat.clone(cloneState);
+ repeatedPanels.push(clone);
+ }
+
+ this.setState({ repeatedPanels });
+ this._prevRepeatValues = values;
+
+ this.publishEvent(new DashboardRepeatsProcessedEvent({ source: this }), true);
+ }
+
+ public setRepeatByVariable(variableName: string | undefined) {
+ const stateUpdate: Partial = { variableName };
+
+ if (this.state.body.state.$variables) {
+ this.state.body.setState({ $variables: undefined });
+ }
+
+ this._variableDependency.setVariableNames(variableName ? [variableName] : []);
+
+ this.setState(stateUpdate);
+ this.performRepeat();
+ }
}
diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx
index d1e96b04f75..47be0b1272a 100644
--- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemEditor.tsx
@@ -2,6 +2,7 @@ import { Switch } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
+import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
import { ResponsiveGridItem } from './ResponsiveGridItem';
@@ -19,6 +20,17 @@ export function getOptions(model: ResponsiveGridItem): OptionsPaneCategoryDescri
})
);
+ category.addItem(
+ new OptionsPaneItemDescriptor({
+ title: t('dashboard.responsive-layout.item-options.repeat.variable.title', 'Repeat by variable'),
+ description: t(
+ 'dashboard.responsive-layout.item-options.repeat.variable.description',
+ 'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.'
+ ),
+ render: () => ,
+ })
+ );
+
return category;
}
@@ -27,3 +39,16 @@ function GridItemNoDataToggle({ item }: { item: ResponsiveGridItem }) {
return item.toggleHideWhenNoData()} />;
}
+
+function RepeatByOption({ item }: { item: ResponsiveGridItem }) {
+ const { variableName } = item.useState();
+
+ return (
+ item.setRepeatByVariable(value)}
+ />
+ );
+}
diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx
index 4a6aad0f385..9a039becb97 100644
--- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItemRenderer.tsx
@@ -9,7 +9,15 @@ export function ResponsiveGridItemRenderer({ model }: SceneComponentProps
+ {model.state.repeatedPanels.map((item) => (
+
+
+
+ ))}
+ >
+ ) : (
diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx
index 01814848447..e92ed0f5466 100644
--- a/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx
@@ -7,7 +7,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
import { BulkActionElement } from '../types/BulkActionElement';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
-import { EditableDashboardElement } from '../types/EditableDashboardElement';
+import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { LayoutParent } from '../types/LayoutParent';
import { getEditOptions, renderActions } from './RowItemEditor';
@@ -35,7 +35,6 @@ export class RowItem
});
public readonly isEditableDashboardElement = true;
- public readonly typeName = 'Row';
public constructor(state?: Partial) {
super({
@@ -45,6 +44,10 @@ export class RowItem
});
}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { typeId: 'row', icon: 'line-alt', name: sceneGraph.interpolate(this, this.state.title, undefined, 'text') };
+ }
+
public getLayout(): DashboardLayoutManager {
return this.state.layout;
}
diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItems.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItems.tsx
index e2523935b56..597eaf495d8 100644
--- a/public/app/features/dashboard-scene/scene/layout-rows/RowItems.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItems.tsx
@@ -1,8 +1,10 @@
import { ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
+import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../types/MultiSelectedEditableDashboardElement';
import { RowItem } from './RowItem';
@@ -10,13 +12,16 @@ import { getEditOptions, renderActions } from './RowItemsEditor';
export class RowItems implements MultiSelectedEditableDashboardElement {
public readonly isMultiSelectedEditableDashboardElement = true;
- public readonly typeName = 'Rows';
public readonly key: string;
public constructor(private _rows: RowItem[]) {
this.key = uuidv4();
}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { name: t('dashboard.edit-pane.elements.rows', 'Rows'), typeId: 'rows', icon: 'folder' };
+ }
+
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
return getEditOptions(this);
}
diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx
index 4f4a2b45766..558d2294899 100644
--- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx
@@ -7,7 +7,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
import { BulkActionElement } from '../types/BulkActionElement';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
-import { EditableDashboardElement } from '../types/EditableDashboardElement';
+import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { LayoutParent } from '../types/LayoutParent';
import { getEditOptions, renderActions } from './TabItemEditor';
@@ -31,7 +31,6 @@ export class TabItem
});
public readonly isEditableDashboardElement = true;
- public readonly typeName = 'Tab';
constructor(state?: Partial) {
super({
@@ -41,6 +40,10 @@ export class TabItem
});
}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { typeId: 'tab', icon: 'tag-alt', name: sceneGraph.interpolate(this, this.state.title, undefined, 'text') };
+ }
+
public getLayout(): DashboardLayoutManager {
return this.state.layout;
}
diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItems.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItems.tsx
index dacfc087772..4c5b89054c6 100644
--- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItems.tsx
+++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItems.tsx
@@ -1,8 +1,10 @@
import { ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
+import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../types/MultiSelectedEditableDashboardElement';
import { TabItem } from './TabItem';
@@ -10,13 +12,16 @@ import { getEditOptions, renderActions } from './TabItemsEditor';
export class TabItems implements MultiSelectedEditableDashboardElement {
public readonly isMultiSelectedEditableDashboardElement = true;
- public readonly typeName = 'Tabs';
public readonly key: string;
public constructor(private _tabs: TabItem[]) {
this.key = uuidv4();
}
+ public getEditableElementInfo(): EditableDashboardElementInfo {
+ return { name: t('dashboard.edit-pane.elements.tabs', 'Tabs'), typeId: 'tabs', icon: 'folder' };
+ }
+
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
return getEditOptions(this);
}
diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
index d4b2da7b609..342138542d5 100644
--- a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
+++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Select } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
@@ -55,10 +56,8 @@ export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
layoutCategory.addItem(
new OptionsPaneItemDescriptor({
- title: 'Type',
- render: function renderTitle() {
- return ;
- },
+ title: t('dashboard.layout.common.layout', 'Layout'),
+ render: () => ,
})
);
diff --git a/public/app/features/dashboard-scene/scene/types/EditableDashboardElement.ts b/public/app/features/dashboard-scene/scene/types/EditableDashboardElement.ts
index 396b4f76431..9a41fc9b867 100644
--- a/public/app/features/dashboard-scene/scene/types/EditableDashboardElement.ts
+++ b/public/app/features/dashboard-scene/scene/types/EditableDashboardElement.ts
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
+import { IconName } from '@grafana/data';
import { SceneObject } from '@grafana/scenes';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
@@ -14,10 +15,8 @@ export interface EditableDashboardElement {
*/
isEditableDashboardElement: true;
- /**
- * Type name of the element
- */
- typeName: Readonly;
+ /** A descriptor used by editing pane */
+ getEditableElementInfo(): EditableDashboardElementInfo;
/**
* Hook that returns edit pane options
@@ -33,6 +32,22 @@ export interface EditableDashboardElement {
* creates a new multi-selection element from a list of selected items
*/
createMultiSelectedElement?(items: SceneObject[]): MultiSelectedEditableDashboardElement;
+
+ /**
+ * Return custom title for the edit panel header
+ */
+ renderTitle?(): ReactNode;
+
+ /**
+ * determines if first edit panel header can be collapsed
+ */
+ isOpenable?: Readonly;
+}
+
+export interface EditableDashboardElementInfo {
+ name: string;
+ typeId: string;
+ icon: IconName;
}
export function isEditableDashboardElement(obj: object): obj is EditableDashboardElement {
diff --git a/public/app/features/dashboard-scene/scene/types/MultiSelectedEditableDashboardElement.ts b/public/app/features/dashboard-scene/scene/types/MultiSelectedEditableDashboardElement.ts
index 8b0d1cc23d1..a5dd47ccbdc 100644
--- a/public/app/features/dashboard-scene/scene/types/MultiSelectedEditableDashboardElement.ts
+++ b/public/app/features/dashboard-scene/scene/types/MultiSelectedEditableDashboardElement.ts
@@ -2,16 +2,16 @@ import { ReactNode } from 'react';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
+import { EditableDashboardElementInfo } from './EditableDashboardElement';
+
export interface MultiSelectedEditableDashboardElement {
/**
* Marks this object as an element that can be selected and edited directly on the canvas
*/
isMultiSelectedEditableDashboardElement: true;
- /**
- * Type name of the element
- */
- typeName: Readonly;
+ /** A descriptor used by editing pane */
+ getEditableElementInfo(): EditableDashboardElementInfo;
/**
* Extremely useful for being able to access the useState inside the contained items
@@ -27,6 +27,16 @@ export interface MultiSelectedEditableDashboardElement {
* Panel Actions
**/
renderActions?(): ReactNode;
+
+ /**
+ * Return custom title for the edit panel header
+ */
+ renderTitle?(): ReactNode;
+
+ /**
+ * determines if first edit panel header can be collapsed
+ */
+ isOpenable?: Readonly;
}
export function isMultiSelectedEditableDashboardElement(obj: object): obj is MultiSelectedEditableDashboardElement {
diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/ResponsiveGridLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/ResponsiveGridLayoutSerializer.ts
index f97b7dd34c6..4e5bb1520bb 100644
--- a/public/app/features/dashboard-scene/serialization/layoutSerializers/ResponsiveGridLayoutSerializer.ts
+++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/ResponsiveGridLayoutSerializer.ts
@@ -1,5 +1,5 @@
import { SceneCSSGridLayout } from '@grafana/scenes';
-import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
+import { DashboardV2Spec, ResponsiveGridLayoutItemKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import { ResponsiveGridItem } from '../../scene/layout-responsive-grid/ResponsiveGridItem';
import { ResponsiveGridLayoutManager } from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
@@ -21,7 +21,7 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
if (!(child instanceof ResponsiveGridItem)) {
throw new Error('Expected ResponsiveGridItem');
}
- return {
+ const layoutItem: ResponsiveGridLayoutItemKind = {
kind: 'ResponsiveGridLayoutItem',
spec: {
element: {
@@ -30,6 +30,15 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
},
},
};
+
+ if (child.state.variableName) {
+ layoutItem.spec.repeat = {
+ mode: 'variable',
+ value: child.state.variableName,
+ };
+ }
+
+ return layoutItem;
}),
},
};
@@ -51,6 +60,7 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
return new ResponsiveGridItem({
key: getGridItemKeyForPanelId(panel.spec.id),
body: buildVizPanel(panel),
+ variableName: item.spec.repeat?.value,
});
});
diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts
index ffd9241e0f5..cd483cafa0c 100644
--- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts
+++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.test.ts
@@ -1,7 +1,10 @@
+import { SceneCSSGridLayout, SceneGridLayout } from '@grafana/scenes';
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import { ResponsiveGridLayoutManager } from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
+import { RowItem } from '../../scene/layout-rows/RowItem';
+import { RowItemRepeaterBehavior } from '../../scene/layout-rows/RowItemRepeaterBehavior';
import { RowsLayoutManager } from '../../scene/layout-rows/RowsLayoutManager';
import { RowsLayoutSerializer } from './RowsLayoutSerializer';
@@ -39,7 +42,14 @@ describe('deserialization', () => {
spec: {
title: 'Row 1',
collapsed: false,
- layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } },
+ layout: {
+ kind: 'ResponsiveGridLayout',
+ spec: {
+ row: 'minmax(min-content, max-content)',
+ col: 'repeat(auto-fit, minmax(400px, 1fr))',
+ items: [],
+ },
+ },
},
},
],
@@ -61,7 +71,14 @@ describe('deserialization', () => {
spec: {
title: 'Row 1',
collapsed: false,
- layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } },
+ layout: {
+ kind: 'ResponsiveGridLayout',
+ spec: {
+ row: 'minmax(min-content, max-content)',
+ col: 'repeat(auto-fit, minmax(400px, 1fr))',
+ items: [],
+ },
+ },
},
},
{
@@ -97,4 +114,177 @@ describe('deserialization', () => {
expect(deserialized).toBeInstanceOf(RowsLayoutManager);
expect(deserialized.state.rows).toHaveLength(0);
});
+
+ it('should deserialize row with repeat behavior', () => {
+ const layout: DashboardV2Spec['layout'] = {
+ kind: 'RowsLayout',
+ spec: {
+ rows: [
+ {
+ kind: 'RowsLayoutRow',
+ spec: {
+ title: 'Repeated Row',
+ collapsed: false,
+ layout: { kind: 'GridLayout', spec: { items: [] } },
+ repeat: { value: 'foo', mode: 'variable' },
+ },
+ },
+ ],
+ },
+ };
+ const serializer = new RowsLayoutSerializer();
+ const deserialized = serializer.deserialize(layout, {}, false);
+
+ expect(deserialized).toBeInstanceOf(RowsLayoutManager);
+ expect(deserialized.state.rows).toHaveLength(1);
+
+ const row = deserialized.state.rows[0];
+ expect(row.state.$behaviors).toBeDefined();
+ const behaviors = row.state.$behaviors ?? [];
+ expect(behaviors).toHaveLength(1);
+ const repeaterBehavior = behaviors[0] as RowItemRepeaterBehavior;
+ expect(repeaterBehavior).toBeInstanceOf(RowItemRepeaterBehavior);
+ expect(repeaterBehavior.state.variableName).toBe('foo');
+ });
+});
+
+describe('serialization', () => {
+ it('should serialize basic row layout', () => {
+ const rowsLayout = new RowsLayoutManager({
+ rows: [
+ new RowItem({
+ title: 'Row 1',
+ isCollapsed: false,
+ layout: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [],
+ isDraggable: true,
+ isResizable: true,
+ }),
+ }),
+ }),
+ ],
+ });
+
+ const serializer = new RowsLayoutSerializer();
+ const serialized = serializer.serialize(rowsLayout);
+
+ expect(serialized).toEqual({
+ kind: 'RowsLayout',
+ spec: {
+ rows: [
+ {
+ kind: 'RowsLayoutRow',
+ spec: {
+ title: 'Row 1',
+ collapsed: false,
+ layout: { kind: 'GridLayout', spec: { items: [] } },
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('should serialize row with repeat behavior', () => {
+ const rowsLayout = new RowsLayoutManager({
+ rows: [
+ new RowItem({
+ title: 'Repeated Row',
+ isCollapsed: false,
+ layout: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [],
+ isDraggable: true,
+ isResizable: true,
+ }),
+ }),
+ $behaviors: [new RowItemRepeaterBehavior({ variableName: 'foo' })],
+ }),
+ ],
+ });
+
+ const serializer = new RowsLayoutSerializer();
+ const serialized = serializer.serialize(rowsLayout);
+
+ expect(serialized).toEqual({
+ kind: 'RowsLayout',
+ spec: {
+ rows: [
+ {
+ kind: 'RowsLayoutRow',
+ spec: {
+ title: 'Repeated Row',
+ collapsed: false,
+ layout: { kind: 'GridLayout', spec: { items: [] } },
+ repeat: { value: 'foo', mode: 'variable' },
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('should serialize multiple rows with different layouts', () => {
+ const rowsLayout = new RowsLayoutManager({
+ rows: [
+ new RowItem({
+ title: 'Row 1',
+ isCollapsed: false,
+ layout: new ResponsiveGridLayoutManager({
+ layout: new SceneCSSGridLayout({
+ children: [],
+ templateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
+ autoRows: 'minmax(min-content, max-content)',
+ }),
+ }),
+ }),
+ new RowItem({
+ title: 'Row 2',
+ isCollapsed: true,
+ layout: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [],
+ isDraggable: true,
+ isResizable: true,
+ }),
+ }),
+ }),
+ ],
+ });
+
+ const serializer = new RowsLayoutSerializer();
+ const serialized = serializer.serialize(rowsLayout);
+
+ expect(serialized).toEqual({
+ kind: 'RowsLayout',
+ spec: {
+ rows: [
+ {
+ kind: 'RowsLayoutRow',
+ spec: {
+ title: 'Row 1',
+ collapsed: false,
+ layout: {
+ kind: 'ResponsiveGridLayout',
+ spec: {
+ row: 'minmax(min-content, max-content)',
+ col: 'repeat(auto-fit, minmax(400px, 1fr))',
+ items: [],
+ },
+ },
+ },
+ },
+ {
+ kind: 'RowsLayoutRow',
+ spec: {
+ title: 'Row 2',
+ collapsed: true,
+ layout: { kind: 'GridLayout', spec: { items: [] } },
+ },
+ },
+ ],
+ },
+ });
+ });
});
diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts
index d1823d6b1a7..c2b9980b55c 100644
--- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts
+++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts
@@ -1,6 +1,8 @@
-import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
+import { SceneObject } from '@grafana/scenes';
+import { DashboardV2Spec, RowsLayoutRowKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import { RowItem } from '../../scene/layout-rows/RowItem';
+import { RowItemRepeaterBehavior } from '../../scene/layout-rows/RowItemRepeaterBehavior';
import { RowsLayoutManager } from '../../scene/layout-rows/RowsLayoutManager';
import { LayoutManagerSerializer } from '../../scene/types/DashboardLayoutManager';
@@ -17,7 +19,7 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
if (layout.kind === 'RowsLayout') {
throw new Error('Nested RowsLayout is not supported');
}
- return {
+ const rowKind: RowsLayoutRowKind = {
kind: 'RowsLayoutRow',
spec: {
title: row.state.title,
@@ -25,6 +27,18 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
layout: layout,
},
};
+
+ if (row.state.$behaviors) {
+ for (const behavior of row.state.$behaviors) {
+ if (behavior instanceof RowItemRepeaterBehavior) {
+ if (rowKind.spec.repeat) {
+ throw new Error('Multiple repeaters are not supported');
+ }
+ rowKind.spec.repeat = { value: behavior.state.variableName, mode: 'variable' };
+ }
+ }
+ }
+ return rowKind;
}),
},
};
@@ -40,9 +54,14 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
}
const rows = layout.spec.rows.map((row) => {
const layout = row.spec.layout;
+ const behaviors: SceneObject[] = [];
+ if (row.spec.repeat) {
+ behaviors.push(new RowItemRepeaterBehavior({ variableName: row.spec.repeat.value }));
+ }
return new RowItem({
title: row.spec.title,
isCollapsed: row.spec.collapsed,
+ $behaviors: behaviors,
layout: layoutSerializerRegistry.get(layout.kind).serializer.deserialize(layout, elements, preload),
});
});
diff --git a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.test.tsx b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.test.tsx
index 0a15aff1dd5..8b2a8891892 100644
--- a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.test.tsx
+++ b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.test.tsx
@@ -52,7 +52,7 @@ describe('VersionHistoryTable', () => {
version: mockVersions[1].version,
index: 1,
confirm: false,
- timestamp: mockVersions[1].created,
+ version_date: mockVersions[1].created,
});
});
});
diff --git a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx
index 48fc15963b8..d98577c2a75 100644
--- a/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx
+++ b/public/app/features/dashboard-scene/settings/version-history/VersionHistoryTable.tsx
@@ -70,7 +70,7 @@ export const VersionHistoryTable = ({ versions, canCompare, onCheck, onRestore }
version: version.version,
index: idx,
confirm: false,
- timestamp: version.created,
+ version_date: version.created,
});
}}
>
diff --git a/public/app/features/dashboard-scene/utils/interactions.ts b/public/app/features/dashboard-scene/utils/interactions.ts
index b52c09e190f..c7a0791e513 100644
--- a/public/app/features/dashboard-scene/utils/interactions.ts
+++ b/public/app/features/dashboard-scene/utils/interactions.ts
@@ -118,7 +118,7 @@ export const DashboardInteractions = {
},
// Dashboards versions interactions
- versionRestoreClicked: (properties: { version: number; index?: number; confirm: boolean; timestamp?: Date }) => {
+ versionRestoreClicked: (properties: { version: number; index?: number; confirm: boolean; version_date?: Date }) => {
reportDashboardInteraction('version_restore_clicked', properties);
},
showMoreVersionsClicked: () => {
diff --git a/public/app/features/dashboard/api/ResponseTransformers.test.ts b/public/app/features/dashboard/api/ResponseTransformers.test.ts
index 4a7a29cf711..41064867a87 100644
--- a/public/app/features/dashboard/api/ResponseTransformers.test.ts
+++ b/public/app/features/dashboard/api/ResponseTransformers.test.ts
@@ -406,6 +406,13 @@ describe('ResponseTransformers', () => {
],
collapsed: true,
},
+ {
+ id: 6,
+ type: 'row',
+ title: 'Row with no panel property',
+ gridPos: { x: 0, y: 25, w: 12, h: 1 },
+ collapsed: true,
+ },
],
};
@@ -483,7 +490,7 @@ describe('ResponseTransformers', () => {
// Panel
expect(spec.layout.kind).toBe('GridLayout');
const layout = spec.layout as GridLayoutKind;
- expect(layout.spec.items).toHaveLength(4);
+ expect(layout.spec.items).toHaveLength(5);
expect(layout.spec.items[0].spec).toEqual({
element: {
kind: 'ElementReference',
@@ -606,6 +613,11 @@ describe('ResponseTransformers', () => {
height: 8,
});
+ const rowWithNoPanelProperty = layout.spec.items[4].spec as GridLayoutRowSpec;
+ expect(rowWithNoPanelProperty.collapsed).toBe(true);
+ expect(rowWithNoPanelProperty.title).toBe('Row with no panel property');
+ expect(rowWithNoPanelProperty.elements).toHaveLength(0);
+
// Variables
validateVariablesV1ToV2(spec.variables[0], dashboardV1.templating?.list?.[0]);
validateVariablesV1ToV2(spec.variables[1], dashboardV1.templating?.list?.[1]);
diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts
index 5ad1d7836d6..4dc01f06a99 100644
--- a/public/app/features/dashboard/api/ResponseTransformers.ts
+++ b/public/app/features/dashboard/api/ResponseTransformers.ts
@@ -298,7 +298,7 @@ function getElementsFromPanels(
const rowElements = [];
- for (const panel of p.panels) {
+ for (const panel of p.panels || []) {
const [element, name] = buildElement(panel);
elements[name] = element;
rowElements.push(buildGridItemKind(panel, name, yOffsetInRows(panel, p.gridPos!.y)));
diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx
index 8e284c59e11..623bf612880 100644
--- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx
+++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx
@@ -21,6 +21,7 @@ export interface OptionsPaneCategoryProps {
isNested?: boolean;
children: ReactNode;
sandboxId?: string;
+ isOpenable?: boolean;
}
const CATEGORY_PARAM_NAME = 'showCategory' as const;
@@ -37,12 +38,13 @@ export const OptionsPaneCategory = React.memo(
itemsCount,
isNested = false,
sandboxId,
+ isOpenable = true,
}: OptionsPaneCategoryProps) => {
const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), {
isExpanded: isOpenDefault,
});
- const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? isOpenDefault);
+ const [isExpanded, setIsExpanded] = useState(!isOpenable || (savedState?.isExpanded ?? isOpenDefault));
const manualClickTime = useRef(0);
const ref = useRef(null);
const [queryParams, updateQueryParams] = useQueryParams();
@@ -66,6 +68,9 @@ export const OptionsPaneCategory = React.memo(
}, [forceOpen, isExpanded, isOpenFromUrl]);
const onToggle = useCallback(() => {
+ if (!isOpenable) {
+ return;
+ }
manualClickTime.current = Date.now();
updateQueryParams(
{
@@ -75,7 +80,7 @@ export const OptionsPaneCategory = React.memo(
);
setSavedState({ isExpanded: !isExpanded });
setIsExpanded(!isExpanded);
- }, [setSavedState, setIsExpanded, updateQueryParams, isExpanded, id]);
+ }, [isOpenable, updateQueryParams, isExpanded, id, setSavedState]);
if (!renderTitle) {
renderTitle = function defaultTitle(isExpanded: boolean) {
@@ -101,6 +106,7 @@ export const OptionsPaneCategory = React.memo(
);
const headerStyles = cx(styles.header, {
+ [styles.headerHover]: isOpenable,
[styles.headerExpanded]: isExpanded,
[styles.headerNested]: isNested,
});
@@ -123,17 +129,19 @@ export const OptionsPaneCategory = React.memo(
-
+ {isOpenable ? (
+
+ ) : null}
{isExpanded && (
@@ -161,15 +169,19 @@ const getStyles = (theme: GrafanaTheme2) => ({
fontSize: '1rem',
fontWeight: theme.typography.fontWeightMedium,
margin: 0,
+ height: theme.spacing(4),
+ display: 'flex',
+ alignItems: 'center',
}),
header: css({
display: 'flex',
- cursor: 'pointer',
alignItems: 'center',
padding: theme.spacing(0.5, 1.5),
color: theme.colors.text.primary,
fontWeight: theme.typography.fontWeightMedium,
-
+ }),
+ headerHover: css({
+ cursor: 'pointer',
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
},
diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx
index 9106be64a5d..bb1c76969d1 100644
--- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx
+++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx
@@ -16,6 +16,7 @@ export interface OptionsPaneCategoryDescriptorProps {
itemsCount?: number;
customRender?: () => React.ReactNode;
sandboxId?: string;
+ isOpenable?: boolean;
}
/**
diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx
index 24bc46790a5..f5c6912b76f 100644
--- a/public/app/features/explore/Logs/Logs.tsx
+++ b/public/app/features/explore/Logs/Logs.tsx
@@ -1065,17 +1065,25 @@ const UnthemedLogs: React.FunctionComponent
= (props: Props) => {
)}
{visualisationType === 'logs' && hasData && config.featureToggles.newLogsPanel && (
<>
-
+
{logsContainerRef.current && (
void;
- onPinToContentOutlineClick?: (row: LogRowModel, onOpenContext: (row: LogRowModel) => void) => void;
addonBefore?: ReactNode[];
addonAfter?: ReactNode[];
}
@@ -66,31 +66,9 @@ export const LogRowMenuCell = memo(
e.stopPropagation();
}, []);
const onShowContextClick = useCallback(
- async (event: MouseEvent) => {
+ async (event: MouseEvent) => {
event.stopPropagation();
- // if ctrl or meta key is pressed, open query in new Explore tab
- if (
- getRowContextQuery &&
- (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey || event.nativeEvent.shiftKey)
- ) {
- const win = window.open('about:blank');
- // for this request we don't want to use the cached filters from a context provider, but always want to refetch and clear
- const query = await getRowContextQuery(row, undefined, false);
- if (query && win) {
- const url = urlUtil.renderUrl(locationUtil.assureBaseUrl(`${getConfig().appSubUrl}explore`), {
- left: JSON.stringify({
- datasource: query.datasource,
- queries: [query],
- range: getDefaultTimeRange(),
- }),
- });
- win.location = url;
-
- return;
- }
- win?.close();
- }
- onOpenContext(row);
+ handleOpenLogsContextClick(event, row, getRowContextQuery, onOpenContext);
},
[onOpenContext, getRowContextQuery, row]
);
diff --git a/public/app/features/logs/components/__mocks__/logRow.ts b/public/app/features/logs/components/__mocks__/logRow.ts
index 0968f242a84..d7777eed731 100644
--- a/public/app/features/logs/components/__mocks__/logRow.ts
+++ b/public/app/features/logs/components/__mocks__/logRow.ts
@@ -1,4 +1,6 @@
-import { FieldType, LogLevel, LogRowModel, toDataFrame } from '@grafana/data';
+import { FieldType, LogLevel, LogRowModel, LogsSortOrder, toDataFrame } from '@grafana/data';
+
+import { LogListModel, preProcessLogs, PreProcessOptions } from '../panel/processing';
export const createLogRow = (overrides?: Partial): LogRowModel => {
const uid = overrides?.uid || '1';
@@ -36,3 +38,16 @@ export const createLogRow = (overrides?: Partial): LogRowModel => {
...overrides,
};
};
+
+export const createLogLine = (
+ overrides?: Partial,
+ processOptions: PreProcessOptions = {
+ escape: false,
+ order: LogsSortOrder.Descending,
+ timeZone: 'browser',
+ wrap: false,
+ }
+): LogListModel => {
+ const logs = preProcessLogs([createLogRow(overrides)], processOptions);
+ return logs[0];
+};
diff --git a/public/app/features/logs/components/panel/InfiniteScroll.tsx b/public/app/features/logs/components/panel/InfiniteScroll.tsx
index 3790f5f0b0f..e09d67577e4 100644
--- a/public/app/features/logs/components/panel/InfiniteScroll.tsx
+++ b/public/app/features/logs/components/panel/InfiniteScroll.tsx
@@ -4,7 +4,7 @@ import { ListChildComponentProps, ListOnItemsRenderedProps } from 'react-window'
import { AbsoluteTimeRange, LogsSortOrder, TimeRange } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
-import { Spinner, useTheme2 } from '@grafana/ui';
+import { Spinner, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { canScrollBottom, getVisibleRange, ScrollDirection, shouldLoadMore } from '../InfiniteScroll';
@@ -59,8 +59,7 @@ export const InfiniteScroll = ({
const lastEvent = useRef(null);
const countRef = useRef(0);
const lastLogOfPage = useRef([]);
- const theme = useTheme2();
- const styles = getStyles(theme);
+ const styles = useStyles2(getStyles);
useEffect(() => {
// Logs have not changed, ignore effect
diff --git a/public/app/features/logs/components/panel/LogLine.test.tsx b/public/app/features/logs/components/panel/LogLine.test.tsx
new file mode 100644
index 00000000000..15640f13ea8
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogLine.test.tsx
@@ -0,0 +1,105 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { createTheme } from '@grafana/data';
+
+import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
+import { createLogLine } from '../__mocks__/logRow';
+
+import { getStyles, LogLine } from './LogLine';
+import { LogListModel } from './processing';
+
+const theme = createTheme();
+const styles = getStyles(theme);
+
+describe('LogLine', () => {
+ let log: LogListModel;
+ beforeEach(() => {
+ log = createLogLine({ labels: { place: 'luna' } });
+ });
+
+ test('Renders a log line', () => {
+ render(
+
+ );
+ expect(screen.getByText(log.timestamp)).toBeInTheDocument();
+ expect(screen.getByText(log.body)).toBeInTheDocument();
+ });
+
+ test('Renders a log line with no timestamp', () => {
+ render(
+
+ );
+ expect(screen.queryByText(log.timestamp)).not.toBeInTheDocument();
+ expect(screen.getByText(log.body)).toBeInTheDocument();
+ });
+
+ test('Renders a log line with displayed fields', () => {
+ render(
+
+ );
+ expect(screen.getByText(log.timestamp)).toBeInTheDocument();
+ expect(screen.queryByText(log.body)).not.toBeInTheDocument();
+ expect(screen.getByText('luna')).toBeInTheDocument();
+ });
+
+ test('Renders a log line with body displayed fields', () => {
+ render(
+
+ );
+ expect(screen.getByText(log.timestamp)).toBeInTheDocument();
+ expect(screen.getByText(log.body)).toBeInTheDocument();
+ expect(screen.getByText('luna')).toBeInTheDocument();
+ });
+
+ describe('Log line menu', () => {
+ test('Renders a log line menu', async () => {
+ render(
+
+ );
+ expect(screen.queryByText('Copy log line')).not.toBeInTheDocument();
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ expect(screen.getByText('Copy log line')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/public/app/features/logs/components/panel/LogLine.tsx b/public/app/features/logs/components/panel/LogLine.tsx
index d484675d424..58004ccc456 100644
--- a/public/app/features/logs/components/panel/LogLine.tsx
+++ b/public/app/features/logs/components/panel/LogLine.tsx
@@ -1,12 +1,15 @@
import { css } from '@emotion/css';
import { CSSProperties, useEffect, useRef } from 'react';
+import tinycolor from 'tinycolor2';
import { GrafanaTheme2 } from '@grafana/data';
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
+import { LogLineMenu } from './LogLineMenu';
+import { useLogIsPinned } from './LogListContext';
import { LogFieldDimension, LogListModel } from './processing';
-import { FIELD_GAP_MULTIPLIER, hasUnderOrOverflow } from './virtualization';
+import { FIELD_GAP_MULTIPLIER, hasUnderOrOverflow, getLineHeight } from './virtualization';
interface Props {
displayedFields: string[];
@@ -32,6 +35,7 @@ export const LogLine = ({
wrapLogMessage,
}: Props) => {
const logLineRef = useRef(null);
+ const pinned = useLogIsPinned(log);
useEffect(() => {
if (!onOverflow || !logLineRef.current) {
@@ -45,7 +49,12 @@ export const LogLine = ({
}, [index, log.uid, onOverflow, style.height]);
return (
-
+
+
@@ -57,7 +66,7 @@ interface LogProps {
displayedFields: string[];
log: LogListModel;
showTime: boolean;
- styles: ReturnType
;
+ styles: LogLineStyles;
}
const Log = ({ displayedFields, log, showTime, styles }: LogProps) => {
@@ -67,7 +76,7 @@ const Log = ({ displayedFields, log, showTime, styles }: LogProps) => {
{log.displayLevel}
{displayedFields.length > 0 ? (
displayedFields.map((field) => (
-
+
{getDisplayedFieldValue(field, log)}
))
@@ -111,6 +120,9 @@ export const getStyles = (theme: GrafanaTheme2) => {
return {
logLine: css({
color: theme.colors.text.primary,
+ display: 'flex',
+ gap: theme.spacing(0.5),
+ flexDirection: 'row',
fontFamily: theme.typography.fontFamilyMonospace,
fontSize: theme.typography.fontSize,
wordBreak: 'break-all',
@@ -129,6 +141,14 @@ export const getStyles = (theme: GrafanaTheme2) => {
},
},
}),
+ pinnedLogLine: css({
+ backgroundColor: tinycolor(theme.colors.info.transparent).setAlpha(0.25).toString(),
+ }),
+ menuIcon: css({
+ height: getLineHeight(),
+ margin: 0,
+ padding: theme.spacing(0, 0, 0, 0.5),
+ }),
logLineMessage: css({
fontFamily: theme.typography.fontFamily,
textAlign: 'center',
diff --git a/public/app/features/logs/components/panel/LogLineMenu.test.tsx b/public/app/features/logs/components/panel/LogLineMenu.test.tsx
new file mode 100644
index 00000000000..27c8b78f9b9
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogLineMenu.test.tsx
@@ -0,0 +1,94 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { createTheme } from '@grafana/data';
+
+import { createLogLine } from '../__mocks__/logRow';
+
+import { getStyles } from './LogLine';
+import { LogLineMenu } from './LogLineMenu';
+import { LogListContext } from './LogListContext';
+import { LogListModel } from './processing';
+
+const theme = createTheme();
+const styles = getStyles(theme);
+
+describe('LogLineMenu', () => {
+ let log: LogListModel;
+ beforeEach(() => {
+ log = createLogLine({ labels: { place: 'luna' }, rowId: '1' });
+ });
+
+ test('Renders the component', async () => {
+ render();
+ expect(screen.queryByText('Copy log line')).not.toBeInTheDocument();
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ expect(screen.getByText('Copy log line')).toBeInTheDocument();
+ });
+
+ describe('Options', () => {
+ test('Allows to copy a permalink', async () => {
+ const onPermalinkClick = jest.fn();
+ render(
+
+
+
+ );
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ await userEvent.click(screen.getByText('Copy link to log line'));
+ expect(onPermalinkClick).toHaveBeenCalledTimes(1);
+ });
+
+ test('Allows to open show context', async () => {
+ const onOpenContext = jest.fn();
+ const logSupportsContext = jest.fn().mockReturnValue(true);
+ const getRowContextQuery = jest.fn();
+ render(
+
+
+
+ );
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ await userEvent.click(screen.getByText('Show context'));
+ expect(onOpenContext).toHaveBeenCalledTimes(1);
+ });
+
+ test('Uses logSupportsContext to control the display of show context', async () => {
+ const onOpenContext = jest.fn();
+ const logSupportsContext = jest.fn().mockReturnValue(false);
+ const getRowContextQuery = jest.fn();
+ render(
+
+
+
+ );
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ expect(screen.queryByText('Show context')).not.toBeInTheDocument();
+ });
+
+ test('Allows to pin log line', async () => {
+ const onPinLine = jest.fn();
+ render(
+
+
+
+ );
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ await userEvent.click(screen.getByText('Pin log'));
+ expect(onPinLine).toHaveBeenCalledTimes(1);
+ });
+
+ test('Allows to unpin log line', async () => {
+ const onUnpinLine = jest.fn();
+ render(
+
+
+
+ );
+ await userEvent.click(screen.getByLabelText('Log menu'));
+ expect(screen.queryByText('Pin log')).not.toBeInTheDocument();
+ await userEvent.click(screen.getByText('Unpin log'));
+ expect(onUnpinLine).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/public/app/features/logs/components/panel/LogLineMenu.tsx b/public/app/features/logs/components/panel/LogLineMenu.tsx
new file mode 100644
index 00000000000..b10c2f17ec6
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogLineMenu.tsx
@@ -0,0 +1,102 @@
+import { useCallback, useMemo, useRef, MouseEvent } from 'react';
+
+import { LogRowContextOptions, LogRowModel } from '@grafana/data';
+import { DataQuery } from '@grafana/schema';
+import { Dropdown, IconButton, Menu } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
+
+import { copyText, handleOpenLogsContextClick } from '../../utils';
+
+import { LogLineStyles } from './LogLine';
+import { useLogIsPinned, useLogListContext } from './LogListContext';
+import { LogListModel } from './processing';
+
+export type GetRowContextQueryFn = (
+ row: LogRowModel,
+ options?: LogRowContextOptions,
+ cacheFilters?: boolean
+) => Promise;
+
+interface Props {
+ log: LogListModel;
+ styles: LogLineStyles;
+}
+
+export const LogLineMenu = ({ log, styles }: Props) => {
+ const { getRowContextQuery, onOpenContext, onPermalinkClick, onPinLine, onUnpinLine, logSupportsContext } =
+ useLogListContext();
+ const pinned = useLogIsPinned(log);
+ const menuRef = useRef(null);
+
+ const copyLogLine = useCallback(() => {
+ copyText(log.entry, menuRef);
+ }, [log.entry]);
+
+ const copyLinkToLogLine = useCallback(() => {
+ onPermalinkClick?.(log);
+ }, [log, onPermalinkClick]);
+
+ const shouldlogSupportsContext = useMemo(
+ () => (logSupportsContext ? logSupportsContext(log) : false),
+ [log, logSupportsContext]
+ );
+
+ const showContext = useCallback(
+ async (event: MouseEvent) => {
+ handleOpenLogsContextClick(event, log, getRowContextQuery, (log: LogRowModel) => onOpenContext?.(log, () => {}));
+ },
+ [onOpenContext, getRowContextQuery, log]
+ );
+
+ const togglePinning = useCallback(() => {
+ if (pinned) {
+ onUnpinLine?.(log);
+ } else {
+ onPinLine?.(log);
+ }
+ }, [log, onPinLine, onUnpinLine, pinned]);
+
+ const menu = useCallback(
+ () => (
+
+ ),
+ [
+ copyLinkToLogLine,
+ copyLogLine,
+ log.rowId,
+ log.uid,
+ onPermalinkClick,
+ onPinLine,
+ onUnpinLine,
+ pinned,
+ shouldlogSupportsContext,
+ showContext,
+ togglePinning,
+ ]
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/public/app/features/logs/components/panel/LogLineMessage.test.tsx b/public/app/features/logs/components/panel/LogLineMessage.test.tsx
new file mode 100644
index 00000000000..0034dac14aa
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogLineMessage.test.tsx
@@ -0,0 +1,32 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { createTheme } from '@grafana/data';
+
+import { getStyles } from './LogLine';
+import { LogLineMessage } from './LogLineMessage';
+
+const theme = createTheme();
+const styles = getStyles(theme);
+
+describe('LogLineMessage', () => {
+ test('Renders a log line message', () => {
+ render(
+
+ Message
+
+ );
+ expect(screen.getByText('Message')).toBeInTheDocument();
+ });
+
+ test('Renders a button with the message', async () => {
+ const handleClick = jest.fn();
+ render(
+
+ Message
+
+ );
+ await userEvent.click(screen.getByText('Message'));
+ expect(handleClick).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/public/app/features/logs/components/panel/LogList.tsx b/public/app/features/logs/components/panel/LogList.tsx
index abc48cbe9ce..ae8234bd060 100644
--- a/public/app/features/logs/components/panel/LogList.tsx
+++ b/public/app/features/logs/components/panel/LogList.tsx
@@ -14,10 +14,12 @@ import {
LogsSortOrder,
TimeRange,
} from '@grafana/data';
-import { useTheme2 } from '@grafana/ui';
+import { PopoverContent, useTheme2 } from '@grafana/ui';
import { InfiniteScroll } from './InfiniteScroll';
import { getGridTemplateColumns } from './LogLine';
+import { GetRowContextQueryFn } from './LogLineMenu';
+import { LogListContext } from './LogListContext';
import { preProcessLogs, LogListModel, calculateFieldDimensions, LogFieldDimension } from './processing';
import {
getLogLineSize,
@@ -36,9 +38,17 @@ interface Props {
eventBus: EventBus;
forceEscape?: boolean;
getFieldLinks?: GetFieldLinksFn;
+ getRowContextQuery?: GetRowContextQueryFn;
initialScrollPosition?: 'top' | 'bottom';
loadMore?: (range: AbsoluteTimeRange) => void;
logs: LogRowModel[];
+ logSupportsContext?: (row: LogRowModel) => boolean;
+ onPermalinkClick?: (row: LogRowModel) => Promise;
+ onPinLine?: (row: LogRowModel) => void;
+ onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
+ onUnpinLine?: (row: LogRowModel) => void;
+ pinLineButtonTooltipTitle?: PopoverContent;
+ pinnedLogs?: string[];
showTime: boolean;
sortOrder: LogsSortOrder;
timeRange: TimeRange;
@@ -61,6 +71,7 @@ export const LogList = ({
timeRange,
timeZone,
wrapLogMessage,
+ ...logListContext
}: Props) => {
const [processedLogs, setProcessedLogs] = useState([]);
const [listHeight, setListHeight] = useState(
@@ -134,40 +145,42 @@ export const LogList = ({
}
return (
-
- {({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
-
- {Renderer}
-
- )}
-
+
+
+ {({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
+
+ {Renderer}
+
+ )}
+
+
);
};
diff --git a/public/app/features/logs/components/panel/LogListContext.test.tsx b/public/app/features/logs/components/panel/LogListContext.test.tsx
new file mode 100644
index 00000000000..3505c554797
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogListContext.test.tsx
@@ -0,0 +1,46 @@
+import { renderHook } from '@testing-library/react';
+import { ReactNode } from 'react';
+
+import { createLogLine } from '../__mocks__/logRow';
+
+import { useLogListContextData, useLogListContext, useLogIsPinned, LogListContext } from './LogListContext';
+
+const log = createLogLine({ rowId: 'yep' });
+const value = {
+ getRowContextQuery: jest.fn(),
+ logSupportsContext: jest.fn(),
+ onPermalinkClick: jest.fn(),
+ onPinLine: jest.fn(),
+ onOpenContext: jest.fn(),
+ onUnpinLine: jest.fn(),
+ pinLineButtonTooltipTitle: 'test',
+ pinnedLogs: ['yep'],
+};
+const wrapper = ({ children }: { children: ReactNode }) => (
+ {children}
+);
+
+test('Provides the Log List Context data', () => {
+ const { result } = renderHook(() => useLogListContext(), { wrapper });
+
+ expect(result.current).toEqual(value);
+});
+
+test('Allows to access context attributes', () => {
+ const { result } = renderHook(() => useLogListContextData('pinnedLogs'), { wrapper });
+
+ expect(result.current).toEqual(value.pinnedLogs);
+});
+
+test('Allows to tell if a log is pinned', () => {
+ const { result } = renderHook(() => useLogIsPinned(log), { wrapper });
+
+ expect(result.current).toBe(true);
+});
+
+test('Allows to tell if a log is pinned', () => {
+ const otherLog = createLogLine({ rowId: 'nope' });
+ const { result } = renderHook(() => useLogIsPinned(otherLog), { wrapper });
+
+ expect(result.current).toBe(false);
+});
diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx
new file mode 100644
index 00000000000..9f654cf4dce
--- /dev/null
+++ b/public/app/features/logs/components/panel/LogListContext.tsx
@@ -0,0 +1,33 @@
+import { createContext, useContext } from 'react';
+
+import { LogRowModel } from '@grafana/data';
+import { PopoverContent } from '@grafana/ui';
+
+import { GetRowContextQueryFn } from './LogLineMenu';
+
+export interface LogListContextData {
+ getRowContextQuery?: GetRowContextQueryFn;
+ logSupportsContext?: (row: LogRowModel) => boolean;
+ onPermalinkClick?: (row: LogRowModel) => Promise;
+ onPinLine?: (row: LogRowModel) => void;
+ onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
+ onUnpinLine?: (row: LogRowModel) => void;
+ pinLineButtonTooltipTitle?: PopoverContent;
+ pinnedLogs?: string[];
+}
+
+export const LogListContext = createContext({});
+
+export const useLogListContextData = (key: keyof LogListContextData) => {
+ const data: LogListContextData = useContext(LogListContext);
+ return data[key];
+};
+
+export const useLogListContext = (): LogListContextData => {
+ return useContext(LogListContext);
+};
+
+export const useLogIsPinned = (log: LogRowModel) => {
+ const { pinnedLogs } = useContext(LogListContext);
+ return pinnedLogs?.some((logId) => logId === log.rowId);
+};
diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts
index 2d62db31370..ef2c2c81b12 100644
--- a/public/app/features/logs/components/panel/processing.ts
+++ b/public/app/features/logs/components/panel/processing.ts
@@ -20,7 +20,7 @@ export interface LogFieldDimension {
width: number;
}
-interface PreProcessOptions {
+export interface PreProcessOptions {
escape: boolean;
getFieldLinks?: GetFieldLinksFn;
order: LogsSortOrder;
diff --git a/public/app/features/logs/components/panel/virtualization.ts b/public/app/features/logs/components/panel/virtualization.ts
index a367b41c050..873e7555c58 100644
--- a/public/app/features/logs/components/panel/virtualization.ts
+++ b/public/app/features/logs/components/panel/virtualization.ts
@@ -8,10 +8,13 @@ let gridSize = 8;
let paddingBottom = gridSize * 0.75;
let lineHeight = 22;
let measurementMode: 'canvas' | 'dom' = 'canvas';
+const iconWidth = 24;
// Controls the space between fields in the log line, timestamp, level, displayed fields, and log line body
export const FIELD_GAP_MULTIPLIER = 1.5;
+export const getLineHeight = () => lineHeight;
+
export function init(theme: GrafanaTheme2) {
const font = `${theme.typography.fontSize}px ${theme.typography.fontFamilyMonospace}`;
const letterSpacing = theme.typography.body.letterSpacing;
@@ -193,7 +196,7 @@ export function hasUnderOrOverflow(element: HTMLDivElement, calculatedHeight?: n
if (element.scrollHeight > height) {
return element.scrollHeight;
}
- const child = element.firstChild;
+ const child = element.children[1];
if (child instanceof HTMLDivElement && child.clientHeight < height) {
return child.clientHeight;
}
@@ -203,7 +206,7 @@ export function hasUnderOrOverflow(element: HTMLDivElement, calculatedHeight?: n
const scrollBarWidth = getScrollbarWidth();
export function getLogContainerWidth(container: HTMLDivElement) {
- return container.clientWidth - scrollBarWidth;
+ return container.clientWidth - scrollBarWidth - iconWidth;
}
export function getScrollbarWidth() {
diff --git a/public/app/features/logs/utils.ts b/public/app/features/logs/utils.ts
index c4a19c0aa6c..b55e2f7a3a2 100644
--- a/public/app/features/logs/utils.ts
+++ b/public/app/features/logs/utils.ts
@@ -1,4 +1,5 @@
import { countBy, chain } from 'lodash';
+import { MouseEvent } from 'react';
import {
LogLevel,
@@ -15,9 +16,14 @@ import {
LogsVolumeType,
NumericLogLevel,
getFieldDisplayName,
+ getDefaultTimeRange,
+ locationUtil,
+ urlUtil,
} from '@grafana/data';
+import { getConfig } from 'app/core/config';
import { getDataframeFields } from './components/logParser';
+import { GetRowContextQueryFn } from './components/panel/LogLineMenu';
/**
* Returns the log level of a log line.
@@ -303,6 +309,34 @@ export const copyText = async (text: string, buttonRef: React.MutableRefObject,
+ row: LogRowModel,
+ getRowContextQuery: GetRowContextQueryFn | undefined,
+ onOpenContext: (row: LogRowModel) => void
+) {
+ // if ctrl or meta key is pressed, open query in new Explore tab
+ if (getRowContextQuery && (event.nativeEvent.ctrlKey || event.nativeEvent.metaKey || event.nativeEvent.shiftKey)) {
+ const win = window.open('about:blank');
+ // for this request we don't want to use the cached filters from a context provider, but always want to refetch and clear
+ const query = await getRowContextQuery(row, undefined, false);
+ if (query && win) {
+ const url = urlUtil.renderUrl(locationUtil.assureBaseUrl(`${getConfig().appSubUrl}explore`), {
+ left: JSON.stringify({
+ datasource: query.datasource,
+ queries: [query],
+ range: getDefaultTimeRange(),
+ }),
+ });
+ win.location = url;
+
+ return;
+ }
+ win?.close();
+ }
+ onOpenContext(row);
+}
+
export function getLogLevelInfo(dataFrame: DataFrame, allDataFrames: DataFrame[]) {
const fieldCache = new FieldCache(dataFrame);
const timeField = fieldCache.getFirstFieldOfType(FieldType.time);
diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
index 98e962b61e0..ef87c5c3782 100644
--- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
+++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts
@@ -272,6 +272,7 @@ export type AnnotationPermission = {
};
export type DashboardMeta = {
annotationsPermissions?: AnnotationPermission;
+ apiVersion?: string;
canAdmin?: boolean;
canDelete?: boolean;
canEdit?: boolean;
diff --git a/public/app/features/playlist/PlaylistSrv.test.ts b/public/app/features/playlist/PlaylistSrv.test.ts
index 687b5e39156..3620f3a1e02 100644
--- a/public/app/features/playlist/PlaylistSrv.test.ts
+++ b/public/app/features/playlist/PlaylistSrv.test.ts
@@ -1,4 +1,3 @@
-// @ts-ignore
import { Store } from 'redux';
import configureMockStore from 'redux-mock-store';
@@ -139,4 +138,29 @@ describe('PlaylistSrv', () => {
expect((srv as any).validPlaylistUrl).toBe('/url/to/bbb');
expect(srv.state.isPlaying).toBe(true);
});
+
+ it('should replace playlist start page in history when starting playlist', async () => {
+ // Start at playlists page
+ locationService.push('/playlists');
+
+ // Navigate to playlist start page
+ locationService.push('/playlists/play/foo');
+
+ // Start the playlist
+ await srv.start('foo');
+
+ // Get history entries
+ const history = locationService.getHistory();
+ const entries = (history as unknown as { entries: Location[] }).entries;
+
+ // The current entry should be the first dashboard
+ expect(entries[entries.length - 1].pathname).toBe('/url/to/aaa');
+
+ // The previous entry should be the playlists page, not the start page
+ expect(entries[entries.length - 2].pathname).toBe('/playlists');
+
+ // Verify the start page (/playlists/play/foo) is not in history
+ const hasStartPage = entries.some((entry: { pathname: string }) => entry.pathname === '/playlists/play/foo');
+ expect(hasStartPage).toBe(false);
+ });
});
diff --git a/public/app/features/playlist/PlaylistSrv.ts b/public/app/features/playlist/PlaylistSrv.ts
index d11f817a369..99b839c857c 100644
--- a/public/app/features/playlist/PlaylistSrv.ts
+++ b/public/app/features/playlist/PlaylistSrv.ts
@@ -39,6 +39,28 @@ export class PlaylistSrv extends StateManagerBase {
this.api = getPlaylistAPI();
}
+ private navigateToDashboard(replaceHistoryEntry = false) {
+ const url = this.urls[this.index];
+ const queryParams = locationService.getSearchObject();
+ const filteredParams = pickBy(queryParams, (value: unknown, key: string) => queryParamsToPreserve[key]);
+ const nextDashboardUrl = locationUtil.stripBaseFromUrl(url);
+
+ this.index++;
+ this.validPlaylistUrl = nextDashboardUrl;
+ this.nextTimeoutId = setTimeout(() => this.next(), this.interval);
+
+ const urlWithParams = nextDashboardUrl + '?' + urlUtil.toUrlParams(filteredParams);
+
+ // When starting the playlist from the PlaylistStartPage component using the playlist URL, we want to replace the
+ // history entry to support the back button
+ // When starting the playlist from the playlist modal, we want to push a new history entry
+ if (replaceHistoryEntry) {
+ locationService.getHistory().replace(urlWithParams);
+ } else {
+ locationService.push(urlWithParams);
+ }
+ }
+
next() {
clearTimeout(this.nextTimeoutId);
@@ -55,16 +77,7 @@ export class PlaylistSrv extends StateManagerBase {
this.index = 0;
}
- const url = this.urls[this.index];
- const queryParams = locationService.getSearchObject();
- const filteredParams = pickBy(queryParams, (value: unknown, key: string) => queryParamsToPreserve[key]);
- const nextDashboardUrl = locationUtil.stripBaseFromUrl(url);
-
- this.index++;
- this.validPlaylistUrl = nextDashboardUrl;
- this.nextTimeoutId = setTimeout(() => this.next(), this.interval);
-
- locationService.push(nextDashboardUrl + '?' + urlUtil.toUrlParams(filteredParams));
+ this.navigateToDashboard();
}
prev() {
@@ -115,7 +128,10 @@ export class PlaylistSrv extends StateManagerBase {
this.urls = urls;
this.setState({ isPlaying: true });
- this.next();
+
+ // Replace current history entry with first dashboard instead of pushing
+ // this is to avoid the back button to go back to the playlist start page which causes a redirection
+ this.navigateToDashboard(true);
return;
}
diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
index 4205371364c..ea6526f0edf 100644
--- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
+++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
@@ -112,7 +112,7 @@ export function InstallControlsButton({
const onUpdate = async () => {
reportInteraction(PLUGIN_UPDATE_INTERACTION_EVENT_NAME, trackingProps);
- await install(plugin.id, latestCompatibleVersion?.version, true);
+ await install(plugin.id, latestCompatibleVersion?.version, PluginStatus.UPDATE);
if (!errorInstalling) {
appEvents.emit(AppEvents.alertSuccess, [`Updated ${plugin.name}`]);
}
diff --git a/public/app/features/plugins/admin/components/UpdateAllModal.tsx b/public/app/features/plugins/admin/components/UpdateAllModal.tsx
index f12113499b9..7830b1b253e 100644
--- a/public/app/features/plugins/admin/components/UpdateAllModal.tsx
+++ b/public/app/features/plugins/admin/components/UpdateAllModal.tsx
@@ -5,7 +5,7 @@ import { ConfirmModal } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useInstall, useInstallStatus } from '../state/hooks';
-import { CatalogPlugin } from '../types';
+import { CatalogPlugin, PluginStatus } from '../types';
import { UpdateModalBody } from './UpdateAllModalBody';
const PLUGINS_UPDATE_ALL_INTERACTION_EVENT_NAME = 'plugins_update_all_clicked';
@@ -100,13 +100,13 @@ export const UpdateAllModal = ({ isOpen, onDismiss, isLoading, plugins }: Props)
if (config.pluginAdminExternalManageEnabled) {
for (let plugin of plugins) {
if (selectedPlugins?.has(plugin.id)) {
- await install(plugin.id, plugin.latestVersion, true);
+ await install(plugin.id, plugin.latestVersion, PluginStatus.UPDATE);
}
}
} else {
plugins.forEach((plugin) => {
if (selectedPlugins?.has(plugin.id)) {
- install(plugin.id, plugin.latestVersion, true);
+ install(plugin.id, plugin.latestVersion, PluginStatus.UPDATE);
}
});
}
diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.tsx
index f0bec011875..7792ffc06a8 100644
--- a/public/app/features/plugins/admin/components/VersionInstallButton.tsx
+++ b/public/app/features/plugins/admin/components/VersionInstallButton.tsx
@@ -9,17 +9,10 @@ import { t } from 'app/core/internationalization';
import { isPreinstalledPlugin } from '../helpers';
import { useInstall } from '../state/hooks';
-import { Version } from '../types';
+import { PluginStatus, Version } from '../types';
const PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME = 'plugins_upgrade_clicked';
const PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME = 'plugins_downgrade_clicked';
-
-enum InstallState {
- INSTALL = 'Install',
- UPGRADE = 'Upgrade',
- DOWNGRADE = 'Downgrade',
-}
-
interface Props {
pluginId: string;
version: Version;
@@ -67,7 +60,7 @@ export const VersionInstallButton = ({
schema_version: '1.0.0',
};
- if (installState === InstallState.UPGRADE) {
+ if (installState === PluginStatus.UPDATE) {
reportInteraction(PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME, trackProps);
} else {
reportInteraction(PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME, {
@@ -76,13 +69,13 @@ export const VersionInstallButton = ({
});
}
- install(pluginId, version.version, true);
+ install(pluginId, version.version, installState);
setIsInstalling(true);
onConfirmInstallation();
};
const onInstallClick = () => {
- if (installState === InstallState.DOWNGRADE) {
+ if (installState === PluginStatus.DOWNGRADE) {
setIsModalOpen(true);
} else {
performInstallation();
@@ -115,7 +108,8 @@ export const VersionInstallButton = ({
tooltip={tooltip}
tooltipPlacement="bottom-start"
>
- {installState} {isInstalling ? : getIcon(installState)}
+ {getLabel(installState)}{' '}
+ {isInstalling ? : getIcon(installState)}
;
}
- if (installState === InstallState.UPGRADE) {
+ if (installState === PluginStatus.UPDATE) {
return ;
}
return '';
}
-function getInstallState(installedVersion?: string, version?: string): InstallState {
+function getInstallState(installedVersion?: string, version?: string): PluginStatus {
if (!installedVersion || !version || !valid(installedVersion) || !valid(version)) {
- return InstallState.INSTALL;
+ return PluginStatus.INSTALL;
}
- return gt(installedVersion, version) ? InstallState.DOWNGRADE : InstallState.UPGRADE;
+ return gt(installedVersion, version) ? PluginStatus.DOWNGRADE : PluginStatus.UPDATE;
}
-function getButtonHiddenState(installState: InstallState, isPreinstalled: { found: boolean; withVersion: boolean }) {
+function getButtonHiddenState(installState: PluginStatus, isPreinstalled: { found: boolean; withVersion: boolean }) {
// Default state for initial install
- if (installState === InstallState.INSTALL) {
+ if (installState === PluginStatus.INSTALL) {
return false;
}
// Handle downgrade case
- if (installState === InstallState.DOWNGRADE) {
+ if (installState === PluginStatus.DOWNGRADE) {
return isPreinstalled.found && Boolean(config.featureToggles.preinstallAutoUpdate);
}
diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts
index eab93de18fa..e08e7241c42 100644
--- a/public/app/features/plugins/admin/state/actions.ts
+++ b/public/app/features/plugins/admin/state/actions.ts
@@ -19,7 +19,7 @@ import {
} from '../api';
import { STATE_PREFIX } from '../constants';
import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers';
-import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin } from '../types';
+import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin, PluginStatus } from '../types';
// Fetches
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
@@ -188,17 +188,23 @@ export const install = createAsyncThunk<
{
id: string;
version?: string;
- isUpdating?: boolean;
+ installType?: PluginStatus;
}
->(`${STATE_PREFIX}/install`, async ({ id, version, isUpdating = false }, thunkApi) => {
- const changes = isUpdating
- ? { isInstalled: true, installedVersion: version, hasUpdate: false }
- : { isInstalled: true, installedVersion: version };
+>(`${STATE_PREFIX}/install`, async ({ id, version, installType = PluginStatus.INSTALL }, thunkApi) => {
+ const changes: Partial = { isInstalled: true, installedVersion: version };
+
+ if (installType === PluginStatus.UPDATE) {
+ changes.hasUpdate = false;
+ }
+ if (installType === PluginStatus.DOWNGRADE) {
+ changes.hasUpdate = true;
+ }
+
try {
await installPlugin(id, version);
await updatePanels();
- if (isUpdating) {
+ if (installType !== PluginStatus.INSTALL) {
invalidatePluginInCache(id);
}
diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts
index da0ba859a3a..457f2f1d73e 100644
--- a/public/app/features/plugins/admin/state/hooks.ts
+++ b/public/app/features/plugins/admin/state/hooks.ts
@@ -4,7 +4,7 @@ import { PluginError, PluginType } from '@grafana/data';
import { useDispatch, useSelector } from 'app/types';
import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers';
-import { CatalogPlugin } from '../types';
+import { CatalogPlugin, PluginStatus } from '../types';
import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions';
import {
@@ -64,7 +64,7 @@ export const useGetErrors = (filterByPluginType?: PluginType): PluginError[] =>
export const useInstall = () => {
const dispatch = useDispatch();
- return (id: string, version?: string, isUpdating?: boolean) => dispatch(install({ id, version, isUpdating }));
+ return (id: string, version?: string, installType?: PluginStatus) => dispatch(install({ id, version, installType }));
};
export const useUnsetInstall = () => {
diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts
index dd664dcc397..0df5baa5a13 100644
--- a/public/app/features/plugins/admin/types.ts
+++ b/public/app/features/plugins/admin/types.ts
@@ -256,6 +256,7 @@ export enum PluginStatus {
UNINSTALL = 'UNINSTALL',
UPDATE = 'UPDATE',
REINSTALL = 'REINSTALL',
+ DOWNGRADE = 'DOWNGRADE',
}
export enum PluginTabLabels {
diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx
index c3e406d5d23..e453b306d84 100644
--- a/public/app/features/plugins/extensions/usePluginComponents.tsx
+++ b/public/app/features/plugins/extensions/usePluginComponents.tsx
@@ -1,7 +1,12 @@
import { useMemo } from 'react';
import { useObservable } from 'react-use';
-import { PluginExtensionComponentMeta, PluginExtensionTypes, usePluginContext } from '@grafana/data';
+import {
+ type ComponentTypeWithExtensionMeta,
+ type PluginExtensionComponentMeta,
+ PluginExtensionTypes,
+ usePluginContext,
+} from '@grafana/data';
import { UsePluginComponentsOptions, UsePluginComponentsResult } from '@grafana/runtime';
import { useAddedComponentsRegistry } from './ExtensionRegistriesContext';
@@ -25,7 +30,7 @@ export function usePluginComponents({
return useMemo(() => {
// For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana.
const enableRestrictions = isGrafanaDevMode() && pluginContext;
- const components: Array & { meta: PluginExtensionComponentMeta }> = [];
+ const components: Array> = [];
const extensionsByPlugin: Record = {};
const pluginId = pluginContext?.meta.id ?? '';
const pointLog = log.child({
@@ -84,7 +89,7 @@ export function usePluginComponents({
export function createComponentWithMeta(
registryItem: AddedComponentRegistryItem,
extensionPointId: string
-): React.ComponentType & { meta: PluginExtensionComponentMeta } {
+): ComponentTypeWithExtensionMeta {
const { component: Component, ...config } = registryItem;
function ComponentWithMeta(props: Props) {
return ;
diff --git a/public/app/features/plugins/loader/sharedDependencies.ts b/public/app/features/plugins/loader/sharedDependencies.ts
index 7059274c717..c294491c6ac 100644
--- a/public/app/features/plugins/loader/sharedDependencies.ts
+++ b/public/app/features/plugins/loader/sharedDependencies.ts
@@ -52,6 +52,7 @@ export const sharedDependenciesMap = {
'@grafana/runtime': grafanaRuntime,
'@grafana/slate-react': () => import('slate-react'),
'@grafana/ui': grafanaUI,
+ '@grafana/ui/unstable': () => import('@grafana/ui/src/unstable'),
'@kusto/monaco-kusto': () => import('@kusto/monaco-kusto'),
'app/core/app_events': {
default: appEvents,
diff --git a/public/app/features/profile/UserProfileEditPage.test.tsx b/public/app/features/profile/UserProfileEditPage.test.tsx
index 9f0adc7cee1..83152dee2e1 100644
--- a/public/app/features/profile/UserProfileEditPage.test.tsx
+++ b/public/app/features/profile/UserProfileEditPage.test.tsx
@@ -1,25 +1,18 @@
-import { render, screen, waitFor, within } from '@testing-library/react';
+import { screen, waitFor, within } from '@testing-library/react';
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
+import { render } from 'test/test-utils';
-import { OrgRole, PluginExtensionComponent, PluginExtensionTypes } from '@grafana/data';
+import { type ComponentTypeWithExtensionMeta, OrgRole } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
-import { setPluginExtensionsHook, UsePluginExtensions } from '@grafana/runtime';
-import * as useQueryParams from 'app/core/hooks/useQueryParams';
+import { setPluginComponentsHook, usePluginComponents } from '@grafana/runtime';
-import { TestProvider } from '../../../test/helpers/TestProvider';
import { backendSrv } from '../../core/services/backend_srv';
+import { createComponentWithMeta } from '../plugins/extensions/usePluginComponents';
import { getMockTeam } from '../teams/__mocks__/teamMocks';
import { Props, UserProfileEditPage } from './UserProfileEditPage';
import { initialUserState } from './state/reducers';
-const mockUseQueryParams = useQueryParams as { useQueryParams: typeof useQueryParams.useQueryParams };
-
-jest.mock('app/core/hooks/useQueryParams', () => ({
- __esModule: true,
- useQueryParams: () => [{}],
-}));
-
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
getDashboardAPI: () => ({
getDashboardDTO: jest.fn().mockResolvedValue({}),
@@ -136,20 +129,23 @@ const _createTabName = (tab: ExtensionPointComponentTabs) => tab;
const _createTabContent = (tabId: ExtensionPointComponentId) => `this is settings for component ${tabId}`;
const generalTabName = 'General';
+const generalTestId = 'user-profile-edit-page';
const tabOneName = _createTabName(ExtensionPointComponentTabs.One);
const tabTwoName = _createTabName(ExtensionPointComponentTabs.Two);
const _createPluginExtensionPointComponent = (
id: ExtensionPointComponentId,
tab: ExtensionPointComponentTabs
-): PluginExtensionComponent => ({
- id,
- type: PluginExtensionTypes.component,
- title: _createTabName(tab),
- description: '', // description isn't used here..
- component: () => {_createTabContent(id)}
,
- pluginId: 'grafana-plugin',
-});
+): ComponentTypeWithExtensionMeta =>
+ createComponentWithMeta<{}>(
+ {
+ title: _createTabName(tab),
+ description: '', // description isn't used here..
+ component: () => {_createTabContent(id)}
,
+ pluginId: 'grafana-plugin',
+ },
+ id
+ );
const PluginExtensionPointComponent1 = _createPluginExtensionPointComponent(
ExtensionPointComponentId.One,
@@ -164,8 +160,8 @@ const PluginExtensionPointComponent3 = _createPluginExtensionPointComponent(
ExtensionPointComponentTabs.Two
);
-async function getTestContext(overrides: Partial = {}) {
- const extensions = overrides.extensions || [];
+async function getTestContext(overrides: Partial = {}) {
+ const components = overrides.components || [];
jest.clearAllMocks();
const putSpy = jest.spyOn(backendSrv, 'put');
@@ -174,18 +170,12 @@ async function getTestContext(overrides: Partial = jest
- .fn()
- .mockReturnValue({ extensions, isLoading: false });
+ const getter: typeof usePluginComponents = jest.fn().mockReturnValue({ components, isLoading: false });
- setPluginExtensionsHook(getter);
+ setPluginComponentsHook(getter);
const props = { ...defaultProps, ...overrides };
- const { rerender } = render(
-
-
-
- );
+ const { rerender } = render();
await waitFor(() => expect(props.initUserProfilePage).toHaveBeenCalledTimes(1));
@@ -334,7 +324,7 @@ describe('UserProfileEditPage', () => {
});
describe('and a plugin registers a component against the user profile settings extension point', () => {
- const extensions = [
+ const components = [
PluginExtensionPointComponent1,
PluginExtensionPointComponent2,
PluginExtensionPointComponent3,
@@ -347,7 +337,7 @@ describe('UserProfileEditPage', () => {
});
it('should group registered components into tabs', async () => {
- await getTestContext({ extensions });
+ await getTestContext({ components });
const { extensionPointTabs, extensionPointTab } = getSelectors();
const _assertTab = (tabId: string, isDefault = false) => {
@@ -363,10 +353,7 @@ describe('UserProfileEditPage', () => {
});
it('should change the active tab when a tab is clicked and update the "tab" query param', async () => {
- const mockUpdateQueryParams = jest.fn();
- mockUseQueryParams.useQueryParams = () => [{}, mockUpdateQueryParams];
-
- await getTestContext({ extensions });
+ await getTestContext({ components });
const { extensionPointTab } = getSelectors();
/**
@@ -378,26 +365,24 @@ describe('UserProfileEditPage', () => {
const tabTwoContent = _createTabContent(ExtensionPointComponentId.Three);
// "General" should be the default content
+ expect(screen.queryByTestId(generalTestId)).toBeInTheDocument();
expect(screen.queryByText(tabOneContent1)).toBeNull();
expect(screen.queryByText(tabOneContent2)).toBeNull();
expect(screen.queryByText(tabTwoContent)).toBeNull();
await userEvent.click(extensionPointTab(tabOneName.toLowerCase()));
- expect(mockUpdateQueryParams).toHaveBeenCalledTimes(1);
- expect(mockUpdateQueryParams).toHaveBeenCalledWith({ tab: tabOneName.toLowerCase() });
- expect(screen.queryByText(tabOneContent1)).not.toBeNull();
- expect(screen.queryByText(tabOneContent2)).not.toBeNull();
+ expect(screen.queryByTestId(generalTestId)).toBeNull();
+ expect(screen.queryByText(tabOneContent1)).toBeInTheDocument();
+ expect(screen.queryByText(tabOneContent2)).toBeInTheDocument();
expect(screen.queryByText(tabTwoContent)).toBeNull();
- mockUpdateQueryParams.mockClear();
await userEvent.click(extensionPointTab(tabTwoName.toLowerCase()));
- expect(mockUpdateQueryParams).toHaveBeenCalledTimes(1);
- expect(mockUpdateQueryParams).toHaveBeenCalledWith({ tab: tabTwoName.toLowerCase() });
+ expect(screen.queryByTestId(generalTestId)).toBeNull();
expect(screen.queryByText(tabOneContent1)).toBeNull();
expect(screen.queryByText(tabOneContent2)).toBeNull();
- expect(screen.queryByText(tabTwoContent)).not.toBeNull();
+ expect(screen.queryByText(tabTwoContent)).toBeInTheDocument();
});
});
});
diff --git a/public/app/features/profile/UserProfileEditPage.tsx b/public/app/features/profile/UserProfileEditPage.tsx
index 4de0ebee3d4..c5f84818cb2 100644
--- a/public/app/features/profile/UserProfileEditPage.tsx
+++ b/public/app/features/profile/UserProfileEditPage.tsx
@@ -1,31 +1,20 @@
-import { Fragment, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { useMount } from 'react-use';
-import { PluginExtensionComponent, PluginExtensionPoints } from '@grafana/data';
-import { selectors } from '@grafana/e2e-selectors';
-import { usePluginComponentExtensions } from '@grafana/runtime';
-import { Tab, TabsBar, TabContent, Stack } from '@grafana/ui';
+import { PluginExtensionPoints } from '@grafana/data';
+import { usePluginComponents } from '@grafana/runtime';
+import { Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences';
-import { useQueryParams } from 'app/core/hooks/useQueryParams';
-import { t } from 'app/core/internationalization';
import { StoreState } from 'app/types';
import UserOrganizations from './UserOrganizations';
import UserProfileEditForm from './UserProfileEditForm';
+import { UserProfileEditTabs } from './UserProfileEditTabs';
import UserSessions from './UserSessions';
import { UserTeams } from './UserTeams';
import { changeUserOrg, initUserProfilePage, revokeUserSession, updateUserProfile } from './state/actions';
-const TAB_QUERY_PARAM = 'tab';
-const GENERAL_SETTINGS_TAB = 'general';
-
-type TabInfo = {
- id: string;
- title: string;
-};
-
export interface OwnProps {}
function mapStateToProps(state: StoreState) {
@@ -68,95 +57,27 @@ export function UserProfileEditPage({
changeUserOrg,
updateUserProfile,
}: Props) {
- const [queryParams, updateQueryParams] = useQueryParams();
- const tabQueryParam = queryParams[TAB_QUERY_PARAM];
- const [activeTab, setActiveTab] = useState(
- typeof tabQueryParam === 'string' ? tabQueryParam : GENERAL_SETTINGS_TAB
- );
-
useMount(() => initUserProfilePage());
- const { extensions } = usePluginComponentExtensions({ extensionPointId: PluginExtensionPoints.UserProfileTab });
-
- const groupedExtensionComponents = extensions.reduce>((acc, extension) => {
- const { title } = extension;
- if (acc[title]) {
- acc[title].push(extension);
- } else {
- acc[title] = [extension];
- }
- return acc;
- }, {});
-
- const convertExtensionComponentTitleToTabId = (title: string) => title.toLowerCase();
-
- const showTabs = extensions.length > 0;
- const tabs: TabInfo[] = [
- {
- id: GENERAL_SETTINGS_TAB,
- title: t('user-profile.tabs.general', 'General'),
- },
- ...Object.keys(groupedExtensionComponents).map((title) => ({
- id: convertExtensionComponentTitleToTabId(title),
- title,
- })),
- ];
-
- const UserProfile = () => (
-
-
-
-
-
-
-
-
-
- );
-
- const UserProfileWithTabs = () => (
-
-
-
- {tabs.map(({ id, title }) => {
- return (
- {
- setActiveTab(id);
- updateQueryParams({ [TAB_QUERY_PARAM]: id });
- }}
- data-testid={selectors.components.UserProfile.extensionPointTab(id)}
- />
- );
- })}
-
-
- {activeTab === GENERAL_SETTINGS_TAB && }
- {Object.entries(groupedExtensionComponents).map(([title, pluginExtensionComponents]) => {
- const tabId = convertExtensionComponentTitleToTabId(title);
-
- if (activeTab === tabId) {
- return (
-
- {pluginExtensionComponents.map(({ component: Component }, index) => (
-
- ))}
-
- );
- }
- return null;
- })}
-
-
-
- );
+ const { components, isLoading } = usePluginComponents({
+ extensionPointId: PluginExtensionPoints.UserProfileTab,
+ });
return (
- {showTabs ? : }
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/public/app/features/profile/UserProfileEditTabs.tsx b/public/app/features/profile/UserProfileEditTabs.tsx
new file mode 100644
index 00000000000..035e0cf01e3
--- /dev/null
+++ b/public/app/features/profile/UserProfileEditTabs.tsx
@@ -0,0 +1,111 @@
+import React, { type ComponentType, Fragment, type ReactElement, useCallback, useMemo } from 'react';
+
+import { type ComponentTypeWithExtensionMeta, type UrlQueryValue } from '@grafana/data';
+import { selectors } from '@grafana/e2e-selectors';
+import { Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
+import { useQueryParams } from 'app/core/hooks/useQueryParams';
+import { t } from 'app/core/internationalization';
+
+const TAB_QUERY_PARAM = 'tab';
+const GENERAL_SETTINGS_TAB = 'general';
+
+type Props = {
+ children?: React.ReactNode;
+ components: ComponentTypeWithExtensionMeta[];
+};
+
+export function UserProfileEditTabs(props: Props): ReactElement {
+ const { children, components } = props;
+ const tabsById = useTabInfoById(components, children);
+ const [activeTab, setActiveTab] = useActiveTab(tabsById);
+ const showTabs = components.length > 0;
+
+ if (showTabs === false) {
+ return <>{children}>;
+ }
+
+ return (
+
+
+
+ {Object.values(tabsById).map(({ tabId, title }) => {
+ return (
+ setActiveTab(tabId)}
+ data-testid={selectors.components.UserProfile.extensionPointTab(tabId)}
+ />
+ );
+ })}
+
+
+ {Boolean(activeTab) && (
+
+ {activeTab?.components.map((Component, index) => )}
+
+ )}
+
+
+
+ );
+}
+
+type TabInfo = {
+ title: string;
+ tabId: string;
+ components: ComponentType[];
+};
+
+function useTabInfoById(components: Props['components'], general: React.ReactNode): Record {
+ return useMemo(() => {
+ const tabs: Record = {
+ [GENERAL_SETTINGS_TAB]: {
+ title: t('user-profile.tabs.general', 'General'),
+ tabId: GENERAL_SETTINGS_TAB,
+ components: [() => <>{general}>],
+ },
+ };
+
+ return components.reduce((acc, component) => {
+ const { title } = component.meta;
+ const tabId = convertTitleToTabId(title);
+
+ if (!acc[tabId]) {
+ acc[tabId] = {
+ title,
+ tabId,
+ components: [],
+ };
+ }
+
+ acc[tabId].components.push(component);
+ return acc;
+ }, tabs);
+ }, [components, general]);
+}
+
+function useActiveTab(tabs: Record): [TabInfo | undefined, (tabId: string) => void] {
+ const [queryParams, updateQueryParams] = useQueryParams();
+ const activeTabId = convertQueryParamToTabId(queryParams[TAB_QUERY_PARAM]);
+ const activeTab = tabs[activeTabId];
+
+ const setActiveTab = useCallback(
+ (tabId: string) => updateQueryParams({ [TAB_QUERY_PARAM]: tabId }),
+ [updateQueryParams]
+ );
+
+ return [activeTab, setActiveTab];
+}
+
+function convertQueryParamToTabId(queryParam: UrlQueryValue) {
+ if (typeof queryParam !== 'string') {
+ return GENERAL_SETTINGS_TAB;
+ }
+ return convertTitleToTabId(queryParam);
+}
+
+function convertTitleToTabId(title: string) {
+ return title.toLowerCase();
+}
diff --git a/public/app/features/visualization/data-hover/DataHoverView.tsx b/public/app/features/visualization/data-hover/DataHoverView.tsx
index adc593a9a14..79785c94eb2 100644
--- a/public/app/features/visualization/data-hover/DataHoverView.tsx
+++ b/public/app/features/visualization/data-hover/DataHoverView.tsx
@@ -23,6 +23,7 @@ export interface Props {
mode?: TooltipDisplayMode | null;
header?: string;
padding?: number;
+ maxHeight?: number;
}
export interface DisplayValue {
@@ -92,7 +93,16 @@ export function getDisplayValuesAndLinks(
return { displayValues, links };
}
-export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, header, padding = 0 }: Props) => {
+export const DataHoverView = ({
+ data,
+ rowIndex,
+ columnIndex,
+ sortOrder,
+ mode,
+ header,
+ padding = 0,
+ maxHeight,
+}: Props) => {
const styles = useStyles2(getStyles, padding);
if (!data || rowIndex == null) {
@@ -108,7 +118,7 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, he
const { displayValues, links } = dispValuesAndLinks;
if (header === 'Exemplar') {
- return ;
+ return ;
}
return (
diff --git a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
index b353b08abba..b2f17d17333 100644
--- a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
+++ b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx
@@ -11,10 +11,11 @@ export interface Props {
displayValues: DisplayValue[];
links?: LinkModel[];
header?: string;
+ maxHeight?: number;
}
-export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }: Props) => {
- const styles = useStyles2(getStyles);
+export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar', maxHeight }: Props) => {
+ const styles = useStyles2(getStyles, 0, maxHeight);
const time = displayValues.find((val) => val.name === 'Time');
displayValues = displayValues.filter((val) => val.name !== 'Time'); // time?
@@ -49,7 +50,7 @@ export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }:
);
};
-const getStyles = (theme: GrafanaTheme2, padding = 0) => {
+const getStyles = (theme: GrafanaTheme2, padding = 0, maxHeight?: number) => {
return {
exemplarWrapper: css({
display: 'flex',
@@ -79,6 +80,8 @@ const getStyles = (theme: GrafanaTheme2, padding = 0) => {
gap: 4,
borderTop: `1px solid ${theme.colors.border.medium}`,
padding: theme.spacing(1),
+ overflowY: 'auto',
+ maxHeight: maxHeight,
}),
exemplarFooter: css({
display: 'flex',
diff --git a/public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx b/public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx
index 51817507c8a..d0a05b262af 100644
--- a/public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx
+++ b/public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx
@@ -54,7 +54,7 @@ export const FitMapViewEditor = ({ labelWidth, value, onChange, context }: Props
const allLayersEditorFragment = (
-
+
);
diff --git a/public/app/plugins/panel/geomap/utils/getLayersExtent.ts b/public/app/plugins/panel/geomap/utils/getLayersExtent.ts
index 2ab535a61d7..ed5a46442d9 100644
--- a/public/app/plugins/panel/geomap/utils/getLayersExtent.ts
+++ b/public/app/plugins/panel/geomap/utils/getLayersExtent.ts
@@ -14,14 +14,19 @@ export function getLayersExtent(
return layers
.filter((l) => l.layer instanceof VectorLayer || l.layer instanceof LayerGroup || l.layer instanceof VectorImage)
.flatMap((ll) => {
+ const layerName = ll.options.name;
const l = ll.layer;
if (l instanceof LayerGroup) {
- return getLayerGroupExtent(l);
+ // If not all layers check for matching layer name
+ if (!allLayers && layerName !== layer) {
+ return [];
+ }
+ return getLayerGroupExtent(l, lastOnly);
} else if (l instanceof VectorLayer || l instanceof VectorImage) {
if (allLayers) {
// Return everything from all layers
return [l.getSource().getExtent()];
- } else if (lastOnly && layer === ll.options.name) {
+ } else if (lastOnly && layer === layerName) {
// Return last only for selected layer
const feat = l.getSource().getFeatures();
const featOfInterest = feat[feat.length - 1];
@@ -30,7 +35,7 @@ export function getLayersExtent(
return [geo.getExtent()];
}
return [];
- } else if (!lastOnly && layer === ll.options.name) {
+ } else if (!lastOnly && layer === layerName) {
// Return all points for selected layer
return [l.getSource().getExtent()];
}
@@ -42,13 +47,30 @@ export function getLayersExtent(
.reduce(extend, createEmpty());
}
-export function getLayerGroupExtent(lg: LayerGroup) {
+export function getLayerGroupExtent(lg: LayerGroup, lastOnly: boolean) {
return lg
.getLayers()
.getArray()
- .filter((l) => l instanceof VectorLayer)
+ .filter((l) => l instanceof VectorLayer || l instanceof VectorImage)
.map((l) => {
- if (l instanceof VectorLayer) {
+ if (l instanceof VectorLayer || l instanceof VectorImage) {
+ if (lastOnly) {
+ // Return last coordinate only
+ const feat = l.getSource().getFeatures();
+ const featOfInterest = feat[feat.length - 1];
+ const geo = featOfInterest?.getGeometry();
+ if (geo) {
+ // Look at flatCoordinates for more robust support including route layer
+ const flatCoordinates = geo.flatCoordinates;
+ const flatCoordinatesLength = flatCoordinates.length;
+ if (flatCoordinatesLength > 1) {
+ const lastX = flatCoordinates[flatCoordinatesLength - 2];
+ const lastY = flatCoordinates[flatCoordinatesLength - 1];
+ return [lastX, lastY, lastX, lastY];
+ }
+ }
+ return [];
+ }
return l.getSource().getExtent() ?? [];
} else {
return [];
diff --git a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
index 896dbeb1feb..d15b13cd50c 100644
--- a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
+++ b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx
@@ -56,6 +56,7 @@ export const HeatmapTooltip = (props: HeatmapTooltipProps) => {
rowIndex={props.dataIdxs[2]}
header={'Exemplar'}
padding={8}
+ maxHeight={props.maxHeight}
/>
);
}
diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx
index 6ec45e27d9a..ff3dfb38214 100644
--- a/public/app/plugins/panel/heatmap/module.tsx
+++ b/public/app/plugins/panel/heatmap/module.tsx
@@ -1,4 +1,4 @@
-import { FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data';
+import { DataFrame, FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
AxisPlacement,
@@ -442,7 +442,9 @@ export const plugin = new PanelPlugin(HeatmapPanel)
settings: {
integer: true,
},
- showIf: (options) => options.tooltip?.mode === TooltipDisplayMode.Multi,
+ showIf: (options: Options, data: DataFrame[] | undefined, annotations: DataFrame[] | undefined) =>
+ options.tooltip?.mode === TooltipDisplayMode.Multi ||
+ annotations?.some((df) => df.meta?.custom?.resultType === 'exemplar'),
});
category = ['Legend'];
@@ -459,6 +461,8 @@ export const plugin = new PanelPlugin(HeatmapPanel)
name: 'Color',
defaultValue: defaultOptions.exemplars.color,
category,
+ showIf: (options: Options, data: DataFrame[] | undefined, annotations: DataFrame[] | undefined) =>
+ annotations?.some((df) => df.meta?.custom?.resultType === 'exemplar'),
});
})
.setSuggestionsSupplier(new HeatmapSuggestionsSupplier())
diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx
index f8866b705f3..e4124886ce2 100644
--- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx
+++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx
@@ -159,6 +159,7 @@ export const TimeSeriesPanel = ({
config={uplotConfig}
exemplars={data.annotations}
timeZone={timeZone}
+ maxHeight={options.tooltip.maxHeight}
/>
)}
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
index 54e79e06743..f41e5e3af7a 100644
--- a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
+++ b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx
@@ -29,6 +29,7 @@ interface ExemplarMarkerProps {
exemplarColor?: string;
clickedExemplarFieldIndex: DataFrameFieldIndex | undefined;
setClickedExemplarFieldIndex: React.Dispatch;
+ maxHeight?: number;
}
export const ExemplarMarker = ({
@@ -39,6 +40,7 @@ export const ExemplarMarker = ({
exemplarColor,
clickedExemplarFieldIndex,
setClickedExemplarFieldIndex,
+ maxHeight,
}: ExemplarMarkerProps) => {
const styles = useStyles2(getExemplarMarkerStyles);
const [isOpen, setIsOpen] = useState(false);
@@ -163,7 +165,7 @@ export const ExemplarMarker = ({
return (
{isLocked && }
-
+
);
}, [
@@ -175,6 +177,7 @@ export const ExemplarMarker = ({
floatingStyles,
getFloatingProps,
refs.setFloating,
+ maxHeight,
]);
const seriesColor = config
diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
index c741f821f88..673f2288dc7 100644
--- a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
+++ b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx
@@ -18,9 +18,10 @@ interface ExemplarsPluginProps {
exemplars: DataFrame[];
timeZone: TimeZone;
visibleSeries?: VisibleExemplarLabels;
+ maxHeight?: number;
}
-export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries }: ExemplarsPluginProps) => {
+export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries, maxHeight }: ExemplarsPluginProps) => {
const plotInstance = useRef();
const [lockedExemplarFieldIndex, setLockedExemplarFieldIndex] = useState();
@@ -83,10 +84,11 @@ export const ExemplarsPlugin = ({ exemplars, timeZone, config, visibleSeries }:
dataFrameFieldIndex={dataFrameFieldIndex}
config={config}
exemplarColor={markerColor}
+ maxHeight={maxHeight}
/>
);
},
- [config, timeZone, visibleSeries, setLockedExemplarFieldIndex, lockedExemplarFieldIndex]
+ [config, timeZone, visibleSeries, setLockedExemplarFieldIndex, lockedExemplarFieldIndex, maxHeight]
);
return (
diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json
index 355610c995d..60ec10f7b67 100644
--- a/public/locales/de-DE/grafana.json
+++ b/public/locales/de-DE/grafana.json
@@ -209,6 +209,8 @@
"alerting": "",
"alerting-change-description": "",
"annotations": "",
+ "compare": "",
+ "compare-with-latest": "",
"compareVersions": "",
"comparing-versions": "",
"condition": "",
@@ -228,17 +230,22 @@
"provisioning": "",
"provisioning-change-description": "",
"queryAndAlertCondition": "",
- "reset": "",
"restore": "",
+ "restore-manually": "",
"restore-modal": {
"body": "",
+ "confirm": "",
+ "error": "",
"summary": "",
"title": ""
},
+ "restore-version": "",
"rule_group": "",
"unknown": "",
"unknown-change-description": "",
- "user-id": ""
+ "user-id": "",
+ "warning-restore-manually": "",
+ "warning-restore-manually-title": ""
},
"annotations": {
"description": "",
@@ -315,6 +322,10 @@
"empty-state": {
"title": ""
},
+ "key-value-map": {
+ "add": "",
+ "confirm-add": ""
+ },
"last-delivery-attempt": "",
"last-delivery-failed": "",
"no-contact-points-found": "",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": ""
- }
+ },
+ "repeat": {
+ "title": "",
+ "variable": {
+ "title": ""
+ }
+ },
+ "title": ""
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "",
+ "objects": "",
+ "panels": "",
+ "rows": "",
+ "tabs": ""
+ },
"objects": {
"multi-select": {
"selection-number": ""
}
},
"open": "",
- "panels": {
- "multi-select": {
- "selection-number": ""
- }
- },
"row": {
"header": {
"hide": "",
@@ -1146,11 +1166,30 @@
"rows": "Gesamtanzahl an Zeilen",
"table-title": "Statistiken"
},
+ "layout": {
+ "common": {
+ "copy": "",
+ "copy-or-duplicate": "",
+ "delete": "",
+ "duplicate": "",
+ "layout": "",
+ "panels-title": ""
+ }
+ },
"options": {
"description": "",
"title": "",
"title-option": ""
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "",
+ "empty": "",
+ "expand": ""
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "",
@@ -1161,6 +1200,12 @@
"description": "",
"item-options": {
"hide-no-data": "",
+ "repeat": {
+ "variable": {
+ "description": "",
+ "title": ""
+ }
+ },
"title": ""
},
"name": "",
@@ -1285,6 +1330,8 @@
"viz-panel": {
"options": {
"description": "",
+ "open-edit": "",
+ "plugin-type-image": "",
"title": "",
"title-option": "",
"transparent-background": ""
@@ -2224,6 +2271,14 @@
"log-line": "",
"no-details": ""
},
+ "log-line-menu": {
+ "copy-link": "",
+ "copy-log": "",
+ "icon-label": "",
+ "pin-to-outline": "",
+ "show-context": "",
+ "unpin-from-outline": ""
+ },
"log-row-message": {
"ellipsis": "",
"more": "",
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index b7ef10ac128..f0e4426b049 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -209,6 +209,8 @@
"alerting": "Alerting",
"alerting-change-description": "This update was made by the alerting system due to other changes. For example, when renaming a contact point that is used for simplified routing, this will update affected rules",
"annotations": "Annotations",
+ "compare": "Compare",
+ "compare-with-latest": "Compare with latest version",
"compareVersions": "Compare versions",
"comparing-versions": "Comparing versions",
"condition": "Alert condition",
@@ -228,17 +230,22 @@
"provisioning": "Provisioning",
"provisioning-change-description": "Version update was made via provisioning",
"queryAndAlertCondition": "Query and alert condition",
- "reset": "Reset to version {{version}}",
"restore": "Restore",
+ "restore-manually": "Your alert rule could not be restored. This may be due to changes to other entities such as contact points, data sources etc. Please manually restore the rule version",
"restore-modal": {
"body": "Are you sure you want to restore the alert rule definition to this version? All unsaved changes will be lost.",
+ "confirm": "Yes, restore configuration",
+ "error": "Could not restore alert rule version ",
"summary": "Summary of changes to be applied:",
- "title": "Restore Version"
+ "title": "Restore version"
},
+ "restore-version": "Restore to version {{version}}",
"rule_group": "Rule group",
"unknown": "Unknown",
"unknown-change-description": "This update was made prior to the implementation of alert rule version history. The user who made the change is not tracked, but future changes will include the user",
- "user-id": "User ID {{uid}}"
+ "user-id": "User ID {{uid}}",
+ "warning-restore-manually": "You are manually restoring an old version of this alert rule. Please review the changes carefully before saving the rule definition.",
+ "warning-restore-manually-title": "Restoring rule manually"
},
"annotations": {
"description": "Add more context to your alert notifications.",
@@ -315,6 +322,10 @@
"empty-state": {
"title": "You don't have any contact points yet"
},
+ "key-value-map": {
+ "add": "Add",
+ "confirm-add": "Confirm to add"
+ },
"last-delivery-attempt": "Last delivery attempt",
"last-delivery-failed": "Last delivery attempt failed",
"no-contact-points-found": "No contact points found",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": "Row options"
- }
+ },
+ "repeat": {
+ "title": "Repeat options",
+ "variable": {
+ "title": "Variable"
+ }
+ },
+ "title": "Row options"
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "Dashboard",
+ "objects": "Objects",
+ "panels": "Panels",
+ "rows": "Rows",
+ "tabs": "Tabs"
+ },
"objects": {
"multi-select": {
"selection-number": "No. of objects selected: {{length}}"
}
},
"open": "Open options pane",
- "panels": {
- "multi-select": {
- "selection-number": "No. of panels selected: {{length}}"
- }
- },
"row": {
"header": {
"hide": "Hide",
@@ -1146,11 +1166,30 @@
"rows": "Total number rows",
"table-title": "Stats"
},
+ "layout": {
+ "common": {
+ "copy": "Copy",
+ "copy-or-duplicate": "Copy or Duplicate",
+ "delete": "Delete",
+ "duplicate": "Duplicate",
+ "layout": "Layout",
+ "panels-title": "{{length}} Panels Selected"
+ }
+ },
"options": {
"description": "Description",
"title": "Dashboard options",
"title-option": "Title"
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "Collapse item",
+ "empty": "(empty)",
+ "expand": "Expand item"
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Dashboard must be saved before alerts can be added.",
@@ -1161,6 +1200,12 @@
"description": "CSS layout that adjusts to the available space",
"item-options": {
"hide-no-data": "Hide when no data",
+ "repeat": {
+ "variable": {
+ "description": "Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.",
+ "title": "Repeat by variable"
+ }
+ },
"title": "Layout options"
},
"name": "Responsive grid",
@@ -1285,7 +1330,9 @@
"viz-panel": {
"options": {
"description": "Description",
- "title": "Panel options",
+ "open-edit": "Open Panel Edit",
+ "plugin-type-image": "Image of plugin type",
+ "title": "Panel",
"title-option": "Title",
"transparent-background": "Transparent background"
}
@@ -2224,6 +2271,14 @@
"log-line": "Log line",
"no-details": "No details available"
},
+ "log-line-menu": {
+ "copy-link": "Copy link to log line",
+ "copy-log": "Copy log line",
+ "icon-label": "Log menu",
+ "pin-to-outline": "Pin log",
+ "show-context": "Show context",
+ "unpin-from-outline": "Unpin log"
+ },
"log-row-message": {
"ellipsis": "… ",
"more": "more",
diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json
index 1fa664da890..7e2083358d4 100644
--- a/public/locales/es-ES/grafana.json
+++ b/public/locales/es-ES/grafana.json
@@ -209,6 +209,8 @@
"alerting": "",
"alerting-change-description": "",
"annotations": "",
+ "compare": "",
+ "compare-with-latest": "",
"compareVersions": "",
"comparing-versions": "",
"condition": "",
@@ -228,17 +230,22 @@
"provisioning": "",
"provisioning-change-description": "",
"queryAndAlertCondition": "",
- "reset": "",
"restore": "",
+ "restore-manually": "",
"restore-modal": {
"body": "",
+ "confirm": "",
+ "error": "",
"summary": "",
"title": ""
},
+ "restore-version": "",
"rule_group": "",
"unknown": "",
"unknown-change-description": "",
- "user-id": ""
+ "user-id": "",
+ "warning-restore-manually": "",
+ "warning-restore-manually-title": ""
},
"annotations": {
"description": "",
@@ -315,6 +322,10 @@
"empty-state": {
"title": ""
},
+ "key-value-map": {
+ "add": "",
+ "confirm-add": ""
+ },
"last-delivery-attempt": "",
"last-delivery-failed": "",
"no-contact-points-found": "",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": ""
- }
+ },
+ "repeat": {
+ "title": "",
+ "variable": {
+ "title": ""
+ }
+ },
+ "title": ""
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "",
+ "objects": "",
+ "panels": "",
+ "rows": "",
+ "tabs": ""
+ },
"objects": {
"multi-select": {
"selection-number": ""
}
},
"open": "",
- "panels": {
- "multi-select": {
- "selection-number": ""
- }
- },
"row": {
"header": {
"hide": "",
@@ -1146,11 +1166,30 @@
"rows": "Número total de filas",
"table-title": "Estadísticas"
},
+ "layout": {
+ "common": {
+ "copy": "",
+ "copy-or-duplicate": "",
+ "delete": "",
+ "duplicate": "",
+ "layout": "",
+ "panels-title": ""
+ }
+ },
"options": {
"description": "",
"title": "",
"title-option": ""
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "",
+ "empty": "",
+ "expand": ""
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "",
@@ -1161,6 +1200,12 @@
"description": "",
"item-options": {
"hide-no-data": "",
+ "repeat": {
+ "variable": {
+ "description": "",
+ "title": ""
+ }
+ },
"title": ""
},
"name": "",
@@ -1285,6 +1330,8 @@
"viz-panel": {
"options": {
"description": "",
+ "open-edit": "",
+ "plugin-type-image": "",
"title": "",
"title-option": "",
"transparent-background": ""
@@ -2224,6 +2271,14 @@
"log-line": "",
"no-details": ""
},
+ "log-line-menu": {
+ "copy-link": "",
+ "copy-log": "",
+ "icon-label": "",
+ "pin-to-outline": "",
+ "show-context": "",
+ "unpin-from-outline": ""
+ },
"log-row-message": {
"ellipsis": "",
"more": "",
diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json
index 4def0fc0e73..5b5045b14c7 100644
--- a/public/locales/fr-FR/grafana.json
+++ b/public/locales/fr-FR/grafana.json
@@ -209,6 +209,8 @@
"alerting": "",
"alerting-change-description": "",
"annotations": "",
+ "compare": "",
+ "compare-with-latest": "",
"compareVersions": "",
"comparing-versions": "",
"condition": "",
@@ -228,17 +230,22 @@
"provisioning": "",
"provisioning-change-description": "",
"queryAndAlertCondition": "",
- "reset": "",
"restore": "",
+ "restore-manually": "",
"restore-modal": {
"body": "",
+ "confirm": "",
+ "error": "",
"summary": "",
"title": ""
},
+ "restore-version": "",
"rule_group": "",
"unknown": "",
"unknown-change-description": "",
- "user-id": ""
+ "user-id": "",
+ "warning-restore-manually": "",
+ "warning-restore-manually-title": ""
},
"annotations": {
"description": "",
@@ -315,6 +322,10 @@
"empty-state": {
"title": ""
},
+ "key-value-map": {
+ "add": "",
+ "confirm-add": ""
+ },
"last-delivery-attempt": "",
"last-delivery-failed": "",
"no-contact-points-found": "",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": ""
- }
+ },
+ "repeat": {
+ "title": "",
+ "variable": {
+ "title": ""
+ }
+ },
+ "title": ""
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "",
+ "objects": "",
+ "panels": "",
+ "rows": "",
+ "tabs": ""
+ },
"objects": {
"multi-select": {
"selection-number": ""
}
},
"open": "",
- "panels": {
- "multi-select": {
- "selection-number": ""
- }
- },
"row": {
"header": {
"hide": "",
@@ -1146,11 +1166,30 @@
"rows": "Nombre total de lignes",
"table-title": "Statistiques"
},
+ "layout": {
+ "common": {
+ "copy": "",
+ "copy-or-duplicate": "",
+ "delete": "",
+ "duplicate": "",
+ "layout": "",
+ "panels-title": ""
+ }
+ },
"options": {
"description": "",
"title": "",
"title-option": ""
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "",
+ "empty": "",
+ "expand": ""
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "",
@@ -1161,6 +1200,12 @@
"description": "",
"item-options": {
"hide-no-data": "",
+ "repeat": {
+ "variable": {
+ "description": "",
+ "title": ""
+ }
+ },
"title": ""
},
"name": "",
@@ -1285,6 +1330,8 @@
"viz-panel": {
"options": {
"description": "",
+ "open-edit": "",
+ "plugin-type-image": "",
"title": "",
"title-option": "",
"transparent-background": ""
@@ -2224,6 +2271,14 @@
"log-line": "",
"no-details": ""
},
+ "log-line-menu": {
+ "copy-link": "",
+ "copy-log": "",
+ "icon-label": "",
+ "pin-to-outline": "",
+ "show-context": "",
+ "unpin-from-outline": ""
+ },
"log-row-message": {
"ellipsis": "",
"more": "",
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 2c1801547e6..08f173af145 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -209,6 +209,8 @@
"alerting": "Åľęřŧįʼnģ",
"alerting-change-description": "Ŧĥįş ūpđäŧę ŵäş mäđę þy ŧĥę äľęřŧįʼnģ şyşŧęm đūę ŧő őŧĥęř čĥäʼnģęş. Főř ęχämpľę, ŵĥęʼn řęʼnämįʼnģ ä čőʼnŧäčŧ pőįʼnŧ ŧĥäŧ įş ūşęđ ƒőř şįmpľįƒįęđ řőūŧįʼnģ, ŧĥįş ŵįľľ ūpđäŧę 䃃ęčŧęđ řūľęş",
"annotations": "Åʼnʼnőŧäŧįőʼnş",
+ "compare": "Cőmpäřę",
+ "compare-with-latest": "Cőmpäřę ŵįŧĥ ľäŧęşŧ vęřşįőʼn",
"compareVersions": "Cőmpäřę vęřşįőʼnş",
"comparing-versions": "Cőmpäřįʼnģ vęřşįőʼnş",
"condition": "Åľęřŧ čőʼnđįŧįőʼn",
@@ -228,17 +230,22 @@
"provisioning": "Přővįşįőʼnįʼnģ",
"provisioning-change-description": "Vęřşįőʼn ūpđäŧę ŵäş mäđę vįä přővįşįőʼnįʼnģ",
"queryAndAlertCondition": "Qūęřy äʼnđ äľęřŧ čőʼnđįŧįőʼn",
- "reset": "Ŗęşęŧ ŧő vęřşįőʼn {{version}}",
"restore": "Ŗęşŧőřę",
+ "restore-manually": "Ÿőūř äľęřŧ řūľę čőūľđ ʼnőŧ þę řęşŧőřęđ. Ŧĥįş mäy þę đūę ŧő čĥäʼnģęş ŧő őŧĥęř ęʼnŧįŧįęş şūčĥ äş čőʼnŧäčŧ pőįʼnŧş, đäŧä şőūřčęş ęŧč. Pľęäşę mäʼnūäľľy řęşŧőřę ŧĥę řūľę vęřşįőʼn",
"restore-modal": {
"body": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő řęşŧőřę ŧĥę äľęřŧ řūľę đęƒįʼnįŧįőʼn ŧő ŧĥįş vęřşįőʼn? Åľľ ūʼnşävęđ čĥäʼnģęş ŵįľľ þę ľőşŧ.",
+ "confirm": "Ÿęş, řęşŧőřę čőʼnƒįģūřäŧįőʼn",
+ "error": "Cőūľđ ʼnőŧ řęşŧőřę äľęřŧ řūľę vęřşįőʼn ",
"summary": "Ŝūmmäřy őƒ čĥäʼnģęş ŧő þę äppľįęđ:",
- "title": "Ŗęşŧőřę Vęřşįőʼn"
+ "title": "Ŗęşŧőřę vęřşįőʼn"
},
+ "restore-version": "Ŗęşŧőřę ŧő vęřşįőʼn {{version}}",
"rule_group": "Ŗūľę ģřőūp",
"unknown": "Ůʼnĸʼnőŵʼn",
"unknown-change-description": "Ŧĥįş ūpđäŧę ŵäş mäđę přįőř ŧő ŧĥę įmpľęmęʼnŧäŧįőʼn őƒ äľęřŧ řūľę vęřşįőʼn ĥįşŧőřy. Ŧĥę ūşęř ŵĥő mäđę ŧĥę čĥäʼnģę įş ʼnőŧ ŧřäčĸęđ, þūŧ ƒūŧūřę čĥäʼnģęş ŵįľľ įʼnčľūđę ŧĥę ūşęř",
- "user-id": "Ůşęř ĨĐ {{uid}}"
+ "user-id": "Ůşęř ĨĐ {{uid}}",
+ "warning-restore-manually": "Ÿőū äřę mäʼnūäľľy řęşŧőřįʼnģ äʼn őľđ vęřşįőʼn őƒ ŧĥįş äľęřŧ řūľę. Pľęäşę řęvįęŵ ŧĥę čĥäʼnģęş čäřęƒūľľy þęƒőřę şävįʼnģ ŧĥę řūľę đęƒįʼnįŧįőʼn.",
+ "warning-restore-manually-title": "Ŗęşŧőřįʼnģ řūľę mäʼnūäľľy"
},
"annotations": {
"description": "Åđđ mőřę čőʼnŧęχŧ ŧő yőūř äľęřŧ ʼnőŧįƒįčäŧįőʼnş.",
@@ -315,6 +322,10 @@
"empty-state": {
"title": "Ÿőū đőʼn'ŧ ĥävę äʼny čőʼnŧäčŧ pőįʼnŧş yęŧ"
},
+ "key-value-map": {
+ "add": "Åđđ",
+ "confirm-add": "Cőʼnƒįřm ŧő äđđ"
+ },
"last-delivery-attempt": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ",
"last-delivery-failed": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ ƒäįľęđ",
"no-contact-points-found": "Ńő čőʼnŧäčŧ pőįʼnŧş ƒőūʼnđ",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": "Ŗőŵ őpŧįőʼnş"
- }
+ },
+ "repeat": {
+ "title": "Ŗępęäŧ őpŧįőʼnş",
+ "variable": {
+ "title": "Väřįäþľę"
+ }
+ },
+ "title": "Ŗőŵ őpŧįőʼnş"
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "Đäşĥþőäřđ",
+ "objects": "Øþĵęčŧş",
+ "panels": "Päʼnęľş",
+ "rows": "Ŗőŵş",
+ "tabs": "Ŧäþş"
+ },
"objects": {
"multi-select": {
"selection-number": "Ńő. őƒ őþĵęčŧş şęľęčŧęđ: {{length}}"
}
},
"open": "Øpęʼn őpŧįőʼnş päʼnę",
- "panels": {
- "multi-select": {
- "selection-number": "Ńő. őƒ päʼnęľş şęľęčŧęđ: {{length}}"
- }
- },
"row": {
"header": {
"hide": "Ħįđę",
@@ -1146,11 +1166,30 @@
"rows": "Ŧőŧäľ ʼnūmþęř řőŵş",
"table-title": "Ŝŧäŧş"
},
+ "layout": {
+ "common": {
+ "copy": "Cőpy",
+ "copy-or-duplicate": "Cőpy őř Đūpľįčäŧę",
+ "delete": "Đęľęŧę",
+ "duplicate": "Đūpľįčäŧę",
+ "layout": "Ŀäyőūŧ",
+ "panels-title": "{{length}} Päʼnęľş Ŝęľęčŧęđ"
+ }
+ },
"options": {
"description": "Đęşčřįpŧįőʼn",
"title": "Đäşĥþőäřđ őpŧįőʼnş",
"title-option": "Ŧįŧľę"
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "Cőľľäpşę įŧęm",
+ "empty": "(ęmpŧy)",
+ "expand": "Ēχpäʼnđ įŧęm"
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Đäşĥþőäřđ mūşŧ þę şävęđ þęƒőřę äľęřŧş čäʼn þę äđđęđ.",
@@ -1161,6 +1200,12 @@
"description": "CŜŜ ľäyőūŧ ŧĥäŧ äđĵūşŧş ŧő ŧĥę äväįľäþľę şpäčę",
"item-options": {
"hide-no-data": "Ħįđę ŵĥęʼn ʼnő đäŧä",
+ "repeat": {
+ "variable": {
+ "description": "Ŗępęäŧ ŧĥįş päʼnęľ ƒőř ęäčĥ väľūę įʼn ŧĥę şęľęčŧęđ väřįäþľę. Ŧĥįş įş ʼnőŧ vįşįþľę ŵĥįľę įʼn ęđįŧ mőđę. Ÿőū ʼnęęđ ŧő ģő þäčĸ ŧő đäşĥþőäřđ äʼnđ ŧĥęʼn ūpđäŧę ŧĥę väřįäþľę őř řęľőäđ ŧĥę đäşĥþőäřđ.",
+ "title": "Ŗępęäŧ þy väřįäþľę"
+ }
+ },
"title": "Ŀäyőūŧ őpŧįőʼnş"
},
"name": "Ŗęşpőʼnşįvę ģřįđ",
@@ -1285,7 +1330,9 @@
"viz-panel": {
"options": {
"description": "Đęşčřįpŧįőʼn",
- "title": "Päʼnęľ őpŧįőʼnş",
+ "open-edit": "Øpęʼn Päʼnęľ Ēđįŧ",
+ "plugin-type-image": "Ĩmäģę őƒ pľūģįʼn ŧypę",
+ "title": "Päʼnęľ",
"title-option": "Ŧįŧľę",
"transparent-background": "Ŧřäʼnşpäřęʼnŧ þäčĸģřőūʼnđ"
}
@@ -2224,6 +2271,14 @@
"log-line": "Ŀőģ ľįʼnę",
"no-details": "Ńő đęŧäįľş äväįľäþľę"
},
+ "log-line-menu": {
+ "copy-link": "Cőpy ľįʼnĸ ŧő ľőģ ľįʼnę",
+ "copy-log": "Cőpy ľőģ ľįʼnę",
+ "icon-label": "Ŀőģ męʼnū",
+ "pin-to-outline": "Pįʼn ľőģ",
+ "show-context": "Ŝĥőŵ čőʼnŧęχŧ",
+ "unpin-from-outline": "Ůʼnpįʼn ľőģ"
+ },
"log-row-message": {
"ellipsis": "… ",
"more": "mőřę",
diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json
index 6e0f3ba0687..05eaeaa5b35 100644
--- a/public/locales/pt-BR/grafana.json
+++ b/public/locales/pt-BR/grafana.json
@@ -209,6 +209,8 @@
"alerting": "",
"alerting-change-description": "",
"annotations": "",
+ "compare": "",
+ "compare-with-latest": "",
"compareVersions": "",
"comparing-versions": "",
"condition": "",
@@ -228,17 +230,22 @@
"provisioning": "",
"provisioning-change-description": "",
"queryAndAlertCondition": "",
- "reset": "",
"restore": "",
+ "restore-manually": "",
"restore-modal": {
"body": "",
+ "confirm": "",
+ "error": "",
"summary": "",
"title": ""
},
+ "restore-version": "",
"rule_group": "",
"unknown": "",
"unknown-change-description": "",
- "user-id": ""
+ "user-id": "",
+ "warning-restore-manually": "",
+ "warning-restore-manually-title": ""
},
"annotations": {
"description": "",
@@ -315,6 +322,10 @@
"empty-state": {
"title": ""
},
+ "key-value-map": {
+ "add": "",
+ "confirm-add": ""
+ },
"last-delivery-attempt": "",
"last-delivery-failed": "",
"no-contact-points-found": "",
@@ -1043,21 +1054,30 @@
},
"modal": {
"title": ""
- }
+ },
+ "repeat": {
+ "title": "",
+ "variable": {
+ "title": ""
+ }
+ },
+ "title": ""
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "",
+ "objects": "",
+ "panels": "",
+ "rows": "",
+ "tabs": ""
+ },
"objects": {
"multi-select": {
"selection-number": ""
}
},
"open": "",
- "panels": {
- "multi-select": {
- "selection-number": ""
- }
- },
"row": {
"header": {
"hide": "",
@@ -1146,11 +1166,30 @@
"rows": "Número total de linhas",
"table-title": "Estatísticas"
},
+ "layout": {
+ "common": {
+ "copy": "",
+ "copy-or-duplicate": "",
+ "delete": "",
+ "duplicate": "",
+ "layout": "",
+ "panels-title": ""
+ }
+ },
"options": {
"description": "",
"title": "",
"title-option": ""
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "",
+ "empty": "",
+ "expand": ""
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "",
@@ -1161,6 +1200,12 @@
"description": "",
"item-options": {
"hide-no-data": "",
+ "repeat": {
+ "variable": {
+ "description": "",
+ "title": ""
+ }
+ },
"title": ""
},
"name": "",
@@ -1285,6 +1330,8 @@
"viz-panel": {
"options": {
"description": "",
+ "open-edit": "",
+ "plugin-type-image": "",
"title": "",
"title-option": "",
"transparent-background": ""
@@ -2224,6 +2271,14 @@
"log-line": "",
"no-details": ""
},
+ "log-line-menu": {
+ "copy-link": "",
+ "copy-log": "",
+ "icon-label": "",
+ "pin-to-outline": "",
+ "show-context": "",
+ "unpin-from-outline": ""
+ },
"log-row-message": {
"ellipsis": "",
"more": "",
diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json
index d1f6b954db5..62c80b1ff34 100644
--- a/public/locales/zh-Hans/grafana.json
+++ b/public/locales/zh-Hans/grafana.json
@@ -209,6 +209,8 @@
"alerting": "",
"alerting-change-description": "",
"annotations": "",
+ "compare": "",
+ "compare-with-latest": "",
"compareVersions": "",
"comparing-versions": "",
"condition": "",
@@ -228,17 +230,22 @@
"provisioning": "",
"provisioning-change-description": "",
"queryAndAlertCondition": "",
- "reset": "",
"restore": "",
+ "restore-manually": "",
"restore-modal": {
"body": "",
+ "confirm": "",
+ "error": "",
"summary": "",
"title": ""
},
+ "restore-version": "",
"rule_group": "",
"unknown": "",
"unknown-change-description": "",
- "user-id": ""
+ "user-id": "",
+ "warning-restore-manually": "",
+ "warning-restore-manually-title": ""
},
"annotations": {
"description": "",
@@ -315,6 +322,10 @@
"empty-state": {
"title": ""
},
+ "key-value-map": {
+ "add": "",
+ "confirm-add": ""
+ },
"last-delivery-attempt": "",
"last-delivery-failed": "",
"no-contact-points-found": "",
@@ -1034,21 +1045,30 @@
},
"modal": {
"title": ""
- }
+ },
+ "repeat": {
+ "title": "",
+ "variable": {
+ "title": ""
+ }
+ },
+ "title": ""
}
},
"edit-pane": {
+ "elements": {
+ "dashboard": "",
+ "objects": "",
+ "panels": "",
+ "rows": "",
+ "tabs": ""
+ },
"objects": {
"multi-select": {
"selection-number": ""
}
},
"open": "",
- "panels": {
- "multi-select": {
- "selection-number": ""
- }
- },
"row": {
"header": {
"hide": "",
@@ -1137,11 +1157,30 @@
"rows": "总行数",
"table-title": "统计信息"
},
+ "layout": {
+ "common": {
+ "copy": "",
+ "copy-or-duplicate": "",
+ "delete": "",
+ "duplicate": "",
+ "layout": "",
+ "panels-title": ""
+ }
+ },
"options": {
"description": "",
"title": "",
"title-option": ""
},
+ "outline": {
+ "tree": {
+ "item": {
+ "collapse": "",
+ "empty": "",
+ "expand": ""
+ }
+ }
+ },
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "",
@@ -1152,6 +1191,12 @@
"description": "",
"item-options": {
"hide-no-data": "",
+ "repeat": {
+ "variable": {
+ "description": "",
+ "title": ""
+ }
+ },
"title": ""
},
"name": "",
@@ -1276,6 +1321,8 @@
"viz-panel": {
"options": {
"description": "",
+ "open-edit": "",
+ "plugin-type-image": "",
"title": "",
"title-option": "",
"transparent-background": ""
@@ -2214,6 +2261,14 @@
"log-line": "",
"no-details": ""
},
+ "log-line-menu": {
+ "copy-link": "",
+ "copy-log": "",
+ "icon-label": "",
+ "pin-to-outline": "",
+ "show-context": "",
+ "unpin-from-outline": ""
+ },
"log-row-message": {
"ellipsis": "",
"more": "",
diff --git a/public/openapi3.json b/public/openapi3.json
index 8f339a39b59..2f9323187d3 100644
--- a/public/openapi3.json
+++ b/public/openapi3.json
@@ -4585,6 +4585,9 @@
"annotationsPermissions": {
"$ref": "#/components/schemas/AnnotationPermission"
},
+ "apiVersion": {
+ "type": "string"
+ },
"canAdmin": {
"type": "boolean"
},
@@ -20308,6 +20311,23 @@
"get": {
"description": "Returns all org users within the current organization. Accessible to users with org admin role.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `org.users:read` with scope `users:*`.",
"operationId": "getOrgUsersForCurrentOrg",
+ "parameters": [
+ {
+ "in": "query",
+ "name": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "in": "query",
+ "name": "limit",
+ "schema": {
+ "format": "int64",
+ "type": "integer"
+ }
+ }
+ ],
"responses": {
"200": {
"$ref": "#/components/responses/getOrgUsersForCurrentOrgResponse"
diff --git a/scripts/prepare-npm-package.js b/scripts/prepare-npm-package.js
new file mode 100644
index 00000000000..07559074842
--- /dev/null
+++ b/scripts/prepare-npm-package.js
@@ -0,0 +1,84 @@
+import PackageJson from '@npmcli/package-json';
+import { mkdir } from 'node:fs/promises';
+import { join, dirname } from 'node:path';
+
+const cwd = process.cwd();
+
+try {
+ const pkgJson = await PackageJson.load(cwd);
+ const cjsIndex = pkgJson.content.publishConfig?.main ?? pkgJson.content.main;
+ const esmIndex = pkgJson.content.publishConfig?.module ?? pkgJson.content.module;
+ const cjsTypes = pkgJson.content.publishConfig?.types ?? pkgJson.content.types;
+ const esmTypes = `./${join(dirname(esmIndex), 'index.d.mts')}`;
+
+ const exports = {
+ './package.json': './package.json',
+ '.': {
+ import: {
+ types: esmTypes,
+ default: esmIndex,
+ },
+ require: {
+ types: cjsTypes,
+ default: cjsIndex,
+ },
+ },
+ };
+
+ pkgJson.update({
+ main: cjsIndex,
+ types: cjsTypes,
+ module: esmIndex,
+ exports,
+ });
+
+ await pkgJson.save();
+
+ // If an alias package name is provided we add an exports entry for the alias
+ // then generate an additional "nested" package.json for typescript resolution that
+ // doesn't use the exports property in package.json.
+ if (process.env.ALIAS_PACKAGE_NAME) {
+ const aliasName = process.env.ALIAS_PACKAGE_NAME;
+ pkgJson.update({
+ exports: {
+ ...pkgJson.content.exports,
+ [`./${aliasName}`]: {
+ import: {
+ types: esmTypes.replace('index', aliasName),
+ default: esmIndex.replace('index', aliasName),
+ },
+ require: {
+ types: cjsTypes.replace('index', aliasName),
+ default: cjsTypes.replace('index', aliasName),
+ },
+ },
+ },
+ files: [...pkgJson.content.files, aliasName],
+ });
+ await pkgJson.save();
+ await createAliasPackageJsonFiles(pkgJson.content, aliasName);
+ }
+} catch (e) {
+ console.error(e);
+ process.exit(1);
+}
+
+async function createAliasPackageJsonFiles(packageJsonContent, aliasName) {
+ const pkgName = `${packageJsonContent.name}/${aliasName}`;
+ try {
+ console.log(`📦 Writing alias package.json for ${pkgName}.`);
+ const pkgJsonPath = `${cwd}/${aliasName}`;
+ await mkdir(pkgJsonPath, { recursive: true });
+ const pkgJson = await PackageJson.create(pkgJsonPath, {
+ data: {
+ name: pkgName,
+ types: `../dist/cjs/${aliasName}.d.cts`,
+ main: `../dist/cjs/${aliasName}.cjs`,
+ module: `../dist/esm/${aliasName}.mjs`,
+ },
+ });
+ await pkgJson.save();
+ } catch (error) {
+ throw new Error(`Error generating package.json for ${pkgName}`, error);
+ }
+}
diff --git a/scripts/prepare-packagejson.js b/scripts/prepare-packagejson.js
deleted file mode 100644
index cab9eed4c98..00000000000
--- a/scripts/prepare-packagejson.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const fs = require('fs');
-
-const cwd = process.cwd();
-const packageJson = require(`${cwd}/package.json`);
-
-const newPackageJson = {
- ...packageJson,
- main: packageJson.publishConfig?.main ?? packageJson.main,
-};
-
-if (packageJson.publishConfig?.types) {
- newPackageJson.types = packageJson.publishConfig.types;
-}
-
-if (packageJson.publishConfig?.module) {
- newPackageJson.module = packageJson.publishConfig.module;
-}
-
-try {
- fs.writeFileSync(`${cwd}/package.json`, JSON.stringify(newPackageJson, null, 2));
-} catch (e) {}
diff --git a/scripts/validate-npm-packages.sh b/scripts/validate-npm-packages.sh
index 838dccd91e1..2f0f1764597 100755
--- a/scripts/validate-npm-packages.sh
+++ b/scripts/validate-npm-packages.sh
@@ -43,14 +43,14 @@ for file in "$ARTIFACTS_DIR"/*.tgz; do
fi
# Assert commonjs builds
- if [ ! -d dist ] || [ ! -f dist/index.js ] || [ ! -f dist/index.d.ts ]; then
+ if [ ! -d dist ] || [ ! -f dist/cjs/index.cjs ] || [ ! -f dist/cjs/index.d.cts ]; then
echo -e "❌ Failed: Missing 'dist' directory or required commonjs files in package $dir_name.\n"
exit 1
fi
- if [ "$(jq -r '.main' package.json)" != "dist/index.js" ] || \
- [ "$(jq -r '.types' package.json)" != "dist/index.d.ts" ]; then
- echo -e "❌ Failed: Incorrect package.json properties in package $dir_name.\n"
+ if [ "$(jq -r '.main' package.json)" != "./dist/cjs/index.cjs" ] || \
+ [ "$(jq -r '.types' package.json)" != "./dist/cjs/index.d.cts" ]; then
+ echo -e "❌ Failed: Incorrect cjs package.json properties in package $dir_name.\n"
exit 1
fi
@@ -58,13 +58,13 @@ for file in "$ARTIFACTS_DIR"/*.tgz; do
esm_packages=("grafana-data" "grafana-ui" "grafana-runtime" "grafana-e2e-selectors" "grafana-schema")
for esm_package in "${esm_packages[@]}"; do
if [[ "$dir_name" == "$esm_package" ]]; then
- if [ ! -d dist/esm ] || [ ! -f dist/esm/index.js ]; then
+ if [ ! -d dist/esm ] || [ ! -f dist/esm/index.mjs ]; then
echo -e "❌ Failed: Missing 'dist/esm' directory or required esm files in package $dir_name.\n"
exit 1
fi
- if [ "$(jq -r '.module' package.json)" != "dist/esm/index.js" ]; then
- echo -e "❌ Failed: Incorrect package.json properties in package $dir_name.\n"
+ if [ "$(jq -r '.module' package.json)" != "./dist/esm/index.mjs" ]; then
+ echo -e "❌ Failed: Incorrect esm package.json properties in package $dir_name.\n"
exit 1
fi
fi
diff --git a/yarn.lock b/yarn.lock
index eb8360924ac..dce63788f1d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -81,7 +81,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.2":
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.26.2":
version: 7.26.2
resolution: "@babel/code-frame@npm:7.26.2"
dependencies:
@@ -92,46 +92,46 @@ __metadata:
languageName: node
linkType: hard
-"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.5":
- version: 7.26.5
- resolution: "@babel/compat-data@npm:7.26.5"
- checksum: 10/afe35751f27bda80390fa221d5e37be55b7fc42cec80de9896086e20394f2306936c4296fcb4d62b683e3b49ba2934661ea7e06196ca2dacdc2e779fbea4a1a9
+"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.5, @babel/compat-data@npm:^7.26.8":
+ version: 7.26.8
+ resolution: "@babel/compat-data@npm:7.26.8"
+ checksum: 10/bdddf577f670e0e12996ef37e134856c8061032edb71a13418c3d4dae8135da28910b7cd6dec6e668ab3a41e42089ef7ee9c54ef52fe0860b54cb420b0d14948
languageName: node
linkType: hard
-"@babel/core@npm:7.26.7, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9":
- version: 7.26.7
- resolution: "@babel/core@npm:7.26.7"
+"@babel/core@npm:7.26.9, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9":
+ version: 7.26.9
+ resolution: "@babel/core@npm:7.26.9"
dependencies:
"@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.26.2"
- "@babel/generator": "npm:^7.26.5"
+ "@babel/generator": "npm:^7.26.9"
"@babel/helper-compilation-targets": "npm:^7.26.5"
"@babel/helper-module-transforms": "npm:^7.26.0"
- "@babel/helpers": "npm:^7.26.7"
- "@babel/parser": "npm:^7.26.7"
- "@babel/template": "npm:^7.25.9"
- "@babel/traverse": "npm:^7.26.7"
- "@babel/types": "npm:^7.26.7"
+ "@babel/helpers": "npm:^7.26.9"
+ "@babel/parser": "npm:^7.26.9"
+ "@babel/template": "npm:^7.26.9"
+ "@babel/traverse": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3"
semver: "npm:^6.3.1"
- checksum: 10/1ca1c9b1366a1ee77ade9c72302f288b2b148e4190e0f36bc032d09c686b2c7973d3309e4eec2c57243508c16cf907c17dec4e34ba95e7a18badd57c61bbcb7c
+ checksum: 10/ceed199dbe25f286a0a59a2ea7879aed37c1f3bb289375d061eda4752cab2ba365e7f9e969c7fd3b9b95c930493db6eeb5a6d6f017dd135fb5a4503449aad753
languageName: node
linkType: hard
-"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.5, @babel/generator@npm:^7.7.2":
- version: 7.26.5
- resolution: "@babel/generator@npm:7.26.5"
+"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2":
+ version: 7.26.9
+ resolution: "@babel/generator@npm:7.26.9"
dependencies:
- "@babel/parser": "npm:^7.26.5"
- "@babel/types": "npm:^7.26.5"
+ "@babel/parser": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
"@jridgewell/gen-mapping": "npm:^0.3.5"
"@jridgewell/trace-mapping": "npm:^0.3.25"
jsesc: "npm:^3.0.2"
- checksum: 10/aa5f176155431d1fb541ca11a7deddec0fc021f20992ced17dc2f688a0a9584e4ff4280f92e8a39302627345cd325762f70f032764806c579c6fd69432542bcb
+ checksum: 10/95075dd6158a49efcc71d7f2c5d20194fcf245348de7723ca35e37cd5800587f1d4de2be6c4ba87b5f5fbb967c052543c109eaab14b43f6a73eb05ccd9a5bb44
languageName: node
linkType: hard
@@ -187,9 +187,9 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.2":
- version: 0.6.2
- resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2"
+"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.3":
+ version: 0.6.3
+ resolution: "@babel/helper-define-polyfill-provider@npm:0.6.3"
dependencies:
"@babel/helper-compilation-targets": "npm:^7.22.6"
"@babel/helper-plugin-utils": "npm:^7.22.5"
@@ -198,7 +198,7 @@ __metadata:
resolve: "npm:^1.14.2"
peerDependencies:
"@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
- checksum: 10/bb32ec12024d3f16e70641bc125d2534a97edbfdabbc9f69001ec9c4ce46f877c7a224c566aa6c8c510c3b0def2e43dc4433bf6a40896ba5ce0cef4ea5ccbcff
+ checksum: 10/b79a77ac8fbf1aaf6c7f99191871760508e87d75a374ff3c39c6599a17d9bb82284797cd451769305764e504546caf22ae63367b22d6e45e32d0a8f4a34aab53
languageName: node
linkType: hard
@@ -329,13 +329,13 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helpers@npm:^7.26.7":
- version: 7.26.7
- resolution: "@babel/helpers@npm:7.26.7"
+"@babel/helpers@npm:^7.26.9":
+ version: 7.26.9
+ resolution: "@babel/helpers@npm:7.26.9"
dependencies:
- "@babel/template": "npm:^7.25.9"
- "@babel/types": "npm:^7.26.7"
- checksum: 10/97593a0c9b3c5e2e7cf824e549b5f6fa6dc739593ad93d5bb36d06883d8124beac63ee2154c9a514dbee68a169d5683ab463e0ac6713ad92fb4854cea35ed4d4
+ "@babel/template": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
+ checksum: 10/267dfa7d04dff7720610497f466aa7b60652b7ec8dde5914527879350c9d655271e892117c5b2f0f083d92d2a8e5e2cf9832d4f98cd7fb72d78f796002af19a1
languageName: node
linkType: hard
@@ -351,14 +351,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.5, @babel/parser@npm:^7.26.7":
- version: 7.26.7
- resolution: "@babel/parser@npm:7.26.7"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.9":
+ version: 7.26.9
+ resolution: "@babel/parser@npm:7.26.9"
dependencies:
- "@babel/types": "npm:^7.26.7"
+ "@babel/types": "npm:^7.26.9"
bin:
parser: ./bin/babel-parser.js
- checksum: 10/3ccc384366ca9a9b49c54f5b24c9d8cff9a505f2fbdd1cfc04941c8e1897084cc32f100e77900c12bc14a176cf88daa3c155faad680d9a23491b997fd2a59ffc
+ checksum: 10/cb84fe3ba556d6a4360f3373cf7eb0901c46608c8d77330cc1ca021d60f5d6ebb4056a8e7f9dd0ef231923ef1fe69c87b11ce9e160d2252e089a20232a2b942b
languageName: node
linkType: hard
@@ -663,16 +663,16 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-async-generator-functions@npm:^7.25.9":
- version: 7.25.9
- resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.9"
+"@babel/plugin-transform-async-generator-functions@npm:^7.26.8":
+ version: 7.26.8
+ resolution: "@babel/plugin-transform-async-generator-functions@npm:7.26.8"
dependencies:
- "@babel/helper-plugin-utils": "npm:^7.25.9"
+ "@babel/helper-plugin-utils": "npm:^7.26.5"
"@babel/helper-remap-async-to-generator": "npm:^7.25.9"
- "@babel/traverse": "npm:^7.25.9"
+ "@babel/traverse": "npm:^7.26.8"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/99306c44a4a791abd51a56d89fa61c4cfe805a58e070c7fb1cbf950886778a6c8c4f25a92d231f91da1746d14a338436073fd83038e607f03a2a98ac5340406b
+ checksum: 10/8fb43823f56281b041dbd358de4f59fccb3e20aac133a439caaeb5aaa30671b3482da9a8515b169fef108148e937c1248b7d6383979c3b30f9348e3fabd29b8e
languageName: node
linkType: hard
@@ -842,15 +842,15 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-for-of@npm:^7.25.9":
- version: 7.25.9
- resolution: "@babel/plugin-transform-for-of@npm:7.25.9"
+"@babel/plugin-transform-for-of@npm:^7.26.9":
+ version: 7.26.9
+ resolution: "@babel/plugin-transform-for-of@npm:7.26.9"
dependencies:
- "@babel/helper-plugin-utils": "npm:^7.25.9"
+ "@babel/helper-plugin-utils": "npm:^7.26.5"
"@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/63a2db7fe06c2e3f5fc1926f478dac66a5f7b3eaeb4a0ffae577e6f3cb3d822cb1ed2ed3798f70f5cb1aa06bc2ad8bcd1f557342f5c425fd83c37a8fc1cfd2ba
+ checksum: 10/25df1ea3bcecc1bcef99f273fbd8f4a73a509ab7ef3db93629817cb02f9d24868ca3760347f864c8fa4ab79ffa86fb09b2f2de1f2ba1f73f27dbe0c3973c6868
languageName: node
linkType: hard
@@ -1219,14 +1219,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-template-literals@npm:^7.25.9":
- version: 7.25.9
- resolution: "@babel/plugin-transform-template-literals@npm:7.25.9"
+"@babel/plugin-transform-template-literals@npm:^7.26.8":
+ version: 7.26.8
+ resolution: "@babel/plugin-transform-template-literals@npm:7.26.8"
dependencies:
- "@babel/helper-plugin-utils": "npm:^7.25.9"
+ "@babel/helper-plugin-utils": "npm:^7.26.5"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/92eb1d6e2d95bd24abbb74fa7640d02b66ff6214e0bb616d7fda298a7821ce15132a4265d576a3502a347a3c9e94b6c69ed265bb0784664592fa076785a3d16a
+ checksum: 10/65874c8844ce906507cd5b9c78950d6173f8339b6416a2a9e763021db5a7045315a6f0e58976ec4af5e960c003ef322576c105130a644addb8f94d1a0821a972
languageName: node
linkType: hard
@@ -1298,11 +1298,11 @@ __metadata:
languageName: node
linkType: hard
-"@babel/preset-env@npm:7.26.7":
- version: 7.26.7
- resolution: "@babel/preset-env@npm:7.26.7"
+"@babel/preset-env@npm:7.26.9":
+ version: 7.26.9
+ resolution: "@babel/preset-env@npm:7.26.9"
dependencies:
- "@babel/compat-data": "npm:^7.26.5"
+ "@babel/compat-data": "npm:^7.26.8"
"@babel/helper-compilation-targets": "npm:^7.26.5"
"@babel/helper-plugin-utils": "npm:^7.26.5"
"@babel/helper-validator-option": "npm:^7.25.9"
@@ -1316,7 +1316,7 @@ __metadata:
"@babel/plugin-syntax-import-attributes": "npm:^7.26.0"
"@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6"
"@babel/plugin-transform-arrow-functions": "npm:^7.25.9"
- "@babel/plugin-transform-async-generator-functions": "npm:^7.25.9"
+ "@babel/plugin-transform-async-generator-functions": "npm:^7.26.8"
"@babel/plugin-transform-async-to-generator": "npm:^7.25.9"
"@babel/plugin-transform-block-scoped-functions": "npm:^7.26.5"
"@babel/plugin-transform-block-scoping": "npm:^7.25.9"
@@ -1331,7 +1331,7 @@ __metadata:
"@babel/plugin-transform-dynamic-import": "npm:^7.25.9"
"@babel/plugin-transform-exponentiation-operator": "npm:^7.26.3"
"@babel/plugin-transform-export-namespace-from": "npm:^7.25.9"
- "@babel/plugin-transform-for-of": "npm:^7.25.9"
+ "@babel/plugin-transform-for-of": "npm:^7.26.9"
"@babel/plugin-transform-function-name": "npm:^7.25.9"
"@babel/plugin-transform-json-strings": "npm:^7.25.9"
"@babel/plugin-transform-literals": "npm:^7.25.9"
@@ -1359,7 +1359,7 @@ __metadata:
"@babel/plugin-transform-shorthand-properties": "npm:^7.25.9"
"@babel/plugin-transform-spread": "npm:^7.25.9"
"@babel/plugin-transform-sticky-regex": "npm:^7.25.9"
- "@babel/plugin-transform-template-literals": "npm:^7.25.9"
+ "@babel/plugin-transform-template-literals": "npm:^7.26.8"
"@babel/plugin-transform-typeof-symbol": "npm:^7.26.7"
"@babel/plugin-transform-unicode-escapes": "npm:^7.25.9"
"@babel/plugin-transform-unicode-property-regex": "npm:^7.25.9"
@@ -1367,13 +1367,13 @@ __metadata:
"@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.9"
"@babel/preset-modules": "npm:0.1.6-no-external-plugins"
babel-plugin-polyfill-corejs2: "npm:^0.4.10"
- babel-plugin-polyfill-corejs3: "npm:^0.10.6"
+ babel-plugin-polyfill-corejs3: "npm:^0.11.0"
babel-plugin-polyfill-regenerator: "npm:^0.6.1"
- core-js-compat: "npm:^3.38.1"
+ core-js-compat: "npm:^3.40.0"
semver: "npm:^6.3.1"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/d5833ac61580ca8ca672466d06edcf523b49f400caa8f4b8f21358a30875a8ca1628a250b89369e8a0be3439f6ae0002af6f64335794b06acaf603906055f43a
+ checksum: 10/ac6fad331760c0bc25ed428b7696b297bad7046a75f30e544b392acfb33709f12316b9a5b0c8606f933d5756e1b9d527b46fda09693db52e851325443dd6a574
languageName: node
linkType: hard
@@ -1416,48 +1416,48 @@ __metadata:
languageName: node
linkType: hard
-"@babel/runtime@npm:7.26.7, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
- version: 7.26.7
- resolution: "@babel/runtime@npm:7.26.7"
+"@babel/runtime@npm:7.26.9, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
+ version: 7.26.9
+ resolution: "@babel/runtime@npm:7.26.9"
dependencies:
regenerator-runtime: "npm:^0.14.0"
- checksum: 10/c7a661a6836b332d9d2e047cba77ba1862c1e4f78cec7146db45808182ef7636d8a7170be9797e5d8fd513180bffb9fa16f6ca1c69341891efec56113cf22bfc
+ checksum: 10/08edd07d774eafbf157fdc8450ed6ddd22416fdd8e2a53e4a00349daba1b502c03ab7f7ad3ad3a7c46b9a24d99b5697591d0f852ee2f84642082ef7dda90b83d
languageName: node
linkType: hard
-"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3":
- version: 7.25.9
- resolution: "@babel/template@npm:7.25.9"
- dependencies:
- "@babel/code-frame": "npm:^7.25.9"
- "@babel/parser": "npm:^7.25.9"
- "@babel/types": "npm:^7.25.9"
- checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2
- languageName: node
- linkType: hard
-
-"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.7":
- version: 7.26.7
- resolution: "@babel/traverse@npm:7.26.7"
+"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3":
+ version: 7.26.9
+ resolution: "@babel/template@npm:7.26.9"
dependencies:
"@babel/code-frame": "npm:^7.26.2"
- "@babel/generator": "npm:^7.26.5"
- "@babel/parser": "npm:^7.26.7"
- "@babel/template": "npm:^7.25.9"
- "@babel/types": "npm:^7.26.7"
- debug: "npm:^4.3.1"
- globals: "npm:^11.1.0"
- checksum: 10/c821c9682fe0b9edf7f7cbe9cc3e0787ffee3f73b52c13b21b463f8979950a6433f5e7e482a74348d22c0b7a05180e6f72b23eb6732328b49c59fc6388ebf6e5
+ "@babel/parser": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
+ checksum: 10/240288cebac95b1cc1cb045ad143365643da0470e905e11731e63280e43480785bd259924f4aea83898ef68e9fa7c176f5f2d1e8b0a059b27966e8ca0b41a1b6
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.5, @babel/types@npm:^7.26.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
- version: 7.26.7
- resolution: "@babel/types@npm:7.26.7"
+"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.26.9":
+ version: 7.26.9
+ resolution: "@babel/traverse@npm:7.26.9"
+ dependencies:
+ "@babel/code-frame": "npm:^7.26.2"
+ "@babel/generator": "npm:^7.26.9"
+ "@babel/parser": "npm:^7.26.9"
+ "@babel/template": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
+ debug: "npm:^4.3.1"
+ globals: "npm:^11.1.0"
+ checksum: 10/c16a79522eafa0a7e40eb556bf1e8a3d50dbb0ff943a80f2c06cee2ec7ff87baa0c5d040a5cff574d9bcb3bed05e7d8c6f13b238a931c97267674b02c6cf45b4
+ languageName: node
+ linkType: hard
+
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.9, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+ version: 7.26.9
+ resolution: "@babel/types@npm:7.26.9"
dependencies:
"@babel/helper-string-parser": "npm:^7.25.9"
"@babel/helper-validator-identifier": "npm:^7.25.9"
- checksum: 10/2264efd02cc261ca5d1c5bc94497c8995238f28afd2b7483b24ea64dd694cf46b00d51815bf0c87f0d0061ea221569c77893aeecb0d4b4bb254e9c2f938d7669
+ checksum: 10/11b62ea7ed64ef7e39cc9b33852c1084064c3b970ae0eaa5db659241cfb776577d1e68cbff4de438bada885d3a827b52cc0f3746112d8e1bc672bb99a8eb5b56
languageName: node
linkType: hard
@@ -2067,13 +2067,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/aix-ppc64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/aix-ppc64@npm:0.24.2"
- conditions: os=aix & cpu=ppc64
- languageName: node
- linkType: hard
-
"@esbuild/aix-ppc64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/aix-ppc64@npm:0.25.0"
@@ -2081,13 +2074,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/android-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/android-arm64@npm:0.24.2"
- conditions: os=android & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/android-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/android-arm64@npm:0.25.0"
@@ -2095,13 +2081,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/android-arm@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/android-arm@npm:0.24.2"
- conditions: os=android & cpu=arm
- languageName: node
- linkType: hard
-
"@esbuild/android-arm@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/android-arm@npm:0.25.0"
@@ -2109,13 +2088,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/android-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/android-x64@npm:0.24.2"
- conditions: os=android & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/android-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/android-x64@npm:0.25.0"
@@ -2123,13 +2095,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/darwin-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/darwin-arm64@npm:0.24.2"
- conditions: os=darwin & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/darwin-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/darwin-arm64@npm:0.25.0"
@@ -2137,13 +2102,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/darwin-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/darwin-x64@npm:0.24.2"
- conditions: os=darwin & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/darwin-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/darwin-x64@npm:0.25.0"
@@ -2151,13 +2109,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/freebsd-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/freebsd-arm64@npm:0.24.2"
- conditions: os=freebsd & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/freebsd-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/freebsd-arm64@npm:0.25.0"
@@ -2165,13 +2116,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/freebsd-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/freebsd-x64@npm:0.24.2"
- conditions: os=freebsd & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/freebsd-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/freebsd-x64@npm:0.25.0"
@@ -2179,13 +2123,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-arm64@npm:0.24.2"
- conditions: os=linux & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/linux-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-arm64@npm:0.25.0"
@@ -2193,13 +2130,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-arm@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-arm@npm:0.24.2"
- conditions: os=linux & cpu=arm
- languageName: node
- linkType: hard
-
"@esbuild/linux-arm@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-arm@npm:0.25.0"
@@ -2207,13 +2137,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-ia32@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-ia32@npm:0.24.2"
- conditions: os=linux & cpu=ia32
- languageName: node
- linkType: hard
-
"@esbuild/linux-ia32@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-ia32@npm:0.25.0"
@@ -2221,13 +2144,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-loong64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-loong64@npm:0.24.2"
- conditions: os=linux & cpu=loong64
- languageName: node
- linkType: hard
-
"@esbuild/linux-loong64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-loong64@npm:0.25.0"
@@ -2235,13 +2151,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-mips64el@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-mips64el@npm:0.24.2"
- conditions: os=linux & cpu=mips64el
- languageName: node
- linkType: hard
-
"@esbuild/linux-mips64el@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-mips64el@npm:0.25.0"
@@ -2249,13 +2158,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-ppc64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-ppc64@npm:0.24.2"
- conditions: os=linux & cpu=ppc64
- languageName: node
- linkType: hard
-
"@esbuild/linux-ppc64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-ppc64@npm:0.25.0"
@@ -2263,13 +2165,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-riscv64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-riscv64@npm:0.24.2"
- conditions: os=linux & cpu=riscv64
- languageName: node
- linkType: hard
-
"@esbuild/linux-riscv64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-riscv64@npm:0.25.0"
@@ -2277,13 +2172,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-s390x@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-s390x@npm:0.24.2"
- conditions: os=linux & cpu=s390x
- languageName: node
- linkType: hard
-
"@esbuild/linux-s390x@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-s390x@npm:0.25.0"
@@ -2291,13 +2179,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/linux-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/linux-x64@npm:0.24.2"
- conditions: os=linux & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/linux-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/linux-x64@npm:0.25.0"
@@ -2305,13 +2186,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/netbsd-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/netbsd-arm64@npm:0.24.2"
- conditions: os=netbsd & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/netbsd-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/netbsd-arm64@npm:0.25.0"
@@ -2319,13 +2193,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/netbsd-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/netbsd-x64@npm:0.24.2"
- conditions: os=netbsd & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/netbsd-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/netbsd-x64@npm:0.25.0"
@@ -2333,13 +2200,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/openbsd-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/openbsd-arm64@npm:0.24.2"
- conditions: os=openbsd & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/openbsd-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/openbsd-arm64@npm:0.25.0"
@@ -2347,13 +2207,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/openbsd-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/openbsd-x64@npm:0.24.2"
- conditions: os=openbsd & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/openbsd-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/openbsd-x64@npm:0.25.0"
@@ -2361,13 +2214,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/sunos-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/sunos-x64@npm:0.24.2"
- conditions: os=sunos & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/sunos-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/sunos-x64@npm:0.25.0"
@@ -2375,13 +2221,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/win32-arm64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/win32-arm64@npm:0.24.2"
- conditions: os=win32 & cpu=arm64
- languageName: node
- linkType: hard
-
"@esbuild/win32-arm64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/win32-arm64@npm:0.25.0"
@@ -2389,13 +2228,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/win32-ia32@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/win32-ia32@npm:0.24.2"
- conditions: os=win32 & cpu=ia32
- languageName: node
- linkType: hard
-
"@esbuild/win32-ia32@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/win32-ia32@npm:0.25.0"
@@ -2403,13 +2235,6 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/win32-x64@npm:0.24.2":
- version: 0.24.2
- resolution: "@esbuild/win32-x64@npm:0.24.2"
- conditions: os=win32 & cpu=x64
- languageName: node
- linkType: hard
-
"@esbuild/win32-x64@npm:0.25.0":
version: 0.25.0
resolution: "@esbuild/win32-x64@npm:0.25.0"
@@ -2564,9 +2389,9 @@ __metadata:
languageName: node
linkType: hard
-"@floating-ui/react@npm:0.27.3":
- version: 0.27.3
- resolution: "@floating-ui/react@npm:0.27.3"
+"@floating-ui/react@npm:0.27.5":
+ version: 0.27.5
+ resolution: "@floating-ui/react@npm:0.27.5"
dependencies:
"@floating-ui/react-dom": "npm:^2.1.2"
"@floating-ui/utils": "npm:^0.2.9"
@@ -2574,7 +2399,7 @@ __metadata:
peerDependencies:
react: ">=17.0.0"
react-dom: ">=17.0.0"
- checksum: 10/91eef58241b2225420fa73dfc501934463310489e08f2a0681b320e0c639c9ee59044ab66645f2901b74a88328208ff4cc4afabe93917a7a28f9d18037965cb6
+ checksum: 10/7728dc43b85e40dc01e5aae19da3e3f88ccfcbecb331a1c978a95e0b5c4e95f9ca6927bd216ae556e0a0e2839524fbf317fd0df93946b3cd0510ddb266746c9a
languageName: node
linkType: hard
@@ -2609,15 +2434,15 @@ __metadata:
languageName: node
linkType: hard
-"@formatjs/ecma402-abstract@npm:2.3.2":
- version: 2.3.2
- resolution: "@formatjs/ecma402-abstract@npm:2.3.2"
+"@formatjs/ecma402-abstract@npm:2.3.3":
+ version: 2.3.3
+ resolution: "@formatjs/ecma402-abstract@npm:2.3.3"
dependencies:
"@formatjs/fast-memoize": "npm:2.2.6"
- "@formatjs/intl-localematcher": "npm:0.5.10"
+ "@formatjs/intl-localematcher": "npm:0.6.0"
decimal.js: "npm:10"
tslib: "npm:2"
- checksum: 10/db31d3d9b36033ea11ec905638ac0c1d2282f5bf53c9c06ee1d0ffd924f4bf64030702c92b56261756c4998dfa6235462689d8eda82d5913f2d7cf636a9416ae
+ checksum: 10/c73a704d5cba3b929a9a303a04b4e8a708bb2dea000ee41808c55b22941a70da01b065697cbc5a25898b2007405abd71f414ab8c327fe953f63ae4120c2cc98d
languageName: node
linkType: hard
@@ -2661,13 +2486,13 @@ __metadata:
linkType: hard
"@formatjs/intl-durationformat@npm:^0.7.0":
- version: 0.7.2
- resolution: "@formatjs/intl-durationformat@npm:0.7.2"
+ version: 0.7.3
+ resolution: "@formatjs/intl-durationformat@npm:0.7.3"
dependencies:
- "@formatjs/ecma402-abstract": "npm:2.3.2"
- "@formatjs/intl-localematcher": "npm:0.5.10"
+ "@formatjs/ecma402-abstract": "npm:2.3.3"
+ "@formatjs/intl-localematcher": "npm:0.6.0"
tslib: "npm:2"
- checksum: 10/863784f3ac51779516fa29c87705af6d4f1b32191a2a48172d2ce16a99e385887a5641703a222ca2b1a0d334e1ab2587be5ef65f48c5ae8a16ba55138356a232
+ checksum: 10/781c4f1574e01d2155ca90593b0605de883937ade3d5057a6adf19e21379fd80fb5985cf7115d348f572fb25b281add8d851fb9011fea770e9b1ea1cf5e9b5a1
languageName: node
linkType: hard
@@ -2680,12 +2505,12 @@ __metadata:
languageName: node
linkType: hard
-"@formatjs/intl-localematcher@npm:0.5.10":
- version: 0.5.10
- resolution: "@formatjs/intl-localematcher@npm:0.5.10"
+"@formatjs/intl-localematcher@npm:0.6.0":
+ version: 0.6.0
+ resolution: "@formatjs/intl-localematcher@npm:0.6.0"
dependencies:
tslib: "npm:2"
- checksum: 10/119e36974607d5d3586570db93358c1f316c0736b83acc81dce88468435a9519a9e58a5abe6ed3078ffe40d6b4ad4613c6371e2a39cd570c9b6f561372ffb14d
+ checksum: 10/d8fd984c14121949d0ba60732a096aed6dccb2ab93770c4bffaea1170c85f5639d2a71fe6e2c68ab62bf6f3583a9c4b1fcd11bd5fb8837bfb2582228c33398c1
languageName: node
linkType: hard
@@ -3285,7 +3110,7 @@ __metadata:
rollup-plugin-dts: "npm:^6.1.1"
rollup-plugin-esbuild: "npm:6.2.0"
rollup-plugin-node-externals: "npm:^8.0.0"
- semver: "npm:7.7.0"
+ semver: "npm:^7.7.0"
tslib: "npm:2.8.1"
typescript: "npm:5.7.3"
languageName: unknown
@@ -3370,8 +3195,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana/flamegraph@workspace:packages/grafana-flamegraph"
dependencies:
- "@babel/core": "npm:7.26.7"
- "@babel/preset-env": "npm:7.26.7"
+ "@babel/core": "npm:7.26.9"
+ "@babel/preset-env": "npm:7.26.9"
"@babel/preset-react": "npm:7.26.3"
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:11.6.0-pre"
@@ -3611,7 +3436,7 @@ __metadata:
resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus"
dependencies:
"@emotion/css": "npm:11.13.5"
- "@floating-ui/react": "npm:0.27.3"
+ "@floating-ui/react": "npm:0.27.5"
"@grafana/data": "npm:11.6.0-pre"
"@grafana/e2e-selectors": "npm:11.6.0-pre"
"@grafana/plugin-ui": "npm:0.10.1"
@@ -3741,7 +3566,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana/saga-icons@workspace:packages/grafana-icons"
dependencies:
- "@babel/core": "npm:7.26.7"
+ "@babel/core": "npm:7.26.9"
"@grafana/tsconfig": "npm:^2.0.0"
"@rollup/plugin-node-resolve": "npm:^16.0.0"
"@rollup/plugin-typescript": "npm:^12.1.0"
@@ -3772,11 +3597,11 @@ __metadata:
languageName: unknown
linkType: soft
-"@grafana/scenes-react@npm:6.0.2":
- version: 6.0.2
- resolution: "@grafana/scenes-react@npm:6.0.2"
+"@grafana/scenes-react@npm:6.1.4":
+ version: 6.1.4
+ resolution: "@grafana/scenes-react@npm:6.1.4"
dependencies:
- "@grafana/scenes": "npm:6.0.2"
+ "@grafana/scenes": "npm:6.1.4"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3788,13 +3613,13 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/9744e01f2ff912229e43cedfa41d626ccdfd034f5b9718b57c593bc90edadade960f76baf1d8ad19eed03709c17c62397df1871b89acc635172aa14f6a20e096
+ checksum: 10/69a344f30937a80e25201c8ce1261f85c10663e68d70103c28603abbee2496c7855d999b2a212132f48af597f3f528de65bade66c05b02c719ca25f8f8c682bd
languageName: node
linkType: hard
-"@grafana/scenes@npm:6.0.2":
- version: 6.0.2
- resolution: "@grafana/scenes@npm:6.0.2"
+"@grafana/scenes@npm:6.1.4":
+ version: 6.1.4
+ resolution: "@grafana/scenes@npm:6.1.4"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
@@ -3812,7 +3637,7 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
- checksum: 10/2584f296db6299ef0a09d51f5c267ebcf7e44bd17b4d6516e38d3220f8f1d7aebc63c5fc6523979c4ac4d3f555416ca573e85e03bd36eb33a11941a5b3497149
+ checksum: 10/708652236c3b4a5bb0e1cd84739bef530b06244e2798e754347eabd8ab040b24c7f39376fd7b93d1049da6c0ee40785294882cba0125ce8171d5627a71ae63db
languageName: node
linkType: hard
@@ -3975,12 +3800,12 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana/ui@workspace:packages/grafana-ui"
dependencies:
- "@babel/core": "npm:7.26.7"
+ "@babel/core": "npm:7.26.9"
"@emotion/css": "npm:11.13.5"
"@emotion/react": "npm:11.14.0"
"@emotion/serialize": "npm:1.3.3"
"@faker-js/faker": "npm:^9.0.0"
- "@floating-ui/react": "npm:0.27.3"
+ "@floating-ui/react": "npm:0.27.5"
"@grafana/data": "npm:11.6.0-pre"
"@grafana/e2e-selectors": "npm:11.6.0-pre"
"@grafana/faro-web-sdk": "npm:^1.13.2"
@@ -3995,22 +3820,22 @@ __metadata:
"@react-aria/overlays": "npm:3.25.0"
"@react-aria/utils": "npm:3.27.0"
"@rollup/plugin-node-resolve": "npm:16.0.0"
- "@storybook/addon-a11y": "npm:^8.4.2"
- "@storybook/addon-actions": "npm:^8.4.2"
- "@storybook/addon-docs": "npm:^8.4.2"
- "@storybook/addon-essentials": "npm:^8.4.2"
- "@storybook/addon-storysource": "npm:^8.4.2"
- "@storybook/addon-webpack5-compiler-swc": "npm:^1.0.5"
- "@storybook/blocks": "npm:^8.4.2"
- "@storybook/components": "npm:^8.4.2"
- "@storybook/core-events": "npm:^8.4.2"
- "@storybook/manager-api": "npm:^8.4.2"
+ "@storybook/addon-a11y": "npm:^8.6.2"
+ "@storybook/addon-actions": "npm:^8.6.2"
+ "@storybook/addon-docs": "npm:^8.6.2"
+ "@storybook/addon-essentials": "npm:^8.6.2"
+ "@storybook/addon-storysource": "npm:^8.6.2"
+ "@storybook/addon-webpack5-compiler-swc": "npm:^2.1.0"
+ "@storybook/blocks": "npm:^8.6.2"
+ "@storybook/components": "npm:^8.6.2"
+ "@storybook/core-events": "npm:^8.6.2"
+ "@storybook/manager-api": "npm:^8.6.2"
"@storybook/mdx2-csf": "npm:1.1.0"
"@storybook/preset-scss": "npm:1.0.3"
- "@storybook/preview-api": "npm:^8.4.2"
- "@storybook/react": "npm:^8.4.2"
- "@storybook/react-webpack5": "npm:^8.4.2"
- "@storybook/theming": "npm:^8.4.2"
+ "@storybook/preview-api": "npm:^8.6.2"
+ "@storybook/react": "npm:^8.6.2"
+ "@storybook/react-webpack5": "npm:^8.6.2"
+ "@storybook/theming": "npm:^8.6.2"
"@tanstack/react-virtual": "npm:^3.5.1"
"@testing-library/dom": "npm:10.4.0"
"@testing-library/jest-dom": "npm:6.6.3"
@@ -4102,7 +3927,7 @@ __metadata:
slate: "npm:0.47.9"
slate-plain-serializer: "npm:0.7.13"
slate-react: "npm:0.22.10"
- storybook: "npm:^8.4.2"
+ storybook: "npm:^8.6.2"
style-loader: "npm:4.0.0"
tinycolor2: "npm:1.6.0"
tslib: "npm:2.8.1"
@@ -5619,6 +5444,21 @@ __metadata:
languageName: node
linkType: hard
+"@npmcli/package-json@npm:^5.2.0":
+ version: 5.2.1
+ resolution: "@npmcli/package-json@npm:5.2.1"
+ dependencies:
+ "@npmcli/git": "npm:^5.0.0"
+ glob: "npm:^10.2.2"
+ hosted-git-info: "npm:^7.0.0"
+ json-parse-even-better-errors: "npm:^3.0.0"
+ normalize-package-data: "npm:^6.0.0"
+ proc-log: "npm:^4.0.0"
+ semver: "npm:^7.5.3"
+ checksum: 10/304a819b93f79a6e0e56cb371961a66d2db72142e310d545ecbbbe4d917025a30601aa8e63a5f0cc28f0fe281c116bdaf79b334619b105a1d027a2b769ecd137
+ languageName: node
+ linkType: hard
+
"@npmcli/promise-spawn@npm:^7.0.0":
version: 7.0.2
resolution: "@npmcli/promise-spawn@npm:7.0.2"
@@ -7567,21 +7407,22 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/addon-a11y@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/addon-a11y@npm:8.4.4"
+"@storybook/addon-a11y@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-a11y@npm:8.6.2"
dependencies:
- "@storybook/addon-highlight": "npm:8.4.4"
+ "@storybook/addon-highlight": "npm:8.6.2"
+ "@storybook/test": "npm:8.6.2"
axe-core: "npm:^4.2.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/1621f3f94adac5cd860c8e80d9efa005300ce1ef52afd6f5d61e44fcd7f8967d94499cb640b86c1f731a67d9a4d1e1db77d403d431a67c4689571f826bbaf151
+ storybook: ^8.6.2
+ checksum: 10/c7a161734c4d587bbc2b926fcf01103203b4f50112f5ea19bdbd4dad4c1c62bb358613e3c6e608335f064ef7b7627f5a1ffeb1f524ec008bb19e86e50b1dfb27
languageName: node
linkType: hard
-"@storybook/addon-actions@npm:8.4.4, @storybook/addon-actions@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/addon-actions@npm:8.4.4"
+"@storybook/addon-actions@npm:8.6.2, @storybook/addon-actions@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-actions@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@types/uuid": "npm:^9.0.1"
@@ -7589,178 +7430,176 @@ __metadata:
polished: "npm:^4.2.2"
uuid: "npm:^9.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/e8af0e8af39fecff49948b7baf2957e54b53b505f1ae4dec29e9284db75405868f7fed9c692b045bbc6a606cb8c03c26563a717c6521d52ca04eebef4e6a4897
+ storybook: ^8.6.2
+ checksum: 10/16127ee35f08fe98df98a688a8724c803274cbe1a81d3ae46727ad6b34bdae913dd905a3a124954805e4f2650e56e18f80de2f4cf7cb0fc0480c185cf342a8e5
languageName: node
linkType: hard
-"@storybook/addon-backgrounds@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-backgrounds@npm:8.4.4"
+"@storybook/addon-backgrounds@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-backgrounds@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
memoizerific: "npm:^1.11.3"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/1b45008c1b7da28e331f90f825fbff497af793665e79bdb4b36767bf1943e5df55f8d1d4fa27a4c4329991aaecbb911a447c119dc2fd094962a22276889e944f
+ storybook: ^8.6.2
+ checksum: 10/b303afb745fb34cb77565f595fdd5f1749927f3be6bc58702f3ed6b8931d89abc099b46940436a1f0509b4a6580f7c58100bf091ba6e5fe7f040704ac723118f
languageName: node
linkType: hard
-"@storybook/addon-controls@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-controls@npm:8.4.4"
+"@storybook/addon-controls@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-controls@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
dequal: "npm:^2.0.2"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/4008300f0cb02c140685a435e4c7a8af6a981c3f21b101add66a503b848f9785ddfcc623389718f3aa14a95308b9715eac16787feb9377da2aad88711e0f365b
+ storybook: ^8.6.2
+ checksum: 10/4d54617c514b6e88d5708e2d3b067a01f3863e83dcf327bdec0b55df36bf8e6269ab1d9e5c5cb0edb27e9554c2d212ce087356b9d68779744854207febd26808
languageName: node
linkType: hard
-"@storybook/addon-docs@npm:8.4.4, @storybook/addon-docs@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/addon-docs@npm:8.4.4"
+"@storybook/addon-docs@npm:8.6.2, @storybook/addon-docs@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-docs@npm:8.6.2"
dependencies:
"@mdx-js/react": "npm:^3.0.0"
- "@storybook/blocks": "npm:8.4.4"
- "@storybook/csf-plugin": "npm:8.4.4"
- "@storybook/react-dom-shim": "npm:8.4.4"
- react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
- react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
+ "@storybook/blocks": "npm:8.6.2"
+ "@storybook/csf-plugin": "npm:8.6.2"
+ "@storybook/react-dom-shim": "npm:8.6.2"
+ react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/991c766ea4366bae9fb0b65d7e86a8325231e3e1f5ee6924d2bb9cdbba9a66d3b20780787987d6dd9c4f9977708f0321791820004b33fd5f5255294ce9786f8c
+ storybook: ^8.6.2
+ checksum: 10/d13752c4f31f01426724dbfdc70313948475dbbf6c43ed1cceca8d37d86424f79369c1b06c5e4daebe581e8702b02c9522ea77aff1864a7228662f4c8517fedf
languageName: node
linkType: hard
-"@storybook/addon-essentials@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/addon-essentials@npm:8.4.4"
+"@storybook/addon-essentials@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-essentials@npm:8.6.2"
dependencies:
- "@storybook/addon-actions": "npm:8.4.4"
- "@storybook/addon-backgrounds": "npm:8.4.4"
- "@storybook/addon-controls": "npm:8.4.4"
- "@storybook/addon-docs": "npm:8.4.4"
- "@storybook/addon-highlight": "npm:8.4.4"
- "@storybook/addon-measure": "npm:8.4.4"
- "@storybook/addon-outline": "npm:8.4.4"
- "@storybook/addon-toolbars": "npm:8.4.4"
- "@storybook/addon-viewport": "npm:8.4.4"
+ "@storybook/addon-actions": "npm:8.6.2"
+ "@storybook/addon-backgrounds": "npm:8.6.2"
+ "@storybook/addon-controls": "npm:8.6.2"
+ "@storybook/addon-docs": "npm:8.6.2"
+ "@storybook/addon-highlight": "npm:8.6.2"
+ "@storybook/addon-measure": "npm:8.6.2"
+ "@storybook/addon-outline": "npm:8.6.2"
+ "@storybook/addon-toolbars": "npm:8.6.2"
+ "@storybook/addon-viewport": "npm:8.6.2"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/c8dfe7be1eac6280e4ae801a33a02f99f47e0430920d22505b80fe8684eaabaa9c977a34f90c3265185f11ded930c5b29cdc11fbaa715eec57cab95ad4343750
+ storybook: ^8.6.2
+ checksum: 10/2e8a9cb6fed038122230929f72dfc94116c7884d838f633b81f1b485bf169129cf8e91bc31cc9949cbfc39d31ada1d5ae4a8bc61c533b47c9b63ea7d5045c468
languageName: node
linkType: hard
-"@storybook/addon-highlight@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-highlight@npm:8.4.4"
+"@storybook/addon-highlight@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-highlight@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/9944ce08c8f36dc4bbdf24536b5ffde328b4032e7165e15f7bd7af5ab5adb0b9a5be49f43ab96068ea00d8acd1450f10436daa70b32ae3492364fe86a31dffb7
+ storybook: ^8.6.2
+ checksum: 10/0bd8298612390daa6d455876c3485e8e60751f51d6a2e67c79d2bb37b27a5059b1fab3ed4117371f6b375a6294a5e038abe6bd65db9259723578e4c55b0abcd2
languageName: node
linkType: hard
-"@storybook/addon-measure@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-measure@npm:8.4.4"
+"@storybook/addon-measure@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-measure@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
tiny-invariant: "npm:^1.3.1"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/cebd85f454026cdf46eac29a80725bb7a353b9e08c7553e2b948488413092b8d85ab1667ed1381dfcede92fd289707b1092d217df7ecc7cd5ea6d8da356b9e48
+ storybook: ^8.6.2
+ checksum: 10/c81946d8459aa953f633503872f30ae13336cfb9c7e539c49dccbeae6f33e57e774ff35a197d3aea46d28a33c0a45a497a14eacd403817fa87572d377d5ad4d9
languageName: node
linkType: hard
-"@storybook/addon-outline@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-outline@npm:8.4.4"
+"@storybook/addon-outline@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-outline@npm:8.6.2"
dependencies:
"@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/1405f812b780b2179f611be6404eeb6ce3bc7760372753c6bc176c298e396b1d90b997e1f31a9e8f828fd4f897489ce17158498c82e4a2c139c1576e250d8102
+ storybook: ^8.6.2
+ checksum: 10/357a72cb76cd8d1d2e7ff5ab1fc152f17b1f8c4ab087d8b94e1f157554a641aa47d96ede8087d5045eda8b99dfc2d94c29c2bdc1dce8f8d7d0c0a0b660aae966
languageName: node
linkType: hard
-"@storybook/addon-storysource@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/addon-storysource@npm:8.4.4"
+"@storybook/addon-storysource@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-storysource@npm:8.6.2"
dependencies:
- "@storybook/source-loader": "npm:8.4.4"
+ "@storybook/source-loader": "npm:8.6.2"
estraverse: "npm:^5.2.0"
tiny-invariant: "npm:^1.3.1"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/639177cbd8c8fdc884bec1b8ef2f9fc83778bd48dcccbaef1d4409548f27c7cbe242571e78f8d4092c988fc7a99fd54f104923842d5864aeb74d59e5a6ff6e03
+ storybook: ^8.6.2
+ checksum: 10/4c06dab42fd2ff88df632960bfc70144e7ab423bec373be7f1b318519a383f33515a296440e77c085345e220af5de77dd9c500a49fcc66e23db9612164d94d9f
languageName: node
linkType: hard
-"@storybook/addon-toolbars@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-toolbars@npm:8.4.4"
+"@storybook/addon-toolbars@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-toolbars@npm:8.6.2"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/efd3cbf3e63f103aacd2471a319ea46ca666f1b6be65c9fe7518322eaf149ea18aefc1146ec2abca2231edb7c2b55f430306d77e041ff10493e79b6b353b9b04
+ storybook: ^8.6.2
+ checksum: 10/7c7863a1e9698128557cf38bdce81aab127958c4892aeaa2f9035042d0fd36edcabb296575cf02a69d8f19abe4b1b114223a6312f25c84dff827314f004303ee
languageName: node
linkType: hard
-"@storybook/addon-viewport@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/addon-viewport@npm:8.4.4"
+"@storybook/addon-viewport@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/addon-viewport@npm:8.6.2"
dependencies:
memoizerific: "npm:^1.11.3"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/94c193f3cc4d759a696ba8f15d0211ea74af8640f79428040eb6914e153308ac54ba0b82e6dff0769ae05c02d229a74809c5999a7631aef5987e00361af4dab2
+ storybook: ^8.6.2
+ checksum: 10/60e67fb0b2f21c889f416bbb7d1729bf3310f56efa23d016d9984f6a9fce39cccccc02422b6ece736a558fef0053c5ac39d46df1735aa2e560e580a3fae3337f
languageName: node
linkType: hard
-"@storybook/addon-webpack5-compiler-swc@npm:^1.0.5":
- version: 1.0.5
- resolution: "@storybook/addon-webpack5-compiler-swc@npm:1.0.5"
+"@storybook/addon-webpack5-compiler-swc@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "@storybook/addon-webpack5-compiler-swc@npm:2.1.0"
dependencies:
- "@swc/core": "npm:^1.7.3"
- swc-loader: "npm:^0.2.3"
- checksum: 10/9856a700a5d9a36e1c35b404ec90331ed19617f73f63256752959ee4e2904142c5b0d883b436b5b70e1ec2e04038b461d5cf1d616243fa8cfedd0b5d69b73caf
+ "@swc/core": "npm:^1.10.8"
+ swc-loader: "npm:^0.2.6"
+ checksum: 10/d91c17ebebdf67ee318f906d0601f3a85ca1500e212fa5a525efff815bd421030bd45167d5e98081dba9ddf224ea37c7ef79003228c3b3547219c0894f5a38de
languageName: node
linkType: hard
-"@storybook/blocks@npm:8.4.4, @storybook/blocks@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/blocks@npm:8.4.4"
+"@storybook/blocks@npm:8.6.2, @storybook/blocks@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/blocks@npm:8.6.2"
dependencies:
- "@storybook/csf": "npm:^0.1.11"
"@storybook/icons": "npm:^1.2.12"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ storybook: ^8.6.2
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
- checksum: 10/1b4686d9f819b8718edadefa0a7fb911a5a7ac80c2d2abbb281c153a84546c2332eb9be062eb31e7a49fa5c9bbc76878dd4afd4629895674a821e37ec00cdb40
+ checksum: 10/8137b042e99572b7bdd6df3484c75d3b1cf78b15bb7d3a7ad09738e94ec21481d295acfe2b59fa547be9ee5a0b075bb485f88a1972948e4703aef6b174b60ead
languageName: node
linkType: hard
-"@storybook/builder-webpack5@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/builder-webpack5@npm:8.4.4"
+"@storybook/builder-webpack5@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/builder-webpack5@npm:8.6.2"
dependencies:
- "@storybook/core-webpack": "npm:8.4.4"
- "@types/node": "npm:^22.0.0"
+ "@storybook/core-webpack": "npm:8.6.2"
"@types/semver": "npm:^7.3.4"
browser-assert: "npm:^1.2.1"
case-sensitive-paths-webpack-plugin: "npm:^2.4.0"
@@ -7785,52 +7624,51 @@ __metadata:
webpack-hot-middleware: "npm:^2.25.1"
webpack-virtual-modules: "npm:^0.6.0"
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.2
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/9894450cf1985b32947a2b27a091d4d5ac23c03f179a4653b7e98fd136b80ff6f4fce663c471eeda7db118d4c8b4bd1256dad5aea2d684bef00b7319aa11fe0c
+ checksum: 10/909d74c281a41a43d17ff9f313231283ac21c9c97ef47d8f8f96753f0b639dbdaeb2d12d46a7cd4fd8001bffcc2d707e83d47ab62fe3757ab30093159a1eded1
languageName: node
linkType: hard
-"@storybook/components@npm:8.4.4, @storybook/components@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/components@npm:8.4.4"
+"@storybook/components@npm:8.6.2, @storybook/components@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/components@npm:8.6.2"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/ca60bd5911669112af8d59e1b424e564a681e37c3f6ac59bceea69a08adddb478b965a7207f266591ec0daca74c8f674fdf3e21fdba5c794b80f13331667913a
+ checksum: 10/d85bb39aedd03a05043194debf3d35965dbe84d386029f63f0aa20ac943f4cc635558f6b08cc8ec013a2cc7f2407ef3c8735a76d4805e41569aaabe8efe59fd4
languageName: node
linkType: hard
-"@storybook/core-events@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/core-events@npm:8.4.4"
+"@storybook/core-events@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/core-events@npm:8.6.2"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/4aca6f98968350a78cfb7b5e709705026e65dfb792d00542ce8c236fef9d7225680dc4f3259b3203bcf8abf3674754fe908e9dfef975d3b4d49baa5a4a5664cf
+ checksum: 10/d2f574be4bc4fd5be82be376954b795dca291dd834b62ef4545eb912bf0c879bd6b4e544613cc399bbe5161c05788d32ad9119811d05921f690417fd3f1448f4
languageName: node
linkType: hard
-"@storybook/core-webpack@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/core-webpack@npm:8.4.4"
+"@storybook/core-webpack@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/core-webpack@npm:8.6.2"
dependencies:
- "@types/node": "npm:^22.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/e9f7677e45eaa7eee5d97b745fc9e64a83f13895e42fdc7eb71285a2e2295140b075e74b8b37d4eda2eb488327b4ec9b83201562b27204f93dc8d246929c04a5
+ storybook: ^8.6.2
+ checksum: 10/666edcb895b034b74fa86bb6d6dc16d26078ffc3524d20917ca27c40b4f722706622f83c4f97a323f8fc441d655a5ab124c9fbea12a2fa2e25000cf1231a5e78
languageName: node
linkType: hard
-"@storybook/core@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/core@npm:8.4.4"
+"@storybook/core@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/core@npm:8.6.2"
dependencies:
- "@storybook/csf": "npm:^0.1.11"
+ "@storybook/theming": "npm:8.6.2"
better-opn: "npm:^3.0.2"
browser-assert: "npm:^1.2.1"
- esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0"
+ esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
esbuild-register: "npm:^3.5.0"
jsdoc-type-pratt-parser: "npm:^4.0.0"
process: "npm:^0.11.10"
@@ -7843,27 +7681,18 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
- checksum: 10/94458d2768e0fe28e75359a13fa15c80080b62f102a1283a6b4eb8a793ecd8d32d3544fc5c3eddfedfb69830bb88f264f72d034daea2a85e1fd7a102f3d57327
+ checksum: 10/57d8af6d822c4cbeab201aec813830d632165c2411bf8708481ad0daa2fa942ce6a912e0ef764e6acb9bd2053baf0614b7726e5827b071f3f845d459ac1f3d3a
languageName: node
linkType: hard
-"@storybook/csf-plugin@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/csf-plugin@npm:8.4.4"
+"@storybook/csf-plugin@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/csf-plugin@npm:8.6.2"
dependencies:
unplugin: "npm:^1.3.1"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/109c54af8aabaa64c2eeb8b0b6f1379b3588b8c1c3d4da828e0a0c8f6479960d64adc4635e911484915ac651061aa15e218cde1dd7be27562ce8e6ba79be7cae
- languageName: node
- linkType: hard
-
-"@storybook/csf@npm:^0.1.11":
- version: 0.1.11
- resolution: "@storybook/csf@npm:0.1.11"
- dependencies:
- type-fest: "npm:^2.19.0"
- checksum: 10/f6eeefe3b92ab206676587da9e22a775da026c055999681580d2ca23c98185736f965adc79039a0ae97ea625f0fbc7915cd4559e5db24229a4805784d0b78584
+ storybook: ^8.6.2
+ checksum: 10/6d71101640975cbe08d5dc9bae30938337b0e999f5724c4802713cf3d8c34671b646158ffde659e9cbcd005153844223cd4f9a4af2aa0d6f5a15b3db8d31f85d
languageName: node
linkType: hard
@@ -7884,12 +7713,24 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/manager-api@npm:8.4.4, @storybook/manager-api@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/manager-api@npm:8.4.4"
+"@storybook/instrumenter@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/instrumenter@npm:8.6.2"
+ dependencies:
+ "@storybook/global": "npm:^5.0.0"
+ "@vitest/utils": "npm:^2.1.1"
+ peerDependencies:
+ storybook: ^8.6.2
+ checksum: 10/40d028d6f8b5ab51eb112bb5b903f64438eaf818c501a78ef77a5e946676e13f15ad8f5188a85e40bf91f987c6ab15c6a33b4e8bef8b8f8e05011013dcda092a
+ languageName: node
+ linkType: hard
+
+"@storybook/manager-api@npm:8.6.2, @storybook/manager-api@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/manager-api@npm:8.6.2"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/9eac4488f55e1860940e0e850a48f4278a7b6e6325c7381007447626715a8f8e52d6f876f6caaa2597787fb19c40a33f96d34b56558190f7be4772cac7805ebc
+ checksum: 10/d344c88c6cad0bcc54767a8ef1d269a9df868c76ae22927c31936e46aaeb47075783d84f8815b4d0c7812ed50b2d4616417429479e3a9ea36043c557eb141590
languageName: node
linkType: hard
@@ -7900,14 +7741,13 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/preset-react-webpack@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/preset-react-webpack@npm:8.4.4"
+"@storybook/preset-react-webpack@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/preset-react-webpack@npm:8.6.2"
dependencies:
- "@storybook/core-webpack": "npm:8.4.4"
- "@storybook/react": "npm:8.4.4"
+ "@storybook/core-webpack": "npm:8.6.2"
+ "@storybook/react": "npm:8.6.2"
"@storybook/react-docgen-typescript-plugin": "npm:1.0.6--canary.9.0c3f3b7.0"
- "@types/node": "npm:^22.0.0"
"@types/semver": "npm:^7.3.4"
find-up: "npm:^5.0.0"
magic-string: "npm:^0.30.5"
@@ -7919,11 +7759,11 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
+ storybook: ^8.6.2
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/acff2bec1907610cca8e17d4e782d1ebe1747861a77426ed2d20f403a6f022c006ac56d9eb085c1ae6110f7630ccc8a8a2c154d9a9f90fb54e082f45f0002d98
+ checksum: 10/d903a14e6e65bdfb56568962f456a103ec3f64d339d4ed3d091befe66dddcff2e31d7a2acee1bbf52cf9a33d09564af2dc36483961f73c812eacb34d71f7a951
languageName: node
linkType: hard
@@ -7938,12 +7778,12 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/preview-api@npm:8.4.4, @storybook/preview-api@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/preview-api@npm:8.4.4"
+"@storybook/preview-api@npm:8.6.2, @storybook/preview-api@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/preview-api@npm:8.6.2"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/ad95ce132d598e8b13da70a319ffdfaf41e5f8629cfb4552a577f3624a92d4a168e1de5220fe7c735babbd5945dd2182448a910b246a458cddde2afe7378461e
+ checksum: 10/5d286ed8c266a8aa63361bbb0245163e6fe3fd85a5e60555a79a579535faf36c30a9ad07bff30b0d9d536d35ec8f5ee78484629b174ecc965cbe0726e749d6de
languageName: node
linkType: hard
@@ -7965,82 +7805,97 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/react-dom-shim@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/react-dom-shim@npm:8.4.4"
+"@storybook/react-dom-shim@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/react-dom-shim@npm:8.6.2"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
- checksum: 10/151b4669746da46d7c24531d0f33dc1e99301d45c4653de4dcf02c557adc2d8eea305963a681ff0eb47c8e175065928509924c69f3007e5a5cafe3a048a9c344
+ storybook: ^8.6.2
+ checksum: 10/f32718a49ccbd7c01233c83d738479eb60c41a3f8855066b85593fb7a07129b1b18a0c93fe64b01dce9c9105293b40c1e131410169d5aaaa2f8b7a7c00f836ee
languageName: node
linkType: hard
-"@storybook/react-webpack5@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/react-webpack5@npm:8.4.4"
+"@storybook/react-webpack5@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/react-webpack5@npm:8.6.2"
dependencies:
- "@storybook/builder-webpack5": "npm:8.4.4"
- "@storybook/preset-react-webpack": "npm:8.4.4"
- "@storybook/react": "npm:8.4.4"
- "@types/node": "npm:^22.0.0"
+ "@storybook/builder-webpack5": "npm:8.6.2"
+ "@storybook/preset-react-webpack": "npm:8.6.2"
+ "@storybook/react": "npm:8.6.2"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
+ storybook: ^8.6.2
typescript: ">= 4.2.x"
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/baa0c17c87d5c21c55cd485a7aea96c3314241c44064b1812be0b5d6bba7ae39521f3d030a0a0f1a155f4de4b5f444a3d61d47dcffa801e19410bac00cd9dfb3
+ checksum: 10/1d8c745d21da7853328a1870797808982864be68783c2a56248ab642a81eaad667f5522cc9f0eba74a9b96658d6fcd9b6a5b9296e235ff8e1566edbc48e5d146
languageName: node
linkType: hard
-"@storybook/react@npm:8.4.4, @storybook/react@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/react@npm:8.4.4"
+"@storybook/react@npm:8.6.2, @storybook/react@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/react@npm:8.6.2"
dependencies:
- "@storybook/components": "npm:8.4.4"
+ "@storybook/components": "npm:8.6.2"
"@storybook/global": "npm:^5.0.0"
- "@storybook/manager-api": "npm:8.4.4"
- "@storybook/preview-api": "npm:8.4.4"
- "@storybook/react-dom-shim": "npm:8.4.4"
- "@storybook/theming": "npm:8.4.4"
+ "@storybook/manager-api": "npm:8.6.2"
+ "@storybook/preview-api": "npm:8.6.2"
+ "@storybook/react-dom-shim": "npm:8.6.2"
+ "@storybook/theming": "npm:8.6.2"
peerDependencies:
- "@storybook/test": 8.4.4
+ "@storybook/test": 8.6.2
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
+ storybook: ^8.6.2
typescript: ">= 4.2.x"
peerDependenciesMeta:
"@storybook/test":
optional: true
typescript:
optional: true
- checksum: 10/06be762e00ebf06398ed233dc6347ab058f4a26c99f373a3d5f8f089fa2b68b4d328e5304e30d3e2aee049d0d947c1627637f0ceb38edc715d4a58bb9256b758
+ checksum: 10/b8a91e6a8aeb9e32e05e12db4df1dafd59b54d82954e481ea9224316bd1e432093259b474f773911e90aeebe441cbb17e8dc0a6940b79b5899eff1f2b4aae334
languageName: node
linkType: hard
-"@storybook/source-loader@npm:8.4.4":
- version: 8.4.4
- resolution: "@storybook/source-loader@npm:8.4.4"
+"@storybook/source-loader@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/source-loader@npm:8.6.2"
dependencies:
- "@storybook/csf": "npm:^0.1.11"
es-toolkit: "npm:^1.22.0"
estraverse: "npm:^5.2.0"
prettier: "npm:^3.1.1"
peerDependencies:
- storybook: ^8.4.4
- checksum: 10/25cf2dcc5bd628ca240ab8527ade65b206036f5f1b6da9481b19c1854832b221f0285e4483925b9b00c9fcb0939bb7d1b866230b2ae1ad8726b97a3387022011
+ storybook: ^8.6.2
+ checksum: 10/8cf43eb6ce2df997272c71f3d9f8e85f3fecb6ca1d176c87fd349ea17e1d821dadb43496af5d10a2c6163fd6d920940a253a075a7774fc9bb5f724c132cfcaf0
languageName: node
linkType: hard
-"@storybook/theming@npm:8.4.4, @storybook/theming@npm:^8.4.2":
- version: 8.4.4
- resolution: "@storybook/theming@npm:8.4.4"
+"@storybook/test@npm:8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/test@npm:8.6.2"
+ dependencies:
+ "@storybook/global": "npm:^5.0.0"
+ "@storybook/instrumenter": "npm:8.6.2"
+ "@testing-library/dom": "npm:10.4.0"
+ "@testing-library/jest-dom": "npm:6.5.0"
+ "@testing-library/user-event": "npm:14.5.2"
+ "@vitest/expect": "npm:2.0.5"
+ "@vitest/spy": "npm:2.0.5"
+ peerDependencies:
+ storybook: ^8.6.2
+ checksum: 10/4cb89e254143374716fcd72c3a9a4c603a9f664162c5755ead7257ce130bc33d3c48c9cbaa35274023f4b961d66c4e84733e35ca663019d770fcfa6ef5b21b84
+ languageName: node
+ linkType: hard
+
+"@storybook/theming@npm:8.6.2, @storybook/theming@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "@storybook/theming@npm:8.6.2"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/8a39c0c0c519a98491de3c8f29d3c9ab2d2bb5ee616933dd07aaed276cd46635e1b3019902b40dc073e7ee0679b5760234c920e455817a13e8d1d753811553ee
+ checksum: 10/81ff1f740edaa000d6abaab5a47b038b46cfc54ddad308335b8d26d7a6f1ee100f617f52d51cd6596f424a534dbda0d9801e3928b4b6f758d9a3e8da6f9d40f5
languageName: node
linkType: hard
@@ -8790,6 +8645,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-darwin-arm64@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-darwin-arm64@npm:1.11.4"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@swc/core-darwin-x64@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-darwin-x64@npm:1.10.12"
@@ -8797,6 +8659,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-darwin-x64@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-darwin-x64@npm:1.11.4"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@swc/core-linux-arm-gnueabihf@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.12"
@@ -8804,6 +8673,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-linux-arm-gnueabihf@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.4"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@swc/core-linux-arm64-gnu@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-linux-arm64-gnu@npm:1.10.12"
@@ -8811,6 +8687,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-linux-arm64-gnu@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-linux-arm64-gnu@npm:1.11.4"
+ conditions: os=linux & cpu=arm64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@swc/core-linux-arm64-musl@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-linux-arm64-musl@npm:1.10.12"
@@ -8818,6 +8701,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-linux-arm64-musl@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-linux-arm64-musl@npm:1.11.4"
+ conditions: os=linux & cpu=arm64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@swc/core-linux-x64-gnu@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-linux-x64-gnu@npm:1.10.12"
@@ -8825,6 +8715,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-linux-x64-gnu@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-linux-x64-gnu@npm:1.11.4"
+ conditions: os=linux & cpu=x64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@swc/core-linux-x64-musl@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-linux-x64-musl@npm:1.10.12"
@@ -8832,6 +8729,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-linux-x64-musl@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-linux-x64-musl@npm:1.11.4"
+ conditions: os=linux & cpu=x64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@swc/core-win32-arm64-msvc@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-win32-arm64-msvc@npm:1.10.12"
@@ -8839,6 +8743,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-win32-arm64-msvc@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-win32-arm64-msvc@npm:1.11.4"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@swc/core-win32-ia32-msvc@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-win32-ia32-msvc@npm:1.10.12"
@@ -8846,6 +8757,13 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core-win32-ia32-msvc@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-win32-ia32-msvc@npm:1.11.4"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@swc/core-win32-x64-msvc@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core-win32-x64-msvc@npm:1.10.12"
@@ -8853,7 +8771,14 @@ __metadata:
languageName: node
linkType: hard
-"@swc/core@npm:1.10.12, @swc/core@npm:^1.7.3":
+"@swc/core-win32-x64-msvc@npm:1.11.4":
+ version: 1.11.4
+ resolution: "@swc/core-win32-x64-msvc@npm:1.11.4"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@swc/core@npm:1.10.12":
version: 1.10.12
resolution: "@swc/core@npm:1.10.12"
dependencies:
@@ -8899,6 +8824,52 @@ __metadata:
languageName: node
linkType: hard
+"@swc/core@npm:^1.10.8":
+ version: 1.11.4
+ resolution: "@swc/core@npm:1.11.4"
+ dependencies:
+ "@swc/core-darwin-arm64": "npm:1.11.4"
+ "@swc/core-darwin-x64": "npm:1.11.4"
+ "@swc/core-linux-arm-gnueabihf": "npm:1.11.4"
+ "@swc/core-linux-arm64-gnu": "npm:1.11.4"
+ "@swc/core-linux-arm64-musl": "npm:1.11.4"
+ "@swc/core-linux-x64-gnu": "npm:1.11.4"
+ "@swc/core-linux-x64-musl": "npm:1.11.4"
+ "@swc/core-win32-arm64-msvc": "npm:1.11.4"
+ "@swc/core-win32-ia32-msvc": "npm:1.11.4"
+ "@swc/core-win32-x64-msvc": "npm:1.11.4"
+ "@swc/counter": "npm:^0.1.3"
+ "@swc/types": "npm:^0.1.19"
+ peerDependencies:
+ "@swc/helpers": "*"
+ dependenciesMeta:
+ "@swc/core-darwin-arm64":
+ optional: true
+ "@swc/core-darwin-x64":
+ optional: true
+ "@swc/core-linux-arm-gnueabihf":
+ optional: true
+ "@swc/core-linux-arm64-gnu":
+ optional: true
+ "@swc/core-linux-arm64-musl":
+ optional: true
+ "@swc/core-linux-x64-gnu":
+ optional: true
+ "@swc/core-linux-x64-musl":
+ optional: true
+ "@swc/core-win32-arm64-msvc":
+ optional: true
+ "@swc/core-win32-ia32-msvc":
+ optional: true
+ "@swc/core-win32-x64-msvc":
+ optional: true
+ peerDependenciesMeta:
+ "@swc/helpers":
+ optional: true
+ checksum: 10/9dce8808f8c17be58c0e42f14d1c257db0418f7f426774e2f05794eb4b9011445c7ac13805e7ae0dad53b858720eabaffe0bcc5366696007e166bc4ab50fbe4d
+ languageName: node
+ linkType: hard
+
"@swc/counter@npm:^0.1.3":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
@@ -8915,12 +8886,12 @@ __metadata:
languageName: node
linkType: hard
-"@swc/types@npm:^0.1.17":
- version: 0.1.17
- resolution: "@swc/types@npm:0.1.17"
+"@swc/types@npm:^0.1.17, @swc/types@npm:^0.1.19":
+ version: 0.1.19
+ resolution: "@swc/types@npm:0.1.19"
dependencies:
"@swc/counter": "npm:^0.1.3"
- checksum: 10/ddef1ad5bfead3acdfc41f14e79ba43a99200eb325afbad5716058dbe36358b0513400e9f22aff32432be84a98ae93df95a20b94192f69b8687144270e4eaa18
+ checksum: 10/693147cc9b23147164ddff9cb89477c369fbeb103319584779352a9ff1c72e0a70b97a89dfd97629040db8956d668d7b7a8fed328ffea46a3e8c18577e396994
languageName: node
linkType: hard
@@ -9023,6 +8994,21 @@ __metadata:
languageName: node
linkType: hard
+"@testing-library/jest-dom@npm:6.5.0":
+ version: 6.5.0
+ resolution: "@testing-library/jest-dom@npm:6.5.0"
+ dependencies:
+ "@adobe/css-tools": "npm:^4.4.0"
+ aria-query: "npm:^5.0.0"
+ chalk: "npm:^3.0.0"
+ css.escape: "npm:^1.5.1"
+ dom-accessibility-api: "npm:^0.6.3"
+ lodash: "npm:^4.17.21"
+ redent: "npm:^3.0.0"
+ checksum: 10/3d2080888af5fd7306f57448beb5a23f55d965e265b5e53394fffc112dfb0678d616a5274ff0200c46c7618f293520f86fc8562eecd8bdbc0dbb3294d63ec431
+ languageName: node
+ linkType: hard
+
"@testing-library/jest-dom@npm:6.6.3, @testing-library/jest-dom@npm:^6.1.2":
version: 6.6.3
resolution: "@testing-library/jest-dom@npm:6.6.3"
@@ -9058,6 +9044,15 @@ __metadata:
languageName: node
linkType: hard
+"@testing-library/user-event@npm:14.5.2":
+ version: 14.5.2
+ resolution: "@testing-library/user-event@npm:14.5.2"
+ peerDependencies:
+ "@testing-library/dom": ">=7.21.4"
+ checksum: 10/49821459d81c6bc435d97128d6386ca24f1e4b3ba8e46cb5a96fe3643efa6e002d88c1b02b7f2ec58da593e805c59b78d7fdf0db565c1f02ba782f63ee984040
+ languageName: node
+ linkType: hard
+
"@testing-library/user-event@npm:14.6.1":
version: 14.6.1
resolution: "@testing-library/user-event@npm:14.6.1"
@@ -10064,7 +10059,7 @@ __metadata:
languageName: node
linkType: hard
-"@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":
+"@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":
version: 22.12.0
resolution: "@types/node@npm:22.12.0"
dependencies:
@@ -10954,6 +10949,68 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/expect@npm:2.0.5":
+ version: 2.0.5
+ resolution: "@vitest/expect@npm:2.0.5"
+ dependencies:
+ "@vitest/spy": "npm:2.0.5"
+ "@vitest/utils": "npm:2.0.5"
+ chai: "npm:^5.1.1"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10/ca9a218f50254b2259fd16166b2d8c9ccc8ee2cc068905e6b3d6281da10967b1590cc7d34b5fa9d429297f97e740450233745583b4cc12272ff11705faf70a37
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:2.0.5":
+ version: 2.0.5
+ resolution: "@vitest/pretty-format@npm:2.0.5"
+ dependencies:
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10/70bf452dd0b8525e658795125b3f11110bd6baadfaa38c5bb91ca763bded35ec6dc80e27964ad4e91b91be6544d35e18ea7748c1997693988f975a7283c3e9a0
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:2.1.9":
+ version: 2.1.9
+ resolution: "@vitest/pretty-format@npm:2.1.9"
+ dependencies:
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10/557dc637c5825abd62ccb15080e59e04d22121e746d8020a0815d7c0c45132fed81b1ff36b26f5991e57a9f1d36e52aa19712abbfe1d0cbcd14252b449a919dc
+ languageName: node
+ linkType: hard
+
+"@vitest/spy@npm:2.0.5":
+ version: 2.0.5
+ resolution: "@vitest/spy@npm:2.0.5"
+ dependencies:
+ tinyspy: "npm:^3.0.0"
+ checksum: 10/ed19f4c3bb4d3853241e8070979615138e24403ce4c137fa48c903b3af2c8b3ada2cc26aca9c1aa323bb314a457a8130a29acbb18dafd4e42737deefb2abf1ca
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:2.0.5":
+ version: 2.0.5
+ resolution: "@vitest/utils@npm:2.0.5"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.0.5"
+ estree-walker: "npm:^3.0.3"
+ loupe: "npm:^3.1.1"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10/d631d56d29c33bc8de631166b2b6691c470187a345469dfef7048befe6027e1c6ff9552f2ee11c8a247522c325c4a64bfcc73f8f0f0c525da39cb9f190f119f8
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:^2.1.1":
+ version: 2.1.9
+ resolution: "@vitest/utils@npm:2.1.9"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.1.9"
+ loupe: "npm:^3.1.2"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10/83d62d5703a3210a2f137c25dc4e797a7a1d74d5d2e14ecc33b274c7710304fa8b5099101c98bc8d66cc2bf18a14f88ebf21f0996a99d0ee1439ae23b49f3961
+ languageName: node
+ linkType: hard
+
"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1, @webassemblyjs/ast@npm:^1.14.1":
version: 1.14.1
resolution: "@webassemblyjs/ast@npm:1.14.1"
@@ -11860,6 +11917,13 @@ __metadata:
languageName: node
linkType: hard
+"assertion-error@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "assertion-error@npm:2.0.1"
+ checksum: 10/a0789dd882211b87116e81e2648ccb7f60340b34f19877dd020b39ebb4714e475eb943e14ba3e22201c221ef6645b7bfe10297e76b6ac95b48a9898c1211ce66
+ languageName: node
+ linkType: hard
+
"ast-types-flow@npm:^0.0.8":
version: 0.0.8
resolution: "ast-types-flow@npm:0.0.8"
@@ -12102,15 +12166,15 @@ __metadata:
languageName: node
linkType: hard
-"babel-plugin-polyfill-corejs3@npm:^0.10.6":
- version: 0.10.6
- resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6"
+"babel-plugin-polyfill-corejs3@npm:^0.11.0":
+ version: 0.11.1
+ resolution: "babel-plugin-polyfill-corejs3@npm:0.11.1"
dependencies:
- "@babel/helper-define-polyfill-provider": "npm:^0.6.2"
- core-js-compat: "npm:^3.38.0"
+ "@babel/helper-define-polyfill-provider": "npm:^0.6.3"
+ core-js-compat: "npm:^3.40.0"
peerDependencies:
"@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
- checksum: 10/360ac9054a57a18c540059dc627ad5d84d15f79790cb3d84d19a02eec7188c67d08a07db789c3822d6f5df22d918e296d1f27c4055fec2e287d328f09ea8a78a
+ checksum: 10/19a2978ee3462cc3b98e7d36e6537bf9fb1fb61f42fd96cb41e9313f2ac6f2c62380d94064366431eff537f342184720fe9bce73eb65fd57c5311d15e8648f62
languageName: node
linkType: hard
@@ -12480,7 +12544,7 @@ __metadata:
languageName: node
linkType: hard
-"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0":
+"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0, browserslist@npm:^4.24.3":
version: 4.24.4
resolution: "browserslist@npm:4.24.4"
dependencies:
@@ -12823,6 +12887,19 @@ __metadata:
languageName: node
linkType: hard
+"chai@npm:^5.1.1":
+ version: 5.2.0
+ resolution: "chai@npm:5.2.0"
+ dependencies:
+ assertion-error: "npm:^2.0.1"
+ check-error: "npm:^2.1.1"
+ deep-eql: "npm:^5.0.1"
+ loupe: "npm:^3.1.0"
+ pathval: "npm:^2.0.0"
+ checksum: 10/2ce03671c159c6a567bf1912756daabdbb7c075f3c0078f1b59d61da8d276936367ee696dfe093b49e1479d9ba93a6074c8e55d49791dddd8061728cdcad249e
+ languageName: node
+ linkType: hard
+
"chalk@npm:4.1.0":
version: 4.1.0
resolution: "chalk@npm:4.1.0"
@@ -12920,6 +12997,13 @@ __metadata:
languageName: node
linkType: hard
+"check-error@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "check-error@npm:2.1.1"
+ checksum: 10/d785ed17b1d4a4796b6e75c765a9a290098cf52ff9728ce0756e8ffd4293d2e419dd30c67200aee34202463b474306913f2fcfaf1890641026d9fc6966fea27a
+ languageName: node
+ linkType: hard
+
"check-more-types@npm:^2.24.0":
version: 2.24.0
resolution: "check-more-types@npm:2.24.0"
@@ -13750,12 +13834,12 @@ __metadata:
languageName: node
linkType: hard
-"core-js-compat@npm:^3.38.0, core-js-compat@npm:^3.38.1":
- version: 3.38.1
- resolution: "core-js-compat@npm:3.38.1"
+"core-js-compat@npm:^3.38.1, core-js-compat@npm:^3.40.0":
+ version: 3.40.0
+ resolution: "core-js-compat@npm:3.40.0"
dependencies:
- browserslist: "npm:^4.23.3"
- checksum: 10/4e2f219354fd268895f79486461a12df96f24ed307321482fe2a43529c5a64e7c16bcba654980ba217d603444f5141d43a79058aeac77511085f065c5da72207
+ browserslist: "npm:^4.24.3"
+ checksum: 10/3dd3d717b3d4ae0d9c2930d39c0f2a21ca6f195fcdd5711bda833557996c4d9f90277eab576423478e95689257e2de8d1a2623d6618084416bd224d10d5df9a4
languageName: node
linkType: hard
@@ -14964,6 +15048,13 @@ __metadata:
languageName: node
linkType: hard
+"deep-eql@npm:^5.0.1":
+ version: 5.0.2
+ resolution: "deep-eql@npm:5.0.2"
+ checksum: 10/a529b81e2ef8821621d20a36959a0328873a3e49d393ad11f8efe8559f31239494c2eb889b80342808674c475802ba95b9d6c4c27641b9a029405104c1b59fcf
+ languageName: node
+ linkType: hard
+
"deep-extend@npm:0.6.0":
version: 0.6.0
resolution: "deep-extend@npm:0.6.0"
@@ -15967,7 +16058,7 @@ __metadata:
languageName: node
linkType: hard
-"esbuild@npm:0.25.0, esbuild@npm:^0.25.0":
+"esbuild@npm:0.25.0, esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0":
version: 0.25.0
resolution: "esbuild@npm:0.25.0"
dependencies:
@@ -16053,92 +16144,6 @@ __metadata:
languageName: node
linkType: hard
-"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0":
- version: 0.24.2
- resolution: "esbuild@npm:0.24.2"
- dependencies:
- "@esbuild/aix-ppc64": "npm:0.24.2"
- "@esbuild/android-arm": "npm:0.24.2"
- "@esbuild/android-arm64": "npm:0.24.2"
- "@esbuild/android-x64": "npm:0.24.2"
- "@esbuild/darwin-arm64": "npm:0.24.2"
- "@esbuild/darwin-x64": "npm:0.24.2"
- "@esbuild/freebsd-arm64": "npm:0.24.2"
- "@esbuild/freebsd-x64": "npm:0.24.2"
- "@esbuild/linux-arm": "npm:0.24.2"
- "@esbuild/linux-arm64": "npm:0.24.2"
- "@esbuild/linux-ia32": "npm:0.24.2"
- "@esbuild/linux-loong64": "npm:0.24.2"
- "@esbuild/linux-mips64el": "npm:0.24.2"
- "@esbuild/linux-ppc64": "npm:0.24.2"
- "@esbuild/linux-riscv64": "npm:0.24.2"
- "@esbuild/linux-s390x": "npm:0.24.2"
- "@esbuild/linux-x64": "npm:0.24.2"
- "@esbuild/netbsd-arm64": "npm:0.24.2"
- "@esbuild/netbsd-x64": "npm:0.24.2"
- "@esbuild/openbsd-arm64": "npm:0.24.2"
- "@esbuild/openbsd-x64": "npm:0.24.2"
- "@esbuild/sunos-x64": "npm:0.24.2"
- "@esbuild/win32-arm64": "npm:0.24.2"
- "@esbuild/win32-ia32": "npm:0.24.2"
- "@esbuild/win32-x64": "npm:0.24.2"
- dependenciesMeta:
- "@esbuild/aix-ppc64":
- optional: true
- "@esbuild/android-arm":
- optional: true
- "@esbuild/android-arm64":
- optional: true
- "@esbuild/android-x64":
- optional: true
- "@esbuild/darwin-arm64":
- optional: true
- "@esbuild/darwin-x64":
- optional: true
- "@esbuild/freebsd-arm64":
- optional: true
- "@esbuild/freebsd-x64":
- optional: true
- "@esbuild/linux-arm":
- optional: true
- "@esbuild/linux-arm64":
- optional: true
- "@esbuild/linux-ia32":
- optional: true
- "@esbuild/linux-loong64":
- optional: true
- "@esbuild/linux-mips64el":
- optional: true
- "@esbuild/linux-ppc64":
- optional: true
- "@esbuild/linux-riscv64":
- optional: true
- "@esbuild/linux-s390x":
- optional: true
- "@esbuild/linux-x64":
- optional: true
- "@esbuild/netbsd-arm64":
- optional: true
- "@esbuild/netbsd-x64":
- optional: true
- "@esbuild/openbsd-arm64":
- optional: true
- "@esbuild/openbsd-x64":
- optional: true
- "@esbuild/sunos-x64":
- optional: true
- "@esbuild/win32-arm64":
- optional: true
- "@esbuild/win32-ia32":
- optional: true
- "@esbuild/win32-x64":
- optional: true
- bin:
- esbuild: bin/esbuild
- checksum: 10/95425071c9f24ff88bf61e0710b636ec0eb24ddf8bd1f7e1edef3044e1221104bbfa7bbb31c18018c8c36fa7902c5c0b843f829b981ebc89160cf5eebdaa58f4
- languageName: node
- linkType: hard
-
"escalade@npm:^3.1.1, escalade@npm:^3.2.0":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
@@ -16605,6 +16610,15 @@ __metadata:
languageName: node
linkType: hard
+"estree-walker@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "estree-walker@npm:3.0.3"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ checksum: 10/a65728d5727b71de172c5df323385755a16c0fdab8234dc756c3854cfee343261ddfbb72a809a5660fac8c75d960bb3e21aa898c2d7e9b19bb298482ca58a3af
+ languageName: node
+ linkType: hard
+
"esutils@npm:^2.0.2":
version: 2.0.3
resolution: "esutils@npm:2.0.3"
@@ -18092,9 +18106,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "grafana@workspace:."
dependencies:
- "@babel/core": "npm:7.26.7"
- "@babel/preset-env": "npm:7.26.7"
- "@babel/runtime": "npm:7.26.7"
+ "@babel/core": "npm:7.26.9"
+ "@babel/preset-env": "npm:7.26.9"
+ "@babel/runtime": "npm:7.26.9"
"@betterer/betterer": "npm:5.4.0"
"@betterer/cli": "npm:5.4.0"
"@bsull/augurs": "npm:^0.9.0"
@@ -18103,7 +18117,7 @@ __metadata:
"@emotion/eslint-plugin": "npm:11.12.0"
"@emotion/react": "npm:11.14.0"
"@fingerprintjs/fingerprintjs": "npm:^3.4.2"
- "@floating-ui/react": "npm:0.27.3"
+ "@floating-ui/react": "npm:0.27.5"
"@formatjs/intl-durationformat": "npm:^0.7.0"
"@glideapps/glide-data-grid": "npm:^6.0.0"
"@grafana/aws-sdk": "npm:0.6.0"
@@ -18126,8 +18140,8 @@ __metadata:
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/saga-icons": "workspace:*"
- "@grafana/scenes": "npm:6.0.2"
- "@grafana/scenes-react": "npm:6.0.2"
+ "@grafana/scenes": "npm:6.1.4"
+ "@grafana/scenes-react": "npm:6.1.4"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/tsconfig": "npm:^2.0.0"
@@ -18144,6 +18158,7 @@ __metadata:
"@manypkg/get-packages": "npm:^2.2.0"
"@msagl/core": "npm:^1.1.19"
"@msagl/parser": "npm:^1.1.19"
+ "@npmcli/package-json": "npm:^5.2.0"
"@opentelemetry/api": "npm:1.9.0"
"@opentelemetry/exporter-collector": "npm:0.25.0"
"@opentelemetry/semantic-conventions": "npm:1.28.0"
@@ -21956,6 +21971,13 @@ __metadata:
languageName: node
linkType: hard
+"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2":
+ version: 3.1.3
+ resolution: "loupe@npm:3.1.3"
+ checksum: 10/9e98c34daf0eba48ccc603595e51f2ae002110982d84879cf78c51de2c632f0c571dfe82ce4210af60c32203d06b443465c269bda925076fe6d9b612cc65c321
+ languageName: node
+ linkType: hard
+
"lower-case@npm:^2.0.2":
version: 2.0.2
resolution: "lower-case@npm:2.0.2"
@@ -24505,6 +24527,13 @@ __metadata:
languageName: node
linkType: hard
+"pathval@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "pathval@npm:2.0.0"
+ checksum: 10/b91575bf9cdf01757afd7b5e521eb8a0b874a49bc972d08e0047cfea0cd3c019f5614521d4bc83d2855e3fcc331db6817dfd533dd8f3d90b16bc76fad2450fc1
+ languageName: node
+ linkType: hard
+
"pbf@npm:3.2.1":
version: 3.2.1
resolution: "pbf@npm:3.2.1"
@@ -26280,7 +26309,7 @@ __metadata:
languageName: node
linkType: hard
-"react-dom@npm:18.3.1, react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0, react-dom@npm:^18.3.1":
+"react-dom@npm:18.3.1, react-dom@npm:^18.3.1":
version: 18.3.1
resolution: "react-dom@npm:18.3.1"
dependencies:
@@ -26292,6 +26321,17 @@ __metadata:
languageName: node
linkType: hard
+"react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
+ version: 19.0.0
+ resolution: "react-dom@npm:19.0.0"
+ dependencies:
+ scheduler: "npm:^0.25.0"
+ peerDependencies:
+ react: ^19.0.0
+ checksum: 10/aa64a2f1991042f516260e8b0eca0ae777b6c8f1aa2b5ae096e80bbb6ac9b005aef2bca697969841d34f7e1819556263476bdfea36c35092e8d9aefde3de2d9a
+ languageName: node
+ linkType: hard
+
"react-draggable@npm:4.4.6, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5":
version: 4.4.6
resolution: "react-draggable@npm:4.4.6"
@@ -27140,7 +27180,7 @@ __metadata:
languageName: node
linkType: hard
-"react@npm:18.3.1, react@npm:^16.8.0 || ^17.0.0 || ^18.0.0, react@npm:^18":
+"react@npm:18.3.1, react@npm:^18":
version: 18.3.1
resolution: "react@npm:18.3.1"
dependencies:
@@ -27149,6 +27189,13 @@ __metadata:
languageName: node
linkType: hard
+"react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0":
+ version: 19.0.0
+ resolution: "react@npm:19.0.0"
+ checksum: 10/2490969c503f644703c88990d20e4011fa6119ddeca451e9de48f6d7ab058d670d2852a5fcd3aa3cd90a923ab2815d532637bd4a814add402ae5c0d4f129ee71
+ languageName: node
+ linkType: hard
+
"read-cmd-shim@npm:4.0.0, read-cmd-shim@npm:^4.0.0":
version: 4.0.0
resolution: "read-cmd-shim@npm:4.0.0"
@@ -28208,6 +28255,13 @@ __metadata:
languageName: node
linkType: hard
+"scheduler@npm:^0.25.0":
+ version: 0.25.0
+ resolution: "scheduler@npm:0.25.0"
+ checksum: 10/e661e38503ab29a153429a99203fefa764f28b35c079719eb5efdd2c1c1086522f6653d8ffce388209682c23891a6d1d32fa6badf53c35fb5b9cd0c55ace42de
+ languageName: node
+ linkType: hard
+
"schema-utils@npm:>1.0.0, schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0":
version: 4.3.0
resolution: "schema-utils@npm:4.3.0"
@@ -28323,6 +28377,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.7.0":
+ version: 7.7.1
+ resolution: "semver@npm:7.7.1"
+ bin:
+ semver: bin/semver.js
+ checksum: 10/4cfa1eb91ef3751e20fc52e47a935a0118d56d6f15a837ab814da0c150778ba2ca4f1a4d9068b33070ea4273629e615066664c2cfcd7c272caf7a8a0f6518b2c
+ languageName: node
+ linkType: hard
+
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
@@ -29369,11 +29432,11 @@ __metadata:
languageName: node
linkType: hard
-"storybook@npm:^8.4.2":
- version: 8.4.4
- resolution: "storybook@npm:8.4.4"
+"storybook@npm:^8.6.2":
+ version: 8.6.2
+ resolution: "storybook@npm:8.6.2"
dependencies:
- "@storybook/core": "npm:8.4.4"
+ "@storybook/core": "npm:8.6.2"
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
@@ -29383,7 +29446,7 @@ __metadata:
getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs
storybook: ./bin/index.cjs
- checksum: 10/e2d27c97a82da9161f5c8f3d2cadbd77701032b5053c714a768930f41fe5aae479c07f8fb5afa98434e45d89ee501fa9866bdea4fdcb16a4723b8238c56d974f
+ checksum: 10/81884ce80d36bfe3170c46ce08168f24a2a27b2698a9ec66ebb1a9727502ab9db3e6e221121a10606ef06db466966b47a12b48b99e519755c508bc183a2de815
languageName: node
linkType: hard
@@ -29995,7 +30058,7 @@ __metadata:
languageName: node
linkType: hard
-"swc-loader@npm:0.2.6, swc-loader@npm:^0.2.3":
+"swc-loader@npm:0.2.6, swc-loader@npm:^0.2.6":
version: 0.2.6
resolution: "swc-loader@npm:0.2.6"
dependencies:
@@ -30304,6 +30367,20 @@ __metadata:
languageName: node
linkType: hard
+"tinyrainbow@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "tinyrainbow@npm:1.2.0"
+ checksum: 10/2924444db6804355e5ba2b6e586c7f77329d93abdd7257a069a0f4530dff9f16de484e80479094e3f39273462541b003a65ee3a6afc2d12555aa745132deba5d
+ languageName: node
+ linkType: hard
+
+"tinyspy@npm:^3.0.0":
+ version: 3.0.2
+ resolution: "tinyspy@npm:3.0.2"
+ checksum: 10/5db671b2ff5cd309de650c8c4761ca945459d7204afb1776db9a04fb4efa28a75f08517a8620c01ee32a577748802231ad92f7d5b194dc003ee7f987a2a06337
+ languageName: node
+ linkType: hard
+
"tlds@npm:1.252.0":
version: 1.252.0
resolution: "tlds@npm:1.252.0"
@@ -30786,13 +30863,6 @@ __metadata:
languageName: node
linkType: hard
-"type-fest@npm:^2.19.0":
- version: 2.19.0
- resolution: "type-fest@npm:2.19.0"
- checksum: 10/7bf9e8fdf34f92c8bb364c0af14ca875fac7e0183f2985498b77be129dc1b3b1ad0a6b3281580f19e48c6105c037fb966ad9934520c69c6434d17fd0af4eed78
- languageName: node
- linkType: hard
-
"type-fest@npm:^3.0.0":
version: 3.13.1
resolution: "type-fest@npm:3.13.1"