Skip to content

feat(ingestion): add Executors tab with create/delete executor pools#364

Open
bmiller-dh wants to merge 3 commits intomasterfrom
feat/ingestion-executor-pools-tab
Open

feat(ingestion): add Executors tab with create/delete executor pools#364
bmiller-dh wants to merge 3 commits intomasterfrom
feat/ingestion-executor-pools-tab

Conversation

@bmiller-dh
Copy link

@bmiller-dh bmiller-dh commented Feb 4, 2026

Summary

  • Executors tab on Manage Data Sources: table of executor pools with row checkboxes, Create pool (modal: Pool ID + optional Name), and Delete Pool(s) with confirmation.
  • Delete guard: Pools in use by ingestion sources cannot be deleted; modal shows which sources use them and only a Close button until dependencies are resolved.
  • Scope: UI + E2E only. This PR does not include backend (GraphQL schema/resolvers/store). The UI calls listExecutorPools, createExecutorPool, deleteExecutorPools; the backend that implements these lives on the target branch (e.g. acryl-main).
  • E2E: New Cypress test ingestionV2/executors.js — navigates to Executors tab and verifies toolbar (Create pool, Delete Pool(s)).
  • Docs: docs/ui-ingestion.md updated with "Managing Executor Pools (Executors tab)".
  • Tests: Unit tests for ExecutorPoolsList, ExecutorPoolsTab, CreateExecutorPoolModal; E2E test for Executors tab.

Checklist

  • Tests added/updated
  • Docs updated
  • Breaking changes: none

Note

Medium Risk
Adds new ingestion UI flows and GraphQL mutations for creating/deleting executor pools, plus a shared modal API change that could affect other confirmation dialogs if misused; theme-loader change is localized but impacts app startup in dev.

Overview
Adds a new Executors tab to Manage Data Sources that lists executor pools and supports creating pools (via a modal requiring Pool ID + optional name) and bulk deletion with selection and pagination.

Deletion is guarded by checking ingestion sources’ executorId usage: the confirmation dialog becomes informational and blocks deletion when selected pools are in use (showing dependent sources).

Also introduces GraphQL operations listExecutorPools, createExecutorPool, and deleteExecutorPools, updates ingestion tab routing (/ingestion/executors), adds docs + Cypress coverage, and extends ConfirmationModal with hideConfirmButton; separately hardens dev-time custom theme loading by switching to import.meta.glob for JSON themes.

Written by Cursor Bugbot for commit ad4f0e0. This will update automatically on new commits. Configure here.

Cursor Bugbot found 2 potential issues for commit ad4f0e0

- Executors tab on Manage Data Sources with table, checkboxes, Create pool and Delete Pool(s)
- Delete guarded when ingestion sources use selected pools (modal shows which sources, Close only)
- GraphQL: listExecutorPools, createExecutorPool, deleteExecutorPools; Java resolvers with in-memory store
- ConfirmationModal: hideConfirmButton for blocked state; theme fix for useSetAppTheme (import.meta.glob)
- Docs: Managing Executor Pools section in docs/ui-ingestion.md
- Unit tests for ExecutorPoolsList, ExecutorPoolsTab, CreateExecutorPoolModal
query: undefined,
},
},
});
Copy link

Choose a reason for hiding this comment

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

Delete guard fails with more than 1000 sources

Medium Severity

The delete guard feature that prevents deleting pools in use by ingestion sources only checks the first 1000 sources (SOURCES_FETCH_COUNT = 1000). If a deployment has more than 1000 ingestion sources, pools used by sources beyond that limit won't be detected as "in use", allowing users to delete pools that are actually still being used. This breaks the safety guarantee described in the PR where "Pools in use by ingestion sources cannot be deleted."

Fix in Cursor Fix in Web

ExecutorPool pool = new ExecutorPool();
pool.setId(id.trim());
pool.setName(name != null && !name.isBlank() ? name.trim() : null);
POOLS.put(id.trim(), pool);
Copy link

Choose a reason for hiding this comment

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

Creating pool with existing ID silently overwrites it

Medium Severity

The create method uses POOLS.put() which silently overwrites any existing pool with the same ID. When a user calls the createExecutorPool mutation with an ID that already exists, the existing pool's name is replaced without any error or warning. Users expect a "create" operation to fail if the resource already exists, not perform an upsert. This could lead to accidental data loss if a user unknowingly provides an ID that's already in use.

Fix in Cursor Fix in Web

int from = Math.min(start, total);
int to = Math.min(start + count, total);
return new ArrayList<>(all.subList(from, to));
}
Copy link

Choose a reason for hiding this comment

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

Negative pagination values cause IndexOutOfBoundsException crash

Medium Severity

The list() method passes user-provided start and count values directly to ArrayList.subList() without validating they're non-negative. A GraphQL query with start: -1 would result in subList(-1, ...) which throws IndexOutOfBoundsException. Similarly, a negative count could make to less than from, throwing IllegalArgumentException. This would cause the API to return a 500 error instead of handling invalid input gracefully.

Additional Locations (1)

Fix in Cursor Fix in Web

@codecov
Copy link

codecov bot commented Feb 4, 2026

Bundle Report

Changes will increase total bundle size by 5.79kB (0.02%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
datahub-react-web-esm 29.57MB 5.79kB (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: datahub-react-web-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/index-*.js 5.79kB 19.39MB 0.03%

Files in assets/index-*.js:

  • ./src/app/ingestV2/executor/ExecutorPoolsTab.tsx → Total Size: 2.17kB

  • ./src/app/useSetAppTheme.tsx → Total Size: 1.64kB

  • ./src/app/ingestV2/ManageIngestionPage.tsx → Total Size: 7.6kB

  • ./src/app/ingestV2/types.ts → Total Size: 490 bytes

  • ./src/app/ingestV2/executor/CreateExecutorPoolModal.tsx → Total Size: 1.92kB

  • ./src/app/sharedV2/modals/ConfirmationModal.tsx → Total Size: 1.39kB

  • ./src/app/ingestV2/executor/ExecutorPoolsList.tsx → Total Size: 6.89kB

int total = all.size();
int from = Math.min(start, total);
int to = Math.min(start + count, total);
return new ArrayList<>(all.subList(from, to));
Copy link

Choose a reason for hiding this comment

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

Unpredictable pool ordering breaks pagination consistency

Low Severity

The ExecutorPoolStore uses ConcurrentHashMap which does not preserve insertion order or provide any deterministic iteration order. Pools created as A, B, C may be returned in any order (e.g., C, A, B), and this order may change between requests. This makes server-side pagination unreliable—a pool on page 1 in one request might appear on page 2 in the next.

Fix in Cursor Fix in Web

- Remove executor pool schema, resolvers, and store from graphql-core
- Add E2E test for Executors tab (ingestionV2/executors.js)
- Backend for list/create/delete executor pools lives on target branch
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

});
setSelectedPoolIds([]);
setShowDeleteConfirm(false);
refetch?.();
Copy link

Choose a reason for hiding this comment

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

Page state not reset after deletion causes empty view

Medium Severity

After successfully deleting pools, selectedPoolIds is cleared and refetch() is called, but the page state is never reset. If a user is on page 2 and deletes all items on that page, after refetch the current page will be empty (pools.slice(25, 50) returns []). Because totalPools > 0, the empty state component won't render, and with hideOnSinglePage the pagination is hidden when only one page of data remains. The user sees an empty table with no visible way to navigate back to page 1.

Additional Locations (2)

Fix in Cursor Fix in Web

query: undefined,
},
},
});
Copy link

Choose a reason for hiding this comment

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

Delete guard silently fails when sources query errors

Medium Severity

The useListIngestionSourcesQuery only destructures data, ignoring error and loading. If this query fails (network error, server error, etc.), sourcesData is undefined, causing sourcesByExecutorId to be an empty object. This makes the delete guard silently non-functional—poolsInUse will always be empty, deleteBlocked will always be false, and users can delete pools that are actually in use by ingestion sources without any warning. The PR explicitly describes this guard as a key feature.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments