diff --git a/.github/playwright/impact-map.generated.json b/.github/playwright/impact-map.generated.json index a5c2053bdd35..4748d14360fc 100644 --- a/.github/playwright/impact-map.generated.json +++ b/.github/playwright/impact-map.generated.json @@ -3328,6 +3328,7 @@ "playwright/e2e/Features/AdvancedSearch.spec.ts", "playwright/e2e/Features/ContextCenterDashboard.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerSqlQuery.spec.ts", + "playwright/e2e/Features/DataQuality/ProfilerIngestionForm.spec.ts", "playwright/e2e/Features/DataQuality/TableTestCaseExportFilters.spec.ts", "playwright/e2e/Features/DomainFilterQueryFilter.spec.ts", "playwright/e2e/Features/LandingPageWidgets/LandingPageContextCenter.spec.ts", @@ -8668,6 +8669,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/AddIngestion.component.tsx" ], "specs": [ + "playwright/e2e/Features/DataQuality/ProfilerIngestionForm.spec.ts", "playwright/e2e/Features/StorageMetadataAgentForm.spec.ts" ] }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiFormFields.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiFormFields.component.test.tsx index 94c836415c7e..4f6dfb81172b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiFormFields.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiFormFields.component.test.tsx @@ -52,13 +52,16 @@ import { } from '../../../pages/AddObservabilityPage/AddObservabilityPage.interface'; import { testAlertDestination } from '../../../rest/alertsAPI'; import { searchQuery } from '../../../rest/searchAPI'; +import { AlertSourceSearch } from '../../../utils/Alerts/AlertSourceSearch'; import alertsClassBase from '../../../utils/AlertsClassBase'; import AlertAiDestinationConfigFields from './AlertAiDestinationConfigFields.component'; import AlertAiDestinationItem from './AlertAiDestinationItem.component'; import AlertAiDestinationSection from './AlertAiDestinationSection.component'; import AlertAiForm from './AlertAiForm.component'; import AlertAiFormFields from './AlertAiFormFields.component'; -import AlertAiRuleSection from './AlertAiRuleSection.component'; +import AlertAiRuleSection, { + RuleArgumentField, +} from './AlertAiRuleSection.component'; import AlertAiSection from './AlertAiSection.component'; import AlertDescriptionCard from './AlertDescriptionCard.component'; @@ -859,6 +862,60 @@ describe('AlertAi form field components', () => { ); }); + it('keeps a typed search when the source search is rebuilt during the debounce', async () => { + jest.useFakeTimers(); + const getSourceSearch = () => + ({ + byId: jest.fn(), + byName: jest.fn(), + containerEntities: [], + indexes: [], + } as unknown as AlertSourceSearch); + const value = { + ...baseValue, + input: { + filters: [ + { + arguments: [{ input: [], name: 'domainList' }], + effect: Effect.Include, + name: 'matchAnyDomain', + }, + ], + }, + } as ModifiedCreateEventSubscription; + const renderField = (sourceSearch: AlertSourceSearch) => ( + + ); + + const { rerender } = render(renderField(getSourceSearch())); + await act(async () => { + jest.advanceTimersByTime(0); + }); + (searchQuery as jest.Mock).mockClear(); + + fireEvent.change(screen.getByTestId('domainList-autocomplete'), { + target: { value: 'dom' }, + }); + // Adding a filter re-asks the server what the selection supports, which hands the field + // a new source search while the typed text is still waiting on the debounce. + rerender(renderField(getSourceSearch())); + await act(async () => { + jest.advanceTimersByTime(500); + }); + + expect(searchQuery).toHaveBeenCalledWith( + expect.objectContaining({ query: 'dom' }) + ); + }); + it('renders fixed runtime options with the fqn autocomplete contract', () => { const onChange = jest.fn(); const selectedRule: ArgumentsInput = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiRuleSection.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiRuleSection.component.tsx index f63728690fe5..dbf7296213a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiRuleSection.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/observability/Alerts/AlertAiRuleSection.component.tsx @@ -316,15 +316,30 @@ const AiArgumentAutocomplete = ({ [hasNoResults, items, t] ); + // Read through a ref so a new source search (the server re-answers capabilities whenever the + // filters change) does not recreate, and so cancel, a search the user is still typing. + const searchParamsRef = useRef({ + argument, + containerEntities: stableContainerEntities, + selectedSource, + sourceSearch, + }); + + useEffect(() => { + searchParamsRef.current = { + argument, + containerEntities: stableContainerEntities, + selectedSource, + sourceSearch, + }; + }, [argument, selectedSource, sourceSearch, stableContainerEntities]); + const debouncedSearch = useMemo( () => debounce(async (searchText: string) => { const nextItems = await searchAlertAiArgumentOptions({ - argument, - containerEntities: stableContainerEntities, + ...searchParamsRef.current, searchText, - selectedSource, - sourceSearch, }); setHasNoResults(searchText.trim() !== '' && nextItems.length === 0); @@ -336,7 +351,7 @@ const AiArgumentAutocomplete = ({ ) ); }, 500), - [argument, selectedSource, sourceSearch, stableContainerEntities] + [] ); useEffect(