in x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx [37:395]
export function LensEditConfigurationFlyout({
attributes,
coreStart,
startDependencies,
visualizationMap,
datasourceMap,
datasourceId,
updatePanelState,
updateSuggestion,
setCurrentAttributes,
closeFlyout,
saveByRef,
savedObjectId,
updateByRefInput,
dataLoading$,
lensAdapters,
navigateToLensEditor,
displayFlyoutHeader,
canEditTextBasedQuery,
isNewPanel,
hidesSuggestions,
onApply: onApplyCallback,
onCancel: onCancelCallback,
isReadOnly,
parentApi,
panelId,
}: EditConfigPanelProps) {
const euiTheme = useEuiTheme();
const previousAttributes = useRef<TypedLensSerializedState['attributes']>(attributes);
const [isInlineFlyoutVisible, setIsInlineFlyoutVisible] = useState(true);
const [isLayerAccordionOpen, setIsLayerAccordionOpen] = useState(true);
const [isSuggestionsAccordionOpen, setIsSuggestionsAccordionOpen] = useState(false);
const [isESQLResultsAccordionOpen, setIsESQLResultsAccordionOpen] = useState(false);
const { datasourceStates, visualization, isLoading, annotationGroups, searchSessionId } =
useLensSelector((state) => state.lens);
const activeVisualization =
visualizationMap[visualization.activeId ?? attributes.visualizationType];
const framePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap));
framePublicAPI.absDateRange = getAbsoluteDateRange(
startDependencies.data.query.timefilter.timefilter
);
const dispatch = useLensDispatch();
const attributesChanged = useMemo<boolean>(() => {
if (isNewPanel) return true;
const previousAttrs = previousAttributes.current;
const datasourceStatesAreSame =
datasourceStates[datasourceId].state && previousAttrs.state.datasourceStates[datasourceId]
? datasourceMap[datasourceId].isEqual(
previousAttrs.state.datasourceStates[datasourceId],
previousAttrs.references,
datasourceStates[datasourceId].state,
attributes.references
)
: false;
if (!datasourceStatesAreSame) return true;
const visualizationState = visualization.state;
const customIsEqual = visualizationMap[previousAttrs.visualizationType]?.isEqual;
const visualizationStateIsEqual = customIsEqual
? (() => {
try {
return customIsEqual(
previousAttrs.state.visualization,
previousAttrs.references,
visualizationState,
attributes.references,
annotationGroups
);
} catch (err) {
return false;
}
})()
: isEqual(visualizationState, previousAttrs.state.visualization);
return !visualizationStateIsEqual;
}, [
datasourceStates,
datasourceId,
datasourceMap,
attributes.references,
visualization.state,
isNewPanel,
visualizationMap,
annotationGroups,
]);
const onCancel = useCallback(() => {
const previousAttrs = previousAttributes.current;
if (attributesChanged) {
if (previousAttrs.visualizationType === visualization.activeId) {
const currentDatasourceState = datasourceMap[datasourceId].injectReferencesToLayers
? datasourceMap[datasourceId]?.injectReferencesToLayers?.(
previousAttrs.state.datasourceStates[datasourceId],
previousAttrs.references
)
: previousAttrs.state.datasourceStates[datasourceId];
updatePanelState?.(currentDatasourceState, previousAttrs.state.visualization);
} else {
updateSuggestion?.(previousAttrs);
}
if (savedObjectId) {
updateByRefInput?.(savedObjectId);
}
}
onCancelCallback?.();
closeFlyout?.();
}, [
attributesChanged,
closeFlyout,
visualization.activeId,
savedObjectId,
datasourceMap,
datasourceId,
updatePanelState,
updateSuggestion,
updateByRefInput,
onCancelCallback,
]);
const textBasedMode = isOfAggregateQueryType(attributes.state.query);
const currentAttributes = useCurrentAttributes({
textBasedMode,
initialAttributes: attributes,
datasourceMap,
visualizationMap,
});
const onApply = useCallback(() => {
if (visualization.activeId == null || !currentAttributes) {
return;
}
if (savedObjectId) {
saveByRef?.(currentAttributes);
updateByRefInput?.(savedObjectId);
}
// check if visualization type changed, if it did, don't pass the previous visualization state
const prevVisState =
previousAttributes.current.visualizationType === visualization.activeId
? previousAttributes.current.state.visualization
: undefined;
const telemetryEvents = activeVisualization.getTelemetryEventsOnSave?.(
visualization.state,
prevVisState
);
if (telemetryEvents && telemetryEvents.length) {
trackSaveUiCounterEvents(telemetryEvents);
}
onApplyCallback?.(currentAttributes);
closeFlyout?.();
}, [
visualization.activeId,
savedObjectId,
closeFlyout,
onApplyCallback,
visualization.state,
activeVisualization,
currentAttributes,
saveByRef,
updateByRefInput,
]);
const { getUserMessages } = useApplicationUserMessages({
coreStart,
framePublicAPI,
activeDatasourceId: datasourceId,
datasourceState: datasourceStates[datasourceId],
datasource: datasourceMap[datasourceId],
dispatch,
visualization: activeVisualization,
visualizationType: visualization.activeId,
visualizationState: visualization,
});
const editorContainer = useRef(null);
const isSaveable = useMemo(() => {
if (!attributesChanged) {
return false;
}
if (!visualization.state || !visualization.activeId) {
return false;
}
const visualizationErrors = getUserMessages(['visualization'], {
severity: 'error',
});
// shouldn't build expression if there is any type of error other than an expression build error
// (in which case we try again every time because the config might have changed)
if (visualizationErrors.every((error) => error.uniqueId === EXPRESSION_BUILD_ERROR_ID)) {
return Boolean(
buildExpression({
visualization: activeVisualization,
visualizationState: visualization.state,
datasourceMap,
datasourceStates,
datasourceLayers: framePublicAPI.datasourceLayers,
indexPatterns: framePublicAPI.dataViews.indexPatterns,
dateRange: framePublicAPI.dateRange,
nowInstant: startDependencies.data.nowProvider.get(),
searchSessionId,
})
);
}
}, [
attributesChanged,
activeVisualization,
datasourceMap,
datasourceStates,
framePublicAPI.dataViews.indexPatterns,
framePublicAPI.dateRange,
framePublicAPI.datasourceLayers,
searchSessionId,
startDependencies.data.nowProvider,
visualization.activeId,
visualization.state,
getUserMessages,
]);
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === keys.ESCAPE) {
closeFlyout?.();
setIsInlineFlyoutVisible(false);
}
};
if (isLoading) return null;
// Example is the Discover editing where we dont want to render the text based editor on the panel, neither the suggestions (for now)
if (!canEditTextBasedQuery && hidesSuggestions) {
return (
<>
{isInlineFlyoutVisible && <EuiWindowEvent event="keydown" handler={onKeyDown} />}
<FlyoutWrapper
isInlineFlyoutVisible={isInlineFlyoutVisible}
displayFlyoutHeader={displayFlyoutHeader}
onCancel={onCancel}
navigateToLensEditor={navigateToLensEditor}
onApply={onApply}
isScrollable
isNewPanel={isNewPanel}
isSaveable={isSaveable}
isReadOnly={isReadOnly}
>
<LayerConfiguration
// TODO: remove this once we support switching to any chart in Discover
onlyAllowSwitchToSubtypes
getUserMessages={getUserMessages}
attributes={attributes}
coreStart={coreStart}
startDependencies={startDependencies}
visualizationMap={visualizationMap}
datasourceMap={datasourceMap}
datasourceId={datasourceId}
hasPadding
framePublicAPI={framePublicAPI}
setIsInlineFlyoutVisible={setIsInlineFlyoutVisible}
updateSuggestion={updateSuggestion}
setCurrentAttributes={setCurrentAttributes}
closeFlyout={closeFlyout}
parentApi={parentApi}
panelId={panelId}
canEditTextBasedQuery={canEditTextBasedQuery}
/>
</FlyoutWrapper>
</>
);
}
return (
<>
{isInlineFlyoutVisible && <EuiWindowEvent event="keydown" handler={onKeyDown} />}
<FlyoutWrapper
isInlineFlyoutVisible={isInlineFlyoutVisible}
displayFlyoutHeader={displayFlyoutHeader}
onCancel={onCancel}
navigateToLensEditor={navigateToLensEditor}
onApply={onApply}
isSaveable={isSaveable}
isScrollable={false}
language={textBasedMode ? getLanguageDisplayName('esql') : ''}
isNewPanel={isNewPanel}
isReadOnly={isReadOnly}
>
<EuiFlexGroup
css={css`
block-size: 100%;
.euiFlexItem,
.euiAccordion,
.euiAccordion__triggerWrapper,
.euiAccordion__childWrapper {
min-block-size: 0;
}
.euiAccordion {
display: flex;
flex: 1;
flex-direction: column;
}
.euiAccordion__childWrapper {
${euiScrollBarStyles(euiTheme)}
overflow-y: auto !important;
pointer-events: none;
padding-left: ${euiThemeVars.euiFormMaxWidth};
margin-left: -${euiThemeVars.euiFormMaxWidth};
> * {
pointer-events: auto;
}
.euiAccordion-isOpen & {
block-size: auto !important;
flex: 1;
}
}
.lnsIndexPatternDimensionEditor-advancedOptions {
.euiAccordion__childWrapper {
flex: none;
overflow: hidden !important;
}
}
`}
direction="column"
gutterSize="none"
>
<div ref={editorContainer} />
<EuiFlexItem
grow={isLayerAccordionOpen ? 1 : false}
css={css`
.euiAccordion__childWrapper {
flex: ${isLayerAccordionOpen ? 1 : 'none'};
}
padding: 0 ${euiThemeVars.euiSize};
`}
>
<EuiAccordion
id="layer-configuration"
buttonContent={
<EuiTitle
size="xxs"
css={css`
padding: 2px;
}
`}
>
<h5>
{i18n.translate('xpack.lens.config.visualizationConfigurationLabel', {
defaultMessage: 'Visualization parameters',
})}
</h5>
</EuiTitle>
}