Skip to content

Commit 7fc380d

Browse files
committed
fix(ui): handle test suite tab loading race (#29561)
1 parent 27932da commit 7fc380d

3 files changed

Lines changed: 402 additions & 37 deletions

File tree

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuite.spec.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
* See the License for the specific language governing permissions and
1111
* limitations under the License.
1212
*/
13-
import { expect } from '@playwright/test';
13+
import { expect, Route } from '@playwright/test';
1414
import { PLAYWRIGHT_INGESTION_TAG_OBJ } from '../../constant/config';
1515
import { Domain } from '../../support/domain/Domain';
16+
import { BundleTestSuiteClass } from '../../support/entity/BundleTestSuiteClass';
1617
import { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
1718
import { TableClass } from '../../support/entity/TableClass';
1819
import { UserClass } from '../../support/user/UserClass';
@@ -50,6 +51,16 @@ const user1 = new UserClass();
5051
const user2 = new UserClass();
5152
const domain1 = new Domain();
5253
const domain2 = new Domain();
54+
const bundleTestSuite = new BundleTestSuiteClass();
55+
56+
const createDeferred = () => {
57+
let resolveDeferred: () => void = () => undefined;
58+
const promise = new Promise<void>((resolve) => {
59+
resolveDeferred = resolve;
60+
});
61+
62+
return { promise, resolve: resolveDeferred };
63+
};
5364

5465
test.beforeAll(async ({ browser }) => {
5566
const { apiContext, afterAction } = await performAdminLogin(browser);
@@ -60,13 +71,83 @@ test.beforeAll(async ({ browser }) => {
6071
await table.createTestCase(apiContext);
6172
await domain1.create(apiContext);
6273
await domain2.create(apiContext);
74+
await bundleTestSuite.createBundleTestSuite(apiContext);
6375
await afterAction();
6476
});
6577

78+
test.afterAll(async ({ browser }) => {
79+
const bundleSuiteName = bundleTestSuite.bundleTestSuiteResponseData?.name;
80+
81+
if (bundleSuiteName) {
82+
const { apiContext, afterAction } = await performAdminLogin(browser);
83+
await apiContext.delete(
84+
`/api/v1/dataQuality/testSuites/name/${encodeURIComponent(
85+
bundleSuiteName
86+
)}?hardDelete=true&recursive=true`
87+
);
88+
await afterAction();
89+
}
90+
});
91+
6692
test.beforeEach(async ({ page }) => {
6793
await redirectToHomePage(page);
6894
});
6995

96+
test('Test suite tab switching keeps active bundle suite data after stale table suite response', async ({
97+
page,
98+
}) => {
99+
const bundleSuiteName =
100+
bundleTestSuite.bundleTestSuiteResponseData?.name ?? '';
101+
const tableFqn = table.entityResponseData?.fullyQualifiedName ?? '';
102+
const tableSuiteRequestReceived = createDeferred();
103+
const tableSuiteResponseRelease = createDeferred();
104+
const tableSuiteResponseFulfilled = createDeferred();
105+
106+
expect(bundleSuiteName).not.toBe('');
107+
expect(tableFqn).not.toBe('');
108+
109+
await page.route(
110+
'**/api/v1/dataQuality/testSuites/search/list**',
111+
async (route: Route) => {
112+
const requestUrl = new URL(route.request().url());
113+
const testSuiteType = requestUrl.searchParams.get('testSuiteType');
114+
115+
if (testSuiteType === 'basic') {
116+
tableSuiteRequestReceived.resolve();
117+
const tableSuiteResponse = await route.fetch();
118+
await tableSuiteResponseRelease.promise;
119+
120+
await route.fulfill({
121+
response: tableSuiteResponse,
122+
});
123+
tableSuiteResponseFulfilled.resolve();
124+
125+
return;
126+
}
127+
128+
await route.continue();
129+
}
130+
);
131+
132+
await page.goto('/data-quality/test-suites/table-suites');
133+
await tableSuiteRequestReceived.promise;
134+
135+
await expect(page.getByTestId('test-suite-table')).toBeVisible();
136+
await expect(page.getByTestId('loader')).toBeVisible();
137+
138+
await page.getByTestId('bundle-suite-radio-btn').click();
139+
140+
await expect(page.getByTestId(bundleSuiteName)).toBeVisible();
141+
142+
tableSuiteResponseRelease.resolve();
143+
await tableSuiteResponseFulfilled.promise;
144+
145+
await expect(page.getByTestId(bundleSuiteName)).toBeVisible();
146+
await expect(
147+
page.getByTestId('test-suite-table').getByText(tableFqn)
148+
).not.toBeVisible();
149+
});
150+
70151
test(
71152
'Logical TestSuite',
72153
PLAYWRIGHT_INGESTION_TAG_OBJ,

openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { ColumnsType } from 'antd/lib/table';
2424
import { AxiosError } from 'axios';
2525
import { isEmpty } from 'lodash';
2626
import QueryString from 'qs';
27-
import { useCallback, useEffect, useMemo, useState } from 'react';
27+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2828
import { useTranslation } from 'react-i18next';
2929
import { Link, useNavigate, useParams } from 'react-router-dom';
3030
import { INITIAL_PAGING_VALUE } from '../../../../constants/constants';
@@ -120,6 +120,7 @@ export const TestSuites = () => {
120120
} = usePaging();
121121

122122
const [isLoading, setIsLoading] = useState<boolean>(true);
123+
const latestRequestId = useRef(0);
123124

124125
const ownerFilterValue = useMemo(() => {
125126
return selectedOwner
@@ -212,41 +213,56 @@ export const TestSuites = () => {
212213
return data;
213214
}, []);
214215

215-
const fetchTestSuites = async (
216-
currentPage = INITIAL_PAGING_VALUE,
217-
params?: ListTestSuitePramsBySearch
218-
) => {
219-
setIsLoading(true);
220-
try {
221-
const result = await getListTestSuitesBySearch({
222-
...params,
223-
fields: [TabSpecificField.OWNERS, TabSpecificField.SUMMARY],
224-
q: searchValue ? `*${searchValue}*` : undefined,
225-
owner: ownerFilterValue?.key,
226-
offset: (currentPage - 1) * pageSize,
227-
includeEmptyTestSuites: subTab !== DataQualitySubTabs.TABLE_SUITES,
228-
testSuiteType:
229-
subTab === DataQualitySubTabs.TABLE_SUITES
230-
? TestSuiteType.basic
231-
: TestSuiteType.logical,
232-
sortField: 'lastResultTimestamp',
233-
sortType: SORT_ORDER.DESC,
234-
});
235-
setTestSuites(result.data);
236-
handlePagingChange(result.paging);
237-
} catch (error) {
238-
showErrorToast(error as AxiosError);
239-
} finally {
240-
setIsLoading(false);
241-
}
242-
};
216+
const fetchTestSuites = useCallback(
217+
async (
218+
currentPage = INITIAL_PAGING_VALUE,
219+
params?: ListTestSuitePramsBySearch
220+
) => {
221+
const requestId = latestRequestId.current + 1;
222+
latestRequestId.current = requestId;
223+
224+
setIsLoading(true);
225+
try {
226+
const result = await getListTestSuitesBySearch({
227+
...params,
228+
fields: [TabSpecificField.OWNERS, TabSpecificField.SUMMARY],
229+
q: searchValue ? `*${searchValue}*` : undefined,
230+
owner: ownerFilterValue?.key,
231+
offset: (currentPage - 1) * pageSize,
232+
includeEmptyTestSuites: subTab !== DataQualitySubTabs.TABLE_SUITES,
233+
testSuiteType:
234+
subTab === DataQualitySubTabs.TABLE_SUITES
235+
? TestSuiteType.basic
236+
: TestSuiteType.logical,
237+
sortField: 'lastResultTimestamp',
238+
sortType: SORT_ORDER.DESC,
239+
});
240+
241+
if (requestId !== latestRequestId.current) {
242+
return;
243+
}
244+
245+
setTestSuites(result.data);
246+
handlePagingChange(result.paging);
247+
} catch (error) {
248+
if (requestId === latestRequestId.current) {
249+
showErrorToast(error as AxiosError);
250+
}
251+
} finally {
252+
if (requestId === latestRequestId.current) {
253+
setIsLoading(false);
254+
}
255+
}
256+
},
257+
[searchValue, ownerFilterValue?.key, pageSize, subTab, handlePagingChange]
258+
);
243259

244260
const handleTestSuitesPageChange = useCallback(
245261
({ currentPage }: PagingHandlerParams) => {
246262
fetchTestSuites(currentPage, { limit: pageSize });
247263
handlePageChange(currentPage);
248264
},
249-
[pageSize, handlePageChange]
265+
[fetchTestSuites, pageSize, handlePageChange]
250266
);
251267

252268
const handleSearchParam = (
@@ -324,7 +340,15 @@ export const TestSuites = () => {
324340
} else {
325341
setIsLoading(false);
326342
}
327-
}, [testSuitePermission, pageSize, searchValue, owner, subTab, currentPage]);
343+
}, [
344+
testSuitePermission,
345+
pageSize,
346+
searchValue,
347+
owner,
348+
subTab,
349+
currentPage,
350+
fetchTestSuites,
351+
]);
328352

329353
if (!testSuitePermission?.ViewAll && !testSuitePermission?.ViewBasic) {
330354
return (

0 commit comments

Comments
 (0)