-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix(search): skip soft-delete script propagation to time-series child aliases #28064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
harshach
wants to merge
32
commits into
main
Choose a base branch
from
fix-soft-delete-time-series-propagation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
a34f908
Entity Time Series
mohityadav766 786a158
test case results
mohityadav766 0775875
Add Playwright
mohityadav766 f49cb13
fix: declare 'summary' on TestSuiteIndex so reindex preserves lastRes…
mohityadav766 9674ce7
fix: skip soft-delete propagation to time-series child aliases
harshach 5dcdf54
feat(reindex): ratio-based promotion policy on the distributed finalizer
harshach 952122b
Update generated TypeScript types
github-actions[bot] 6edcbe1
feat(search): typed capability registry + IndexUpdateScript abstraction
harshach 0799663
Fix java style
harshach 9ca9bc1
fix(search): live-indexing scripts mirror reindex tag/tier/cert separ…
harshach c11fb62
test(playwright): cross-entity live + reindex filter separation matrix
harshach 059d7b5
test(playwright): glossary-rename cascade + Database/Schema matrix en…
harshach a575f53
review: address PR #28064 review comments
harshach b79d6ee
Update generated TypeScript types
github-actions[bot] 707ba69
review: remove unused SOFT_DELETE_RESTORE_SCRIPT + isolate soft-delet…
harshach fd0222d
style: spotless reformat on TestCaseSoftDeleteSearchIT
harshach 4a61650
fix(it): inline try/catch in TestCaseSoftDeleteSearchIT cleanup
harshach c0af4ba
Merge branch 'main' into fix-soft-delete-time-series-propagation
mohityadav766 f8c7293
review: address 5 open PR comments + bump IncidentManager poll timeout
harshach 8d393aa
fix(playwright): CI failures in SearchSeparation + ServiceEntityVersi…
harshach f232452
fix(playwright): correct fullyQualifiedName query + relocate suite fa…
mohityadav766 09c3381
Merge branch 'main' into fix-soft-delete-time-series-propagation
mohityadav766 9ae37a5
docs(playwright): update README import path after suite move
mohityadav766 d4acd8d
fix(search): guard TAG_RESEPARATION_SCRIPT writes behind containsKey(…
mohityadav766 73501ed
Merge branch 'main' into fix-soft-delete-time-series-propagation
mohityadav766 1c4f020
Merge remote-tracking branch 'origin/fix-soft-delete-time-series-prop…
mohityadav766 9274e60
review: address PR review-4284133687 comments
harshach ccb4a0d
review: replace test.slow() in hook with explicit describe/hook timeouts
harshach 6660555
Merge branch 'main' into fix-soft-delete-time-series-propagation
harshach f010cfb
fix(search): guard TAG_RESEPARATION_SCRIPT tier write behind null check
harshach b5e1fa8
fix(search): wildcard "?*" instead of term(field,"") for non-empty check
harshach a82fda0
Merge branch 'main' into fix-soft-delete-time-series-propagation
harshach File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
231 changes: 231 additions & 0 deletions
231
...integration-tests/src/test/java/org/openmetadata/it/tests/TestCaseSoftDeleteSearchIT.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| /* | ||
| * Copyright 2026 Collate | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.openmetadata.it.tests; | ||
|
|
||
| import static org.awaitility.Awaitility.await; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import com.fasterxml.jackson.databind.JsonNode; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import java.time.Duration; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.TestInstance; | ||
| import org.junit.jupiter.api.parallel.Execution; | ||
| import org.junit.jupiter.api.parallel.ExecutionMode; | ||
| import org.junit.jupiter.api.parallel.Isolated; | ||
| import org.openmetadata.it.bootstrap.SharedEntities; | ||
| import org.openmetadata.it.util.SdkClients; | ||
| import org.openmetadata.schema.api.data.CreateDatabase; | ||
| import org.openmetadata.schema.api.data.CreateDatabaseSchema; | ||
| import org.openmetadata.schema.api.data.CreateTable; | ||
| import org.openmetadata.schema.api.tests.CreateTestCase; | ||
| import org.openmetadata.schema.api.tests.CreateTestCaseResolutionStatus; | ||
| import org.openmetadata.schema.entity.data.Database; | ||
| import org.openmetadata.schema.entity.data.DatabaseSchema; | ||
| import org.openmetadata.schema.entity.data.Table; | ||
| import org.openmetadata.schema.tests.TestCase; | ||
| import org.openmetadata.schema.tests.type.Severity; | ||
| import org.openmetadata.schema.tests.type.TestCaseResolutionStatus; | ||
| import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes; | ||
| import org.openmetadata.schema.type.Column; | ||
| import org.openmetadata.schema.type.ColumnDataType; | ||
| import org.openmetadata.sdk.client.OpenMetadataClient; | ||
| import org.openmetadata.sdk.models.ListParams; | ||
| import org.openmetadata.sdk.models.ListResponse; | ||
|
|
||
| /** | ||
| * Regression for the live-indexing soft-delete propagation bug. {@code SOFT_DELETE_RESTORE_SCRIPT} | ||
| * was stamping a top-level {@code deleted} field onto child docs of every alias listed in the | ||
| * parent's {@code indexMapping}. For {@code testCase}, two of those children | ||
| * ({@code testCaseResolutionStatus}, {@code testCaseResult}) are time-series indexes whose | ||
| * Java schemas declare no {@code deleted} field. The poisoned doc broke Jackson on read and | ||
| * the Incident Manager UI surfaced an "Unrecognized field 'deleted'" toast. | ||
| * | ||
| * <p>This test exercises the end-to-end path: create a TC + result + incident, soft-delete the | ||
| * TC, and confirm that (a) the resolution-status listing API still parses cleanly and (b) the | ||
| * underlying ES doc carries no top-level {@code deleted} field. | ||
| */ | ||
| @Execution(ExecutionMode.SAME_THREAD) | ||
| @Isolated | ||
| @TestInstance(TestInstance.Lifecycle.PER_CLASS) | ||
| public class TestCaseSoftDeleteSearchIT { | ||
|
|
||
| private static final ObjectMapper MAPPER = new ObjectMapper(); | ||
|
|
||
| @Test | ||
| void softDeletingTestCaseDoesNotPollutePropagatedTimeSeriesDocs() throws Exception { | ||
| OpenMetadataClient client = SdkClients.adminClient(); | ||
|
|
||
| long ts = System.currentTimeMillis(); | ||
| Database database = null; | ||
| DatabaseSchema schema = null; | ||
| Table table = null; | ||
| TestCase testCase = null; | ||
| try { | ||
| database = | ||
| client | ||
| .databases() | ||
| .create( | ||
| new CreateDatabase() | ||
| .withName("soft_delete_db_" + ts) | ||
| .withService(SharedEntities.get().MYSQL_SERVICE.getFullyQualifiedName())); | ||
| schema = | ||
| client | ||
| .databaseSchemas() | ||
| .create( | ||
| new CreateDatabaseSchema() | ||
| .withName("soft_delete_schema_" + ts) | ||
| .withDatabase(database.getFullyQualifiedName())); | ||
| table = | ||
| client | ||
| .tables() | ||
| .create( | ||
| new CreateTable() | ||
| .withName("soft_delete_table_" + ts) | ||
| .withDatabaseSchema(schema.getFullyQualifiedName()) | ||
| .withColumns( | ||
| List.of( | ||
| new Column().withName("id").withDataType(ColumnDataType.BIGINT)))); | ||
|
|
||
| String testDefFqn = | ||
| client | ||
| .testDefinitions() | ||
| .list(new ListParams().withLimit(1)) | ||
| .getData() | ||
| .get(0) | ||
| .getFullyQualifiedName(); | ||
|
|
||
| testCase = | ||
| client | ||
| .testCases() | ||
| .create( | ||
| new CreateTestCase() | ||
| .withName("soft_delete_tc_" + ts) | ||
| .withEntityLink( | ||
| "<#E::table::" + table.getFullyQualifiedName() + "::columns::id>") | ||
| .withTestDefinition(testDefFqn)); | ||
|
|
||
| client | ||
| .testCaseResolutionStatuses() | ||
| .create( | ||
| new CreateTestCaseResolutionStatus() | ||
| .withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.New) | ||
| .withTestCaseReference(testCase.getFullyQualifiedName()) | ||
| .withSeverity(Severity.Severity2)); | ||
|
|
||
| awaitIncidentIndexed(client, testCase.getFullyQualifiedName()); | ||
|
|
||
| client | ||
| .testCases() | ||
| .delete(testCase.getId().toString(), Map.of("hardDelete", "false", "recursive", "true")); | ||
|
|
||
| assertListingApiReturnsCleanlyAfterSoftDelete(client, testCase.getFullyQualifiedName()); | ||
| assertNoTopLevelDeletedFieldOnIncidentDoc(client, testCase.getFullyQualifiedName()); | ||
| } finally { | ||
| // Hard-delete the entire database tree so the test leaves no artefacts behind. The | ||
| // testCase + resolution statuses are recursively cascaded with the parent table. | ||
| // Best-effort cleanup — assertion failures take precedence over cleanup exceptions. | ||
| if (database != null) { | ||
| try { | ||
| client | ||
| .databases() | ||
| .delete( | ||
| database.getId().toString(), Map.of("hardDelete", "true", "recursive", "true")); | ||
| } catch (Exception ignored) { | ||
| // intentionally swallowed | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void awaitIncidentIndexed(OpenMetadataClient client, String testCaseFqn) { | ||
| await("Wait for resolution status to be searchable") | ||
| .atMost(Duration.ofMinutes(2)) | ||
| .pollInterval(Duration.ofSeconds(2)) | ||
| .ignoreExceptions() | ||
| .untilAsserted( | ||
| () -> { | ||
| ListResponse<TestCaseResolutionStatus> resp = | ||
| client | ||
| .testCaseResolutionStatuses() | ||
| .searchList( | ||
| new ListParams() | ||
| .withLimit(1) | ||
| .withLatest(true) | ||
| .addFilter("testCaseFQN", testCaseFqn)); | ||
| assertNotNull(resp); | ||
| assertEquals( | ||
| 1, | ||
| resp.getData().size(), | ||
| "Incident for the test case should be indexed before we soft-delete"); | ||
| }); | ||
| } | ||
|
|
||
| private void assertListingApiReturnsCleanlyAfterSoftDelete( | ||
| OpenMetadataClient client, String testCaseFqn) { | ||
| await("API returns parseable body after soft-delete propagation") | ||
| .atMost(Duration.ofMinutes(1)) | ||
| .pollInterval(Duration.ofSeconds(2)) | ||
| .ignoreExceptions() | ||
| .untilAsserted( | ||
| () -> { | ||
| ListResponse<TestCaseResolutionStatus> resp = | ||
| client | ||
| .testCaseResolutionStatuses() | ||
| .searchList( | ||
| new ListParams() | ||
| .withLimit(10) | ||
| .withLatest(true) | ||
| .addFilter("testCaseFQN", testCaseFqn)); | ||
| assertNotNull( | ||
| resp, "list endpoint must return a body; null implies a deserialization failure"); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * The fix in {@link org.openmetadata.service.search.SearchRepository#softDeleteOrRestoredChildren} | ||
| * filters out time-series child aliases before invoking the soft-delete script. Confirm by | ||
| * querying ES directly for any TCRS doc that has a top-level {@code deleted} field — there | ||
| * must be none for our test case. | ||
| */ | ||
| private void assertNoTopLevelDeletedFieldOnIncidentDoc( | ||
| OpenMetadataClient client, String testCaseFqn) throws Exception { | ||
| String rawJson = | ||
| client | ||
| .search() | ||
| .query( | ||
| "testCaseReference.fullyQualifiedName.keyword:\"" | ||
| + testCaseFqn | ||
| + "\" AND _exists_:deleted") | ||
| .index("test_case_resolution_status_search_index") | ||
| .size(5) | ||
| .execute(); | ||
| JsonNode root = MAPPER.readTree(rawJson); | ||
| JsonNode hits = root.path("hits").path("hits"); | ||
| assertTrue( | ||
| hits.isArray(), () -> "ES response missing hits.hits array; raw response was: " + rawJson); | ||
| assertFalse( | ||
| hits.elements().hasNext(), | ||
| () -> | ||
| "No `deleted` field should exist on testCaseResolutionStatus docs after a parent" | ||
| + " soft-delete; found " | ||
| + hits.size() | ||
| + " polluted docs. Raw response: " | ||
| + rawJson); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.