Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
45f9361
docs(ui): add antd migration docs home and mapping-guide template
chirag-madlani Jul 27, 2026
3e19464
feat(tooling): add antd codemod scaffold with move-named-imports tran…
chirag-madlani Jul 27, 2026
e19913e
feat(tooling): add antd migration sweep-ledger generator
chirag-madlani Jul 27, 2026
54de9a3
fix(tooling): correct selector-count label to total occurrences
chirag-madlani Jul 27, 2026
d159c57
test(ui): add visual-regression project with static-page baselines
chirag-madlani Jul 27, 2026
fdf0069
test(ui): add entity-details, RJSF form and modal visual baselines
chirag-madlani Jul 28, 2026
290dd75
ci: add visual-regression workflow for openmetadata-ui PRs
chirag-madlani Jul 28, 2026
c27d6c4
chore(ui): add knip config and dead-code seed list for antd migration
chirag-madlani Jul 28, 2026
31a6a8c
fix(tooling): skip vendored openmetadata-ui dir in antd ledger scan
chirag-madlani Jul 28, 2026
19e3381
ci: exclude visual-regression specs from e2e shard planning
chirag-madlani Jul 28, 2026
ca32ef8
style(ui): organize imports in visual-regression specs
chirag-madlani Jul 28, 2026
c251c2c
fix(tooling): count named specifiers in combined default+named antd i…
chirag-madlani Jul 28, 2026
f61c8ec
fix(tooling): preserve leading comments when codemod merges into exis…
chirag-madlani Jul 28, 2026
e08e413
test(ui): adopt CI-rendered baselines for 8 visual-regression pages
chirag-madlani Jul 28, 2026
a4af627
test(ui): drop roles page from visual suite (non-deterministic seeded…
chirag-madlani Jul 28, 2026
66189f3
fix(tooling): preserve type-only imports in move-named-imports codemod
chirag-madlani Jul 28, 2026
b2d2f54
ci: pin free-disk-space action to release SHA
chirag-madlani Jul 28, 2026
df5fb41
test(ui): widen landing-page diff tolerance to absorb async-widget va…
chirag-madlani Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/playwright/impact-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"playwright/e2e/Pages/DataInsight*.spec.ts",
"playwright/e2e/Features/KnowledgeGraph.spec.ts",
"playwright/e2e/Features/OntologyExplorerRdf.spec.ts",
"playwright/e2e/Features/OntologyImportRdf.spec.ts"
"playwright/e2e/Features/OntologyImportRdf.spec.ts",
"playwright/e2e/VisualRegression/**"
],
"smoke": [
{
Expand Down
61 changes: 61 additions & 0 deletions .github/scripts/tests/test_playwright_ci_planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,67 @@ def test_dedicated_rdf_specs_are_not_selected_by_the_main_workflow():
)


def test_visual_regression_specs_are_not_selected_by_the_main_workflow():
impact_map = json.loads(
(SCRIPTS.parents[0] / "playwright/impact-map.json").read_text()
)

assert "playwright/e2e/VisualRegression/**" in impact_map["delegatedSpecs"]


def test_changed_visual_regression_spec_is_delegated_not_selected(tmp_path, monkeypatch):
selector = load_script("select_playwright_tests")
spec_dir = tmp_path / selector.UI_ROOT / "playwright/e2e/VisualRegression"
spec_dir.mkdir(parents=True)
spec_path = spec_dir / "entityDetails.spec.ts"
spec_path.write_text("test('visual', () => undefined);\n")
impact_map = tmp_path / "impact-map.json"
impact_map.write_text(
json.dumps(
{
"smoke": [],
"canary": [],
"delegatedSpecs": ["playwright/e2e/VisualRegression/**"],
"sharedInfrastructure": [],
"mappings": [],
}
)
)
changed = tmp_path / "changed.txt"
changed.write_text(
f"{selector.UI_ROOT}playwright/e2e/VisualRegression/entityDetails.spec.ts\n"
)
output = tmp_path / "selection.json"
monkeypatch.chdir(tmp_path)
monkeypatch.delenv("GITHUB_OUTPUT", raising=False)
monkeypatch.setattr(
sys,
"argv",
[
"select_playwright_tests.py",
"--event-name",
"pull_request_target",
"--changed-files",
str(changed),
"--impact-map",
str(impact_map),
"--output",
str(output),
],
)

selector.main()

selection = json.loads(output.read_text())
assert selection["selectors"] == []
assert selection["directChangedSpecs"] == []
assert (
"playwright/e2e/VisualRegression/entityDetails.spec.ts"
in selection["delegatedChangedSpecs"]
)
assert selection["unmappedFiles"] == []


def test_summary_reconciles_results_and_evaluates_performance_independently():
workflow = (
SCRIPTS.parents[0] / "workflows/playwright-postgresql-e2e.yml"
Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/playwright-visual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright 2021 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.

# This workflow runs the Playwright visual-regression suite
# (openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression) against a MySQL-backed
# OpenMetadata server. The environment-bootstrap steps (checkout, Java/Node setup, server build +
# start, sample-data seeding) mirror .github/workflows/playwright-mysql-e2e.yml verbatim, minus the
# pull_request_target-only labeling/gating steps and the shard matrix, which don't apply to a single
# fast (~18 spec) suite triggered on regular `pull_request`.
#
# Rendering parity with the baselines: the VisualRegression snapshots were captured using the
# mcr.microsoft.com/playwright:v1.57.0-jammy image (pinned to the @playwright/test version in
# package.json). The e2e workflow installs Chromium on the bare ubuntu-latest runner and runs tests
# there directly - fine for functional assertions, but font hinting/anti-aliasing on the bare runner
# image differs enough from the official Playwright container to make pixel-diff assertions flaky or
# outright wrong. So only the test-execution step below runs inside that exact container (via
# `docker run --network host`, reaching the host-run server at localhost:8585); the
# bootstrap/build/seed steps still run directly on the runner exactly as in the e2e workflow.
name: playwright-visual-regression

on:
pull_request:
paths:
- "openmetadata-ui/**"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: playwright-visual-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
playwright-visual-regression:
name: playwright-visual-regression
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.draft }}
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: false
Comment thread
greptile-apps[bot] marked this conversation as resolved.
swap-storage: true
docker-images: false
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Cache Maven Dependencies
id: cache-output
uses: actions/cache@v5
with:
path: ~/.m2
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-

- name: Setup Openmetadata Test Environment
uses: ./.github/actions/setup-openmetadata-test-environment
with:
python-version: "3.10"
args: "-d mysql"
ingestion_dependency: "all"
install-test-dependencies: "false"

- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version-file: "openmetadata-ui/src/main/resources/ui/.nvmrc"

- name: Install dependencies
working-directory: openmetadata-ui/src/main/resources/ui/
run: yarn --ignore-scripts --frozen-lockfile

# Runs inside the same Playwright image the baselines were generated with (see header
# comment) instead of on the bare runner. `--network host` lets the container reach the
# OpenMetadata server that the bootstrap step above already started on localhost:8585.
# `--ipc host` follows Playwright's own Docker guidance to avoid Chromium running out of
# shared memory. node_modules were installed on the host above and are bind-mounted in, so
# only `npx` (bundled with the container's Node) is needed - no dependency on yarn/corepack
# being set up inside the image.
- name: Run visual regression suite
working-directory: openmetadata-ui/src/main/resources/ui
env:
PLAYWRIGHT_TEST_BASE_URL: http://localhost:8585
run: |
docker run --rm \
--network host \
--ipc host \
-e CI=true \
-e PLAYWRIGHT_TEST_BASE_URL \
-v "$PWD:/work" \
-w /work \
mcr.microsoft.com/playwright:v1.57.0-jammy \
npx playwright test --project=visual-regression

- name: Upload visual diff artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: visual-regression-diffs
path: |
openmetadata-ui/src/main/resources/ui/playwright/output/test-results/**
openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/**
retention-days: 14

- name: Clean Up
if: always()
continue-on-error: true
run: |
cd ./docker/development
docker compose down --remove-orphans
sudo rm -rf "${PWD}/docker-volume"
24 changes: 24 additions & 0 deletions docs/antd-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# AntD → @openmetadata/ui-core-components migration

Program design: see the Collate repo, `docs/superpowers/specs/2026-07-27-antd-untitled-migration-design.md`.

## The sweep playbook (per antd component)

1. **Gap check** — grep the *actually used* prop surface across openmetadata-ui,
collate-ui and collate-local-webserver/ui; compare against the
`ui-core-components` equivalent. Missing capability is added to
`openmetadata-ui-core-components` first, in its own PR, with unit tests.
2. **Mapping guide** — copy `TEMPLATE.md` to `<component>.md` in this folder and
fill it in. The guide is the review contract for every PR of that sweep.
3. **Codemod** (mechanical components only) — see `tooling/antd-codemods/`.
4. **Chunked PRs** — ~20–40 files per PR, chunked by directory. Each PR also
deletes the dead code it orphans (grep/knip-proven only) and regenerates
visual baselines for intentionally changed pages.
5. **Ledger update** — regenerate the ledger (see `tooling/antd-migration/`).

## Rules encoded in every guide

- Semantic Tailwind tokens only (`tw:bg-primary`), never raw palette or hex.
- No `tw:ring-*` — borders use `border`/`outline` (see upstream `docs/colors.md` §2.3.1).
- No string literals — `t('label.…')`, checking existing keys in both locale files first.
- QueryBuilder files are owned by PR #29849 — do not touch.
31 changes: 31 additions & 0 deletions docs/antd-migration/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# <AntdComponent> → <CoreComponent> mapping guide

**Sweep status:** ledger row `<component>`
**Core component:** `@openmetadata/ui-core-components` → `<import path>`
**Codemod:** `tooling/antd-codemods/transforms/<name>.js` (or "manual — no codemod")

## Import

| Before | After |
|---|---|
| `import { X } from 'antd';` | `import { Y } from '@openmetadata/ui-core-components';` |

## Prop mapping

| antd prop (as used in repo) | core equivalent | notes |
|---|---|---|
| | | |

## No direct equivalent — do this instead

| antd usage | replacement pattern |
|---|---|
| | |

## Before / after examples

<!-- 2–3 real examples lifted from the codebase -->

## CSS to delete with this sweep

<!-- .ant-* override selectors and component .less files this sweep orphans -->
1 change: 1 addition & 0 deletions docs/antd-migration/dead-code-seed.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions openmetadata-ui/src/main/resources/ui/knip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/index.tsx"],
"project": ["src/**/*.{ts,tsx}"],
"ignore": [
"src/generated/**",
"src/locale/**",
"**/*.test.{ts,tsx}",
"**/mocks/**",
"**/__mocks__/**"
],
"ignoreDependencies": [".*"]
}
2 changes: 2 additions & 0 deletions openmetadata-ui/src/main/resources/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"organize-imports": "yarn organize-imports:cli tsconfig[.*].json",
"organize-imports:playwright": "yarn organize-imports:cli './playwright/tsconfig[.*].json'",
"playwright:run": "playwright test",
"playwright:visual": "playwright test --project=visual-regression",
"playwright:visual:update": "playwright test --project=visual-regression --update-snapshots",
"playwright:open": "playwright test --ui",
"playwright:codegen": "playwright codegen",
"generate:app-docs": "node generateApplicationDocs.js",
Expand Down
11 changes: 11 additions & 0 deletions openmetadata-ui/src/main/resources/ui/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,19 @@ export default defineConfig({
'**/IntakeForm.spec.ts',
...dedicatedStateTestIgnore,
'**/DomainIsolation/**',
'**/VisualRegression/**',
],
},
{
name: 'visual-regression',
testMatch: '**/VisualRegression/**/*.spec.ts',
dependencies: ['setup', 'entity-data-setup'],
use: {
...devices['Desktop Chrome'],
viewport: { width: 1440, height: 900 },
storageState: 'playwright/.auth/admin.json',
},
},
// Only register the h2 project when explicitly opted in. Always-on registration would force
// Playwright to do discovery for it on every default run even though its spec files are
// skipped — small cost, but pointless when the h2 server isn't running.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.
*/
import { expect, Page, test } from '@playwright/test';
import { TableClass } from '../../support/entity/TableClass';
import { getDefaultAdminAPIContext } from '../../utils/common';
import {
gotoForScreenshot,
SCREENSHOT_OPTS,
} from '../../utils/visualRegression';

/**
* Entity names are randomized by the support classes (TableClass generates a
* `pw-database-service-<uuid>` service, a `pw-table-<fullUuid>` table and
* `<uuid>`-suffixed column names), so mask every element that renders a
* name; the layout, tabs and schema table are the subject under test.
*
* - `entity-header-display-name` / `entity-header-name`: display name + name
* lines in EntityHeaderTitle (both contain random uuids).
* - `breadcrumb`: service > database > schema names.
* - `column-name`: each column-name link in SchemaTable (uuid-suffixed).
* - `.custom-property-right-panel-container`: right-panel
* CustomPropertyTable (no testid in the right-panel variant) — property
* names come from seeded custom properties with random `cp-<uuid>` names.
*/
const NAME_MASKS = [
'[data-testid="entity-header-display-name"]',
'[data-testid="entity-header-name"]',
'[data-testid="breadcrumb"]',
'[data-testid="column-name"]',
'.custom-property-right-panel-container',
];

const table = new TableClass();

test.beforeAll(async ({ browser }) => {
const { apiContext, afterAction } = await getDefaultAdminAPIContext(browser);
await table.create(apiContext);
await afterAction();
});

test.afterAll(async ({ browser }) => {
const { apiContext, afterAction } = await getDefaultAdminAPIContext(browser);
await table.delete(apiContext);
await afterAction();
});

const maskFor = (page: Page) =>
NAME_MASKS.map((selector) => page.locator(selector));

test('table entity details (schema tab) matches baseline', async ({ page }) => {
await gotoForScreenshot(
page,
`/table/${encodeURIComponent(
table.entityResponseData?.fullyQualifiedName ?? ''
)}`
);
await expect(page).toHaveScreenshot('table-details-schema.png', {
...SCREENSHOT_OPTS,
mask: maskFor(page),
});
});
Loading
Loading