Skip to content

[index management] Refactor code to allow emitting multiple index lists#249120

Closed
mattkime wants to merge 3 commits intomainfrom
index_mgmt_data_load_refactor
Closed

[index management] Refactor code to allow emitting multiple index lists#249120
mattkime wants to merge 3 commits intomainfrom
index_mgmt_data_load_refactor

Conversation

@mattkime
Copy link
Copy Markdown
Contributor

@mattkime mattkime commented Jan 14, 2026

Summary

Since we need to load field list data from up to 6 api calls and need to keep the UI resilient and responsive I'd like emit a new index list a) after the initial get index list is loaded b) after any of the other calls append data to it.

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

  • Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the docker list
  • This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The release_note:breaking label should be applied in these situations.
  • Flaky Test Runner was used on any tests changed
  • The PR description includes the appropriate Release Notes section, and the correct release_note:* label is applied per the guidelines
  • Review the backport guidelines and apply applicable backport:* labels.

Identify risks

Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging.

@elasticmachine
Copy link
Copy Markdown
Contributor

elasticmachine commented Jan 14, 2026

🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!
  • Click to trigger kibana-deploy-project-from-pr for this PR!
  • Click to trigger kibana-deploy-cloud-from-pr for this PR!
  • Click to trigger kibana-entity-store-performance-from-pr for this PR!

@elasticmachine
Copy link
Copy Markdown
Contributor

elasticmachine commented Jan 15, 2026

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] Jest Tests #7 / Create Index can open & close the create index modal
  • [job] [logs] Jest Tests #7 / Create Index can open & close the create index modal
  • [job] [logs] Jest Tests #7 / Create Index shows the create index button
  • [job] [logs] Jest Tests #7 / Create Index shows the create index button
  • [job] [logs] Jest Tests #7 / data stream column doesn't show data stream link if the index doesn't have a data stream
  • [job] [logs] Jest Tests #7 / data stream column doesn't show data stream link if the index doesn't have a data stream
  • [job] [logs] Jest Tests #7 / data stream column navigates to the data stream in the Data Streams tab
  • [job] [logs] Jest Tests #7 / data stream column navigates to the data stream in the Data Streams tab
  • [job] [logs] Jest Tests #7 / empty list component renders "no indices found" prompt for search
  • [job] [logs] Jest Tests #7 / empty list component renders "no indices found" prompt for search
  • [job] [logs] Jest Tests #7 / empty list component renders the default empty list content
  • [job] [logs] Jest Tests #7 / empty list component renders the default empty list content
  • [job] [logs] Jest Tests #7 / index actions should be able to clear an index's cache
  • [job] [logs] Jest Tests #7 / index actions should be able to clear an index's cache
  • [job] [logs] Jest Tests #7 / index actions should be able to close an open index
  • [job] [logs] Jest Tests #7 / index actions should be able to close an open index
  • [job] [logs] Jest Tests #7 / index actions should be able to flush index
  • [job] [logs] Jest Tests #7 / index actions should be able to flush index
  • [job] [logs] Jest Tests #7 / index actions should be able to force merge an index
  • [job] [logs] Jest Tests #7 / index actions should be able to force merge an index
  • [job] [logs] Jest Tests #7 / index actions should be able to open a closed index
  • [job] [logs] Jest Tests #7 / index actions should be able to open a closed index
  • [job] [logs] Jest Tests #7 / index actions should be able to refresh index
  • [job] [logs] Jest Tests #7 / index actions should be able to refresh index
  • [job] [logs] Jest Tests #7 / index page works with % character in index name
  • [job] [logs] Jest Tests #7 / index page works with % character in index name
  • [job] [logs] Jest Tests #7 / Index stats renders no index stats when enableIndexStats is false, enableSizeAndDocCount is false
  • [job] [logs] Jest Tests #7 / Index stats renders no index stats when enableIndexStats is false, enableSizeAndDocCount is false
  • [job] [logs] Jest Tests #7 / Index stats renders only size and docs count when enableIndexStats is false, enableSizeAndDocCount is true
  • [job] [logs] Jest Tests #7 / Index stats renders only size and docs count when enableIndexStats is false, enableSizeAndDocCount is true
  • [job] [logs] Jest Tests #7 / Index stats renders the table column with all index stats when enableIndexStats is true
  • [job] [logs] Jest Tests #7 / Index stats renders the table column with all index stats when enableIndexStats is true
  • [job] [logs] Jest Tests #7 / navigates to the index details page when the index name is clicked
  • [job] [logs] Jest Tests #7 / navigates to the index details page when the index name is clicked
  • [job] [logs] Jest Tests #7 / on component mount toggles the include hidden button through URL hash correctly
  • [job] [logs] Jest Tests #7 / on component mount toggles the include hidden button through URL hash correctly
  • [job] [logs] Jest Tests #7 / on component mount updates the breadcrumbs to indices
  • [job] [logs] Jest Tests #7 / on component mount updates the breadcrumbs to indices
  • [job] [logs] Jest Tests #7 / Data Streams tab when there are special characters detail panel clicking the indices count navigates to the backing indices
  • [job] [logs] Jest Tests #7 / Data Streams tab when there are special characters detail panel clicking the indices count navigates to the backing indices

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
indexManagement 736.6KB 736.6KB -66.0B

History

@mattkime mattkime changed the title subscribe to updates instead of loading data once [index management] Refactor code to allow emitting multiple index lists Jan 15, 2026
loadIndices$.subscribe((indices) => {
store.dispatch(loadIndicesSuccess({ indices }));
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the right place to park this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I should pass the loadIndices function via the AppContextProvider.

There's also a reloadIndices function somewhere in there that I should examine.

},
loadIndices: () => {
dispatch(loadIndices());
},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was firing off the request and then getting the data back. I need something that could respond to multiple data sets being passed.

Copy link
Copy Markdown
Contributor

@kapral18 kapral18 Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was firing off the request and then getting the data back. I need something that could respond to multiple data sets being passed.

Totally get the intent, but we don't need RxJS here to achieve this. Redux already provides the “emit + subscribe” mechanism: each dispatch() is an emit, and the UI is already subscribed via react-redux (connect/selectors). So “multiple data sets” maps naturally to “dispatch multiple times as each chunk arrives”.

Here's an async/await shape (pseudo-code — names like currentRequestId / indicesEnrichmentSuccess are illustrative; you'd need to add the corresponding state/actions if you want this exact pattern) that probably matches what you're aiming for:

import { v4 as uuidv4 } from 'uuid';

export const loadIndices = () => async (dispatch, getState, { api }) => {
  const requestId = uuidv4();
  dispatch(loadIndicesStart({ requestId }));

  // 1) base list first (renders quickly)
  try {
    const base = await api.getIndicesBase();
    if (getState().indices.currentRequestId !== requestId) return; // ignore stale
    dispatch(loadIndicesSuccess({ requestId, indices: base }));
  } catch (error) {
    if (getState().indices.currentRequestId !== requestId) return; // ignore stale
    dispatch(loadIndicesError({ requestId, error }));
    return;
  }

  // 2) slow/optional enrichers in parallel (best-effort, progressive)
  const run = async (name, fn) => {
    try {
      const patch = await fn();
      if (getState().indices.currentRequestId !== requestId) return; // ignore stale
      dispatch(indicesEnrichmentSuccess({ requestId, name, patch }));
    } catch (error) {
      if (getState().indices.currentRequestId !== requestId) return; // ignore stale
      dispatch(indicesEnrichmentError({ requestId, name, error }));
    }
  };

  void run('stats', () => api.getIndicesStats());
  void run('other', () => api.getOtherEnrichment());
  // ...repeat for the other calls
};

The key idea is just: dispatch base ASAP, then dispatch progressive updates as each slow call returns, and record enrich failures without breaking the base list.

@@ -142,9 +143,12 @@ export async function updateDSFailureStore(

export async function loadIndices() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this function currently passes a single response to the Subject, the plan is to integrate code that would call a number of endpoints and mush the data together and return it.

export async function loadIndices() {
const response = await httpService.httpClient.get<any>(`${API_BASE_PATH}/indices`);
return response.data ? response.data : response;
internalLoadIndicesSubject.next(response.data);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this could update the store directly instead of using RxJS

Copy link
Copy Markdown
Contributor

@kapral18 kapral18 Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d keep services/api.ts free of direct store dispatching; better to orchestrate progressive loads in a thunk and let Redux dispatches be the “emissions”.

@kapral18
Copy link
Copy Markdown
Contributor

kapral18 commented Jan 15, 2026

I totally get what you’re aiming for (progressive loading + resilience when some calls are slow/fail). The part I’d push back on is using an RxJS Subject + manual subscribe as the mechanism for “emit + subscribe” — in this app it ends up fighting React/Redux instead of working with it.

Why I think it’s a net-negative here:

  • Redux already is the “emit/subscribe” system. Every dispatch() is an emit, and the UI is already subscribed via react-redux (connect + selectors). Adding a second stream means two competing flows to reason about.
  • It makes the loading/error story harder to keep correct. With the current shape, services/api.loadIndices() just .next()s, so unless you add another wrapper, loadIndicesStart/loadIndicesError don’t naturally line up with “a load started/failed”.
  • It introduces lifecycle footguns that React/Redux normally handle for you. A module-level Subject never completes, so any subscription needs explicit cleanup on unmount, and doing subscribe() in render makes it easy to accidentally create multiple subscriptions over time.
  • It spreads the “entrypoint” for loading across the app. Some callers dispatch, some call the API function directly, and some rely on the subscription side-effect. That makes it harder to test and harder to update later.
  • It’s extra machinery for something Redux already does well. The actual hard part here is progressive orchestration + partial failures + “latest request wins”, and you can solve those cleanly in a thunk without introducing RxJS into the UI layer.

What I’d do instead (and why I believe it’s better):

  • Keep loadIndices as a thunk/action entrypoint. That gives you one place to own loading/error state and one thing the UI calls (props.loadIndices() via connect or dispatch(loadIndices())).
  • Fetch the base list first, then dispatch it immediately so the table renders quickly.
  • Kick off the slow/optional calls in parallel and as each one returns, merge into the current list and dispatch again (either reuse loadIndicesSuccess or add a dedicated “patch/enrich” action).
  • Treat enrichment failures as best-effort: keep showing the base list and store an “enrichment failed” flag/message so you can omit those fields and/or show a small warning instead of failing the entire page.
  • Add a requestId in state so if the user reloads quickly, late responses from an older request don’t overwrite newer data.

Also: #246276 already moves in the right direction by splitting the expensive work into separate endpoints (/indices_get vs /indices_stats). That split is exactly what makes the Redux approach straightforward: dispatch the /indices_get result first, then enrich with /indices_stats when/if it returns, without needing an RxJS stream in the UI.

import { IndexTablePagination, PAGE_SIZE_OPTIONS } from './index_table_pagination';
import { DocCountCell } from './doc_count';
import { docCountApi } from './get_doc_count';
import { loadIndices } from '../../../../services/api';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulling loadIndices from services/api here means this component is directly triggering the fetch/emission path, instead of going through the existing Redux async flow. In this PR, store/actions/load_indices.js only defines loadIndicesStart/loadIndicesSuccess/loadIndicesError, so indicesLoading/indicesError won't naturally reflect retries/failures when callers invoke services/api.loadIndices() directly.

I think it’ll be easier to keep the progressive behavior and keep React/Redux happy if IndexTable calls a connected prop (e.g. props.loadIndices() from index_table.container.js), and that entrypoint dispatches start/error and kicks off the progressive load.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think I need to learn the redux way of doing things. This is my first time using it in quite a while and this isn't a simple use case so its not immediately clear how to. Sorting my thoughts out in another PR where I'm only concerned with data loading is helpful so I can have a better idea of the specific needs I have for working with the state system.

@mattkime mattkime closed this Jan 26, 2026
@sophiec20 sophiec20 deleted the index_mgmt_data_load_refactor branch March 11, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants