feat: incorporate feature flag
This commit is contained in:
@@ -1250,4 +1250,9 @@ export interface FeatureToggles {
|
||||
* @default false
|
||||
*/
|
||||
kubernetesAnnotations?: boolean;
|
||||
/**
|
||||
* Enables the new correlations editor in Explore
|
||||
* @default false
|
||||
*/
|
||||
correlationsExploreEditor?: boolean;
|
||||
}
|
||||
|
||||
@@ -2165,6 +2165,14 @@ var (
|
||||
Owner: grafanaBackendServicesSquad,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "correlationsExploreEditor",
|
||||
Description: "Enables the new correlations editor in Explore",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaDataProSquad,
|
||||
FrontendOnly: true,
|
||||
Expression: "false",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -278,3 +278,4 @@ onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false
|
||||
panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false
|
||||
dashboardTemplates,experimental,@grafana/sharing-squad,false,false,false
|
||||
kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false
|
||||
correlationsExploreEditor,experimental,@grafana/datapro,false,false,true
|
||||
|
||||
|
Generated
+4
@@ -1121,4 +1121,8 @@ const (
|
||||
// FlagKubernetesAnnotations
|
||||
// Enables app platform API for annotations
|
||||
FlagKubernetesAnnotations = "kubernetesAnnotations"
|
||||
|
||||
// FlagCorrelationsExploreEditor
|
||||
// Enables the new correlations editor in Explore
|
||||
FlagCorrelationsExploreEditor = "correlationsExploreEditor"
|
||||
)
|
||||
|
||||
@@ -1012,6 +1012,20 @@
|
||||
"expression": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "correlationsExploreEditor",
|
||||
"resourceVersion": "1764186796607",
|
||||
"creationTimestamp": "2025-11-26T19:53:16Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables the new correlations editor in Explore",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/datapro",
|
||||
"frontend": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "crashDetection",
|
||||
|
||||
@@ -132,8 +132,8 @@ export const CorrelationTransformationAddModal = ({
|
||||
isOpen={true}
|
||||
title={
|
||||
transformationToEdit
|
||||
? t('explore.correlation-transformation-add-modal.title-edit', 'Edit custom variable')
|
||||
: t('explore.correlation-transformation-add-modal.title-add', 'Add custom variable')
|
||||
? t('explore.correlation-transformation-add-modal.title-edit-custom-variable', 'Edit custom variable')
|
||||
: t('explore.correlation-transformation-add-modal.title-add-custom-variable', 'Add custom variable')
|
||||
}
|
||||
onDismiss={onCancel}
|
||||
className={css({ width: '700px' })}
|
||||
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBeforeUnload, useUnmount } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
|
||||
import { CORRELATION_EDITOR_POST_CONFIRM_ACTION, ExploreItemState } from 'app/types/explore';
|
||||
import { useDispatch, useSelector } from 'app/types/store';
|
||||
|
||||
import { saveCurrentCorrelation } from '../../state/correlations';
|
||||
import { changeDatasource } from '../../state/datasource';
|
||||
import { changeCorrelationHelperData } from '../../state/explorePane';
|
||||
import { changeCorrelationEditorDetails, splitClose } from '../../state/main';
|
||||
import { runQueries } from '../../state/query';
|
||||
import { selectCorrelationDetails, selectIsHelperShowing } from '../../state/selectors';
|
||||
import { showModalMessage } from '../correlationEditLogic';
|
||||
|
||||
import { CorrelationUnsavedChangesModalLegacy } from './CorrelationUnsavedChangesModalLegacy';
|
||||
|
||||
export const CorrelationEditorModeBarLegacy = ({ panes }: { panes: Array<[string, ExploreItemState]> }) => {
|
||||
const dispatch = useDispatch();
|
||||
const styles = useStyles2(getStyles);
|
||||
const correlationDetails = useSelector(selectCorrelationDetails);
|
||||
const isHelperShowing = useSelector(selectIsHelperShowing);
|
||||
const [saveMessage, setSaveMessage] = useState<string | undefined>(undefined); // undefined means do not show
|
||||
|
||||
// handle refreshing and closing the tab
|
||||
useBeforeUnload(correlationDetails?.correlationDirty || false, 'Save correlation?');
|
||||
useBeforeUnload(
|
||||
(!correlationDetails?.correlationDirty && correlationDetails?.queryEditorDirty) || false,
|
||||
'The query editor was changed. Save correlation before continuing?'
|
||||
);
|
||||
|
||||
// decide if we are displaying prompt, perform action if not
|
||||
useEffect(() => {
|
||||
if (correlationDetails?.isExiting) {
|
||||
const { correlationDirty, queryEditorDirty } = correlationDetails;
|
||||
let isActionLeft = undefined;
|
||||
let action = undefined;
|
||||
if (correlationDetails.postConfirmAction) {
|
||||
isActionLeft = correlationDetails.postConfirmAction.isActionLeft;
|
||||
action = correlationDetails.postConfirmAction.action;
|
||||
} else {
|
||||
// closing the editor only
|
||||
action = CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_EDITOR;
|
||||
isActionLeft = false;
|
||||
}
|
||||
|
||||
const modalMessage = showModalMessage(action, isActionLeft, correlationDirty, queryEditorDirty);
|
||||
if (modalMessage !== undefined) {
|
||||
setSaveMessage(modalMessage);
|
||||
} else {
|
||||
// if no prompt, perform action
|
||||
if (
|
||||
action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE &&
|
||||
correlationDetails.postConfirmAction
|
||||
) {
|
||||
const { exploreId, changeDatasourceUid } = correlationDetails?.postConfirmAction;
|
||||
if (exploreId && changeDatasourceUid) {
|
||||
dispatch(
|
||||
changeDatasource({ exploreId, datasource: changeDatasourceUid, options: { importQueries: true } })
|
||||
);
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
isExiting: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_PANE &&
|
||||
correlationDetails.postConfirmAction
|
||||
) {
|
||||
const { exploreId } = correlationDetails?.postConfirmAction;
|
||||
if (exploreId !== undefined) {
|
||||
dispatch(splitClose(exploreId));
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
isExiting: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else if (action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_EDITOR) {
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
editorMode: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [correlationDetails, dispatch, isHelperShowing]);
|
||||
|
||||
// clear data when unmounted
|
||||
useUnmount(() => {
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
editorMode: false,
|
||||
isExiting: false,
|
||||
correlationDirty: false,
|
||||
label: undefined,
|
||||
description: undefined,
|
||||
canSave: false,
|
||||
})
|
||||
);
|
||||
|
||||
panes.forEach((pane) => {
|
||||
dispatch(
|
||||
changeCorrelationHelperData({
|
||||
exploreId: pane[0],
|
||||
correlationEditorHelperData: undefined,
|
||||
})
|
||||
);
|
||||
dispatch(runQueries({ exploreId: pane[0] }));
|
||||
});
|
||||
});
|
||||
|
||||
const resetEditor = () => {
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
editorMode: true,
|
||||
isExiting: false,
|
||||
correlationDirty: false,
|
||||
label: undefined,
|
||||
description: undefined,
|
||||
canSave: false,
|
||||
})
|
||||
);
|
||||
|
||||
panes.forEach((pane) => {
|
||||
dispatch(
|
||||
changeCorrelationHelperData({
|
||||
exploreId: pane[0],
|
||||
correlationEditorHelperData: undefined,
|
||||
})
|
||||
);
|
||||
dispatch(runQueries({ exploreId: pane[0] }));
|
||||
});
|
||||
};
|
||||
|
||||
const closePane = (exploreId: string) => {
|
||||
setSaveMessage(undefined);
|
||||
dispatch(splitClose(exploreId));
|
||||
reportInteraction('grafana_explore_split_view_closed');
|
||||
};
|
||||
|
||||
const changeDatasourcePostAction = (exploreId: string, datasourceUid: string) => {
|
||||
setSaveMessage(undefined);
|
||||
dispatch(changeDatasource({ exploreId, datasource: datasourceUid, options: { importQueries: true } }));
|
||||
};
|
||||
|
||||
const saveCorrelationPostAction = (skipPostConfirmAction: boolean) => {
|
||||
dispatch(
|
||||
saveCurrentCorrelation(
|
||||
correlationDetails?.label,
|
||||
correlationDetails?.description,
|
||||
correlationDetails?.transformations
|
||||
)
|
||||
);
|
||||
if (!skipPostConfirmAction && correlationDetails?.postConfirmAction !== undefined) {
|
||||
const { exploreId, action, changeDatasourceUid } = correlationDetails?.postConfirmAction;
|
||||
if (action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_PANE) {
|
||||
closePane(exploreId);
|
||||
resetEditor();
|
||||
} else if (
|
||||
action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE &&
|
||||
changeDatasourceUid !== undefined
|
||||
) {
|
||||
changeDatasource({ exploreId, datasource: changeDatasourceUid });
|
||||
resetEditor();
|
||||
}
|
||||
} else {
|
||||
dispatch(changeCorrelationEditorDetails({ editorMode: false, correlationDirty: false, isExiting: false }));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Handle navigating outside Explore */}
|
||||
<Prompt
|
||||
message={(location) => {
|
||||
if (
|
||||
location.pathname !== '/explore' &&
|
||||
correlationDetails?.editorMode &&
|
||||
correlationDetails?.correlationDirty
|
||||
) {
|
||||
return 'You have unsaved correlation data. Continue?';
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{saveMessage !== undefined && (
|
||||
<CorrelationUnsavedChangesModalLegacy
|
||||
onDiscard={() => {
|
||||
if (correlationDetails?.postConfirmAction !== undefined) {
|
||||
const { exploreId, action, changeDatasourceUid } = correlationDetails?.postConfirmAction;
|
||||
if (action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_PANE) {
|
||||
closePane(exploreId);
|
||||
} else if (
|
||||
action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE &&
|
||||
changeDatasourceUid !== undefined
|
||||
) {
|
||||
changeDatasourcePostAction(exploreId, changeDatasourceUid);
|
||||
}
|
||||
dispatch(changeCorrelationEditorDetails({ isExiting: false }));
|
||||
} else {
|
||||
// exit correlations mode
|
||||
// if we are discarding the in progress correlation, reset everything
|
||||
// this modal only shows if the editorMode is false, so we just need to update the dirty state
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({
|
||||
editorMode: false,
|
||||
correlationDirty: false,
|
||||
isExiting: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
// if we are cancelling the exit, set the editor mode back to true and hide the prompt
|
||||
dispatch(changeCorrelationEditorDetails({ isExiting: false }));
|
||||
setSaveMessage(undefined);
|
||||
}}
|
||||
onSave={() => {
|
||||
saveCorrelationPostAction(false);
|
||||
}}
|
||||
message={saveMessage}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.correlationEditorTop}>
|
||||
<Stack gap={2} justifyContent="flex-end" alignItems="center">
|
||||
<Tooltip
|
||||
content={t(
|
||||
'explore.correlation-editor-mode-bar.content-correlations-editor-explore-experimental-feature',
|
||||
'Correlations editor in Explore is an experimental feature.'
|
||||
)}
|
||||
>
|
||||
<Icon className={styles.iconColor} name="info-circle" size="xl" />
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={!correlationDetails?.canSave}
|
||||
fill="outline"
|
||||
className={correlationDetails?.canSave ? styles.buttonColor : styles.disabledButtonColor}
|
||||
onClick={() => {
|
||||
saveCorrelationPostAction(true);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="explore.correlation-editor-mode-bar.save">Save</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
fill="outline"
|
||||
className={styles.buttonColor}
|
||||
icon="times"
|
||||
onClick={() => {
|
||||
dispatch(changeCorrelationEditorDetails({ isExiting: true }));
|
||||
reportInteraction('grafana_explore_correlation_editor_exit_pressed');
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="explore.correlation-editor-mode-bar.exit-correlation-editor">Exit correlation editor</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const contrastColor = theme.colors.getContrastText(theme.colors.primary.main);
|
||||
const lighterBackgroundColor = colorManipulator.lighten(theme.colors.primary.main, 0.1);
|
||||
const darkerBackgroundColor = colorManipulator.darken(theme.colors.primary.main, 0.2);
|
||||
|
||||
const disabledColor = colorManipulator.darken(contrastColor, 0.2);
|
||||
|
||||
return {
|
||||
correlationEditorTop: css({
|
||||
backgroundColor: theme.colors.primary.main,
|
||||
marginTop: '3px',
|
||||
padding: theme.spacing(1),
|
||||
}),
|
||||
iconColor: css({
|
||||
color: contrastColor,
|
||||
}),
|
||||
buttonColor: css({
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor,
|
||||
'&:hover': {
|
||||
color: contrastColor,
|
||||
borderColor: contrastColor,
|
||||
backgroundColor: lighterBackgroundColor,
|
||||
},
|
||||
}),
|
||||
// important needed to override disabled state styling
|
||||
disabledButtonColor: css({
|
||||
color: `${disabledColor} !important`,
|
||||
backgroundColor: `${darkerBackgroundColor} !important`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,295 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useId, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataLinkTransformationConfig, ExploreCorrelationHelperData, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Collapse,
|
||||
DeleteButton,
|
||||
Field,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
Stack,
|
||||
Tooltip,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { useDispatch, useSelector } from 'app/types/store';
|
||||
|
||||
import { getTransformationVars } from '../../../correlations/transformations';
|
||||
import { generateDefaultLabel } from '../../../correlations/utils';
|
||||
import { changeCorrelationHelperData } from '../../state/explorePane';
|
||||
import { changeCorrelationEditorDetails } from '../../state/main';
|
||||
import { selectCorrelationDetails, selectPanes } from '../../state/selectors';
|
||||
|
||||
import { CorrelationTransformationAddModalLegacy } from './CorrelationTransformationAddModalLegacy';
|
||||
|
||||
interface Props {
|
||||
exploreId: string;
|
||||
correlations: ExploreCorrelationHelperData;
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const CorrelationHelperLegacy = ({ exploreId, correlations }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const styles = useStyles2(getStyles);
|
||||
const panes = useSelector(selectPanes);
|
||||
const panesVals = Object.values(panes);
|
||||
const { value: defaultLabel, loading: loadingLabel } = useAsync(
|
||||
async () => await generateDefaultLabel(panesVals[0]!, panesVals[1]!),
|
||||
[
|
||||
panesVals[0]?.datasourceInstance,
|
||||
panesVals[0]?.queries[0].datasource,
|
||||
panesVals[1]?.datasourceInstance,
|
||||
panesVals[1]?.queries[0].datasource,
|
||||
]
|
||||
);
|
||||
|
||||
const { register, watch, getValues, setValue } = useForm<FormValues>();
|
||||
const [isLabelDescOpen, setIsLabelDescOpen] = useState(false);
|
||||
const [isTransformOpen, setIsTransformOpen] = useState(false);
|
||||
const [showTransformationAddModal, setShowTransformationAddModal] = useState(false);
|
||||
const [transformations, setTransformations] = useState<DataLinkTransformationConfig[]>([]);
|
||||
const [transformationIdxToEdit, setTransformationIdxToEdit] = useState<number | undefined>(undefined);
|
||||
const correlationDetails = useSelector(selectCorrelationDetails);
|
||||
const id = useId();
|
||||
|
||||
// only fire once on mount to allow save button to enable / disable when unmounted
|
||||
useEffect(() => {
|
||||
dispatch(changeCorrelationEditorDetails({ canSave: true }));
|
||||
return () => {
|
||||
dispatch(changeCorrelationEditorDetails({ canSave: false }));
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!loadingLabel &&
|
||||
defaultLabel !== undefined &&
|
||||
!correlationDetails?.correlationDirty &&
|
||||
getValues('label') !== ''
|
||||
) {
|
||||
setValue('label', defaultLabel);
|
||||
}
|
||||
}, [correlationDetails?.correlationDirty, defaultLabel, getValues, loadingLabel, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((value) => {
|
||||
let dirty = correlationDetails?.correlationDirty || false;
|
||||
let description = value.description || '';
|
||||
if (!dirty && (value.label !== defaultLabel || description !== '')) {
|
||||
dirty = true;
|
||||
} else if (dirty && value.label === defaultLabel && description.trim() === '') {
|
||||
dirty = false;
|
||||
}
|
||||
dispatch(
|
||||
changeCorrelationEditorDetails({ label: value.label, description: value.description, correlationDirty: dirty })
|
||||
);
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [correlationDetails?.correlationDirty, defaultLabel, dispatch, watch]);
|
||||
|
||||
useEffect(() => {
|
||||
const dirty =
|
||||
!correlationDetails?.correlationDirty && transformations.length > 0 ? true : correlationDetails?.correlationDirty;
|
||||
dispatch(changeCorrelationEditorDetails({ transformations: transformations, correlationDirty: dirty }));
|
||||
let transVarRecords: Record<string, string> = {};
|
||||
transformations.forEach((transformation) => {
|
||||
const transformationVars = getTransformationVars(
|
||||
{
|
||||
type: transformation.type,
|
||||
expression: transformation.expression,
|
||||
mapValue: transformation.mapValue,
|
||||
},
|
||||
correlations.vars[transformation.field!],
|
||||
transformation.field!
|
||||
);
|
||||
|
||||
Object.keys(transformationVars).forEach((key) => {
|
||||
transVarRecords[key] = transformationVars[key]?.value;
|
||||
});
|
||||
});
|
||||
|
||||
dispatch(
|
||||
changeCorrelationHelperData({
|
||||
exploreId: exploreId,
|
||||
correlationEditorHelperData: {
|
||||
resultField: correlations.resultField,
|
||||
origVars: correlations.origVars,
|
||||
vars: { ...correlations.origVars, ...transVarRecords },
|
||||
},
|
||||
})
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch, transformations]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTransformationAddModal && (
|
||||
<CorrelationTransformationAddModalLegacy
|
||||
onCancel={() => {
|
||||
setTransformationIdxToEdit(undefined);
|
||||
setShowTransformationAddModal(false);
|
||||
}}
|
||||
onSave={(transformation: DataLinkTransformationConfig) => {
|
||||
if (transformationIdxToEdit !== undefined) {
|
||||
const editTransformations = [...transformations];
|
||||
editTransformations[transformationIdxToEdit] = transformation;
|
||||
setTransformations(editTransformations);
|
||||
setTransformationIdxToEdit(undefined);
|
||||
} else {
|
||||
setTransformations([...transformations, transformation]);
|
||||
}
|
||||
setShowTransformationAddModal(false);
|
||||
}}
|
||||
fieldList={correlations.origVars}
|
||||
transformationToEdit={
|
||||
transformationIdxToEdit !== undefined ? transformations[transformationIdxToEdit] : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Alert title={t('explore.correlation-helper.title-correlation-details', 'Correlation details')} severity="info">
|
||||
<Trans
|
||||
i18nKey="explore.correlation-helper.body-correlation-details"
|
||||
values={{ resultField: correlations.resultField }}
|
||||
>
|
||||
The correlation link will appear by the <code>{'{{resultField}}'}</code> field. You can use the following
|
||||
variables to set up your correlations:
|
||||
</Trans>
|
||||
<pre>
|
||||
{Object.entries(correlations.vars).map((entry) => {
|
||||
return `\$\{${entry[0]}\} = ${entry[1]}\n`;
|
||||
})}
|
||||
</pre>
|
||||
<Collapse
|
||||
isOpen={isLabelDescOpen}
|
||||
onToggle={() => {
|
||||
setIsLabelDescOpen(!isLabelDescOpen);
|
||||
}}
|
||||
label={
|
||||
<Stack gap={1} direction="row" wrap="wrap" alignItems="center">
|
||||
<Trans i18nKey="explore.correlation-helper.label-description-header">Label / Description</Trans>
|
||||
{!isLabelDescOpen && !loadingLabel && (
|
||||
<span className={styles.labelCollapseDetails}>{`Label: ${getValues('label') || defaultLabel}`}</span>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Field noMargin label={t('explore.correlation-helper.label-label', 'Label')} htmlFor={`${id}-label`}>
|
||||
<Input
|
||||
{...register('label')}
|
||||
id={`${id}-label`}
|
||||
onBlur={() => {
|
||||
if (getValues('label') === '' && defaultLabel !== undefined) {
|
||||
setValue('label', defaultLabel);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
noMargin
|
||||
label={t('explore.correlation-helper.label-description', 'Description')}
|
||||
htmlFor={`${id}-description`}
|
||||
>
|
||||
<Input {...register('description')} id={`${id}-description`} />
|
||||
</Field>
|
||||
</Collapse>
|
||||
<Collapse
|
||||
isOpen={isTransformOpen}
|
||||
onToggle={() => {
|
||||
setIsTransformOpen(!isTransformOpen);
|
||||
}}
|
||||
label={
|
||||
<Stack gap={1} direction="row" wrap="wrap" alignItems="center">
|
||||
<Trans i18nKey="explore.correlation-helper.transformations">Transformations</Trans>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'explore.correlation-helper.tooltip-transformations',
|
||||
'A transformation extracts one or more variables out of a single field.'
|
||||
)}
|
||||
>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
fill="outline"
|
||||
onClick={() => {
|
||||
setShowTransformationAddModal(true);
|
||||
}}
|
||||
className={styles.transformationAction}
|
||||
>
|
||||
<Trans i18nKey="explore.correlation-helper.add-transformation">Add transformation</Trans>
|
||||
</Button>
|
||||
{transformations.map((transformation, i) => {
|
||||
const { type, field, expression, mapValue } = transformation;
|
||||
const detailsString = [
|
||||
(mapValue ?? '').length > 0 ? `Variable name: ${mapValue}` : undefined,
|
||||
(expression ?? '').length > 0 ? (
|
||||
<Trans i18nKey="explore.correlation-helper.expression" values={{ expression }}>
|
||||
Expression: <code>{'{{expression}}'}</code>
|
||||
</Trans>
|
||||
) : undefined,
|
||||
].filter((val) => val);
|
||||
return (
|
||||
<Card noMargin key={`trans-${i}`}>
|
||||
<Card.Heading>
|
||||
{field}: {type}
|
||||
</Card.Heading>
|
||||
{detailsString.length > 0 && (
|
||||
<Card.Meta className={styles.transformationMeta}>{detailsString}</Card.Meta>
|
||||
)}
|
||||
<Card.SecondaryActions>
|
||||
<IconButton
|
||||
key="edit"
|
||||
name="edit"
|
||||
aria-label={t('explore.correlation-helper.aria-label-edit-transformation', 'Edit transformation')}
|
||||
onClick={() => {
|
||||
setTransformationIdxToEdit(i);
|
||||
setShowTransformationAddModal(true);
|
||||
}}
|
||||
/>
|
||||
<DeleteButton
|
||||
aria-label={t(
|
||||
'explore.correlation-helper.aria-label-delete-transformation',
|
||||
'Delete transformation'
|
||||
)}
|
||||
onConfirm={() => setTransformations(transformations.filter((_, idx) => i !== idx))}
|
||||
closeOnConfirm
|
||||
/>
|
||||
</Card.SecondaryActions>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
</Alert>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
labelCollapseDetails: css({
|
||||
marginLeft: theme.spacing(2),
|
||||
...theme.typography['bodySmall'],
|
||||
fontStyle: 'italic',
|
||||
}),
|
||||
transformationAction: css({
|
||||
marginBottom: theme.spacing(2),
|
||||
}),
|
||||
transformationMeta: css({
|
||||
alignItems: 'baseline',
|
||||
}),
|
||||
};
|
||||
};
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useId, useState, useMemo, useEffect } from 'react';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
|
||||
import { DataLinkTransformationConfig, ScopedVars } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Field, Icon, Input, Label, Modal, Select, Tooltip, Stack } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
getSupportedTransTypeDetails,
|
||||
getTransformOptions,
|
||||
TransformationFieldDetails,
|
||||
} from '../../../correlations/Forms/types';
|
||||
import { getTransformationVars } from '../../../correlations/transformations';
|
||||
|
||||
interface CorrelationTransformationAddModalProps {
|
||||
onCancel: () => void;
|
||||
onSave: (transformation: DataLinkTransformationConfig) => void;
|
||||
fieldList: Record<string, string>;
|
||||
transformationToEdit?: DataLinkTransformationConfig;
|
||||
}
|
||||
|
||||
interface ShowFormFields {
|
||||
expressionDetails: TransformationFieldDetails;
|
||||
mapValueDetails: TransformationFieldDetails;
|
||||
}
|
||||
|
||||
const LabelWithTooltip = ({ label, tooltipText }: { label: string; tooltipText: string }) => (
|
||||
<Stack gap={1} direction="row" wrap="wrap" alignItems="flex-start">
|
||||
<Label>{label}</Label>
|
||||
<Tooltip content={tooltipText}>
|
||||
<Icon name="info-circle" size="sm" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export const CorrelationTransformationAddModalLegacy = ({
|
||||
onSave,
|
||||
onCancel,
|
||||
fieldList,
|
||||
transformationToEdit,
|
||||
}: CorrelationTransformationAddModalProps) => {
|
||||
const [exampleValue, setExampleValue] = useState<string | undefined>(undefined);
|
||||
const [transformationVars, setTransformationVars] = useState<ScopedVars>({});
|
||||
const [formFieldsVis, setFormFieldsVis] = useState<ShowFormFields>({
|
||||
mapValueDetails: { show: false },
|
||||
expressionDetails: { show: false },
|
||||
});
|
||||
const [isExpValid, setIsExpValid] = useState(false); // keep the highlighter from erroring on bad expressions
|
||||
const [validToSave, setValidToSave] = useState(false);
|
||||
const { getValues, control, register, watch } = useForm<DataLinkTransformationConfig>({
|
||||
defaultValues: useMemo(() => {
|
||||
if (transformationToEdit) {
|
||||
const exampleVal = fieldList[transformationToEdit?.field!];
|
||||
setExampleValue(exampleVal);
|
||||
if (transformationToEdit?.expression) {
|
||||
setIsExpValid(true);
|
||||
}
|
||||
const transformationTypeDetails = getSupportedTransTypeDetails(transformationToEdit?.type!);
|
||||
setFormFieldsVis({
|
||||
mapValueDetails: transformationTypeDetails.mapValueDetails,
|
||||
expressionDetails: transformationTypeDetails.expressionDetails,
|
||||
});
|
||||
|
||||
const transformationVars = getTransformationVars(
|
||||
{
|
||||
type: transformationToEdit?.type!,
|
||||
expression: transformationToEdit?.expression,
|
||||
mapValue: transformationToEdit?.mapValue,
|
||||
},
|
||||
exampleVal || '',
|
||||
transformationToEdit?.field!
|
||||
);
|
||||
setTransformationVars({ ...transformationVars });
|
||||
setValidToSave(true);
|
||||
return {
|
||||
type: transformationToEdit?.type,
|
||||
field: transformationToEdit?.field,
|
||||
mapValue: transformationToEdit?.mapValue,
|
||||
expression: transformationToEdit?.expression,
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}, [fieldList, transformationToEdit]),
|
||||
});
|
||||
const id = useId();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((formValues) => {
|
||||
const expression = formValues.expression;
|
||||
let isExpressionValid = false;
|
||||
if (expression !== undefined) {
|
||||
isExpressionValid = true;
|
||||
try {
|
||||
new RegExp(expression);
|
||||
} catch (e) {
|
||||
isExpressionValid = false;
|
||||
}
|
||||
} else {
|
||||
isExpressionValid = !formFieldsVis.expressionDetails.show;
|
||||
}
|
||||
setIsExpValid(isExpressionValid);
|
||||
let transKeys = [];
|
||||
if (formValues.type) {
|
||||
const transformationVars = getTransformationVars(
|
||||
{
|
||||
type: formValues.type,
|
||||
expression: isExpressionValid ? expression : '',
|
||||
mapValue: formValues.mapValue,
|
||||
},
|
||||
fieldList[formValues.field!] || '',
|
||||
formValues.field!
|
||||
);
|
||||
|
||||
transKeys = Object.keys(transformationVars);
|
||||
setTransformationVars(transKeys.length > 0 ? { ...transformationVars } : {});
|
||||
}
|
||||
|
||||
if (transKeys.length === 0 || !isExpressionValid) {
|
||||
setValidToSave(false);
|
||||
} else {
|
||||
setValidToSave(true);
|
||||
}
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [fieldList, formFieldsVis.expressionDetails.show, watch]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
title={
|
||||
transformationToEdit
|
||||
? t('explore.correlation-transformation-add-modal.title-edit', 'Edit transformation')
|
||||
: t('explore.correlation-transformation-add-modal.title-add', 'Add transformation')
|
||||
}
|
||||
onDismiss={onCancel}
|
||||
className={css({ width: '700px' })}
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="explore.correlation-transformation-add-modal.body">
|
||||
A transformation extracts variables out of a single field. These variables will be available along with your
|
||||
field variables.
|
||||
</Trans>
|
||||
</p>
|
||||
<Field noMargin label={t('explore.correlation-transformation-add-modal.label-field', 'Field')}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
{...field}
|
||||
onChange={(value) => {
|
||||
if (value.value) {
|
||||
onChange(value.value);
|
||||
setExampleValue(fieldList[value.value]);
|
||||
}
|
||||
}}
|
||||
options={Object.entries(fieldList).map((entry) => {
|
||||
return { label: entry[0], value: entry[0] };
|
||||
})}
|
||||
aria-label={t('explore.correlation-transformation-add-modal.aria-label-field', 'Field')}
|
||||
/>
|
||||
)}
|
||||
name={`field` as const}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{exampleValue && (
|
||||
<>
|
||||
<pre>
|
||||
<Highlighter
|
||||
textToHighlight={exampleValue}
|
||||
searchWords={[isExpValid ? (getValues('expression') ?? '') : '']}
|
||||
autoEscape={false}
|
||||
/>
|
||||
</pre>
|
||||
<Field noMargin label={t('explore.correlation-transformation-add-modal.label-type', 'Type')}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
{...field}
|
||||
onChange={(value) => {
|
||||
onChange(value.value);
|
||||
const transformationTypeDetails = getSupportedTransTypeDetails(value.value!);
|
||||
setFormFieldsVis({
|
||||
mapValueDetails: transformationTypeDetails.mapValueDetails,
|
||||
expressionDetails: transformationTypeDetails.expressionDetails,
|
||||
});
|
||||
}}
|
||||
options={getTransformOptions()}
|
||||
aria-label={t('explore.correlation-transformation-add-modal.aria-label-type', 'Type')}
|
||||
/>
|
||||
)}
|
||||
name={`type` as const}
|
||||
/>
|
||||
</Field>
|
||||
{formFieldsVis.expressionDetails.show && (
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
formFieldsVis.expressionDetails.helpText ? (
|
||||
<LabelWithTooltip
|
||||
label={t('explore.correlation-transformation-add-modal.label-expression', 'Expression')}
|
||||
tooltipText={formFieldsVis.expressionDetails.helpText}
|
||||
/>
|
||||
) : (
|
||||
t('explore.correlation-transformation-add-modal.label-expression-without-tooltip', 'Expression')
|
||||
)
|
||||
}
|
||||
htmlFor={`${id}-expression`}
|
||||
required={formFieldsVis.expressionDetails.required}
|
||||
>
|
||||
<Input {...register('expression')} id={`${id}-expression`} />
|
||||
</Field>
|
||||
)}
|
||||
{formFieldsVis.mapValueDetails.show && (
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
formFieldsVis.mapValueDetails.helpText ? (
|
||||
<LabelWithTooltip
|
||||
label={t('explore.correlation-transformation-add-modal.label-variable-name', 'Variable name')}
|
||||
tooltipText={formFieldsVis.mapValueDetails.helpText}
|
||||
/>
|
||||
) : (
|
||||
t('explore.correlation-transformation-add-modal.label-variable-name-without-tooltip', 'Variable name')
|
||||
)
|
||||
}
|
||||
htmlFor={`${id}-mapValue`}
|
||||
>
|
||||
<Input {...register('mapValue')} id={`${id}-mapValue`} />
|
||||
</Field>
|
||||
)}
|
||||
{Object.entries(transformationVars).length > 0 && (
|
||||
<>
|
||||
<Trans i18nKey="explore.correlation-transformation-add-modal.added-variables">
|
||||
This transformation will add the following variables:
|
||||
</Trans>
|
||||
<pre>
|
||||
{Object.entries(transformationVars).map((entry) => {
|
||||
return `\$\{${entry[0]}\} = ${entry[1]?.value}\n`;
|
||||
})}
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" onClick={onCancel} fill="outline">
|
||||
<Trans i18nKey="explore.correlation-transformation-add-modal.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => onSave(getValues())} disabled={!validToSave}>
|
||||
{transformationToEdit
|
||||
? t('explore.correlation-transformation-add-modal.edit-transformation', 'Edit transformation')
|
||||
: t('explore.correlation-transformation-add-modal.add-transformation', 'Add transformation to correlation')}
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Modal } from '@grafana/ui';
|
||||
|
||||
interface UnsavedChangesModalProps {
|
||||
message: string;
|
||||
onDiscard: () => void;
|
||||
onCancel: () => void;
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
export const CorrelationUnsavedChangesModalLegacy = ({
|
||||
onSave,
|
||||
onDiscard,
|
||||
onCancel,
|
||||
message,
|
||||
}: UnsavedChangesModalProps) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
title={t(
|
||||
'explore.correlation-unsaved-changes-modal.title-unsaved-changes-to-correlation',
|
||||
'Unsaved changes to correlation'
|
||||
)}
|
||||
onDismiss={onCancel}
|
||||
icon="exclamation-triangle"
|
||||
className={css({ width: '600px' })}
|
||||
>
|
||||
<h5>{message}</h5>
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" onClick={onCancel} fill="outline">
|
||||
<Trans i18nKey="explore.correlation-unsaved-changes-modal.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={onDiscard}>
|
||||
<Trans i18nKey="explore.correlation-unsaved-changes-modal.continue-without-saving">
|
||||
Continue without saving
|
||||
</Trans>
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onSave}>
|
||||
<Trans i18nKey="explore.correlation-unsaved-changes-modal.save-correlation">Save correlation</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import {
|
||||
AdHocFilterItem,
|
||||
@@ -41,6 +41,7 @@ import { CONTENT_OUTLINE_LOCAL_STORAGE_KEYS, ContentOutline } from './ContentOut
|
||||
import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext';
|
||||
import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem';
|
||||
import { CorrelationHelper } from './CorrelationEditor/CorrelationHelper/CorrelationHelper';
|
||||
import { CorrelationHelperLegacy } from './CorrelationEditor/Legacy/CorrelationHelperLegacy';
|
||||
import { CustomContainer } from './CustomContainer';
|
||||
import { ExploreToolbar } from './ExploreToolbar';
|
||||
import { FlameGraphExploreContainer } from './FlameGraph/FlameGraphExploreContainer';
|
||||
@@ -597,6 +598,7 @@ export class Explore extends PureComponent<Props, ExploreState> {
|
||||
queryLibraryRef,
|
||||
} = this.props;
|
||||
const { contentOutlineVisible } = this.state;
|
||||
const correlationsExploreEditor = config.featureToggles.correlationsExploreEditor;
|
||||
const styles = getStyles(theme);
|
||||
const showPanels = queryResponse && queryResponse.state !== LoadingState.NotStarted;
|
||||
const richHistoryRowButtonHidden = !supportedFeatures().queryHistoryAvailable;
|
||||
@@ -617,7 +619,11 @@ export class Explore extends PureComponent<Props, ExploreState> {
|
||||
const isCorrelationsEditorMode = correlationEditorDetails?.editorMode;
|
||||
const showCorrelationHelper = Boolean(isCorrelationsEditorMode || correlationEditorDetails?.correlationDirty);
|
||||
if (showCorrelationHelper && correlationEditorHelperData !== undefined) {
|
||||
correlationsBox = <CorrelationHelper exploreId={exploreId} correlations={correlationEditorHelperData} />;
|
||||
correlationsBox = correlationsExploreEditor ? (
|
||||
<CorrelationHelper exploreId={exploreId} correlations={correlationEditorHelperData} />
|
||||
) : (
|
||||
<CorrelationHelperLegacy exploreId={exploreId} correlations={correlationEditorHelperData} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ExploreQueryParams } from 'app/types/explore';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { CorrelationEditorModeBar } from './CorrelationEditor/CorrelationEditorModeBar';
|
||||
import { CorrelationEditorModeBarLegacy } from './CorrelationEditor/Legacy/CorrelationEditorModeBarLegacy';
|
||||
import { ExploreActions } from './ExploreActions';
|
||||
import { ExploreDrawer } from './ExploreDrawer';
|
||||
import { ExplorePaneContainer } from './ExplorePaneContainer';
|
||||
@@ -32,7 +33,8 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor
|
||||
}
|
||||
|
||||
function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const correlationsExploreEditor = config.featureToggles.correlationsExploreEditor;
|
||||
const styles = useStyles2(getStyles, correlationsExploreEditor);
|
||||
const theme = useTheme2();
|
||||
useTimeSrvFix();
|
||||
useStateSync(props.queryParams);
|
||||
@@ -72,7 +74,12 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa
|
||||
<Trans i18nKey="nav.explore.title" />
|
||||
</h1>
|
||||
<ExploreActions />
|
||||
{showCorrelationEditorBar && <CorrelationEditorModeBar panes={panes} />}
|
||||
{showCorrelationEditorBar &&
|
||||
(correlationsExploreEditor ? (
|
||||
<CorrelationEditorModeBar panes={panes} />
|
||||
) : (
|
||||
<CorrelationEditorModeBarLegacy panes={panes} />
|
||||
))}
|
||||
<SplitPaneWrapper
|
||||
splitOrientation="vertical"
|
||||
paneSize={widthCalc}
|
||||
@@ -109,7 +116,7 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const getStyles = (theme: GrafanaTheme2, correlationsExploreEditor?: boolean) => {
|
||||
return {
|
||||
pageScrollbarWrapper: css({
|
||||
width: '100%',
|
||||
@@ -121,6 +128,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
}),
|
||||
correlationsEditorIndicator: css({
|
||||
overflow: 'scroll',
|
||||
borderLeft: correlationsExploreEditor ? 'none' : `4px solid ${theme.colors.primary.main}`,
|
||||
borderRight: correlationsExploreEditor ? 'none' : `4px solid ${theme.colors.primary.main}`,
|
||||
borderBottom: correlationsExploreEditor ? 'none' : `4px solid ${theme.colors.primary.main}`,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { shallowEqual } from 'react-redux';
|
||||
import { DataSourceInstanceSettings, RawTimeRange, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Components } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import {
|
||||
defaultIntervals,
|
||||
PageToolbar,
|
||||
@@ -225,6 +225,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
|
||||
<ShortLinkButtonMenu key="share" />,
|
||||
];
|
||||
|
||||
const correlationsExploreEditor = config.featureToggles.correlationsExploreEditor;
|
||||
const showBuilderIndicator = isCorrelationsEditorMode && !isLeftPane;
|
||||
const showSourceIndicator = isCorrelationsEditorMode && isLeftPane && splitted;
|
||||
|
||||
@@ -232,26 +233,37 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
|
||||
<div>
|
||||
{refreshInterval && <SetInterval func={onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
<AppChromeUpdate actions={navBarActions} />
|
||||
{showSourceIndicator && (
|
||||
<div className={styles.badgeWrapper}>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'explore.toolbar.source-correlation-query',
|
||||
'Run a query and click a correlation link to start building your correlation'
|
||||
)}
|
||||
>
|
||||
<Badge color="blue" icon="arrow-from-right" text={t('explore.toolbar.source-query', 'Source query')} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{showBuilderIndicator && (
|
||||
<div className={styles.badgeWrapper}>
|
||||
<Tooltip
|
||||
content={t('explore.toolbar.build-correlation-query', 'Build and test your correlation target query here')}
|
||||
>
|
||||
<Badge color="orange" icon="crosshair" text={t('explore.toolbar.target-query-builder', 'Correlation')} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{correlationsExploreEditor && (
|
||||
<>
|
||||
{showSourceIndicator && (
|
||||
<div className={styles.badgeWrapper}>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'explore.toolbar.source-correlation-query',
|
||||
'Run a query and click a correlation link to start building your correlation'
|
||||
)}
|
||||
>
|
||||
<Badge color="blue" icon="arrow-from-right" text={t('explore.toolbar.source-query', 'Source query')} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{showBuilderIndicator && (
|
||||
<div className={styles.badgeWrapper}>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'explore.toolbar.build-correlation-query',
|
||||
'Build and test your correlation target query here'
|
||||
)}
|
||||
>
|
||||
<Badge
|
||||
color="orange"
|
||||
icon="crosshair"
|
||||
text={t('explore.toolbar.target-query-builder', 'Correlation')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<PageToolbar
|
||||
aria-label={t('explore.toolbar.aria-label', 'Explore toolbar')}
|
||||
|
||||
@@ -7013,7 +7013,7 @@
|
||||
"edit-mode-title": "Correlation Editor Mode",
|
||||
"exit-correlation-editor": "Exit correlation editor",
|
||||
"experimental": "Experimental",
|
||||
"instructions": "Step 1: Run a query and click a table cell link or a \"🔗 Correlate with\" button.",
|
||||
"instructions": "Step 1: Run a query and click a table cell link or a <1></1> <strong>Correlate with</strong> button.",
|
||||
"instructions-2": "Step 2: In the right pane (Correlation), build and test your correlation query.",
|
||||
"instructions-3": "Step 3: Click Save to create the correlation.",
|
||||
"save": "Save"
|
||||
@@ -7034,13 +7034,22 @@
|
||||
},
|
||||
"correlation-helper": {
|
||||
"add-custom-variable": "Add custom variable",
|
||||
"add-transformation": "Add transformation",
|
||||
"aria-label-delete-transformation": "Delete transformation",
|
||||
"aria-label-edit-transformation": "Edit transformation",
|
||||
"body-correlation-details": "The correlation link will appear by the <1>{{resultField}}</1> field. You can use the following variables to set up your correlations:",
|
||||
"body-correlation-details-link": "When saved, the <1>{{resultField}}</1> field will have a clickable link that opens the specified URL.",
|
||||
"body-correlation-details-query": "When saved, the <1>{{resultField}}</1> field will have a clickable link that runs your target query below.",
|
||||
"body-variables": "Use these variables in your target query. When a correlation link is clicked, each variable is filled in with its value from that row.",
|
||||
"expression": "Expression: <1>{{expression}}</1>",
|
||||
"label-description": "Description",
|
||||
"label-description-header": "Label / Description",
|
||||
"label-label": "Label",
|
||||
"title-correlation-details": "Correlation details",
|
||||
"title-correlation-info": "Correlation Info",
|
||||
"title-variables": "Variables (optional)"
|
||||
"title-variables": "Variables (optional)",
|
||||
"tooltip-transformations": "A transformation extracts one or more variables out of a single field.",
|
||||
"transformations": "Transformations"
|
||||
},
|
||||
"correlation-tour": {
|
||||
"back": "Back",
|
||||
@@ -7056,7 +7065,7 @@
|
||||
"skip": "Skip tour",
|
||||
"step-counter": "Step {{current}} of {{total}}",
|
||||
"step-number": "Step {{step}}",
|
||||
"step1-body": "Run a query that returns data. You can then click a link in a table cell, or use the \"🔗 Correlate with [field name]\" button to start creating a correlation.",
|
||||
"step1-body": "Run a query that returns data. You can then click a link in a table cell, or use the <2></2> <strong>Correlate with [field name]</strong> button to start creating a correlation.",
|
||||
"step1-tip": "Tip: Look for these correlation links in table cells or log lines",
|
||||
"step1-title": "Step 1: Run a Query and Click a Link",
|
||||
"step2-body": "After clicking a correlation link, the <strong>right pane</strong> (target) opens with a query editor. Build and test your query here.",
|
||||
@@ -7074,6 +7083,7 @@
|
||||
"added-variables": "This custom variable will add the following variables:",
|
||||
"aria-label-field": "Field",
|
||||
"aria-label-type": "Type",
|
||||
"body": "A transformation extracts variables out of a single field. These variables will be available along with your field variables.",
|
||||
"cancel": "Cancel",
|
||||
"description-field": "Select the field from which to extract a value for your variable",
|
||||
"edit-transformation": "Edit transformation",
|
||||
@@ -7084,8 +7094,10 @@
|
||||
"label-type": "Type",
|
||||
"label-variable-name": "Variable name",
|
||||
"label-variable-name-without-tooltip": "Variable name",
|
||||
"title-add": "Add custom variable",
|
||||
"title-edit": "Edit custom variable"
|
||||
"title-add": "Add transformation",
|
||||
"title-add-custom-variable": "Add custom variable",
|
||||
"title-edit": "Edit transformation",
|
||||
"title-edit-custom-variable": "Edit custom variable"
|
||||
},
|
||||
"correlation-unsaved-changes-modal": {
|
||||
"cancel": "Cancel",
|
||||
|
||||
Reference in New Issue
Block a user