function DiscoverDocumentsComponent()

in src/platform/plugins/shared/discover/public/application/main/components/layout/discover_documents.tsx [97:486]


function DiscoverDocumentsComponent({
  viewModeToggle,
  dataView,
  onAddFilter,
  stateContainer,
  onFieldEdited,
}: {
  viewModeToggle: React.ReactElement | undefined;
  dataView: DataView;
  onAddFilter?: DocViewFilterFn;
  stateContainer: DiscoverStateContainer;
  onFieldEdited?: () => void;
}) {
  const services = useDiscoverServices();
  const dispatch = useInternalStateDispatch();
  const documents$ = stateContainer.dataState.data$.documents$;
  const savedSearch = useSavedSearchInitial();
  const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
  const requestParams = useCurrentTabSelector((state) => state.dataRequestParams);
  const [
    dataSource,
    query,
    sort,
    rowHeight,
    headerRowHeight,
    rowsPerPage,
    grid,
    columns,
    sampleSizeState,
    density,
  ] = useAppStateSelector((state) => {
    return [
      state.dataSource,
      state.query,
      state.sort,
      state.rowHeight,
      state.headerRowHeight,
      state.rowsPerPage,
      state.grid,
      state.columns,
      state.sampleSize,
      state.density,
    ];
  });
  const expandedDoc = useInternalStateSelector((state) => state.expandedDoc);
  const initialDocViewerTabId = useInternalStateSelector((state) => state.initialDocViewerTabId);
  const isEsqlMode = useIsEsqlMode();
  const documentState = useDataState(documents$);
  const isDataLoading =
    documentState.fetchStatus === FetchStatus.LOADING ||
    documentState.fetchStatus === FetchStatus.PARTIAL;

  // This is needed to prevent EuiDataGrid pushing onSort because the data view has been switched.
  // It's just necessary for non ES|QL requests since they don't have a partial result state, that's
  // considered as loading state in the Component.
  // 1. When switching the data view, the sorting in the URL is reset to the default sorting of the selected data view.
  // 2. The new sort param is already available in this component and propagated to the EuiDataGrid.
  // 3. currentColumns are still referring to the old state
  // 4. since the new sort by field isn't available in currentColumns EuiDataGrid is emitting a 'onSort', which is unsorting the grid
  // 5. this is propagated to Discover's URL and causes an unwanted change of state to an unsorted state
  // This solution switches to the loading state in this component when the URL index doesn't match the dataView.id
  const isDataViewLoading =
    useCurrentTabSelector((state) => state.isDataViewLoading) && !isEsqlMode;
  const isEmptyDataResult =
    isEsqlMode || !documentState.result || documentState.result.length === 0;
  const rows = useMemo(() => documentState.result || [], [documentState.result]);

  const { isMoreDataLoading, totalHits, onFetchMoreRecords } = useFetchMoreRecords({
    stateContainer,
  });

  const setAppState = useCallback<UseColumnsProps['setAppState']>(
    ({ settings, ...rest }) => {
      stateContainer.appState.update({ ...rest, grid: settings as DiscoverGridSettings });
    },
    [stateContainer]
  );

  const {
    columns: currentColumns,
    onAddColumn,
    onRemoveColumn,
    onSetColumns,
  } = useColumns({
    capabilities,
    defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
    dataView,
    dataViews,
    setAppState,
    columns,
    sort,
    settings: grid,
  });

  const onAddColumnWithTracking = useCallback(
    (columnName: string) => {
      onAddColumn(columnName);
      void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
    },
    [onAddColumn, ebtManager, fieldsMetadata]
  );

  const onRemoveColumnWithTracking = useCallback(
    (columnName: string) => {
      onRemoveColumn(columnName);
      void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
    },
    [onRemoveColumn, ebtManager, fieldsMetadata]
  );

  const docViewerRef = useRef<DocViewerApi>(null);
  const setExpandedDoc = useCallback(
    (doc: DataTableRecord | undefined, options?: { initialTabId?: string }) => {
      dispatch(
        internalStateActions.setExpandedDoc({
          expandedDoc: doc,
          initialDocViewerTabId: options?.initialTabId,
        })
      );
      if (options?.initialTabId) {
        docViewerRef.current?.setSelectedTabId(options.initialTabId);
      }
    },
    [dispatch]
  );

  const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
    (colSettings) => onResize(colSettings, stateContainer),
    [stateContainer]
  );

  const onUpdateRowsPerPage = useCallback(
    (nextRowsPerPage: number) => {
      stateContainer.appState.update({ rowsPerPage: nextRowsPerPage });
    },
    [stateContainer]
  );

  const onUpdateSampleSize = useCallback(
    (newSampleSize: number) => {
      stateContainer.appState.update({ sampleSize: newSampleSize });
    },
    [stateContainer]
  );

  const onSort = useCallback(
    (nextSort: string[][]) => {
      stateContainer.appState.update({ sort: nextSort });
    },
    [stateContainer]
  );

  const onUpdateRowHeight = useCallback(
    (newRowHeight: number) => {
      stateContainer.appState.update({ rowHeight: newRowHeight });
    },
    [stateContainer]
  );

  const onUpdateHeaderRowHeight = useCallback(
    (newHeaderRowHeight: number) => {
      stateContainer.appState.update({ headerRowHeight: newHeaderRowHeight });
    },
    [stateContainer]
  );

  const onUpdateDensity = useCallback(
    (newDensity: DataGridDensity) => {
      stateContainer.appState.update({ density: newDensity });
    },
    [stateContainer]
  );

  // should be aligned with embeddable `showTimeCol` prop
  const showTimeCol = useMemo(
    () => !uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false),
    [uiSettings]
  );

  const columnsMeta: DataTableColumnsMeta | undefined = useMemo(
    () =>
      documentState.esqlQueryColumns
        ? getTextBasedColumnsMeta(documentState.esqlQueryColumns)
        : undefined,
    [documentState.esqlQueryColumns]
  );
  const { filters } = useQuerySubscriber({ data: services.data });

  const cellActionsMetadata = useAdditionalCellActions({
    dataSource,
    dataView,
    query,
    filters,
    timeRange: requestParams.timeRangeAbsolute,
  });

  const renderDocumentView = useCallback(
    (
      hit: DataTableRecord,
      displayedRows: DataTableRecord[],
      displayedColumns: string[],
      customColumnsMeta?: DataTableColumnsMeta
    ) => (
      <DiscoverGridFlyout
        dataView={dataView}
        hit={hit}
        hits={displayedRows}
        // if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
        columns={displayedColumns}
        columnsMeta={customColumnsMeta}
        savedSearchId={savedSearch.id}
        onFilter={onAddFilter}
        onRemoveColumn={onRemoveColumnWithTracking}
        onAddColumn={onAddColumnWithTracking}
        onClose={() => setExpandedDoc(undefined)}
        setExpandedDoc={setExpandedDoc}
        query={query}
        initialTabId={initialDocViewerTabId}
        docViewerRef={docViewerRef}
      />
    ),
    [
      dataView,
      savedSearch.id,
      onAddFilter,
      onRemoveColumnWithTracking,
      onAddColumnWithTracking,
      setExpandedDoc,
      query,
      initialDocViewerTabId,
    ]
  );

  const configRowHeight = uiSettings.get(ROW_HEIGHT_OPTION);
  const cellRendererParams: CellRenderersExtensionParams = useMemo(
    () => ({
      actions: { addFilter: onAddFilter },
      dataView,
      density: density ?? getDataGridDensity(services.storage, 'discover'),
      rowHeight: getRowHeight({
        storage: services.storage,
        consumer: 'discover',
        rowHeightState: rowHeight,
        configRowHeight,
      }),
    }),
    [onAddFilter, dataView, density, services.storage, rowHeight, configRowHeight]
  );

  const { rowAdditionalLeadingControls } = useDiscoverCustomization('data_table') || {};
  const { customCellRenderer, customGridColumnsConfiguration } =
    useContextualGridCustomisations(cellRendererParams) || {};
  const additionalFieldGroups = useAdditionalFieldGroups();

  const getCellRenderersAccessor = useProfileAccessor('getCellRenderers');
  const cellRenderers = useMemo(() => {
    const getCellRenderers = getCellRenderersAccessor(() => customCellRenderer ?? {});
    return getCellRenderers(cellRendererParams);
  }, [cellRendererParams, customCellRenderer, getCellRenderersAccessor]);

  const documents = useObservable(stateContainer.dataState.data$.documents$);

  const callouts = useMemo(
    () => (
      <>
        <SelectedVSAvailableCallout
          esqlQueryColumns={documents?.esqlQueryColumns}
          // If `_source` is in the columns, we should exclude it from the callout
          selectedColumns={currentColumns.filter((col) => col !== '_source')}
        />
        <SearchResponseWarningsCallout warnings={documentState.interceptedWarnings ?? []} />
      </>
    ),
    [currentColumns, documents?.esqlQueryColumns, documentState.interceptedWarnings]
  );

  const loadingIndicator = useMemo(
    () =>
      isDataLoading ? (
        <EuiProgress
          data-test-subj="discoverDataGridUpdating"
          size="xs"
          color="accent"
          position="absolute"
          css={progressStyle}
        />
      ) : null,
    [isDataLoading]
  );

  const renderCustomToolbarWithElements = useMemo(
    () =>
      getRenderCustomToolbarWithElements({
        leftSide: viewModeToggle,
        bottomSection: (
          <>
            {callouts}
            {loadingIndicator}
          </>
        ),
      }),
    [viewModeToggle, callouts, loadingIndicator]
  );

  if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) {
    return (
      // class is used in tests
      <div className="dscDocuments__loading" css={dscDocumentsLoadingCss}>
        <EuiText size="xs" color="subdued">
          <EuiLoadingSpinner />
          <EuiSpacer size="s" />
          <FormattedMessage id="discover.loadingDocuments" defaultMessage="Loading documents" />
        </EuiText>
      </div>
    );
  }

  return (
    // class is used in tests
    <EuiFlexItem className="dscTable" aria-labelledby="documentsAriaLabel" css={containerStyles}>
      <EuiScreenReaderOnly>
        <h2 id="documentsAriaLabel">
          <FormattedMessage id="discover.documentsAriaLabel" defaultMessage="Documents" />
        </h2>
      </EuiScreenReaderOnly>
      <div className="unifiedDataTable">
        <CellActionsProvider getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}>
          <DiscoverGridMemoized
            ariaLabelledBy="documentsAriaLabel"
            columns={currentColumns}
            columnsMeta={columnsMeta}
            expandedDoc={expandedDoc}
            dataView={dataView}
            loadingState={
              isDataLoading
                ? DataLoadingState.loading
                : isMoreDataLoading
                ? DataLoadingState.loadingMore
                : DataLoadingState.loaded
            }
            rows={rows}
            sort={(sort as SortOrder[]) || []}
            searchDescription={savedSearch.description}
            searchTitle={savedSearch.title}
            setExpandedDoc={setExpandedDoc}
            showTimeCol={showTimeCol}
            settings={grid}
            onFilter={onAddFilter as DocViewFilterFn}
            onSetColumns={onSetColumns}
            onSort={onSort}
            onResize={onResizeDataGrid}
            configHeaderRowHeight={3}
            headerRowHeightState={headerRowHeight}
            onUpdateHeaderRowHeight={onUpdateHeaderRowHeight}
            rowHeightState={rowHeight}
            onUpdateRowHeight={onUpdateRowHeight}
            isSortEnabled={true}
            isPlainRecord={isEsqlMode}
            isPaginationEnabled={!isEsqlMode}
            rowsPerPageState={rowsPerPage ?? getDefaultRowsPerPage(services.uiSettings)}
            onUpdateRowsPerPage={onUpdateRowsPerPage}
            maxAllowedSampleSize={getMaxAllowedSampleSize(services.uiSettings)}
            sampleSizeState={getAllowedSampleSize(sampleSizeState, services.uiSettings)}
            onUpdateSampleSize={!isEsqlMode ? onUpdateSampleSize : undefined}
            onFieldEdited={onFieldEdited}
            configRowHeight={configRowHeight}
            showMultiFields={uiSettings.get(SHOW_MULTIFIELDS)}
            maxDocFieldsDisplayed={uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)}
            renderDocumentView={renderDocumentView}
            renderCustomToolbar={renderCustomToolbarWithElements}
            services={services}
            totalHits={totalHits}
            onFetchMoreRecords={onFetchMoreRecords}
            externalCustomRenderers={cellRenderers}
            customGridColumnsConfiguration={customGridColumnsConfiguration}
            rowAdditionalLeadingControls={rowAdditionalLeadingControls}
            additionalFieldGroups={additionalFieldGroups}
            dataGridDensityState={density}
            onUpdateDataGridDensity={onUpdateDensity}
            onUpdateESQLQuery={stateContainer.actions.updateESQLQuery}
            query={query}
            cellActionsTriggerId={DISCOVER_CELL_ACTIONS_TRIGGER.id}
            cellActionsMetadata={cellActionsMetadata}
            cellActionsHandling="append"
          />
        </CellActionsProvider>
      </div>
    </EuiFlexItem>
  );
}