Skip to content

Commit 8f10aef

Browse files
authored
Merge branch 'main' into perf/ai-context-strip-render-only-markup
2 parents 7e39ec7 + 2b11dda commit 8f10aef

872 files changed

Lines changed: 53318 additions & 9182 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/rules/frontend-performance.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,31 @@ Applies to UI `*.{ts,tsx}`. Component/hook conventions in `frontend-react.md`; s
3838
Exception: `@openmetadata/ui-core-components` is consumed by bare package name — keep that.
3939
- **Never `import _ from 'lodash'` or `import * as _ from 'lodash'`.** Use named members only:
4040
`import { isEmpty, groupBy } from 'lodash';`.
41-
- **Lazy-load routes and heavy widgets** with `React.lazy` + `Suspense` (existing pattern:
42-
`src/utils/CustomizeMyDataPageWidgetUtils.tsx`, `LineageProvider`). Anything importing a graph,
43-
editor, chart, diff, or markdown renderer must be lazy — never in a route's top-level import.
41+
- **Lazy-load routes and heavy widgets** with `React.lazy`. Every lazy component must be passed to
42+
an approved helper from `components/AppRouter/withSuspenseFallback`, or rendered in a real React
43+
`Suspense` boundary with an explicit `fallback`. Anything importing a graph, editor, chart, diff,
44+
or markdown renderer must be lazy — never in a route's top-level import.
45+
- **Router modules must not statically import page modules.** Runtime imports whose path contains
46+
`pages/` must use `import()` so the page stays in a deferred chunk. Type-only imports are allowed.
47+
- Keep critical entity headers eager, then defer secondary tabs and expensive visualizations behind
48+
the nearest useful loading boundary. Preserve `ref` behavior when wrapping a ref-sensitive lazy
49+
component.
50+
- Prefer direct imports for pure utilities. Code splitting a small utility creates request overhead
51+
without reducing meaningful render work.
4452
- Do not add a new dependency for something the repo already has, and never add a second UI
4553
component library (see `component-library.md`).
4654

55+
## Loading and prefetching
56+
57+
- Loading fallbacks belong at the smallest boundary that can render independently; avoid replacing
58+
an entire page when only a secondary panel is deferred.
59+
- Prefetch only from a clear user signal or an established high-confidence navigation path. Do not
60+
indiscriminately idle-prefetch settings, admin, graph, or editor chunks.
61+
- React Query prefetching is intentional cache warming: use the same query key and stale-time policy
62+
as the destination query so it does not become a duplicate fetch.
63+
- When deferring extension or plugin requests, validate that no synchronous render path still needs
64+
their data; moving the request later must not introduce a render-time waterfall.
65+
4766
## Re-renders
4867

4968
- **Inline object/array/function props defeat `React.memo`** — a new identity every render makes the
@@ -72,3 +91,38 @@ Applies to UI `*.{ts,tsx}`. Component/hook conventions in `frontend-react.md`; s
7291
- Never render the full result set and hide overflow with CSS.
7392
- Debounce/throttle search inputs, `resize`, and `scroll` handlers; passive listeners for scroll.
7493
- Guard against layout thrash: don't read `getBoundingClientRect` in a loop that also writes styles.
94+
95+
## Caches
96+
97+
- Every module-level `Map` or `Set` used as a cache must have an explicit numeric or named uppercase
98+
size limit and an eviction path using `delete` or `clear`. Prefer LRU behavior when stale entries
99+
are inexpensive to recreate.
100+
- Cache hits and misses both consume capacity. Bound negative-result caches as carefully as successful
101+
results, and dispose retained browser resources such as textures when evicting them.
102+
- Before adding a UI cache, check whether the API client, React Query, or a lower layer already owns
103+
the same data. Avoid parallel caches with different invalidation rules.
104+
105+
## Mechanically enforced rules
106+
107+
ESLint blocks these zero-backlog invariants:
108+
109+
- `openmetadata-performance/no-eager-page-imports` in AppRouter modules.
110+
- `openmetadata-performance/require-suspense-fallback` for every React lazy call.
111+
- `openmetadata-performance/no-unbounded-module-cache` for module-level cache-like `Map` and `Set`
112+
bindings.
113+
114+
ESLint also reports these backlog-bearing invariants as non-blocking warnings:
115+
116+
- Pure utilities cannot contain JSX or depend on React, UI/state layers, pages, hooks, or REST.
117+
- Lower layers cannot import pages; page features cannot import other page features; REST and hooks
118+
cannot depend upward on UI layers.
119+
- Runtime circular imports and app-internal barrel imports are reported; type-only imports do not
120+
create runtime cycle/barrel findings.
121+
- Lodash default/namespace imports, REST calls inside iteration, and potentially sequential REST
122+
calls are reported for direct-import, bulk-fetch, or parallelization review.
123+
124+
The exact rule names, measured baselines, and CI visibility are documented in
125+
`docs/ui-code-quality-gate.md`.
126+
127+
Use a narrow `eslint-disable-next-line` only when a test deliberately exercises the boundary helper
128+
itself and therefore owns the fallback in the test case. Include the reason beside the suppression.

.github/actions/setup-openmetadata-test-environment/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ runs:
143143
source env/bin/activate
144144
uv pip install "setuptools<81"
145145
uv pip install --no-build-isolation "cx_Oracle>=8.3.0,<9"
146-
uv pip install --no-deps "sqlalchemy-redshift==0.8.14" "sqlalchemy-ibmi==0.9.3" "pydoris-custom==1.1.0"
146+
uv pip install --no-deps "sqlalchemy-ibmi==0.9.3" "pydoris-custom==1.1.0"
147147
uv pip install "${{ github.workspace }}/ingestion[all]"
148148
uv pip install "${{ github.workspace }}/ingestion[test]"
149149
uv pip install nox

.github/playwright/impact-map.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,23 @@
9595
"projects": ["SearchRBAC"],
9696
"specs": ["playwright/e2e/Flow/SearchRBAC.spec.ts"]
9797
},
98+
{
99+
"sources": [
100+
"openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/**",
101+
"openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.ts",
102+
"openmetadata-ui/src/main/resources/ui/src/utils/FeedUtilsPure.ts",
103+
"openmetadata-ui/src/main/resources/ui/src/interface/feed.interface.ts",
104+
"openmetadata-ui/src/main/resources/ui/src/rest/feedsAPI.ts",
105+
"openmetadata-ui/src/main/resources/ui/src/rest/tasksAPI.ts"
106+
],
107+
"specs": [
108+
"playwright/e2e/Features/ActivityFeed.spec.ts",
109+
"playwright/e2e/Features/Tasks/**/*.spec.ts",
110+
"playwright/e2e/Pages/Tasks.spec.ts",
111+
"playwright/e2e/Pages/TasksUIFlow.spec.ts",
112+
"playwright/e2e/Pages/TaskComments.spec.ts"
113+
]
114+
},
98115
{
99116
"sources": [
100117
"openmetadata-ui/src/main/resources/ui/src/components/Lineage/**",
@@ -207,6 +224,30 @@
207224
"playwright/e2e/Pages/TestSuite*.spec.ts"
208225
]
209226
},
227+
{
228+
"sources": [
229+
"openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/IngestionPipelineRepository.java",
230+
"openmetadata-service/src/main/java/org/openmetadata/service/logstorage/**",
231+
"openmetadata-service/src/main/java/org/openmetadata/service/sse/**",
232+
"openmetadata-ui/src/main/resources/ui/src/components/ServiceAgents/**",
233+
"openmetadata-ui/src/main/resources/ui/src/components/common/LogViewerModal/**",
234+
"openmetadata-ui/src/main/resources/ui/src/hooks/useEntityLogs.ts",
235+
"openmetadata-ui/src/main/resources/ui/src/hooks/useIngestionLogSource.ts",
236+
"openmetadata-ui/src/main/resources/ui/src/hooks/useLogsModal.tsx",
237+
"openmetadata-ui/src/main/resources/ui/src/hooks/usePaginatedLiveLog.ts",
238+
"openmetadata-ui/src/main/resources/ui/src/hooks/usePollingEffect.ts",
239+
"openmetadata-ui/src/main/resources/ui/src/utils/AgentsProgressPureUtils.ts",
240+
"openmetadata-ui/src/main/resources/ui/src/utils/SseStreamUtils.ts",
241+
"openmetadata-ui/src/main/resources/ui/src/utils/logsPolling.ts"
242+
],
243+
"projects": ["chromium", "Ingestion"],
244+
"specs": [
245+
"playwright/e2e/Features/AgentLogStreamHandover.spec.ts",
246+
"playwright/e2e/Pages/AppRunsHistoryLogs.spec.ts",
247+
"playwright/e2e/Pages/IngestionLogStreamLive.spec.ts",
248+
"playwright/e2e/Pages/LogsViewer.spec.ts"
249+
]
250+
},
210251
{
211252
"sources": [
212253
"openmetadata-ui/src/main/resources/ui/src/components/Glossary/**",
@@ -477,6 +518,41 @@
477518
"playwright/e2e/Flow/ApiServiceRest.spec.ts",
478519
"playwright/e2e/Features/AutoPilot.spec.ts"
479520
]
521+
},
522+
{
523+
"_comment": "The landing-page Activity Feed widget is driven by ActivityFeedProvider and feedsAPI, and is exercised by specs that none of these paths live in. Without this entry a widget change selected only the specs it edited directly, and a broken filter reached the merge queue before any of these ran.",
524+
"sources": [
525+
"openmetadata-ui/src/main/resources/ui/src/components/MyData/**",
526+
"openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/**",
527+
"openmetadata-ui/src/main/resources/ui/src/rest/feedsAPI.ts"
528+
],
529+
"projects": ["chromium", "Basic"],
530+
"specs": [
531+
"playwright/e2e/Features/ActivityFeed.spec.ts",
532+
"playwright/e2e/Features/ActivityAPI.spec.ts",
533+
"playwright/e2e/Features/ActivityStream.spec.ts",
534+
"playwright/e2e/Features/ActivityFeedTabBadge.spec.ts",
535+
"playwright/e2e/Flow/CustomizeWidgets.spec.ts"
536+
]
537+
},
538+
{
539+
"_comment": "The customize-landing-page and widget-filter playwright helpers are shared by every landing-page/customization spec. Without this entry a helper-only change (e.g. a wait fix in customizeLandingPage.ts or widgetFilters.ts) counted as an unmapped file, so CustomizeWidgets/CustomizeLandingPage and the other specs that execute the changed helpers never ran on the PR. Specs listed are the exact importers of these modules; DataInsight.spec.ts runs in the Data Insight project.",
540+
"sources": [
541+
"openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts",
542+
"openmetadata-ui/src/main/resources/ui/playwright/utils/widgetFilters.ts"
543+
],
544+
"projects": ["chromium", "Data Insight"],
545+
"specs": [
546+
"playwright/e2e/Features/ActivityFeed.spec.ts",
547+
"playwright/e2e/Features/CuratedAssets.spec.ts",
548+
"playwright/e2e/Features/LandingPageWidgets/**/*.spec.ts",
549+
"playwright/e2e/Features/NavigationBlocker.spec.ts",
550+
"playwright/e2e/Features/SettingsNavigationPage.spec.ts",
551+
"playwright/e2e/Flow/CustomizeLandingPage.spec.ts",
552+
"playwright/e2e/Flow/CustomizeWidgets.spec.ts",
553+
"playwright/e2e/Flow/PersonaFlow.spec.ts",
554+
"playwright/e2e/Pages/DataInsight.spec.ts"
555+
]
480556
}
481557
]
482558
}

.github/playwright/timing-baseline.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

.github/scripts/select_playwright_tests.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ def main() -> None:
144144
for entry in impact_map["canary"]:
145145
add_selection(selected, entry, repo_root)
146146

147+
delegated_patterns = impact_map.get("delegatedSpecs", [])
148+
selected = {
149+
spec: projects
150+
for spec, projects in selected.items()
151+
if not matches(spec, delegated_patterns)
152+
}
153+
147154
plan = {
148155
"version": 1,
149156
"mode": "targeted",

.github/scripts/tests/test_playwright_ci_planning.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,75 @@ def test_dedicated_rdf_specs_are_not_selected_by_the_main_workflow():
21492149
)
21502150

21512151

2152+
def test_impact_mapping_excludes_delegated_specs(tmp_path, monkeypatch):
2153+
selector = load_script("select_playwright_tests")
2154+
source_path = (
2155+
tmp_path / selector.UI_ROOT / "src/components/OntologyExplorer/view.ts"
2156+
)
2157+
source_path.parent.mkdir(parents=True)
2158+
source_path.write_text("export const view = {};\n")
2159+
spec_dir = tmp_path / selector.UI_ROOT / "playwright/e2e/Features"
2160+
spec_dir.mkdir(parents=True)
2161+
(spec_dir / "OntologyExplorer.spec.ts").write_text(
2162+
"test('ontology', () => undefined);\n"
2163+
)
2164+
(spec_dir / "OntologyExplorerRdf.spec.ts").write_text(
2165+
"test('rdf', () => undefined);\n"
2166+
)
2167+
impact_map = tmp_path / "impact-map.json"
2168+
impact_map.write_text(
2169+
json.dumps(
2170+
{
2171+
"smoke": [],
2172+
"canary": [],
2173+
"delegatedSpecs": [
2174+
"playwright/e2e/Features/OntologyExplorerRdf.spec.ts"
2175+
],
2176+
"sharedInfrastructure": [],
2177+
"mappings": [
2178+
{
2179+
"sources": [
2180+
f"{selector.UI_ROOT}src/components/OntologyExplorer/**"
2181+
],
2182+
"projects": ["chromium"],
2183+
"specs": ["playwright/e2e/Features/OntologyExplorer*.spec.ts"],
2184+
}
2185+
],
2186+
}
2187+
)
2188+
)
2189+
changed = tmp_path / "changed.txt"
2190+
changed.write_text(f"{source_path.relative_to(tmp_path)}\n")
2191+
output = tmp_path / "selection.json"
2192+
monkeypatch.chdir(tmp_path)
2193+
monkeypatch.delenv("GITHUB_OUTPUT", raising=False)
2194+
monkeypatch.setattr(
2195+
sys,
2196+
"argv",
2197+
[
2198+
"select_playwright_tests.py",
2199+
"--event-name",
2200+
"pull_request_target",
2201+
"--changed-files",
2202+
str(changed),
2203+
"--impact-map",
2204+
str(impact_map),
2205+
"--output",
2206+
str(output),
2207+
],
2208+
)
2209+
2210+
selector.main()
2211+
2212+
selection = json.loads(output.read_text())
2213+
assert selection["selectors"] == [
2214+
{
2215+
"projects": ["chromium"],
2216+
"spec": "playwright/e2e/Features/OntologyExplorer.spec.ts",
2217+
}
2218+
]
2219+
2220+
21522221
@pytest.mark.parametrize(
21532222
"spec",
21542223
[

.github/workflows/playwright-sso-login-nightly.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,15 @@ jobs:
115115

116116
- name: Start Keycloak SAML IdP
117117
if: startsWith(matrix.provider.name, 'keycloak-')
118+
env:
119+
# Must name the host this leg's browser uses, or Keycloak answers
120+
# "Invalid Request". The '-crosssite' leg fronts the IdP on 127.0.0.1.
121+
KEYCLOAK_HOSTNAME: ${{ matrix.provider.name == 'keycloak-azure-saml-crosssite' && 'http://127.0.0.1:8080' || 'http://localhost:8080' }}
118122
run: |
119123
docker compose -f docker/local-sso/keycloak-saml/docker-compose.yml up -d
124+
# The OM container needs to reach Keycloak by name; localhost:8080 is
125+
# its own loopback. Tolerates a missing network and a repeat attach.
126+
docker network connect ometa_network openmetadata-keycloak-saml || true
120127
timeout 180 bash -c 'until curl -fsS http://localhost:8080/realms/om-azure-saml >/dev/null; do sleep 2; done'
121128
122129
- name: Run SSO Login Spec

0 commit comments

Comments
 (0)