This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 2587ff2ccd9 [v3-4-test] Make asset state store entries scrollable 
(#75870) (#76079)
2587ff2ccd9 is described below

commit 2587ff2ccd9a954376d0a9b2a26b6a6f8e1d08ce
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 30 19:10:57 2026 +0000

    [v3-4-test] Make asset state store entries scrollable (#75870) (#76079)
    
    With many state-store entries, the table can extend beyond the visible 
asset details panel, leaving users unable to reach older entries.
    (cherry picked from commit 2064ab5af2be6c727999285d5d65c09855914e83)
    
    Co-authored-by: Timur Ramazanov <[email protected]>
---
 .../src/airflow/ui/src/pages/Asset/AssetLayout.tsx |  3 +
 .../Asset/AssetStateStore/AssetStateStore.tsx      |  2 +-
 .../src/airflow/ui/tests/e2e/specs/asset.spec.ts   | 75 ++++++++++++++++++++++
 3 files changed, 79 insertions(+), 1 deletion(-)

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 ac007410c49..7bd53037598 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
             >
               <Header asset={asset} />
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 da27f134b42..7fb4d171ce0 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 (
-    <Box px={4}>
+    <Box display="flex" flex={1} flexDirection="column" minH={0} 
overflow="hidden" px={4}>
       <ErrorAlert error={error} />
       <DataTable
         columns={columns}
diff --git a/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts 
b/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
index 2af18527fe5..59adb649653 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { randomUUID } from "node:crypto";
 import { testConfig } from "playwright.config";
 
 import { expect } from "tests/e2e/fixtures";
@@ -71,4 +72,78 @@ test.describe("Assets Page", () => {
     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)}`),
+        ),
+      );
+    }
+  });
 });

Reply via email to