export function LensEditConfigurationFlyout()

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>
              }