diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx index ac007410c496b..7bd53037598a5 100644 --- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx @@ -134,6 +134,9 @@ export const AssetLayout = () => { display="flex" flexDirection="column" gap={3} + height="100%" + minH={0} + minW={0} paddingInlineStart={2} // not 3 because the transparent resize handle is 1 unit wide >
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetStateStore/AssetStateStore.tsx b/airflow-core/src/airflow/ui/src/pages/Asset/AssetStateStore/AssetStateStore.tsx index da27f134b42cc..7fb4d171ce0fe 100644 --- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetStateStore/AssetStateStore.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetStateStore/AssetStateStore.tsx @@ -155,7 +155,7 @@ export const AssetStateStore = () => { const columns = getColumns({ assetId, translate }); return ( - + { await assetDetailPage.verifyProducingTasks(); await assetDetailPage.verifyScheduledDags(); }); + + test("asset state store entries remain scrollable when the table overflows", async ({ + assetData: _data, + assetDetailPage, + authenticatedRequest, + browserName, + page, + }) => { + test.skip(browserName !== "chromium", "The asset fixture is shared across browser projects."); + + const assetResponse = await authenticatedRequest.get( + `/api/v2/assets?uri=${encodeURIComponent(testConfig.asset.name)}&limit=1`, + ); + + expect(assetResponse.ok()).toBe(true); + + const { assets } = (await assetResponse.json()) as { assets: Array<{ id: number }> }; + const [asset] = assets; + + if (asset === undefined) { + throw new Error(`Test asset ${testConfig.asset.name} was not found`); + } + + const keyPrefix = `000-e2e-scroll-${randomUUID()}`; + const keys = Array.from({ length: 20 }, (_, index) => `${keyPrefix}-${String(index).padStart(3, "0")}`); + + try { + const responses = await Promise.all( + keys.map((key, index) => + authenticatedRequest.put(`/api/v2/assets/${asset.id}/state-store/${encodeURIComponent(key)}`, { + data: { value: { index, value: `scroll test ${index}` } }, + }), + ), + ); + + expect(responses.every((response) => response.ok())).toBe(true); + + await assetDetailPage.goto(); + await assetDetailPage.clickOnAsset(testConfig.asset.name); + await page.getByRole("link", { exact: true, name: "Asset State Store" }).click(); + + const table = page.getByTestId("table-list"); + const lastKey = `${keyPrefix}-019`; + + await expect(table.getByText(lastKey)).toBeAttached(); + + const canScroll = await table.evaluate((element) => { + let parent = element.parentElement; + + while (parent !== null) { + const { overflowY } = window.getComputedStyle(parent); + + if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) { + parent.scrollTop = parent.scrollHeight; + + return parent.scrollTop > 0; + } + + parent = parent.parentElement; + } + + return false; + }); + + expect(canScroll).toBe(true); + await expect(table.locator("tbody tr").last()).toBeInViewport(); + } finally { + await Promise.all( + keys.map((key) => + authenticatedRequest.delete(`/api/v2/assets/${asset.id}/state-store/${encodeURIComponent(key)}`), + ), + ); + } + }); });