diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
new file mode 100644
index 0000000..84807e1
--- /dev/null
+++ b/.github/workflows/ui-tests.yml
@@ -0,0 +1,44 @@
+name: UI Tests
+
+on:
+ push:
+ branches: [ "main" ]
+ paths:
+ - 'ui/**'
+ - '.github/workflows/ui-tests.yml'
+ pull_request:
+ branches: [ "main" ]
+ paths:
+ - 'ui/**'
+ - '.github/workflows/ui-tests.yml'
+
+jobs:
+ ui-test:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ defaults:
+ run:
+ working-directory: ui
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+
+ - name: Set up node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: '18'
+ cache: 'yarn'
+ cache-dependency-path: ui/yarn.lock
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile --network-timeout 1000000
+
+ # Jest unit + component tests (all HTTP is mocked; no external services).
+ - name: Run unit + component tests
+ run: CI=true yarn test --watchAll=false
+
+ # Production build type-checks the whole app (tsc is stricter than jest).
+ - name: Production build
+ run: yarn build
diff --git a/ui/package.json b/ui/package.json
index 265f8eb..d1c0c30 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -62,5 +62,11 @@
"resolutions": {
"react-scripts/resolve-url-loader/postcss": "8.4.49",
"react-scripts/@svgr/webpack/**/nth-check": "2.1.1"
+ },
+ "jest": {
+ "moduleNameMapper": {
+ "^axios$": "axios/dist/node/axios.cjs",
+ "^antd/es/(.*)$": "antd/lib/$1"
+ }
}
}
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 1a21173..f8ff195 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -10,8 +10,10 @@ import {
} from 'antd';
import {
createBrowserRouter,
+ Navigate,
Outlet,
RouterProvider,
+ useLocation,
useNavigate,
} from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
@@ -30,6 +32,10 @@ import Login from './pages/Login';
import { AuthProvider, useAuth } from './context/auth-context';
import { UserOutlined } from '@ant-design/icons';
import ModelVersionDetails from './pages/ModelVersionDetails';
+import ExternalData from './pages/ExternalData';
+import CredentialDetails from './pages/CredentialDetails';
+import ExternalLocationDetails from './pages/ExternalLocationDetails';
+import UsersList from './pages/UsersList';
// TODO:
// As of [19/02/2025], this implementation should be updated once the following PR are merged.
@@ -73,6 +79,30 @@ const router = createBrowserRouter([
path: '/models/:catalog/:schema/:model/versions/:version',
element: ,
},
+ {
+ path: '/external-data',
+ element: ,
+ },
+ {
+ path: '/external-data/external-locations',
+ element: ,
+ },
+ {
+ path: '/external-data/credentials',
+ element: ,
+ },
+ {
+ path: '/external-data/external-locations/:name',
+ element: ,
+ },
+ {
+ path: '/external-data/credentials/:name',
+ element: ,
+ },
+ {
+ path: '/users',
+ element: ,
+ },
],
},
]);
@@ -80,6 +110,13 @@ const router = createBrowserRouter([
function AppProvider() {
const { logout, currentUser } = useAuth();
const navigate = useNavigate();
+ const { pathname } = useLocation();
+
+ const selectedNavKey = pathname.startsWith('/external-data')
+ ? 'external-data'
+ : pathname.startsWith('/users')
+ ? 'users'
+ : 'catalogs';
const profileMenuItems = useMemo(
(): MenuProps['items'] => [
@@ -146,13 +183,23 @@ function AppProvider() {