Skip to content

Commit 1eced60

Browse files
Refactor code splitting and optimize chunk loading in Vite config (#26906)
* refactor: implement code splitting for pages and optimize manual chunking strategy in vite config * handle chunk loading error gracefully * address comments * fix lint comment * address copilot comments * fix chunk and initial load time * fix theme issues * fix packaging * update imports * improve imports * run checkstyle * fix build * fix css imports * update * improve imports * lazy load local options * refactor entityUtils * update * update * fix failures * apply checkstyle * fix build issue * address comments * fix import error * fix import error * fix tests * fix tests * move local to classBase approach * fix tests and update local usage * fix import error * fix unit tests * fix entityUtils imports * fix test and checkstyle * fix failing playwright * fix plugin issue * address comments * address comments * fix unit tests * address comment * fix localization issue with playwright * fix login.spec fix icon issue * fix login spec failure * fix login spec * address comments * final fix to icons * fix failing playwrights * fix checkstyle (cherry picked from commit 5f8098b)
1 parent 80b0035 commit 1eced60

174 files changed

Lines changed: 4576 additions & 4086 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.

openmetadata-ui/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
<!--arguments>build</arguments-->
153153
<environmentVariables>
154154
<NODE_OPTIONS>--max-old-space-size=${node.heap.size}</NODE_OPTIONS>
155+
<APP_VERSION>${project.version}</APP_VERSION>
155156
</environmentVariables>
156157
</configuration>
157158
</execution>

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { clickOutside, redirectToHomePage } from '../../utils/common';
2020
import {
2121
followEntity,
2222
validateFollowedEntityToWidget,
23+
waitForAllLoadersToDisappear,
2324
} from '../../utils/entity';
2425

2526
const user = new UserClass();
@@ -52,6 +53,7 @@ test.describe('Verify RTL Layout for landing page', () => {
5253
.locator('.ant-dropdown:visible [data-menu-id*="-he-HE"]')
5354
.click();
5455
await page.waitForLoadState('domcontentloaded');
56+
await waitForAllLoadersToDisappear(page);
5557
});
5658

5759
test('Verify DataAssets widget functionality', async ({

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Customproperties-part1.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ test.describe(
142142

143143
await page.locator("pre[role='presentation']").last().click();
144144
await page.keyboard.type(
145-
"SELECT id, name, email\nFROM users\nWHERE active = true\nAND department = 'engineering'\nORDER BY created_at DESC\nLIMIT 100"
145+
"SELECT id, name, email\n\nFROM users\n\nWHERE active = true\n\nAND department = 'engineering'\n\nORDER BY created_at DESC\n\nLIMIT 100"
146146
);
147147

148148
const patchResponse = page.waitForResponse(

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ import { JWT_EXPIRY_TIME_MAP, LOGIN_ERROR_MESSAGE } from '../../constant/login';
1515
import { AdminClass } from '../../support/user/AdminClass';
1616
import { UserClass } from '../../support/user/UserClass';
1717
import { performAdminLogin } from '../../utils/admin';
18-
import { clickOutside, redirectToHomePage } from '../../utils/common';
18+
import {
19+
clickOutside,
20+
getDefaultAdminAPIContext,
21+
redirectToHomePage,
22+
visitOwnProfilePage,
23+
} from '../../utils/common';
24+
import { waitForAllLoadersToDisappear } from '../../utils/entity';
1925
import { updateJWTTokenExpiryTime } from '../../utils/login';
20-
import { visitUserProfilePage } from '../../utils/user';
2126

2227
const user = new UserClass();
2328
const CREDENTIALS = user.data;
@@ -149,36 +154,56 @@ test.describe('Login flow should work properly', () => {
149154
await page.locator('[data-testid="go-back-button"]').click();
150155
});
151156

152-
test('Refresh should work', async ({ browser }) => {
153-
const browserContext = await browser.newContext();
154-
const { apiContext, afterAction } = await performAdminLogin(browser);
155-
const page1 = await browserContext.newPage(),
156-
page2 = await browserContext.newPage();
157+
test('Refresh should work', async ({ page: page1, browser }) => {
158+
test.slow();
159+
160+
const { apiContext, afterAction } = await getDefaultAdminAPIContext(
161+
browser
162+
);
163+
const context = page1.context();
164+
const page2 = await context.newPage();
157165

158166
const testUser = new UserClass();
159167
await testUser.create(apiContext);
168+
await testUser.setAdminRole(apiContext);
160169

161170
await test.step('Login and wait for refresh call is made', async () => {
162171
// User login
163172

164173
await testUser.login(page1);
165174
await redirectToHomePage(page1);
175+
await waitForAllLoadersToDisappear(page1);
166176
await redirectToHomePage(page2);
177+
await waitForAllLoadersToDisappear(page2);
178+
await page2.reload();
167179

168180
// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for token refresh timer to fire
169181
await page1.waitForTimeout(3 * 60 * 1000);
170182

171-
await redirectToHomePage(page1);
183+
await page1.bringToFront();
184+
await visitOwnProfilePage(page1);
185+
await waitForAllLoadersToDisappear(page1);
186+
await expect(page1.getByTestId('user-display-name')).toHaveText(
187+
testUser.responseData.displayName ?? testUser.responseData.name
188+
);
172189

173-
await visitUserProfilePage(page1, testUser.responseData.name);
174-
await redirectToHomePage(page2);
175-
await visitUserProfilePage(page2, testUser.responseData.name);
190+
await page2.bringToFront();
191+
await page2.evaluate(() => {
192+
document.dispatchEvent(
193+
new Event('visibilitychange', { bubbles: true })
194+
);
195+
});
196+
197+
await visitOwnProfilePage(page2);
198+
await waitForAllLoadersToDisappear(page2);
199+
await expect(page2.getByTestId('user-display-name')).toHaveText(
200+
testUser.responseData.displayName ?? testUser.responseData.name
201+
);
176202

177203
await page1.close();
178204
await page2.close();
179205
});
180206

181-
await browserContext.close();
182207
await afterAction();
183208
});
184209

openmetadata-ui/src/main/resources/ui/src/App.test.tsx

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,58 @@
1212
*/
1313

1414
import { render } from '@testing-library/react';
15+
import React from 'react';
1516
import App from './App';
16-
import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider';
17+
import AppRouter from './components/AppRouter/AppRouter';
1718

18-
jest.mock('./components/AppRouter/AppRouter', () => {
19-
return jest.fn().mockReturnValue(<p>AppRouter</p>);
20-
});
19+
const mockAuthProvider = jest.fn();
2120

22-
jest.mock('./components/Auth/AuthProviders/AuthProvider', () => {
23-
return {
24-
AuthProvider: jest
25-
.fn()
26-
.mockImplementation(({ children }) => <>{children}</>),
27-
AuthContext: {
28-
Provider: jest.fn().mockImplementation(({ children }) => <>{children}</>),
29-
},
30-
};
31-
});
21+
jest.mock('./components/AppRouter/AppRouter', () => ({
22+
__esModule: true,
23+
default: function AppRouter() {
24+
return React.createElement(
25+
'div',
26+
{ 'data-testid': 'app-router' },
27+
'AppRouter'
28+
);
29+
},
30+
}));
31+
32+
jest.mock('./components/Auth/AuthProviders/AuthProvider', () => ({
33+
AuthProvider: function AuthProvider({
34+
children,
35+
childComponentType,
36+
}: {
37+
children: React.ReactNode;
38+
childComponentType: React.ComponentType;
39+
}) {
40+
mockAuthProvider({ childComponentType });
41+
42+
return React.createElement(
43+
'div',
44+
{ 'data-testid': 'auth-provider' },
45+
children
46+
);
47+
},
48+
}));
49+
50+
describe('App', () => {
51+
beforeEach(() => {
52+
jest.clearAllMocks();
53+
});
54+
55+
it('should render AuthProvider wrapping AppRouter', () => {
56+
const { getByTestId } = render(React.createElement(App));
57+
58+
expect(getByTestId('auth-provider')).toBeInTheDocument();
59+
expect(getByTestId('app-router')).toBeInTheDocument();
60+
});
61+
62+
it('should pass AppRouter as childComponentType to AuthProvider', () => {
63+
render(React.createElement(App));
3264

33-
it('renders learn react link', () => {
34-
const { getAllByTestId } = render(
35-
<AuthProvider childComponentType={App}>
36-
<App />
37-
</AuthProvider>
38-
);
39-
const linkElement = getAllByTestId(/content-wrapper/i);
40-
linkElement.map((elm) => expect(elm).toBeInTheDocument());
65+
expect(mockAuthProvider).toHaveBeenCalledWith({
66+
childComponentType: AppRouter,
67+
});
68+
});
4169
});

openmetadata-ui/src/main/resources/ui/src/App.tsx

Lines changed: 4 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -11,171 +11,15 @@
1111
* limitations under the License.
1212
*/
1313

14-
import { isEmpty } from 'lodash';
15-
import { FC, ReactNode, useEffect, useMemo } from 'react';
16-
import { RouterProvider } from 'react-aria-components';
17-
import { HelmetProvider } from 'react-helmet-async';
18-
import { I18nextProvider } from 'react-i18next';
19-
import { BrowserRouter, useNavigate } from 'react-router-dom';
20-
import { useShallow } from 'zustand/react/shallow';
14+
import { FC } from 'react';
2115
import AppRouter from './components/AppRouter/AppRouter';
2216
import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider';
23-
import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary';
24-
import { EntityExportModalProvider } from './components/Entity/EntityExportModalProvider/EntityExportModalProvider.component';
25-
import ApplicationsProvider from './components/Settings/Applications/ApplicationsProvider/ApplicationsProvider';
26-
import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider';
27-
import AirflowStatusProvider from './context/AirflowStatusProvider/AirflowStatusProvider';
28-
import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider';
29-
import AsyncDeleteProvider from './context/AsyncDeleteProvider/AsyncDeleteProvider';
30-
import PermissionProvider from './context/PermissionProvider/PermissionProvider';
31-
import TourProvider from './context/TourProvider/TourProvider';
32-
import WebSocketProvider from './context/WebSocketProvider/WebSocketProvider';
33-
import { useApplicationStore } from './hooks/useApplicationStore';
34-
import {
35-
getCustomUiThemePreference,
36-
getSystemConfig,
37-
} from './rest/settingConfigAPI';
38-
import { getBasePath } from './utils/HistoryUtils';
39-
40-
import GlobalStyles from '@mui/material/GlobalStyles';
41-
import { ThemeProvider } from '@mui/material/styles';
42-
import {
43-
createMuiTheme,
44-
SnackbarContent,
45-
} from '@openmetadata/ui-core-components';
46-
import { SnackbarProvider } from 'notistack';
47-
import { DndProvider } from 'react-dnd';
48-
import { HTML5Backend } from 'react-dnd-html5-backend';
49-
import { DEFAULT_THEME } from './constants/Appearance.constants';
50-
import RuleEnforcementProvider from './context/RuleEnforcementProvider/RuleEnforcementProvider';
51-
import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider';
52-
import i18n from './utils/i18next/LocalUtil';
53-
import { getThemeConfig } from './utils/ThemeUtils';
54-
55-
const ReactAriaRouterBridge = ({ children }: { children: ReactNode }) => {
56-
const navigate = useNavigate();
57-
58-
return <RouterProvider navigate={navigate}>{children}</RouterProvider>;
59-
};
6017

6118
const App: FC = () => {
62-
const { applicationConfig, setApplicationConfig, setRdfEnabled } =
63-
useApplicationStore(
64-
useShallow((state) => ({
65-
applicationConfig: state.applicationConfig,
66-
setApplicationConfig: state.setApplicationConfig,
67-
setRdfEnabled: state.setRdfEnabled,
68-
}))
69-
);
70-
71-
// Create dynamic MUI theme based on user customizations
72-
const muiTheme = useMemo(
73-
() => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME),
74-
[applicationConfig?.customTheme]
75-
);
76-
77-
const fetchApplicationConfig = async () => {
78-
try {
79-
const [themeData, systemConfig] = await Promise.all([
80-
getCustomUiThemePreference(),
81-
getSystemConfig(),
82-
]);
83-
84-
setApplicationConfig({
85-
...themeData,
86-
customTheme: getThemeConfig(themeData.customTheme),
87-
});
88-
89-
// Set RDF enabled state
90-
setRdfEnabled(systemConfig.rdfEnabled || false);
91-
} catch (error) {
92-
// eslint-disable-next-line no-console
93-
console.error(error);
94-
}
95-
};
96-
97-
useEffect(() => {
98-
fetchApplicationConfig();
99-
}, []);
100-
101-
useEffect(() => {
102-
const faviconHref = isEmpty(
103-
applicationConfig?.customLogoConfig?.customFaviconUrlPath
104-
)
105-
? '/favicon.png'
106-
: applicationConfig?.customLogoConfig?.customFaviconUrlPath ??
107-
'/favicon.png';
108-
const link = document.querySelectorAll('link[rel~="icon"]');
109-
110-
if (!isEmpty(link)) {
111-
link.forEach((item) => {
112-
item.setAttribute('href', faviconHref);
113-
});
114-
}
115-
}, [applicationConfig]);
116-
11719
return (
118-
<div className="main-container">
119-
<div className="content-wrapper" data-testid="content-wrapper">
120-
<BrowserRouter basename={getBasePath()}>
121-
<ReactAriaRouterBridge>
122-
<I18nextProvider i18n={i18n}>
123-
<HelmetProvider>
124-
<ErrorBoundary>
125-
<AntDConfigProvider>
126-
<UntitledUIThemeProvider
127-
brandColors={applicationConfig?.customTheme}>
128-
<ThemeProvider theme={muiTheme}>
129-
<GlobalStyles styles={{ html: { fontSize: '14px' } }} />
130-
<SnackbarProvider
131-
Components={{
132-
default: SnackbarContent,
133-
error: SnackbarContent,
134-
success: SnackbarContent,
135-
warning: SnackbarContent,
136-
info: SnackbarContent,
137-
}}
138-
anchorOrigin={{
139-
vertical: 'top',
140-
horizontal: 'right',
141-
}}
142-
autoHideDuration={6000}
143-
maxSnack={3}>
144-
<AuthProvider childComponentType={AppRouter}>
145-
<TourProvider>
146-
<WebAnalyticsProvider>
147-
<PermissionProvider>
148-
<WebSocketProvider>
149-
<ApplicationsProvider>
150-
<AsyncDeleteProvider>
151-
<EntityExportModalProvider>
152-
<AirflowStatusProvider>
153-
<RuleEnforcementProvider>
154-
<DndProvider
155-
backend={HTML5Backend}>
156-
<AppRouter />
157-
</DndProvider>
158-
</RuleEnforcementProvider>
159-
</AirflowStatusProvider>
160-
</EntityExportModalProvider>
161-
</AsyncDeleteProvider>
162-
</ApplicationsProvider>
163-
</WebSocketProvider>
164-
</PermissionProvider>
165-
</WebAnalyticsProvider>
166-
</TourProvider>
167-
</AuthProvider>
168-
</SnackbarProvider>
169-
</ThemeProvider>
170-
</UntitledUIThemeProvider>
171-
</AntDConfigProvider>
172-
</ErrorBoundary>
173-
</HelmetProvider>
174-
</I18nextProvider>
175-
</ReactAriaRouterBridge>
176-
</BrowserRouter>
177-
</div>
178-
</div>
20+
<AuthProvider childComponentType={AppRouter}>
21+
<AppRouter />
22+
</AuthProvider>
17923
);
18024
};
18125

0 commit comments

Comments
 (0)