Skip to content

Commit 2a40d2b

Browse files
committed
Merge branch 'main' of https://github.com/opendatahub-io/odh-dashboard into RHOAIENG-51775-enable-external-vector-stores-in-playground
2 parents d88c8ca + 5a1fdad commit 2a40d2b

357 files changed

Lines changed: 17461 additions & 1835 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/module-onboarding.md

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,16 @@ Add `install:module` script if the package has its own `node_modules`:
8383

8484
Each package needs a unique port. See [docs/onboard-modular-architecture.md § Configure the Port](../../docs/onboard-modular-architecture.md#3-configure-the-port) for where to update it (both `Makefile` and `package.json`).
8585

86-
Current port assignments:
87-
88-
| Package | Frontend Port | BFF Port |
89-
|---|---|---|
90-
| model-registry | 9100 | 4000 |
91-
| gen-ai | 9102 | 8080 |
92-
| eval-hub | 9105 | 4002 |
93-
| maas | 9104 | 8081 |
94-
| notebooks | 9105 ||
95-
| autorag | 9107 | 4001 |
96-
| automl | 9108 | 4003 |
97-
| mlflow | 9110 | 4020 |
98-
99-
Note: eval-hub and notebooks both use frontend port 9105 (conflict).
86+
To see current port assignments and detect conflicts, run:
87+
88+
```bash
89+
npm run validate:ports
90+
```
91+
92+
The source of truth for each package is:
93+
- **Frontend port**: `module-federation.local.port` in the package's `package.json`
94+
- **BFF port (local dev/E2E)**: `bffConfig.port` in the package's `package.json` (used by CI E2E workflow and Cypress scripts; not currently conflict-checked by the validator)
95+
- **Production service port**: `service.port` in `federation-configmap.yaml` (validated by the conflict-check script)
10096

10197
### 4. Directory structure
10298

.claude/rules/react.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,66 @@ const MyContextProvider: React.FC<PropsWithChildren> = ({ children }) => {
188188

189189
**Always** wrap context values in `React.useMemo` to prevent unnecessary re-renders of consumers.
190190

191+
## Navigation
192+
193+
### Prefer `<Link>` over `useNavigate`
194+
195+
Use React Router's `<Link>` (or PatternFly's `component` prop pattern) for navigation instead of calling `navigate()` imperatively. Links are accessible by default (right-click, Cmd+click, screen readers), while `navigate()` breaks these expectations.
196+
197+
```tsx
198+
// Bad — imperative navigation in a click handler
199+
import { useNavigate } from 'react-router-dom';
200+
201+
const navigate = useNavigate();
202+
<Button onClick={() => navigate('/projects')}>View projects</Button>
203+
204+
// Good — declarative link
205+
import { Link } from 'react-router-dom';
206+
207+
<Button component={(props) => <Link {...props} to="/projects" />}>
208+
View projects
209+
</Button>
210+
```
211+
212+
For PatternFly empty states and other components that accept `href` props, prefer those over `handleClick` + `navigate`:
213+
214+
```tsx
215+
// Bad
216+
<ModelsEmptyState
217+
actionButtonText="Deploy a model"
218+
handleActionButtonClick={() => navigate(`/deployments/${ns}`)}
219+
/>
220+
221+
// Good
222+
<ModelsEmptyState
223+
actionButtonText="Deploy a model"
224+
actionButtonHref={`/deployments/${ns}`}
225+
/>
226+
```
227+
228+
### When `useNavigate` is acceptable
229+
230+
Reserve `useNavigate` for programmatic navigation that cannot be expressed as a static href:
231+
232+
- **Post-action redirects** — navigating after an async operation completes:
233+
234+
```tsx
235+
const navigate = useNavigate();
236+
237+
const handleSubmit = async () => {
238+
await api.createResource(data);
239+
navigate(`/resources/${data.name}`);
240+
};
241+
```
242+
243+
- **Cancel / go-back buttons** — returning to a known route:
244+
245+
```tsx
246+
const navigate = useNavigate();
247+
248+
<Button variant="link" onClick={() => navigate(redirectPath)}>Cancel</Button>
249+
```
250+
191251
## Performance
192252

193253
### Memoization
@@ -411,6 +471,10 @@ When reviewing React code, verify:
411471
- [ ] Primitive values preferred in dependency arrays
412472
- [ ] No barrel file imports — import from source modules directly
413473

474+
### Navigation
475+
- [ ] `<Link>` used instead of `navigate()` for user-initiated navigation
476+
- [ ] `useNavigate` only used for post-action programmatic redirects
477+
414478
### Accessibility
415479
- [ ] `aria-label` on icon buttons and non-text interactive elements
416480
- [ ] Semantic HTML elements used
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
description: Theming external libraries (Perses, etc.) with PatternFly tokens in ODH Dashboard
3+
globs: "packages/observability/**,packages/*/src/**/*theme*,packages/*/src/**/*Theme*"
4+
alwaysApply: false
5+
---
6+
7+
# Third-Party Library Theming — ODH Dashboard
8+
9+
Reference implementation: `packages/observability/src/perses/theme.ts`
10+
11+
For the MLflow federated UI theming approach (Emotion token translation + SCSS shell overrides), see the [MLflow fork](https://github.com/opendatahub-io/mlflow/tree/master/mlflow/server/js/src/common/styles/patternfly) — that pattern lives in the fork, not here.
12+
13+
---
14+
15+
## Invariant 1 — ECharts/canvas: use `.value`, not CSS vars
16+
17+
Canvas-based renderers cannot resolve CSS custom properties at paint time. Passing a `var(--pf-t--...)` string to an ECharts option or canvas `fillStyle` silently produces the wrong color.
18+
19+
```ts
20+
import { t_color_white, t_color_gray_95 } from '@patternfly/react-tokens';
21+
22+
// Correct — resolved hex value
23+
const textColor = isDark ? t_color_white.value : t_color_gray_95.value;
24+
25+
// These overrides are passed to generateChartsTheme(), not as top-level MUI theme properties
26+
const chartsTheme = generateChartsTheme(muiTheme, {
27+
echartsTheme: {
28+
color: palette, // hex array, never CSS var strings
29+
tooltip: {
30+
// CSS vars ARE fine for non-canvas properties (e.g. tooltip is DOM-rendered)
31+
backgroundColor: 'var(--pf-t--global--background--color--inverse--default)',
32+
},
33+
},
34+
thresholds: {
35+
defaultColor: textColor, // .value, not .var
36+
},
37+
});
38+
```
39+
40+
Use `@patternfly/react-tokens` `.value` anywhere the value is consumed by a canvas renderer. Use `var(--pf-t--*)` strings everywhere else.
41+
42+
---
43+
44+
## Invariant 2 — Dark mode source: `useThemeContext()` only
45+
46+
Never infer dark mode from `prefers-color-scheme`, a local prop, or any source other than the dashboard theme context.
47+
48+
```ts
49+
import { useThemeContext } from '@odh-dashboard/internal/app/ThemeContext';
50+
51+
const { theme: contextTheme } = useThemeContext();
52+
const theme: PatternFlyTheme = contextTheme === 'dark' ? 'dark' : 'light';
53+
```
54+
55+
The full theme object must recompute when context changes:
56+
57+
```ts
58+
return React.useMemo(() => {
59+
const muiTheme = getTheme(theme, { ...mapPatternFlyThemeToMUI(theme) });
60+
// ... build chartsTheme ...
61+
return { muiTheme, chartsTheme };
62+
}, [theme]);
63+
```
64+
65+
---
66+
67+
## Invariant 3 — `ThemeProvider` at the library boundary, not the app root
68+
69+
Placing an MUI `ThemeProvider` at the app root leaks MUI styles into PF-only components. Scope it as close to the library's render boundary as possible.
70+
71+
```tsx
72+
// Correct — scoped to the Perses render boundary
73+
// packages/observability/src/perses/PersesWrapper.tsx
74+
const { muiTheme, chartsTheme } = usePatternFlyTheme();
75+
return (
76+
<ThemeProvider theme={muiTheme}>
77+
<ChartsProvider chartsTheme={chartsTheme}>
78+
{children}
79+
</ChartsProvider>
80+
</ThemeProvider>
81+
);
82+
83+
// Wrong — app root wrapping bleeds MUI styles into unrelated PF components
84+
// frontend/src/app/App.tsx
85+
return <ThemeProvider theme={muiTheme}><App /></ThemeProvider>;
86+
```
87+
88+
One wrapper component per external library. Never scatter `ThemeProvider` across individual pages.

.claude/skills/dev-workflow/reference.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,18 @@ The `local` field in `module-federation` config tells the backend where to proxy
9292
| Backend | 4000 | Node.js/Fastify reverse proxy | Yes |
9393
| Host frontend | 4010 | Webpack dev server (host) | Yes |
9494

95-
| Package | Frontend Port | BFF Port (federated) | Has `start:dev`? |
96-
|---|---|---|---|
97-
| model-registry | 9100 | **4000** | Yes |
98-
| gen-ai | 9102 | 8080 (no federated target) | No (use Makefile) |
99-
| eval-hub | 9105 | 4002 | No (use Makefile) |
100-
| maas | 9104 | 8081 | No (use Makefile) |
101-
| notebooks | 9105 || Yes |
102-
| autorag | 9107 | 4001 | No (use Makefile) |
103-
| automl | 9108 | 4003 | No (use Makefile) |
104-
| mlflow | 9110 | 4020 | No (use Makefile) |
95+
To see all current port assignments and detect conflicts:
96+
97+
```bash
98+
npm run validate:ports
99+
```
105100

106-
**Known port conflicts:**
101+
The source of truth for each package is:
102+
- **Frontend port**: `module-federation.local.port` in the package's `package.json`
103+
- **BFF port (local dev/E2E)**: `bffConfig.port` in the package's `package.json` (used by CI E2E workflow and Cypress scripts; not currently conflict-checked by the validator)
104+
- **Production service port**: `service.port` in `federation-configmap.yaml` (validated by CI via `npm run validate:ports`)
105+
106+
**Known port conflict:**
107107

108108
- **model-registry BFF (4000) vs dashboard backend (4000).** Both default to port 4000. To run them together, change the dashboard backend port via `.env.development.local`:
109109

@@ -114,10 +114,6 @@ The `local` field in `module-federation` config tells the backend where to proxy
114114

115115
The frontend's webpack proxy target (`_BACKEND_PORT`) and the backend itself both pick up this override. The model-registry BFF keeps port 4000 (hardcoded in its Makefile). Avoid using 4020 — mlflow's BFF uses that port.
116116

117-
- **eval-hub (9105) vs notebooks (9105).** Both declare `module-federation.local.port: 9105`. Cannot run both simultaneously without changing one.
118-
119-
- **mlflow BFF (4020).** Conflicts with `BACKEND_PORT=4020` if used as a workaround. Use 4050 or another unused port for the dashboard backend instead.
120-
121117
### Running multi-component dev
122118

123119
The local backend is required for federated package development — it reads `module-federation.local` config and proxies `/_mf/{name}/*` to each package's local dev server. Without the local backend, the host cannot load your local package changes.

.github/workflows/cypress-e2e-test.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,11 @@ jobs:
857857
find ${{ github.workspace }}/packages/cypress/results -type d -empty -delete 2>/dev/null || true
858858
find ${{ github.workspace }}/packages/cypress/screenshots -type d -empty -delete 2>/dev/null || true
859859
find ${{ github.workspace }}/packages/cypress/videos -type d -empty -delete 2>/dev/null || true
860-
860+
861+
# Fix read-only envtest binaries and directories from previous BFF runs
862+
# (setup-envtest downloads k8s binaries with 0555 permissions, which blocks actions/checkout cleanup)
863+
chmod -R u+w ${{ github.workspace }}/packages/*/bff/bin ${{ github.workspace }}/packages/*/upstream/bff/bin 2>/dev/null || true
864+
861865
echo "✅ Cleanup complete (non-critical, continued on any errors)"
862866
863867
- name: Checkout code
@@ -1093,7 +1097,7 @@ jobs:
10931097
echo "🚀 Starting webpack dev server on port ${WEBPACK_PORT} ($CLUSTER_NAME)..."
10941098
10951099
# Start webpack with explicit dashboard host (ensures correct proxy target for all tests)
1096-
cd frontend && env ODH_DASHBOARD_HOST=${DASHBOARD_HOST} ODH_PORT=${WEBPACK_PORT} npm run start:dev:ext > /tmp/webpack_${WEBPACK_PORT}.log 2>&1 &
1100+
cd frontend && env ODH_DASHBOARD_HOST=${DASHBOARD_HOST} ODH_PORT=${WEBPACK_PORT} ODH_TOKEN_REFRESH=true npm run start:dev:ext > /tmp/webpack_${WEBPACK_PORT}.log 2>&1 &
10971101
SERVER_PID=$!
10981102
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
10991103
echo "$SERVER_PID" > "$PORT_INFO_DIR/port-${WEBPACK_PORT}.pid"
@@ -1414,6 +1418,9 @@ jobs:
14141418
echo "✅ Cleaned up $BFF_KILLED_COUNT BFF process(es) for run_id: ${{ github.run_id }}"
14151419
fi
14161420
1421+
# Fix read-only envtest binaries and directories so next checkout can clean the workspace
1422+
chmod -R u+w ${{ github.workspace }}/packages/*/bff/bin ${{ github.workspace }}/packages/*/upstream/bff/bin 2>/dev/null || true
1423+
14171424
- name: Stop Cypress Servers
14181425
run: |
14191426
echo "🛑 Stopping webpack dev server for run_id: ${{ github.run_id }}..."

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ jobs:
3939
- name: Install dependencies
4040
run: npm install
4141

42+
- name: Validate module federation ports
43+
run: npm run validate:ports
44+
4245
- name: Check for uncommitted changes
4346
run: git diff --exit-code
4447

.husky/pre-commit

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ fi
1111

1212
echo "🔍 Running lint-staged to check staged files..."
1313

14+
# If any package.json or federation manifest is staged, validate port uniqueness
15+
if git diff --cached --name-only | grep -qE 'package\.json$|federation-configmap\.yaml$'; then
16+
echo "📦 Port-related file changed — validating module federation ports..."
17+
if ! node scripts/validate-module-ports.js; then
18+
echo ""
19+
echo "💥 Module federation port conflict detected!"
20+
echo " Run 'npm run validate:ports' to see current assignments."
21+
echo ""
22+
exit 1
23+
fi
24+
fi
25+
1426
# Run lint-staged and if it fails, show helpful instructions
1527
if ! npx lint-staged; then
1628
echo ""

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Rules live in `.claude/rules/`. Read the relevant rule file before starting the
9595
| **React** | `react.md` | When writing React components, hooks, or pages |
9696
| **Security** | `security.md` | When working on auth, secrets, input validation, or K8s API interactions |
9797
| **Testing Standards** | `testing-standards.md` | When working across multiple test types or choosing a testing strategy |
98+
| **Third-Party Theming** | `third-party-theming.md` | When theming external libraries (Perses, MLflow, etc.) or mapping PF tokens into non-PF component systems |
9899
| **Unit Tests** | `unit-tests.md` | When creating or modifying Jest unit tests for utilities, hooks, or components |
99100

100101
## Agent Skills

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"type-check": "tsc --noEmit",
3535
"server": "NODE_ENV=production node ./dist/server.js",
3636
"lint": "eslint .",
37+
"lint:fix": "eslint . --fix",
3738
"test-unit": "jest --silent",
3839
"test-unit-coverage": "jest --silent --coverage"
3940
},

backend/src/utils/notebookUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,18 @@ const getImageTag = (image: ImageInfo, imageTagName: string): ImageTag => {
137137
};
138138
};
139139

140+
const getMlflowAnnotation = (fastify: KubeFastifyInstance): Record<string, string> => {
141+
const isMlflowFlagEnabled = getDashboardConfig().spec.dashboardConfig.mlflow;
142+
if (!isMlflowFlagEnabled) {
143+
return {};
144+
}
145+
const dscStatus = getClusterStatus(fastify);
146+
if (dscStatus?.components?.mlflowoperator?.managementState === 'Managed') {
147+
return { 'opendatahub.io/mlflow-instance': 'mlflow' };
148+
}
149+
return {};
150+
};
151+
140152
export const assembleNotebook = async (
141153
fastify: KubeFastifyInstance,
142154
data: NotebookData,
@@ -226,6 +238,7 @@ export const assembleNotebook = async (
226238
'opendatahub.io/hardware-profile-name': selectedHardwareProfile?.metadata.name || '',
227239
'opendatahub.io/hardware-profile-namespace':
228240
selectedHardwareProfile?.metadata.namespace || '',
241+
...getMlflowAnnotation(fastify),
229242
},
230243
name: name,
231244
namespace: namespace,
@@ -399,6 +412,7 @@ export const createNotebook = async (
399412
}
400413

401414
notebookAssembled.metadata.annotations['notebooks.opendatahub.io/inject-auth'] = 'true';
415+
402416
const notebookContainers = notebookAssembled.spec.template.spec.containers;
403417

404418
if (!notebookContainers[0]) {

0 commit comments

Comments
 (0)