Skip to content

RDF, cleanup relations and remove unnecessary bindings, add distributed mode for RDF reindex#26902

Open
harshach wants to merge 7 commits intomainfrom
rdf_v2
Open

RDF, cleanup relations and remove unnecessary bindings, add distributed mode for RDF reindex#26902
harshach wants to merge 7 commits intomainfrom
rdf_v2

Conversation

@harshach
Copy link
Copy Markdown
Collaborator

@harshach harshach commented Apr 1, 2026

Describe your changes:

Fixes

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • RDF relation cleanup and filtering:
    • Removed unnecessary RDF bindings and optimized entity graph traversal with batch queries
    • Added entity type and relationship type filtering to knowledge graph exploration
  • Distributed RDF indexing:
    • New distributed coordination framework with partition-based work distribution and server load balancing
    • Added recovery mechanisms, stale partition reclamation, and lock management
  • RDF batch processing refactor:
    • Extracted RdfBatchProcessor utility with shared logic for entity and relationship indexing
    • Centralized relationship exclusion rules (audit entities, follower/vote relations)
  • Schema and configuration updates:
    • Created database tables for distributed job tracking, partitions, locks, and per-server stats
    • Added useDistributedIndexing and partitionSize configuration options

This will update automatically on new commits.

@harshach harshach requested review from a team, akash-jain-10 and tutte as code owners April 1, 2026 03:03
Copilot AI review requested due to automatic review settings April 1, 2026 03:03
@github-actions github-actions bot added backend safe to test Add this label to run secure Github workflows on PRs labels Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

❌ Lint Check Failed — ESLint + Prettier + Organise Imports (src)

The following files have style issues that need to be fixed:
src/types/knowledgeGraph.types.ts src/utils/ApplicationSchemas/RdfIndexApp.json src/components/KnowledgeGraph/KnowledgeGraph.interface.ts src/components/KnowledgeGraph/KnowledgeGraph.tsx src/components/Settings/Applications/AppDetails/AppDetails.component.tsx src/components/Settings/Applications/AppDetails/AppDetails.test.tsx src/components/Settings/Applications/AppDetails/ApplicationsClassBase.test.ts src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.interface.ts src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx src/components/Settings/Applications/AppSchedule/AppScheduleProps.interface.ts src/constants/constants.ts src/context/AirflowStatusProvider/AirflowStatusProvider.test.tsx src/context/AirflowStatusProvider/AirflowStatusProvider.tsx src/rest/rdfAPI.ts

Fix locally (fast — only for changed files in the branch):

make ui-checkstyle-src-changed

Or to fix all files:

make ui-checkstyle-src

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

Comment on lines 294 to 296
}

try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Edge Case: RDF store clear removed but recreateIndex config still exists

The code that cleared the RDF store before re-indexing (rdfRepository.clearAll()) was removed from reIndexFromStartToEnd() (line 294-296 deleted). However, the recreateIndex configuration option still exists in both the JSON schema (rdfIndexingAppConfig.json line 103-108) and the UI schema (RdfIndexApp.json line 90-95). If a user enables recreateIndex, the setting is silently ignored, leading to stale data remaining in the RDF store during a full reindex.

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the RDF knowledge graph/reindexing feature set by adding UI/schema support for RDF indexing configuration (including distributed mode), improving runtime behavior around app pages, and wiring RDF job status updates through WebSockets.

Changes:

  • Added a new RDF indexing application schema and made application-run configuration UI resilient to missing schemas.
  • Extended RDF graph explore API types/params to support entity/relationship filtering and filter options in responses.
  • Added an RDF WebSocket broadcast channel and subscribed the App Runs History UI to RDF job status updates; optimized Airflow status fetching based on route.

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/ApplicationSchemas/RdfIndexApp.json New UI schema for RDF indexing configuration (incl. distributed options).
openmetadata-ui/src/main/resources/ui/src/rest/rdfAPI.ts Adds typed graph filter params/options and updates entity-graph API signature.
openmetadata-ui/src/main/resources/ui/src/components/KnowledgeGraph/KnowledgeGraph.interface.ts Adds filter option types to KnowledgeGraph data model.
openmetadata-ui/src/main/resources/ui/src/context/AirflowStatusProvider/AirflowStatusProvider.tsx Skips Airflow status fetch on routes where it’s not needed.
openmetadata-ui/src/main/resources/ui/src/context/AirflowStatusProvider/AirflowStatusProvider.test.tsx Adds route-aware tests via MemoryRouter and validates skipping behavior.
openmetadata-ui/src/main/resources/ui/src/constants/constants.ts Adds RDF_INDEX_JOB_BROADCAST_CHANNEL socket event constant.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppSchedule/AppScheduleProps.interface.ts Makes jsonSchema optional to support apps without a schema.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.interface.ts Makes jsonSchema optional to support schema-less runs/history UI.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx Disables config UI when schema missing and subscribes to RDF job WebSocket updates.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx Adds WebSocket mocking, schema-unavailable test, and RDF channel subscription test.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx Handles missing schema imports by clearing schema and showing a toast.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.test.ts Verifies RDF schema import works and includes new distributed fields.
openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexCoordinator.java Implements distributed RDF indexing coordinator flow (partition claim/aggregate refresh).
openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java Registers the distributed RDF job participant at application startup.

Comment on lines +209 to +223
public RdfIndexPartition claimNextPartition(UUID jobId) {
long claimAt = (System.currentTimeMillis() * 1000) + claimCounter.incrementAndGet();
int updated =
collectionDAO
.rdfIndexPartitionDAO()
.claimNextPartitionAtomic(jobId.toString(), serverId, claimAt);
if (updated <= 0) {
return null;
}

RdfIndexPartitionRecord record =
collectionDAO
.rdfIndexPartitionDAO()
.findLatestClaimedPartition(jobId.toString(), serverId, claimAt);
return record != null ? toPartition(record) : null;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

claimAt is being computed as System.currentTimeMillis() * 1000 + counter and passed as the :now parameter to claimNextPartitionAtomic, which sets claimedAt/startedAt/lastUpdateAt to that value. This inflates timestamps by 1000× and will break stale-heartbeat detection and any time-based reporting. Use a real epoch-millis now for timestamp columns, and if you need a unique claim token for findLatestClaimedPartition, generate it separately (or change the lookup query to not require exact claimedAt equality).

Copilot uses AI. Check for mistakes.
Comment on lines +449 to +452
if (status == IndexJobStatus.COMPLETED
|| status == IndexJobStatus.COMPLETED_WITH_ERRORS
|| status == IndexJobStatus.FAILED
|| status == IndexJobStatus.STOPPED) {
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

refreshAggregatedJob() sets completedAt(now) every time a terminal status is observed. This can rewrite the original completion timestamp on subsequent refreshes (e.g., other partitions finishing later, retries, or periodic refresh). Consider only setting completedAt if it is currently null, or persisting the first time the job becomes terminal so completion time remains stable.

Suggested change
if (status == IndexJobStatus.COMPLETED
|| status == IndexJobStatus.COMPLETED_WITH_ERRORS
|| status == IndexJobStatus.FAILED
|| status == IndexJobStatus.STOPPED) {
if (completedAt == null
&& (status == IndexJobStatus.COMPLETED
|| status == IndexJobStatus.COMPLETED_WITH_ERRORS
|| status == IndexJobStatus.FAILED
|| status == IndexJobStatus.STOPPED)) {

Copilot uses AI. Check for mistakes.
Comment on lines +1136 to +1138
CollectionDAO collectionDAO = jdbi.onDemand(CollectionDAO.class);
RdfDistributedJobParticipant participant = new RdfDistributedJobParticipant(collectionDAO);
environment.lifecycle().manage(participant);
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The catch block logs only ex.getMessage() and drops the stack trace, which makes diagnosing startup failures in distributed RDF job participant registration difficult. Log the exception object as well (e.g., LOG.warn(..., ex)) or rethrow if registration is required for correctness.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +120
try {
const schema = await applicationsClassBase.importSchema(fqn);
setJsonSchema(schema);
} catch (_) {
setJsonSchema(undefined);
showErrorToast(
t('message.no-application-schema-found', { appName: fqn })
);
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The schema import error is swallowed (catch (_)) and always shows the generic "no schema" toast. If the dynamic import fails for other reasons (e.g., chunk load error), this message can be misleading and makes debugging harder. Consider capturing the error and either passing it to showErrorToast (with the fallback message) or at least logging it for diagnosis.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +88
]
},
"default": [
"all"
],
"uiFieldType": "treeSelect",
"uniqueItems": true
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

entities.items.enum does not include the value "all", but entities.default is set to ["all"]. This makes the default invalid against the schema and can break form defaults/validation. Either add "all" to the enum or change the default to valid enum values.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 64%
64.7% (58938/91084) 44.2% (30885/69862) 47.51% (9338/19654)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🔴 Playwright Results — 12 failure(s), 42 flaky

✅ 3410 passed · ❌ 12 failed · 🟡 42 flaky · ⏭️ 226 skipped

Shard Passed Failed Flaky Skipped
🔴 Shard 1 449 2 4 2
🔴 Shard 2 599 5 15 32
🔴 Shard 3 613 2 5 27
🔴 Shard 4 609 3 9 50
🟡 Shard 5 585 0 2 67
🟡 Shard 6 555 0 7 48

Genuine Failures (failed on all attempts)

Features/CustomizeDetailPage.spec.ts › Table - customization should work (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveCount�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('customize-tab-card').getByRole('button').filter({ hasNotText: 'Add Tab' })
Expected: �[32m10�[39m
Received: �[31m11�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveCount" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('customize-tab-card').getByRole('button').filter({ hasNotText: 'Add Tab' })�[22m
�[2m    19 × locator resolved to 11 elements�[22m
�[2m       - unexpected value "11"�[22m

Features/Pagination.spec.ts › should test pagination on Bots page (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeDisabled�[2m(�[22m�[2m)�[22m failed

Locator: getByTestId('previous')
Expected: disabled
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeDisabled" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('previous')�[22m

Features/DataQuality/AddTestCaseNewFlow.spec.ts › Add Table Test Case (shard 2)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveCount�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('ingestion-list-table').locator('[data-row-key*="pw-database-service-843e67d4.pw-database-6b3754e0.pw-database-schema-eee09447.pw-table-3742ab91-4f8c-4f12-8d43-f9bbff602c8d.testSuite"]')
Expected: �[32m1�[39m
Received: �[31m0�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveCount" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('ingestion-list-table').locator('[data-row-key*="pw-database-service-843e67d4.pw-database-6b3754e0.pw-database-schema-eee09447.pw-table-3742ab91-4f8c-4f12-8d43-f9bbff602c8d.testSuite"]')�[22m
�[2m    19 × locator resolved to 0 elements�[22m
�[2m       - unexpected value "0"�[22m

Features/DataQuality/AddTestCaseNewFlow.spec.ts › Add Column Test Case (shard 2)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveCount�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('ingestion-list-table').locator('[data-row-key*="pw-database-service-79f61224.pw-database-2aabb139.pw-database-schema-6bffa865.pw-table-5203af1d-f655-43d2-b7b8-d1a2485a92e7.testSuite"]')
Expected: �[32m1�[39m
Received: �[31m0�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveCount" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('ingestion-list-table').locator('[data-row-key*="pw-database-service-79f61224.pw-database-2aabb139.pw-database-schema-6bffa865.pw-table-5203af1d-f655-43d2-b7b8-d1a2485a92e7.testSuite"]')�[22m
�[2m    19 × locator resolved to 0 elements�[22m
�[2m       - unexpected value "0"�[22m

Features/DataQuality/AddTestCaseNewFlow.spec.ts › Add multiple test case from table details page and validate pipeline (shard 2)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/DataQuality/DataQuality.spec.ts › Table test case (shard 2)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/DataQuality/Dimensionality.spec.ts › Dimensionality Tests (shard 2)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/TestSuiteMultiPipeline.spec.ts › TestSuite multi pipeline support (shard 3)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/TestSuiteMultiPipeline.spec.ts › Edit the pipeline's test case (shard 3)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoContainText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: getByRole('row', { name: /pw-test-suite-pipeline-11991f36/ }).getByTestId(/test-case-count-/)
Expected substring: �[32m"2"�[39m
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toContainText" with timeout 15000ms�[22m
�[2m  - waiting for getByRole('row', { name: /pw-test-suite-pipeline-11991f36/ }).getByTestId(/test-case-count-/)�[22m

Pages/DataContracts.spec.ts › Create Data Contract and validate for Table (shard 4)
�[31mTest timeout of 540000ms exceeded.�[39m
Pages/DataContracts.spec.ts › Pagination in Schema Tab with Selection Persistent (shard 4)
�[31mTest timeout of 540000ms exceeded.�[39m
Pages/DataProducts.spec.ts › Pagination (shard 4)
�[31mTest timeout of 60000ms exceeded.�[39m
🟡 42 flaky test(s) (passed on retry)
  • Features/CustomizeDetailPage.spec.ts › Database Schema - customization should work (shard 1, 1 retry)
  • Features/MetricCustomUnitFlow.spec.ts › Should create metric and test unit of measurement updates (shard 1, 1 retry)
  • Pages/AuditLogs.spec.ts › should apply both User and EntityType filters simultaneously (shard 1, 1 retry)
  • Pages/UserCreationWithPersona.spec.ts › Create user with persona and verify on profile (shard 1, 1 retry)
  • Features/AdvancedSearch.spec.ts › Verify Rule functionality for field Database with AND operator (shard 2, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Keyboard Delete selection (shard 2, 1 retry)
  • Features/DataQuality/BundleSuiteBulkOperations.spec.ts › Bulk selection operations (shard 2, 1 retry)
  • Features/DataQuality/ColumnLevelTests.spec.ts › Column Values Sum To Be Between (shard 2, 1 retry)
  • Features/DataQuality/DataQualityPermissions.spec.ts › User with TEST_CASE.VIEW_BASIC can view test case CONTENT details in UI (shard 2, 1 retry)
  • Features/DataQuality/IncidentManagerDateFilter.spec.ts › Clear selected date range (shard 2, 1 retry)
  • Features/DataQuality/TableLevelTests.spec.ts › Table Difference (shard 2, 1 retry)
  • Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts › Admin: Complete export-import-validate flow (shard 2, 1 retry)
  • Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts › EditAll User: Complete export-import-validate flow (shard 2, 1 retry)
  • Features/DataQuality/TestCaseIncidentPermissions.spec.ts › User with TEST_CASE.EDIT_ALL can see edit icon on incidents (shard 2, 1 retry)
  • Features/DataQuality/TestCaseIncidentPermissions.spec.ts › User with only VIEW cannot PATCH incidents (shard 2, 1 retry)
  • Features/DataQuality/TestCaseResultPermissions.spec.ts › User with only VIEW cannot PATCH results (shard 2, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should move term to root of different glossary (shard 2, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should move term with children to different glossary (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should delete parent term and cascade delete children (shard 3, 1 retry)
  • Features/Permissions/GlossaryPermissions.spec.ts › Team-based permissions work correctly (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Flow/ExploreDiscovery.spec.ts › Should not display soft deleted assets in search suggestions (shard 3, 1 retry)
  • Flow/ObservabilityAlerts.spec.ts › Table alert (shard 3, 1 retry)
  • Pages/Customproperties-part2.spec.ts › entityReferenceList shows item count, scrollable list, no expand toggle (shard 4, 1 retry)
  • Pages/DataContractInheritance.spec.ts › Full Contract Inheritance - Asset inherits full contract from Data Product (shard 4, 2 retries)
  • Pages/DataContractInheritance.spec.ts › Edit Inherited Contract - Creates new asset contract instead of modifying parent (shard 4, 2 retries)
  • Pages/DataProducts.spec.ts › Create Data Product and Manage Assets (shard 4, 2 retries)
  • Pages/Domains.spec.ts › Rename domain with deeply nested subdomains (3+ levels) verifies FQN propagation (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Delete Api Endpoint (shard 4, 1 retry)
  • ... and 12 more

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

Copilot AI review requested due to automatic review settings April 1, 2026 20:37
const schema = await applicationsClassBase.importSchema(fqn);
setJsonSchema(schema);
} catch (error) {
console.error(`Failed to load application schema for ${fqn}`, error);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix

AI 39 minutes ago

General fix: ensure that untrusted input is never used as the format string for logging functions. Either (a) do not use it in the first argument when there are extra arguments, or (b) separate static text (format string) from dynamic values using %s placeholders or multiple arguments, so any % in user input is not treated as a format specifier.

Best fix here without changing existing functionality: adjust the console.error call at line 116 in AppDetails.component.tsx so that the first argument is a static, trusted format string, and fqn plus the error object are passed as additional arguments. The visible log message remains effectively the same, but any % characters in fqn are safely treated as data. For example:

console.error('Failed to load application schema for %s', fqn, error);

This change is localized to the fetchAppDetails function in AppDetails.component.tsx. No new imports are required, and no behavior outside logging format is altered. All other files (useRequiredParams.ts, useFqn.ts) do not need modification for this specific issue.


Suggested changeset 1
openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
--- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
@@ -113,7 +113,7 @@
         const schema = await applicationsClassBase.importSchema(fqn);
         setJsonSchema(schema);
       } catch (error) {
-        console.error(`Failed to load application schema for ${fqn}`, error);
+        console.error('Failed to load application schema for %s', fqn, error);
         setJsonSchema(undefined);
         showErrorToast(
           t('message.no-application-schema-found', { appName: fqn })
EOF
@@ -113,7 +113,7 @@
const schema = await applicationsClassBase.importSchema(fqn);
setJsonSchema(schema);
} catch (error) {
console.error(`Failed to load application schema for ${fqn}`, error);
console.error('Failed to load application schema for %s', fqn, error);
setJsonSchema(undefined);
showErrorToast(
t('message.no-application-schema-found', { appName: fqn })
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Apr 1, 2026

Code Review ⚠️ Changes requested 4 resolved / 11 findings

RDF cleanup and distributed reindex addition resolves concurrency and injection vulnerabilities (distributed worker backoff, SPARQL injection, activeWorkers synchronization, job cleanup), but leaves 6 open findings including missing explorationMode dependency, hardcoded localhost fallback, and improper key binding in FailedTestCaseSampleData.

⚠️ Bug: useImperativeHandle missing explorationMode in deps array

The useImperativeHandle hook uses explorationMode at line 119 to decide the zoom level after fitView, but the dependency array at line 178 is [extractNodePositions, graphRef] — missing explorationMode. This means when the user switches between 'data' and 'model' modes, the fitView handle method captures a stale closure and applies the wrong zoom factor (FIT_VIEW_ZOOM_OUT vs FIT_VIEW_ZOOM_OUT_DATA_MODE).

Suggested fix
Add `explorationMode` to the dependency array:

      [extractNodePositions, graphRef, explorationMode]
💡 Edge Case: RDF store clear removed but recreateIndex config still exists

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/RdfIndexApp.java:294-296 📄 openmetadata-spec/src/main/resources/json/schema/entity/applications/configuration/internal/rdfIndexingAppConfig.json:103-108

The code that cleared the RDF store before re-indexing (rdfRepository.clearAll()) was removed from reIndexFromStartToEnd() (line 294-296 deleted). However, the recreateIndex configuration option still exists in both the JSON schema (rdfIndexingAppConfig.json line 103-108) and the UI schema (RdfIndexApp.json line 90-95). If a user enables recreateIndex, the setting is silently ignored, leading to stale data remaining in the RDF store during a full reindex.

💡 Bug: FailedTestCaseSampleData uses rowKey="name" but rows lack a name field

The Table component uses rowKey="name" but the data rows are constructed dynamically from column names (line 146-153). Unless one of the table's columns happens to be called "name", every row will have an undefined key, causing React duplicate-key warnings and potential rendering bugs when rows are added/removed.

Suggested fix
Use a row index as the key instead:

const data = (sampleData?.rows ?? []).map((item, index) => {
  const dataObject: Record<string, SampleDataType> = {};
  (sampleData?.columns ?? []).forEach((col, i) => {
    dataObject[col] = item[i];
  });
  dataObject.__rowKey = index;
  return dataObject;
});

// Then in the Table:
rowKey="__rowKey"
💡 Edge Case: resolveBaseUrl falls back to hardcoded localhost in production

When both MCP config and system settings fail to provide a base URL, resolveBaseUrl() silently falls back to http://localhost:8585. In a production deployment where these settings are misconfigured, the MCP OAuth redirect will silently point to localhost, causing authentication failures that are hard to diagnose. The warnings are only at WARN level and may be missed.

Suggested fix
Log at ERROR level when falling back to localhost, or throw an exception instead of silently using a wrong URL:

LOG.error("No base URL configured — MCP OAuth redirects will use http://localhost:8585. "
    + "Set the base URL in MCP config or system settings.");
return "http://localhost:8585";
💡 Bug: McpCallbackServlet response wrapper doesn't cover handleSSOCallback

In doGet(), the responseWrapper only wraps the ssoHandler.handleCallback() call to intercept redirects/errors. However, the subsequent handleSSOCallbackWithDbState() (called via userSSOProvider) writes directly to the real response, not the wrapper. If that method commits the response and then an exception is thrown, the catch block's response.isCommitted() check will find the response already committed, and the client receives a partial/incorrect response instead of a proper error page.

💡 Bug: exportAsSvg wraps a PNG raster inside SVG, not a true vector

The exportAsSvg function (lines 156-176) calls graph.toDataURL with type: 'image/png' and then embeds that raster data URL inside an <svg><image> wrapper. The resulting file is not a true vector SVG — it cannot be scaled without quality loss and cannot be edited as vector graphics. Users choosing 'SVG' export expect vector output. This is misleading and the file will be significantly larger than a proper SVG while offering no vector benefits.

Suggested fix
If G6 supports `graph.toDataURL({ type: 'image/svg+xml' })` or `graph.toSVG()`, use that for true vector export. Otherwise, rename the option to clarify it's a raster export (e.g., 'SVG (raster)').
💡 Edge Case: assetToTermMap silently drops multi-term asset connections

In useGraphData.ts lines 696-718, the assetToTermMap is typed as Record<string, string> (one term per asset). If an asset is connected to multiple terms via edges, only the last edge's term wins. This means the asset is positioned around only one term in the ring layout (positionAssetNodes), and visually disappears from the other term's ring. While multi-term assets may be uncommon, the silent overwrite makes debugging difficult.

✅ 4 resolved
Bug: Distributed worker loop can spin indefinitely with no backoff

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:186-195
In workerLoop() (line 177-199), when claimNextPartition() returns null (no claimable partitions), the worker sleeps for only 1 second before retrying. There's no check for whether all partitions are actually completed — only whether the job is terminal. If partition status updates are delayed (e.g., network latency to DB), multiple workers can busy-loop with 1-second intervals, repeatedly querying the database. Consider adding an exponential backoff or checking countPendingPartitions() to determine if work remains.

Security: SPARQL injection via unsanitized entityType in query construction

📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfRepository.java:771 📄 openmetadata-service/src/main/java/org/openmetadata/service/rdf/RdfRepository.java:1524-1538 📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/rdf/RdfResource.java:241-255
The entityType query parameter from the REST API is concatenated directly into SPARQL query strings without sanitization or validation. In RdfRepository.getEntityGraph(), the entityType is used to construct a URI that is embedded into SPARQL via buildEntityGraphBatchQuery(). Similarly, buildLineageQuery() directly interpolates entityType into SPARQL strings. While these endpoints require admin authorization, a compromised or malicious admin account could inject arbitrary SPARQL, potentially reading or modifying the entire RDF store.

Suggested fix: Validate entityType against a whitelist of known entity types (e.g., from Entity.getEntityList()) before using it in query construction. Additionally, ensure all URI components are properly escaped/encoded.

Bug: activeWorkers list has unsynchronized concurrent access

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:34 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:36 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:129-131 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:165-166
activeWorkers is declared as a plain ArrayList (line 34) but is written to in runWorkers() (line 165) and iterated in stop() (line 129). Since stop() can be called from any thread while runWorkers() is still adding workers, this is a classic concurrent modification race condition that can throw ConcurrentModificationException or silently skip workers during shutdown.

Additionally, currentJob (line 36) is accessed from multiple threads (worker threads via getJobWithFreshStats at line 179, main thread) without synchronization or volatile declaration, risking torn reads.

Bug: COORDINATED_JOBS not cleaned up on createJob/execute errors

📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:85-97 📄 openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/distributed/DistributedRdfIndexExecutor.java:201-215
In execute() (line 91), the job ID is added to the static COORDINATED_JOBS set. However, if an exception occurs during startCoordinatorThreads() or runWorkers() before finalizeCoordinatorJob() is reached, the job ID is never removed from COORDINATED_JOBS. This permanently prevents participants from joining this job ID (line 84 in RdfDistributedJobParticipant), effectively leaking the entry for the lifetime of the JVM.

🤖 Prompt for agents
Code Review: RDF cleanup and distributed reindex addition resolves concurrency and injection vulnerabilities (distributed worker backoff, SPARQL injection, activeWorkers synchronization, job cleanup), but leaves 6 open findings including missing explorationMode dependency, hardcoded localhost fallback, and improper key binding in FailedTestCaseSampleData.

1. 💡 Edge Case: RDF store clear removed but recreateIndex config still exists
   Files: openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/rdf/RdfIndexApp.java:294-296, openmetadata-spec/src/main/resources/json/schema/entity/applications/configuration/internal/rdfIndexingAppConfig.json:103-108

   The code that cleared the RDF store before re-indexing (`rdfRepository.clearAll()`) was removed from `reIndexFromStartToEnd()` (line 294-296 deleted). However, the `recreateIndex` configuration option still exists in both the JSON schema (rdfIndexingAppConfig.json line 103-108) and the UI schema (RdfIndexApp.json line 90-95). If a user enables `recreateIndex`, the setting is silently ignored, leading to stale data remaining in the RDF store during a full reindex.

2. 💡 Bug: FailedTestCaseSampleData uses rowKey="name" but rows lack a name field

   The `Table` component uses `rowKey="name"` but the data rows are constructed dynamically from column names (line 146-153). Unless one of the table's columns happens to be called "name", every row will have an `undefined` key, causing React duplicate-key warnings and potential rendering bugs when rows are added/removed.

   Suggested fix:
   Use a row index as the key instead:
   
   const data = (sampleData?.rows ?? []).map((item, index) => {
     const dataObject: Record<string, SampleDataType> = {};
     (sampleData?.columns ?? []).forEach((col, i) => {
       dataObject[col] = item[i];
     });
     dataObject.__rowKey = index;
     return dataObject;
   });
   
   // Then in the Table:
   rowKey="__rowKey"

3. 💡 Edge Case: resolveBaseUrl falls back to hardcoded localhost in production

   When both MCP config and system settings fail to provide a base URL, `resolveBaseUrl()` silently falls back to `http://localhost:8585`. In a production deployment where these settings are misconfigured, the MCP OAuth redirect will silently point to localhost, causing authentication failures that are hard to diagnose. The warnings are only at WARN level and may be missed.

   Suggested fix:
   Log at ERROR level when falling back to localhost, or throw an exception instead of silently using a wrong URL:
   
   LOG.error("No base URL configured — MCP OAuth redirects will use http://localhost:8585. "
       + "Set the base URL in MCP config or system settings.");
   return "http://localhost:8585";

4. 💡 Bug: McpCallbackServlet response wrapper doesn't cover handleSSOCallback

   In `doGet()`, the `responseWrapper` only wraps the `ssoHandler.handleCallback()` call to intercept redirects/errors. However, the subsequent `handleSSOCallbackWithDbState()` (called via `userSSOProvider`) writes directly to the real `response`, not the wrapper. If that method commits the response and then an exception is thrown, the catch block's `response.isCommitted()` check will find the response already committed, and the client receives a partial/incorrect response instead of a proper error page.

5. ⚠️ Bug: useImperativeHandle missing `explorationMode` in deps array

   The `useImperativeHandle` hook uses `explorationMode` at line 119 to decide the zoom level after `fitView`, but the dependency array at line 178 is `[extractNodePositions, graphRef]` — missing `explorationMode`. This means when the user switches between 'data' and 'model' modes, the `fitView` handle method captures a stale closure and applies the wrong zoom factor (`FIT_VIEW_ZOOM_OUT` vs `FIT_VIEW_ZOOM_OUT_DATA_MODE`).

   Suggested fix:
   Add `explorationMode` to the dependency array:
   
         [extractNodePositions, graphRef, explorationMode]

6. 💡 Bug: exportAsSvg wraps a PNG raster inside SVG, not a true vector

   The `exportAsSvg` function (lines 156-176) calls `graph.toDataURL` with `type: 'image/png'` and then embeds that raster data URL inside an `<svg><image>` wrapper. The resulting file is not a true vector SVG — it cannot be scaled without quality loss and cannot be edited as vector graphics. Users choosing 'SVG' export expect vector output. This is misleading and the file will be significantly larger than a proper SVG while offering no vector benefits.

   Suggested fix:
   If G6 supports `graph.toDataURL({ type: 'image/svg+xml' })` or `graph.toSVG()`, use that for true vector export. Otherwise, rename the option to clarify it's a raster export (e.g., 'SVG (raster)').

7. 💡 Edge Case: assetToTermMap silently drops multi-term asset connections

   In `useGraphData.ts` lines 696-718, the `assetToTermMap` is typed as `Record<string, string>` (one term per asset). If an asset is connected to multiple terms via edges, only the last edge's term wins. This means the asset is positioned around only one term in the ring layout (`positionAssetNodes`), and visually disappears from the other term's ring. While multi-term assets may be uncommon, the silent overwrite makes debugging difficult.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated 14 comments.

Comment on lines 151 to 156
try {
boolean containsAll = jobData.getEntities().contains(ALL);
if (containsAll) {
jobData.setEntities(getAll());
jobData.setEntities(resolveEntityTypes(jobData.getEntities()));
if (jobData.getEntities().isEmpty()) {
throw new IllegalStateException(
"No repository-backed entity types configured for RDF indexing");
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

resolveEntityTypes() returns an empty set when jobData.getEntities() is null/empty, and execute() then throws "No repository-backed entity types...". This contradicts the UI/spec schema and default app configs in this PR which set entities: [] to mean "index all supported entities"; as-is, the default/scheduled RDF index job will fail immediately. Consider treating null/empty entities as ALL (e.g., populate with Entity.getEntityList() or call getAll()), and only error when the resolved set is empty after filtering unsupported entities.

Copilot uses AI. Check for mistakes.
Comment on lines +297 to +299
private String buildErrorResponse(String message) {
return String.format("{\"error\": \"%s\"}", message);
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

buildErrorResponse() interpolates message into a JSON string without escaping. If the message contains quotes/newlines (or user-controlled data), the response becomes invalid JSON and could enable response injection. Prefer building the JSON with an object mapper (or returning a Map/POJO) and set the response MediaType.APPLICATION_JSON.

Copilot uses AI. Check for mistakes.
"auditLog",
"webAnalyticEvent",
"entityUsage",
"eventSubscription",
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

EXCLUDED_RELATIONSHIP_ENTITY_TYPES includes "eventSubscription", but the canonical entity type string is "eventsubscription" (see Entity.EVENT_SUBSCRIPTION). As written, event subscription relationships won't be excluded and will add noisy edges to the RDF graph. Update the constant to use the canonical entity type string.

Suggested change
"eventSubscription",
"eventsubscription",

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package org.openmetadata.service.apps.bundles.rdf;

import java.util.ArrayList;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new Java file is missing the standard Apache 2.0 license header block that other OpenMetadata service classes include (e.g., .../searchIndex/distributed/DistributedJobParticipant.java). Add the header to avoid license/format checks failing in CI.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package org.openmetadata.service.apps.bundles.rdf.distributed;

import java.util.List;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new Java file is missing the standard Apache 2.0 license header block that other OpenMetadata service classes include (e.g., .../searchIndex/distributed/DistributedJobParticipant.java). Add the header to avoid license/format checks failing in CI.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package org.openmetadata.service.apps.bundles.rdf.distributed;

import java.util.Map;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new Java file is missing the standard Apache 2.0 license header block that other OpenMetadata service classes include (e.g., .../searchIndex/distributed/DistributedJobParticipant.java). Add the header to avoid license/format checks failing in CI.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package org.openmetadata.service.apps.bundles.rdf.distributed;

import org.openmetadata.schema.system.EntityStats;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new Java file is missing the standard Apache 2.0 license header block that other OpenMetadata service classes include (e.g., .../searchIndex/distributed/DistributedJobParticipant.java). Add the header to avoid license/format checks failing in CI.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package org.openmetadata.service.apps.bundles.rdf.distributed;

import io.dropwizard.lifecycle.Managed;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new Java file is missing the standard Apache 2.0 license header block that other OpenMetadata service classes include (e.g., .../searchIndex/distributed/DistributedJobParticipant.java). Add the header to avoid license/format checks failing in CI.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +37
var mockSocket = {
on: jest.fn(),
off: jest.fn(),
};
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

var is used for mockSocket here; elsewhere in the UI test suite the pattern is const/let. Using var is function-scoped and can lead to accidental reassignments across tests. Switch this to const (or let if you truly need reassignment).

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +117
try {
const schema = await applicationsClassBase.importSchema(fqn);
setJsonSchema(schema);
} catch (error) {
console.error(`Failed to load application schema for ${fqn}`, error);
setJsonSchema(undefined);
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This console.error will violate the repo's no-console ESLint rule (errors on any console usage) unless explicitly disabled. Either add the standard // eslint-disable-next-line no-console comment (as done elsewhere in the codebase) or route this through the project's logging/toast utilities.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

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

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants