From 2b01c812aba683a26ad10a97aa73edd15275ae70 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:09:07 +0530 Subject: [PATCH 01/47] refactor: implement code splitting for pages and optimize manual chunking strategy in vite config --- .../ui/src/components/AppRouter/AppRouter.tsx | 65 +++++++- .../AppRouter/AuthenticatedAppRouter.tsx | 61 +++++++- .../src/main/resources/ui/vite.config.ts | 143 ++++++++++++++++-- 3 files changed, 241 insertions(+), 28 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 135c5548b8e7..e36a3177259e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -12,7 +12,7 @@ */ import { isEmpty, isNil } from 'lodash'; -import { useCallback, useEffect } from 'react'; +import { lazy, useCallback, useEffect } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { useAnalytics } from 'use-analytics'; import { useShallow } from 'zustand/react/shallow'; @@ -20,16 +20,67 @@ import { ROUTES } from '../../constants/constants'; import { CustomEventTypes } from '../../generated/analytics/webAnalyticEventData'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; -import AccessNotAllowedPage from '../../pages/AccessNotAllowedPage/AccessNotAllowedPage'; -import { LogoutPage } from '../../pages/LogoutPage/LogoutPage'; -import PageNotFound from '../../pages/PageNotFound/PageNotFound'; -import SamlCallback from '../../pages/SamlCallback'; -import SignUpPage from '../../pages/SignUp/SignUpPage'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; -import AppContainer from '../AppContainer/AppContainer'; import Loader from '../common/Loader/Loader'; import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; +import withSuspenseFallback from './withSuspenseFallback'; + +// Lazy-load the entire authenticated shell so login page users never download it +const AppContainer = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "AppContainer" */ '../AppContainer/AppContainer' + ) + ) +); + +// Lazy-load infrequently-visited unauthenticated pages +const AccessNotAllowedPage = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "AccessNotAllowedPage" */ '../../pages/AccessNotAllowedPage/AccessNotAllowedPage' + ) + ) +); + +const LogoutPage = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "LogoutPage" */ '../../pages/LogoutPage/LogoutPage' + ).then((m) => ({ default: m.LogoutPage })) + ) +); + +const PageNotFound = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "PageNotFound" */ '../../pages/PageNotFound/PageNotFound' + ) + ) +); + +const SamlCallback = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "SamlCallback" */ '../../pages/SamlCallback' + ) + ) +); + +const SignUpPage = withSuspenseFallback( + lazy( + () => + import( + /* webpackChunkName: "SignUpPage" */ '../../pages/SignUp/SignUpPage' + ) + ) +); const AppRouter = () => { const location = useCustomLocation(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx index 7b755c527488..989cf139b9b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx @@ -21,18 +21,67 @@ import { import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; import { ResourceEntity } from '../../context/PermissionProvider/PermissionProvider.interface'; import { Operation } from '../../generated/entity/policies/policy'; -import AddCustomMetricPage from '../../pages/AddCustomMetricPage/AddCustomMetricPage'; -import { CustomizablePage } from '../../pages/CustomizablePage/CustomizablePage'; -import DataQualityPage from '../../pages/DataQuality/DataQualityPage'; -import ForbiddenPage from '../../pages/ForbiddenPage/ForbiddenPage'; -import PlatformLineage from '../../pages/PlatformLineage/PlatformLineage'; -import TagPage from '../../pages/TagPage/TagPage'; import { checkPermission, userPermissions } from '../../utils/PermissionsUtils'; import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; import AdminProtectedRoute from './AdminProtectedRoute'; import withSuspenseFallback from './withSuspenseFallback'; +// Previously statically imported — lazify so they stay out of the main chunk +const AddCustomMetricPage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "AddCustomMetricPage" */ '../../pages/AddCustomMetricPage/AddCustomMetricPage' + ) + ) +); + +const CustomizablePage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "CustomizablePage" */ '../../pages/CustomizablePage/CustomizablePage' + ).then((m) => ({ default: m.CustomizablePage })) + ) +); + +const DataQualityPage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "DataQualityPage" */ '../../pages/DataQuality/DataQualityPage' + ) + ) +); + +const ForbiddenPage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "ForbiddenPage" */ '../../pages/ForbiddenPage/ForbiddenPage' + ) + ) +); + +const PlatformLineage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "PlatformLineage" */ '../../pages/PlatformLineage/PlatformLineage' + ) + ) +); + +const TagPage = withSuspenseFallback( + React.lazy( + () => + import( + /* webpackChunkName: "TagPage" */ '../../pages/TagPage/TagPage' + ) + ) +); + const DomainRouter = withSuspenseFallback( React.lazy( () => import(/* webpackChunkName: "DomainRouter" */ './DomainRouter') diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index 9a02c03e3c6a..e0aa541957fa 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -13,7 +13,7 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; -import path from 'path'; +import path from 'node:path'; import { defineConfig, loadEnv } from 'vite'; import viteCompression from 'vite-plugin-compression'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; @@ -38,19 +38,19 @@ export default defineConfig(({ mode }) => { // Don't replace ${basePath} placeholder - it will be replaced at runtime by Java backend // Add ${basePath} prefix to asset paths (with or without leading slash) return html - .replace( + .replaceAll( /(]*src=["'])(\.\/)?assets\//g, '$1${basePath}assets/' ) - .replace( + .replaceAll( /(]*href=["'])(\.\/)?assets\//g, '$1${basePath}assets/' ) - .replace( + .replaceAll( /(]*src=["'])(\.\/)?assets\//g, '$1${basePath}assets/' ) - .replace( + .replaceAll( /(]*src=["'])(\.\/)?images\//g, '$1${basePath}images/' ); @@ -73,6 +73,9 @@ export default defineConfig(({ mode }) => { ext: '.gz', threshold: 1024, // Only compress files larger than 1KB deleteOriginFile: false, // Keep original files for fallback + // Skip binary formats that are already compressed — re-compressing + // them wastes build CPU and saves zero bytes. + filter: /\.(js|mjs|css|html|svg|json|wasm)(\?.*)?$/i, }), mode === 'production' && viteCompression({ @@ -80,6 +83,8 @@ export default defineConfig(({ mode }) => { ext: '.br', threshold: 1024, // Only compress files larger than 1KB deleteOriginFile: false, // Keep original files for fallback + // Same exclusion list — woff2 is already brotli-compressed internally. + filter: /\.(js|mjs|css|html|svg|json|wasm)(\?.*)?$/i, }), ].filter(Boolean), @@ -169,23 +174,131 @@ export default defineConfig(({ mode }) => { cssMinify: 'esbuild', cssCodeSplit: true, reportCompressedSize: false, - chunkSizeWarningLimit: 5000, + // Each named chunk is now bounded; raise the warning limit only so CI + // doesn't complain about the few legitimately large vendor chunks. + chunkSizeWarningLimit: 3000, rollupOptions: { output: { - manualChunks: { - 'react-vendor': ['react', 'react-dom', 'react-router-dom'], - 'antd-vendor': ['antd', '@ant-design/icons'], - 'editor-vendor': [ + /** + * manualChunks object — Rollup resolves each package name and groups + * all its internal modules into the named chunk automatically. + * + * Order within this object does not matter; what matters is which + * chunk a package is assigned to. Keep the comment groups as a guide. + */ + /** + * Function-based manualChunks using an explicit package map. + * This achieves what the object pattern does, but importantly + * allows us to add a catch-all at the end so unlisted node_modules + * are properly split out of the main index.js bundle. + */ + manualChunks: ((packageMap: Record) => (id: string) => { + // Find if this module belongs to any of our explicit vendor chunks + for (const [chunkName, packages] of Object.entries(packageMap)) { + if (packages.some((pkg) => id.includes(`node_modules/${pkg}/`))) { + return chunkName; + } + } + + // --- THE CRITICAL CATCH-ALL --- + // If it's a node_module but wasn't in the list above, group it here + // instead of letting it bloat the main application index.js! + if (id.includes('node_modules/')) { + return 'vendor-misc'; + } + })({ + // ── Login critical path (loaded before auth resolves) ───────────── + 'vendor-react': ['react', 'react-dom', 'scheduler'], + 'vendor-router': ['react-router-dom', 'react-router'], + 'vendor-zustand': ['zustand'], + 'vendor-antd': ['antd', '@ant-design/icons'], + 'vendor-i18n': ['i18next', 'react-i18next'], + + // ── Core UI Components (massive library) ────────────────────────── + 'vendor-core': ['@openmetadata/ui-core-components'], + + // ── MUI / Emotion (authenticated pages only) ────────────────────── + 'vendor-mui': [ + '@mui/material', + '@mui/system', + '@mui/icons-material', + '@mui/x-date-pickers', + '@mui/x-tree-view', + '@emotion/react', + '@emotion/styled', + ], + + // ── Rich-text editors ───────────────────────────────────────────── + 'vendor-editor-tiptap': [ '@tiptap/react', + '@tiptap/core', '@tiptap/starter-kit', '@tiptap/extension-link', + '@tiptap/extension-placeholder', + '@tiptap/extension-table', + '@tiptap/extension-table-cell', + '@tiptap/extension-table-header', + '@tiptap/extension-table-row', + '@tiptap/extension-task-item', + '@tiptap/extension-task-list', + '@tiptap/suggestion', ], - 'chart-vendor': ['recharts', 'reactflow'], - }, + 'vendor-editor-quill': [ + 'quill', + 'react-quill-new', + '@toast-ui/react-editor', + '@windmillcode/quill-emoji', + 'quill-mention', + 'quilljs-markdown', + ], + 'vendor-codemirror': ['codemirror', 'react-codemirror2'], + + // ── Data Parsers & AST (very heavy) ─────────────────────────────── + 'vendor-antlr': ['antlr4'], + 'vendor-schema': ['@apidevtools/json-schema-ref-parser'], + 'vendor-markdown': ['showdown', 'turndown', 'dompurify', 'html-react-parser'], + + // ── Charts & graphs ─────────────────────────────────────────────── + 'vendor-recharts': ['recharts'], + 'vendor-reactflow': ['reactflow', '@dagrejs/dagre'], + 'vendor-g6': ['@antv/g6', 'elkjs'], + 'vendor-vis': ['vis-network', 'vis-data'], + + // ── Auth SDKs ───────────────────────────────────────────────────── + 'vendor-auth-okta-auth0': [ + '@okta/okta-react', + '@okta/okta-auth-js', + '@auth0/auth0-react', + 'oidc-client', + ], + 'vendor-auth-azure': [ + '@azure/msal-browser', + '@azure/msal-react', + ], + + // ── Forms & Query schemas ───────────────────────────────────────── + 'vendor-query-builder': ['@react-awesome-query-builder/antd'], + 'vendor-rjsf': [ + '@rjsf/core', + '@rjsf/utils', + '@rjsf/validator-ajv8', + 'ajv', + ], + + // ── DnD, tour, sockets ──────────────────────────────────────────── + 'vendor-dnd': ['react-dnd', 'react-dnd-html5-backend'], + 'vendor-tour': ['@deuex-solutions/react-tour'], + 'vendor-socketio': ['socket.io-client'], + + // ── Utilities ───────────────────────────────────────────────────── + 'vendor-lodash': ['lodash'], + 'vendor-rapidoc': ['rapidoc'], + 'vendor-analytics': ['analytics', 'use-analytics'], + }), assetFileNames: (assetInfo) => { - const fileName = assetInfo.name || ''; - const info = fileName.split('.'); - const ext = info[info.length - 1]; + const fileName = assetInfo.names || ''; + + const ext = fileName.at(-1) ?? ''; if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) { return `images/[name]-[hash][extname]`; From e134644fae2326b8d2f5a2f0859d555b93daa396 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:02:22 +0530 Subject: [PATCH 02/47] handle chunk loading error gracefully --- openmetadata-ui/pom.xml | 1 + .../common/ErrorBoundary/ErrorFallback.tsx | 6 +++++- .../src/main/resources/ui/vite.config.ts | 13 ++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/pom.xml b/openmetadata-ui/pom.xml index 2731d546a49a..bb0539c119cc 100644 --- a/openmetadata-ui/pom.xml +++ b/openmetadata-ui/pom.xml @@ -152,6 +152,7 @@ --max-old-space-size=${node.heap.size} + ${project.version} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx index 9fae684b4927..87f907759714 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx @@ -27,7 +27,11 @@ const ErrorFallback: React.FC = ({ }) => { const navigate = useNavigate(); - const isChunkLoadError = error.message?.startsWith('Loading chunk'); + const isChunkLoadError = + error?.name === 'ChunkLoadError' || + error.message?.startsWith('Loading chunk') || // Legacy Webpack + error.message?.toLowerCase().includes('failed to fetch dynamically imported module') || // Vite + error.message?.toLowerCase().includes('importing a module script failed'); // Vite (Safari) const message = isChunkLoadError ? t('message.please-refresh-the-page') diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index e0aa541957fa..7b551013eabb 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -13,7 +13,7 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; -import path from 'node:path'; +import path from 'path'; import { defineConfig, loadEnv } from 'vite'; import viteCompression from 'vite-plugin-compression'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; @@ -56,6 +56,17 @@ export default defineConfig(({ mode }) => { ); }, }, + { + name: 'html-cache-buster', + transformIndexHtml(html: string) { + // APP_VERSION is injected by the Maven build + const appVersion = process.env.APP_VERSION || 'unknown'; + return html.replaceAll( + /(href|src)="([^"]+\.(?:js|css))"/g, + `$1="$2?v=${appVersion}"` + ); + }, + }, tailwindcss(), react(), svgr(), From d9f1e8a835fd8917b94156d3a163ee71e1fa6b42 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:19:10 +0530 Subject: [PATCH 03/47] address comments --- .../ui/src/components/AppRouter/AppRouter.tsx | 18 ++++------ .../AppRouter/AuthenticatedAppRouter.tsx | 36 +++++++------------ .../src/main/resources/ui/vite.config.ts | 9 ++--- 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index e36a3177259e..039878f923d3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -30,8 +30,7 @@ import withSuspenseFallback from './withSuspenseFallback'; const AppContainer = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "AppContainer" */ '../AppContainer/AppContainer' + import( '../AppContainer/AppContainer' ) ) ); @@ -40,8 +39,7 @@ const AppContainer = withSuspenseFallback( const AccessNotAllowedPage = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "AccessNotAllowedPage" */ '../../pages/AccessNotAllowedPage/AccessNotAllowedPage' + import( '../../pages/AccessNotAllowedPage/AccessNotAllowedPage' ) ) ); @@ -49,8 +47,7 @@ const AccessNotAllowedPage = withSuspenseFallback( const LogoutPage = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "LogoutPage" */ '../../pages/LogoutPage/LogoutPage' + import( '../../pages/LogoutPage/LogoutPage' ).then((m) => ({ default: m.LogoutPage })) ) ); @@ -58,8 +55,7 @@ const LogoutPage = withSuspenseFallback( const PageNotFound = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "PageNotFound" */ '../../pages/PageNotFound/PageNotFound' + import( '../../pages/PageNotFound/PageNotFound' ) ) ); @@ -67,8 +63,7 @@ const PageNotFound = withSuspenseFallback( const SamlCallback = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "SamlCallback" */ '../../pages/SamlCallback' + import( '../../pages/SamlCallback' ) ) ); @@ -76,8 +71,7 @@ const SamlCallback = withSuspenseFallback( const SignUpPage = withSuspenseFallback( lazy( () => - import( - /* webpackChunkName: "SignUpPage" */ '../../pages/SignUp/SignUpPage' + import( '../../pages/SignUp/SignUpPage' ) ) ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx index 989cf139b9b7..71b5c600c420 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx @@ -31,8 +31,7 @@ import withSuspenseFallback from './withSuspenseFallback'; const AddCustomMetricPage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "AddCustomMetricPage" */ '../../pages/AddCustomMetricPage/AddCustomMetricPage' + import( '../../pages/AddCustomMetricPage/AddCustomMetricPage' ) ) ); @@ -40,8 +39,7 @@ const AddCustomMetricPage = withSuspenseFallback( const CustomizablePage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "CustomizablePage" */ '../../pages/CustomizablePage/CustomizablePage' + import( '../../pages/CustomizablePage/CustomizablePage' ).then((m) => ({ default: m.CustomizablePage })) ) ); @@ -49,8 +47,7 @@ const CustomizablePage = withSuspenseFallback( const DataQualityPage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "DataQualityPage" */ '../../pages/DataQuality/DataQualityPage' + import( '../../pages/DataQuality/DataQualityPage' ) ) ); @@ -58,8 +55,7 @@ const DataQualityPage = withSuspenseFallback( const ForbiddenPage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "ForbiddenPage" */ '../../pages/ForbiddenPage/ForbiddenPage' + import( '../../pages/ForbiddenPage/ForbiddenPage' ) ) ); @@ -67,8 +63,7 @@ const ForbiddenPage = withSuspenseFallback( const PlatformLineage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "PlatformLineage" */ '../../pages/PlatformLineage/PlatformLineage' + import( '../../pages/PlatformLineage/PlatformLineage' ) ) ); @@ -76,48 +71,44 @@ const PlatformLineage = withSuspenseFallback( const TagPage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "TagPage" */ '../../pages/TagPage/TagPage' + import( '../../pages/TagPage/TagPage' ) ) ); const DomainRouter = withSuspenseFallback( React.lazy( - () => import(/* webpackChunkName: "DomainRouter" */ './DomainRouter') + () => import( './DomainRouter') ) ); const DataProductListPage = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "DataProductListPage" */ '../DataProduct/DataProductListPage' + import( '../DataProduct/DataProductListPage' ) ) ); const SettingsRouter = withSuspenseFallback( React.lazy( - () => import(/* webpackChunkName: "SettingsRouter" */ './SettingsRouter') + () => import( './SettingsRouter') ) ); const EntityRouter = withSuspenseFallback( React.lazy( - () => import(/* webpackChunkName: "EntityRouter" */ './EntityRouter') + () => import( './EntityRouter') ) ); const ClassificationRouter = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "ClassificationRouter" */ './ClassificationRouter' + import( './ClassificationRouter' ) ) ); const GlossaryRouter = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "GlossaryRouter" */ './GlossaryRouter/GlossaryRouter' + import( './GlossaryRouter/GlossaryRouter' ) ) ); @@ -125,8 +116,7 @@ const GlossaryRouter = withSuspenseFallback( const GlossaryTermRouter = withSuspenseFallback( React.lazy( () => - import( - /* webpackChunkName: "GlossaryTermRouter" */ './GlossaryTermRouter/GlossaryTermRouter' + import( './GlossaryTermRouter/GlossaryTermRouter' ) ) ); diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index 7b551013eabb..21bbe85548f7 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -13,7 +13,7 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; -import path from 'path'; +import path from 'node:path'; import { defineConfig, loadEnv } from 'vite'; import viteCompression from 'vite-plugin-compression'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; @@ -62,8 +62,9 @@ export default defineConfig(({ mode }) => { // APP_VERSION is injected by the Maven build const appVersion = process.env.APP_VERSION || 'unknown'; return html.replaceAll( - /(href|src)="([^"]+\.(?:js|css))"/g, - `$1="$2?v=${appVersion}"` + /(href|src)="([^"]+\.(?:js|css))(\?[^"]*)?"/g, + (_, attr, path, qs) => + `${attr}="${path}${qs ? qs + '&' : '?'}v=${appVersion}"` ); }, }, @@ -187,7 +188,7 @@ export default defineConfig(({ mode }) => { reportCompressedSize: false, // Each named chunk is now bounded; raise the warning limit only so CI // doesn't complain about the few legitimately large vendor chunks. - chunkSizeWarningLimit: 3000, + chunkSizeWarningLimit: 5000, rollupOptions: { output: { /** From a889d045741a2ab8c8bf6c8f965cbc6bcaf69852 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:13:41 +0530 Subject: [PATCH 04/47] fix lint comment --- .../ui/src/components/AppRouter/AppRouter.tsx | 38 +++------- .../AppRouter/AuthenticatedAppRouter.tsx | 72 +++++-------------- .../AppRouter/UnAuthenticatedAppRouter.tsx | 15 +++- .../common/ErrorBoundary/ErrorFallback.tsx | 4 +- 4 files changed, 40 insertions(+), 89 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 039878f923d3..456f260e651c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -28,52 +28,32 @@ import withSuspenseFallback from './withSuspenseFallback'; // Lazy-load the entire authenticated shell so login page users never download it const AppContainer = withSuspenseFallback( - lazy( - () => - import( '../AppContainer/AppContainer' - ) - ) + lazy(() => import('../AppContainer/AppContainer')) ); // Lazy-load infrequently-visited unauthenticated pages const AccessNotAllowedPage = withSuspenseFallback( - lazy( - () => - import( '../../pages/AccessNotAllowedPage/AccessNotAllowedPage' - ) - ) + lazy(() => import('../../pages/AccessNotAllowedPage/AccessNotAllowedPage')) ); const LogoutPage = withSuspenseFallback( - lazy( - () => - import( '../../pages/LogoutPage/LogoutPage' - ).then((m) => ({ default: m.LogoutPage })) + lazy(() => + import('../../pages/LogoutPage/LogoutPage').then((m) => ({ + default: m.LogoutPage, + })) ) ); const PageNotFound = withSuspenseFallback( - lazy( - () => - import( '../../pages/PageNotFound/PageNotFound' - ) - ) + lazy(() => import('../../pages/PageNotFound/PageNotFound')) ); const SamlCallback = withSuspenseFallback( - lazy( - () => - import( '../../pages/SamlCallback' - ) - ) + lazy(() => import('../../pages/SamlCallback')) ); const SignUpPage = withSuspenseFallback( - lazy( - () => - import( '../../pages/SignUp/SignUpPage' - ) - ) + lazy(() => import('../../pages/SignUp/SignUpPage')) ); const AppRouter = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx index 71b5c600c420..d072eddd2eeb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx @@ -30,95 +30,55 @@ import withSuspenseFallback from './withSuspenseFallback'; // Previously statically imported — lazify so they stay out of the main chunk const AddCustomMetricPage = withSuspenseFallback( React.lazy( - () => - import( '../../pages/AddCustomMetricPage/AddCustomMetricPage' - ) + () => import('../../pages/AddCustomMetricPage/AddCustomMetricPage') ) ); const CustomizablePage = withSuspenseFallback( - React.lazy( - () => - import( '../../pages/CustomizablePage/CustomizablePage' - ).then((m) => ({ default: m.CustomizablePage })) + React.lazy(() => + import('../../pages/CustomizablePage/CustomizablePage').then((m) => ({ + default: m.CustomizablePage, + })) ) ); const DataQualityPage = withSuspenseFallback( - React.lazy( - () => - import( '../../pages/DataQuality/DataQualityPage' - ) - ) + React.lazy(() => import('../../pages/DataQuality/DataQualityPage')) ); const ForbiddenPage = withSuspenseFallback( - React.lazy( - () => - import( '../../pages/ForbiddenPage/ForbiddenPage' - ) - ) + React.lazy(() => import('../../pages/ForbiddenPage/ForbiddenPage')) ); const PlatformLineage = withSuspenseFallback( - React.lazy( - () => - import( '../../pages/PlatformLineage/PlatformLineage' - ) - ) + React.lazy(() => import('../../pages/PlatformLineage/PlatformLineage')) ); const TagPage = withSuspenseFallback( - React.lazy( - () => - import( '../../pages/TagPage/TagPage' - ) - ) + React.lazy(() => import('../../pages/TagPage/TagPage')) ); const DomainRouter = withSuspenseFallback( - React.lazy( - () => import( './DomainRouter') - ) + React.lazy(() => import('./DomainRouter')) ); const DataProductListPage = withSuspenseFallback( - React.lazy( - () => - import( '../DataProduct/DataProductListPage' - ) - ) + React.lazy(() => import('../DataProduct/DataProductListPage')) ); const SettingsRouter = withSuspenseFallback( - React.lazy( - () => import( './SettingsRouter') - ) + React.lazy(() => import('./SettingsRouter')) ); const EntityRouter = withSuspenseFallback( - React.lazy( - () => import( './EntityRouter') - ) + React.lazy(() => import('./EntityRouter')) ); const ClassificationRouter = withSuspenseFallback( - React.lazy( - () => - import( './ClassificationRouter' - ) - ) + React.lazy(() => import('./ClassificationRouter')) ); const GlossaryRouter = withSuspenseFallback( - React.lazy( - () => - import( './GlossaryRouter/GlossaryRouter' - ) - ) + React.lazy(() => import('./GlossaryRouter/GlossaryRouter')) ); const GlossaryTermRouter = withSuspenseFallback( - React.lazy( - () => - import( './GlossaryTermRouter/GlossaryTermRouter' - ) - ) + React.lazy(() => import('./GlossaryTermRouter/GlossaryTermRouter')) ); const MyDataPage = withSuspenseFallback( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx index 3ab9fd01af6d..ddefb8d07445 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx @@ -18,10 +18,7 @@ import { ROUTES } from '../../constants/constants'; import { AuthProvider } from '../../generated/configuration/authenticationConfiguration'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; -import PageNotFound from '../../pages/PageNotFound/PageNotFound'; -import AccountActivationConfirmation from '../../pages/SignUp/account-activation-confirmation.component'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; -import Auth0Callback from '../Auth/AppCallbacks/Auth0Callback/Auth0Callback'; import withSuspenseFallback from './withSuspenseFallback'; const SigninPage = withSuspenseFallback( @@ -40,6 +37,18 @@ const BasicSignupPage = withSuspenseFallback( lazy(() => import('../../pages/SignUp/BasicSignup.component')) ); +const PageNotFound = withSuspenseFallback( + lazy(() => import('../../pages/PageNotFound/PageNotFound')) +); + +const AccountActivationConfirmation = withSuspenseFallback( + lazy(() => import('../../pages/SignUp/account-activation-confirmation.component')) +); + +const Auth0Callback = withSuspenseFallback( + lazy(() => import('../Auth/AppCallbacks/Auth0Callback/Auth0Callback')) +); + export const UnAuthenticatedAppRouter = () => { const location = useCustomLocation(); const { authConfig, isSigningUp } = useApplicationStore( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx index 87f907759714..458fb33992a8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorBoundary/ErrorFallback.tsx @@ -30,7 +30,9 @@ const ErrorFallback: React.FC = ({ const isChunkLoadError = error?.name === 'ChunkLoadError' || error.message?.startsWith('Loading chunk') || // Legacy Webpack - error.message?.toLowerCase().includes('failed to fetch dynamically imported module') || // Vite + error.message + ?.toLowerCase() + .includes('failed to fetch dynamically imported module') || // Vite error.message?.toLowerCase().includes('importing a module script failed'); // Vite (Safari) const message = isChunkLoadError From 3a6bfd233ee8eec3bef472805f344eaa1560af2b Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:15:42 +0530 Subject: [PATCH 05/47] address copilot comments --- openmetadata-ui/src/main/resources/ui/vite.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index 21bbe85548f7..7e8d17429da6 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -308,11 +308,11 @@ export default defineConfig(({ mode }) => { 'vendor-analytics': ['analytics', 'use-analytics'], }), assetFileNames: (assetInfo) => { - const fileName = assetInfo.names || ''; - - const ext = fileName.at(-1) ?? ''; + const names = assetInfo.names ?? []; + const fileName = names.length > 0 ? names[0] : ''; + const ext = fileName ? path.extname(fileName).toLowerCase() : ''; - if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) { + if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(ext)) { return `images/[name]-[hash][extname]`; } From 515db4806a62de0d95667821cb110daa3665305b Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:47:16 +0530 Subject: [PATCH 06/47] fix chunk and initial load time --- .../src/main/resources/ui/src/App.tsx | 98 +------------- .../src/main/resources/ui/src/AppRoot.tsx | 52 ++++++++ .../ui/src/components/AppRouter/AppRouter.tsx | 105 +++++++++------ .../components/AppRouter/AuthenticatedApp.tsx | 99 +++++++++++++++ .../AppRouter/UnauthenticatedApp.tsx | 25 ++++ .../AppAuthenticators/LazyAuthenticators.tsx | 99 +++++++++++++++ .../Auth/AuthProviders/AuthProvider.tsx | 40 +++--- .../Layout/CarouselLayout/CarouselLayout.tsx | 9 +- .../ui/src/constants/LoginClassBase.ts | 24 ++-- .../src/main/resources/ui/src/index.tsx | 4 +- .../ui/src/pages/LoginPage/LoginCarousel.tsx | 26 +++- .../src/main/resources/ui/vite.config.ts | 120 +----------------- 12 files changed, 413 insertions(+), 288 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/App.tsx b/openmetadata-ui/src/main/resources/ui/src/App.tsx index ad60e07b8459..7b62459cd8e6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/App.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/App.tsx @@ -12,43 +12,15 @@ */ import { isEmpty } from 'lodash'; -import { FC, useEffect, useMemo } from 'react'; -import { HelmetProvider } from 'react-helmet-async'; -import { I18nextProvider } from 'react-i18next'; -import { BrowserRouter } from 'react-router-dom'; +import { FC, useEffect } from 'react'; import { useShallow } from 'zustand/react/shallow'; import AppRouter from './components/AppRouter/AppRouter'; import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider'; -import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; -import { EntityExportModalProvider } from './components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; -import ApplicationsProvider from './components/Settings/Applications/ApplicationsProvider/ApplicationsProvider'; -import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider'; -import AirflowStatusProvider from './context/AirflowStatusProvider/AirflowStatusProvider'; -import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; -import AsyncDeleteProvider from './context/AsyncDeleteProvider/AsyncDeleteProvider'; -import PermissionProvider from './context/PermissionProvider/PermissionProvider'; -import TourProvider from './context/TourProvider/TourProvider'; -import WebSocketProvider from './context/WebSocketProvider/WebSocketProvider'; import { useApplicationStore } from './hooks/useApplicationStore'; import { getCustomUiThemePreference, getSystemConfig, } from './rest/settingConfigAPI'; -import { getBasePath } from './utils/HistoryUtils'; - -import GlobalStyles from '@mui/material/GlobalStyles'; -import { ThemeProvider } from '@mui/material/styles'; -import { - createMuiTheme, - SnackbarContent, -} from '@openmetadata/ui-core-components'; -import { SnackbarProvider } from 'notistack'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { DEFAULT_THEME } from './constants/Appearance.constants'; -import RuleEnforcementProvider from './context/RuleEnforcementProvider/RuleEnforcementProvider'; -import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider'; -import i18n from './utils/i18next/LocalUtil'; import { getThemeConfig } from './utils/ThemeUtils'; const App: FC = () => { @@ -61,12 +33,6 @@ const App: FC = () => { })) ); - // Create dynamic MUI theme based on user customizations - const muiTheme = useMemo( - () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), - [applicationConfig?.customTheme] - ); - const fetchApplicationConfig = async () => { try { const [themeData, systemConfig] = await Promise.all([ @@ -79,7 +45,6 @@ const App: FC = () => { customTheme: getThemeConfig(themeData.customTheme), }); - // Set RDF enabled state setRdfEnabled(systemConfig.rdfEnabled || false); } catch (error) { // eslint-disable-next-line no-console @@ -108,64 +73,9 @@ const App: FC = () => { }, [applicationConfig]); return ( -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+ + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx new file mode 100644 index 000000000000..9158b6e25043 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2025 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 { FC, lazy, Suspense, useEffect } from 'react'; +import { HelmetProvider } from 'react-helmet-async'; +import { I18nextProvider } from 'react-i18next'; +import { BrowserRouter } from 'react-router-dom'; +import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; +import Loader from './components/common/Loader/Loader'; +import { useApplicationStore } from './hooks/useApplicationStore'; +import { getBasePath } from './utils/HistoryUtils'; +import i18n from './utils/i18next/LocalUtil'; + +const App = lazy(() => import('./App')); + +const AppRoot: FC = () => { + const { initializeAuthState } = useApplicationStore(); + + useEffect(() => { + initializeAuthState(); + }, [initializeAuthState]); + + return ( +
+
+ + + + + }> + + + + + + +
+
+ ); +}; + +export default AppRoot; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 456f260e651c..c0f21e4f2543 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -26,7 +26,14 @@ import { useApplicationsProvider } from '../Settings/Applications/ApplicationsPr import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; import withSuspenseFallback from './withSuspenseFallback'; -// Lazy-load the entire authenticated shell so login page users never download it +const AuthenticatedApp = withSuspenseFallback( + lazy(() => import('./AuthenticatedApp')) +); + +const UnauthenticatedApp = withSuspenseFallback( + lazy(() => import('./UnauthenticatedApp')) +); + const AppContainer = withSuspenseFallback( lazy(() => import('../AppContainer/AppContainer')) ); @@ -125,47 +132,65 @@ const AppRouter = () => { return ; } + if (isAuthenticated) { + return ( + + + } path={ROUTES.NOT_FOUND} /> + } path={ROUTES.LOGOUT} /> + } + path={ROUTES.UNAUTHORISED} + /> + + ) : ( + + ) + } + path={ROUTES.SIGNUP} + /> + } path={ROUTES.AUTH_CALLBACK} /> + + {plugins?.flatMap((plugin) => { + const routes = plugin.getRoutes?.() || []; + const appRoutes = routes.filter( + (route) => route.position === RoutePosition.APP + ); + + return appRoutes.map((route, idx) => ( + + )); + })} + + } path="*" /> + + + ); + } + return ( - - } path={ROUTES.NOT_FOUND} /> - } path={ROUTES.LOGOUT} /> - } path={ROUTES.UNAUTHORISED} /> - - ) : ( - - ) - } - path={ROUTES.SIGNUP} - /> - {/* When authenticating from an SSO provider page (e.g., SAML Apps), if the user is already logged in, - * the callbacks should be available. This ensures consistent behavior across different authentication scenarios. - */} - } path={ROUTES.AUTH_CALLBACK} /> - - {/* Render APP position plugin routes (they handle their own layouts) */} - {isAuthenticated && - plugins?.flatMap((plugin) => { - const routes = plugin.getRoutes?.() || []; - // Filter routes with APP position - const appRoutes = routes.filter( - (route) => route.position === RoutePosition.APP - ); - - return appRoutes.map((route, idx) => ( - - )); - })} - - {/* Default authenticated and unauthenticated routes */} - {isAuthenticated ? ( - } path="*" /> - ) : ( + + + } path={ROUTES.NOT_FOUND} /> + } path={ROUTES.LOGOUT} /> + } path={ROUTES.UNAUTHORISED} /> + + ) : ( + + ) + } + path={ROUTES.SIGNUP} + /> + } path={ROUTES.AUTH_CALLBACK} /> } path="*" /> - )} - + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx new file mode 100644 index 000000000000..3479a4712924 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2022 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 GlobalStyles from '@mui/material/GlobalStyles'; +import { ThemeProvider } from '@mui/material/styles'; +import { + createMuiTheme, + SnackbarContent, +} from '@openmetadata/ui-core-components'; +import { SnackbarProvider } from 'notistack'; +import { FC, useMemo } from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { useShallow } from 'zustand/react/shallow'; +import { DEFAULT_THEME } from '../../constants/Appearance.constants'; +import AirflowStatusProvider from '../../context/AirflowStatusProvider/AirflowStatusProvider'; +import AsyncDeleteProvider from '../../context/AsyncDeleteProvider/AsyncDeleteProvider'; +import PermissionProvider from '../../context/PermissionProvider/PermissionProvider'; +import RuleEnforcementProvider from '../../context/RuleEnforcementProvider/RuleEnforcementProvider'; +import TourProvider from '../../context/TourProvider/TourProvider'; +import { ThemeProvider as UntitledUIThemeProvider } from '../../context/UntitledUIThemeProvider/theme-provider'; +import WebSocketProvider from '../../context/WebSocketProvider/WebSocketProvider'; +import { useApplicationStore } from '../../hooks/useApplicationStore'; +import { EntityExportModalProvider } from '../Entity/EntityExportModalProvider/EntityExportModalProvider.component'; +import ApplicationsProvider from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; +import WebAnalyticsProvider from '../WebAnalytics/WebAnalyticsProvider'; + +interface AuthenticatedAppProps { + children: React.ReactNode; +} + +const AuthenticatedApp: FC = ({ children }) => { + const { applicationConfig } = useApplicationStore( + useShallow((state) => ({ + applicationConfig: state.applicationConfig, + })) + ); + + const muiTheme = useMemo( + () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), + [applicationConfig?.customTheme] + ); + + return ( + + + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + ); +}; + +export default AuthenticatedApp; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx new file mode 100644 index 000000000000..64a0cc88b205 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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 { FC } from 'react'; +import AntDConfigProvider from '../../context/AntDConfigProvider/AntDConfigProvider'; + +interface UnauthenticatedAppProps { + children: React.ReactNode; +} + +const UnauthenticatedApp: FC = ({ children }) => { + return {children}; +}; + +export default UnauthenticatedApp; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx new file mode 100644 index 000000000000..6b6df88f312c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2025 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 { forwardRef, lazy, ReactNode, Suspense } from 'react'; +import Loader from '../../common/Loader/Loader'; +import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; + +const Auth0Authenticator = lazy(() => import('./Auth0Authenticator')); +const BasicAuthAuthenticator = lazy(() => import('./BasicAuthAuthenticator')); +const MsalAuthenticator = lazy(() => import('./MsalAuthenticator')); +const OidcAuthenticator = lazy(() => import('./OidcAuthenticator')); +const OktaAuthenticator = lazy(() => import('./OktaAuthenticator')); +const GenericAuthenticator = lazy(() => + import('./GenericAuthenticator').then((m) => ({ + default: m.GenericAuthenticator, + })) +); + +export const LazyAuth0Authenticator = forwardRef< + AuthenticatorRef, + { children: ReactNode } +>((props, ref) => ( + }> + + +)); + +LazyAuth0Authenticator.displayName = 'LazyAuth0Authenticator'; + +export const LazyBasicAuthAuthenticator = forwardRef< + AuthenticatorRef, + { children: ReactNode } +>((props, ref) => ( + }> + + +)); + +LazyBasicAuthAuthenticator.displayName = 'LazyBasicAuthAuthenticator'; + +export const LazyMsalAuthenticator = forwardRef< + AuthenticatorRef, + { children: ReactNode } +>((props, ref) => ( + }> + + +)); + +LazyMsalAuthenticator.displayName = 'LazyMsalAuthenticator'; + +export const LazyOidcAuthenticator = forwardRef< + AuthenticatorRef, + { + children: ReactNode; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + childComponentType: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + userConfig: any; + } +>((props, ref) => ( + }> + + +)); + +LazyOidcAuthenticator.displayName = 'LazyOidcAuthenticator'; + +export const LazyOktaAuthenticator = forwardRef< + AuthenticatorRef, + { children: ReactNode } +>((props, ref) => ( + }> + + +)); + +LazyOktaAuthenticator.displayName = 'LazyOktaAuthenticator'; + +export const LazyGenericAuthenticator = forwardRef< + AuthenticatorRef, + { children: ReactNode } +>((props, ref) => ( + }> + + +)); + +LazyGenericAuthenticator.displayName = 'LazyGenericAuthenticator'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index e4e620c68a54..5cace9a90639 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -78,12 +78,14 @@ import { showErrorToast, showInfoToast } from '../../../utils/ToastUtils'; import { checkIfUpdateRequired } from '../../../utils/UserDataUtils'; import { resetWebAnalyticSession } from '../../../utils/WebAnalyticsUtils'; import Loader from '../../common/Loader/Loader'; -import Auth0Authenticator from '../AppAuthenticators/Auth0Authenticator'; -import BasicAuthAuthenticator from '../AppAuthenticators/BasicAuthAuthenticator'; -import { GenericAuthenticator } from '../AppAuthenticators/GenericAuthenticator'; -import MsalAuthenticator from '../AppAuthenticators/MsalAuthenticator'; -import OidcAuthenticator from '../AppAuthenticators/OidcAuthenticator'; -import OktaAuthenticator from '../AppAuthenticators/OktaAuthenticator'; +import { + LazyAuth0Authenticator, + LazyBasicAuthAuthenticator, + LazyGenericAuthenticator, + LazyMsalAuthenticator, + LazyOidcAuthenticator, + LazyOktaAuthenticator, +} from '../AppAuthenticators/LazyAuthenticators'; import { AuthenticatorRef, OidcUser } from './AuthProvider.interface'; import BasicAuthProvider from './BasicAuthProvider'; import OktaAuthProvider from './OktaAuthProvider'; @@ -609,7 +611,6 @@ export const AuthProvider = ({ }; const getProtectedApp = () => { - // Show loader if application is loading or authenticating const childElement = isApplicationLoading || isAuthenticating ? ( @@ -617,15 +618,14 @@ export const AuthProvider = ({ children ); - // Handling for SAML moved to GenericAuthenticator if ( clientType === ClientType.Confidential || authConfig?.provider === AuthProviderEnum.Saml ) { return ( - + {childElement} - + ); } switch (authConfig?.provider) { @@ -633,9 +633,9 @@ export const AuthProvider = ({ case AuthProviderEnum.Basic: { return ( - + {childElement} - + ); } @@ -647,18 +647,18 @@ export const AuthProvider = ({ clientId={authConfig.clientId?.toString() ?? ''} domain={authConfig.authority?.toString() ?? ''} redirectUri={authConfig.callbackUrl?.toString()}> - + {childElement} - + ); } case AuthProviderEnum.Okta: { return ( - + {childElement} - + ); } @@ -666,20 +666,20 @@ export const AuthProvider = ({ case AuthProviderEnum.CustomOidc: case AuthProviderEnum.AwsCognito: { return ( - {childElement} - + ); } case AuthProviderEnum.Azure: { return msalInstance ? ( - + {childElement} - + ) : ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx index 6056176245ad..45c2c245d238 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx @@ -13,11 +13,12 @@ import { Col, Grid, Layout, Row } from 'antd'; import { Content } from 'antd/lib/layout/layout'; import classNames from 'classnames'; -import { ReactNode } from 'react'; -import LoginCarousel from '../../../pages/LoginPage/LoginCarousel'; +import { lazy, ReactNode, Suspense } from 'react'; import DocumentTitle from '../../common/DocumentTitle/DocumentTitle'; import './carousel-layout.less'; +const LoginCarousel = lazy(() => import('../../../pages/LoginPage/LoginCarousel')); + export const CarouselLayout = ({ pageTitle, children, @@ -44,7 +45,9 @@ export const CarouselLayout = ({ 'form-carousel-container', carouselClassName )}> - + }> + + )} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts index e1a6d6992fb7..bdf37576584b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts @@ -10,33 +10,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import dataCollaborationImg from '../assets/img/login-screen/data-collaboration/data-collaboration.png'; -import discoveryImg from '../assets/img/login-screen/discovery/data-discovery.png'; -import governanceImg from '../assets/img/login-screen/governance/governance.png'; -import observabilityImg from '../assets/img/login-screen/observability/data-observability.png'; class LoginClassBase { public getLoginCarouselContent() { const carouselContent = [ { title: 'governance', - image: governanceImg, + imagePath: () => + import( + '../assets/img/login-screen/governance/governance.png' + ).then((m) => m.default), descriptionKey: 'assess-data-reliability-with-data-profiler-lineage', }, { title: 'data-collaboration', - image: dataCollaborationImg, + imagePath: () => + import( + '../assets/img/login-screen/data-collaboration/data-collaboration.png' + ).then((m) => m.default), descriptionKey: 'deeply-understand-table-relations-message', }, { title: 'data-observability', - image: observabilityImg, + imagePath: () => + import( + '../assets/img/login-screen/observability/data-observability.png' + ).then((m) => m.default), descriptionKey: 'discover-your-data-and-unlock-the-value-of-data-assets', }, { title: 'data-discovery', - image: discoveryImg, + imagePath: () => + import( + '../assets/img/login-screen/discovery/data-discovery.png' + ).then((m) => m.default), descriptionKey: 'enables-end-to-end-metadata-management', }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/index.tsx b/openmetadata-ui/src/main/resources/ui/src/index.tsx index 09913d94229d..611c3a989354 100644 --- a/openmetadata-ui/src/main/resources/ui/src/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/index.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import App from './App'; +import AppRoot from './AppRoot'; import './styles/index'; import { getBasePath } from './utils/HistoryUtils'; @@ -25,7 +25,7 @@ const root = createRoot(container); root.render( - + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx index a6a9623918f4..0ef6b11ccbde 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx @@ -13,15 +13,30 @@ import { Carousel, Typography } from 'antd'; import { uniqueId } from 'lodash'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import loginClassBase from '../../constants/LoginClassBase'; const LoginCarousel = () => { const [currentIndex, setCurrentIndex] = useState(0); + const [loadedImages, setLoadedImages] = useState>({}); const carouselContent = loginClassBase.getLoginCarouselContent(); const { t } = useTranslation(); + useEffect(() => { + const loadImage = async (index: number) => { + if (!loadedImages[index] && carouselContent[index]) { + const imageSrc = await carouselContent[index].imagePath(); + setLoadedImages((prev) => ({ ...prev, [index]: imageSrc })); + } + }; + + loadImage(currentIndex); + + const nextIndex = (currentIndex + 1) % carouselContent.length; + loadImage(nextIndex); + }, [currentIndex, carouselContent, loadedImages]); + return ( {

- slider + {loadedImages[idx] && ( + slider + )} ))}
diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index 7e8d17429da6..6a164a29b344 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -186,127 +186,9 @@ export default defineConfig(({ mode }) => { cssMinify: 'esbuild', cssCodeSplit: true, reportCompressedSize: false, - // Each named chunk is now bounded; raise the warning limit only so CI - // doesn't complain about the few legitimately large vendor chunks. - chunkSizeWarningLimit: 5000, + chunkSizeWarningLimit: 1500, rollupOptions: { output: { - /** - * manualChunks object — Rollup resolves each package name and groups - * all its internal modules into the named chunk automatically. - * - * Order within this object does not matter; what matters is which - * chunk a package is assigned to. Keep the comment groups as a guide. - */ - /** - * Function-based manualChunks using an explicit package map. - * This achieves what the object pattern does, but importantly - * allows us to add a catch-all at the end so unlisted node_modules - * are properly split out of the main index.js bundle. - */ - manualChunks: ((packageMap: Record) => (id: string) => { - // Find if this module belongs to any of our explicit vendor chunks - for (const [chunkName, packages] of Object.entries(packageMap)) { - if (packages.some((pkg) => id.includes(`node_modules/${pkg}/`))) { - return chunkName; - } - } - - // --- THE CRITICAL CATCH-ALL --- - // If it's a node_module but wasn't in the list above, group it here - // instead of letting it bloat the main application index.js! - if (id.includes('node_modules/')) { - return 'vendor-misc'; - } - })({ - // ── Login critical path (loaded before auth resolves) ───────────── - 'vendor-react': ['react', 'react-dom', 'scheduler'], - 'vendor-router': ['react-router-dom', 'react-router'], - 'vendor-zustand': ['zustand'], - 'vendor-antd': ['antd', '@ant-design/icons'], - 'vendor-i18n': ['i18next', 'react-i18next'], - - // ── Core UI Components (massive library) ────────────────────────── - 'vendor-core': ['@openmetadata/ui-core-components'], - - // ── MUI / Emotion (authenticated pages only) ────────────────────── - 'vendor-mui': [ - '@mui/material', - '@mui/system', - '@mui/icons-material', - '@mui/x-date-pickers', - '@mui/x-tree-view', - '@emotion/react', - '@emotion/styled', - ], - - // ── Rich-text editors ───────────────────────────────────────────── - 'vendor-editor-tiptap': [ - '@tiptap/react', - '@tiptap/core', - '@tiptap/starter-kit', - '@tiptap/extension-link', - '@tiptap/extension-placeholder', - '@tiptap/extension-table', - '@tiptap/extension-table-cell', - '@tiptap/extension-table-header', - '@tiptap/extension-table-row', - '@tiptap/extension-task-item', - '@tiptap/extension-task-list', - '@tiptap/suggestion', - ], - 'vendor-editor-quill': [ - 'quill', - 'react-quill-new', - '@toast-ui/react-editor', - '@windmillcode/quill-emoji', - 'quill-mention', - 'quilljs-markdown', - ], - 'vendor-codemirror': ['codemirror', 'react-codemirror2'], - - // ── Data Parsers & AST (very heavy) ─────────────────────────────── - 'vendor-antlr': ['antlr4'], - 'vendor-schema': ['@apidevtools/json-schema-ref-parser'], - 'vendor-markdown': ['showdown', 'turndown', 'dompurify', 'html-react-parser'], - - // ── Charts & graphs ─────────────────────────────────────────────── - 'vendor-recharts': ['recharts'], - 'vendor-reactflow': ['reactflow', '@dagrejs/dagre'], - 'vendor-g6': ['@antv/g6', 'elkjs'], - 'vendor-vis': ['vis-network', 'vis-data'], - - // ── Auth SDKs ───────────────────────────────────────────────────── - 'vendor-auth-okta-auth0': [ - '@okta/okta-react', - '@okta/okta-auth-js', - '@auth0/auth0-react', - 'oidc-client', - ], - 'vendor-auth-azure': [ - '@azure/msal-browser', - '@azure/msal-react', - ], - - // ── Forms & Query schemas ───────────────────────────────────────── - 'vendor-query-builder': ['@react-awesome-query-builder/antd'], - 'vendor-rjsf': [ - '@rjsf/core', - '@rjsf/utils', - '@rjsf/validator-ajv8', - 'ajv', - ], - - // ── DnD, tour, sockets ──────────────────────────────────────────── - 'vendor-dnd': ['react-dnd', 'react-dnd-html5-backend'], - 'vendor-tour': ['@deuex-solutions/react-tour'], - 'vendor-socketio': ['socket.io-client'], - - // ── Utilities ───────────────────────────────────────────────────── - 'vendor-lodash': ['lodash'], - 'vendor-rapidoc': ['rapidoc'], - 'vendor-analytics': ['analytics', 'use-analytics'], - }), assetFileNames: (assetInfo) => { const names = assetInfo.names ?? []; const fileName = names.length > 0 ? names[0] : ''; From 9b0ee898b4cfc02aaa71cf5c98d0fe6f229658b1 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 3 Apr 2026 00:31:53 +0530 Subject: [PATCH 07/47] fix theme issues --- .../src/main/resources/ui/src/App.tsx | 1 + .../src/main/resources/ui/src/AppRoot.tsx | 49 ++++++++++++++----- .../ui/src/components/AppRouter/AppRouter.tsx | 40 +++++++-------- .../AppRouter/UnauthenticatedApp.tsx | 25 ---------- 4 files changed, 55 insertions(+), 60 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/App.tsx b/openmetadata-ui/src/main/resources/ui/src/App.tsx index 7b62459cd8e6..dcc0639ae44d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/App.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/App.tsx @@ -21,6 +21,7 @@ import { getCustomUiThemePreference, getSystemConfig, } from './rest/settingConfigAPI'; + import { getThemeConfig } from './utils/ThemeUtils'; const App: FC = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index 9158b6e25043..0f2d7fa94770 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -11,12 +11,18 @@ * limitations under the License. */ -import { FC, lazy, Suspense, useEffect } from 'react'; +import { GlobalStyles, ThemeProvider } from '@mui/material'; +import { createMuiTheme } from '@openmetadata/ui-core-components'; +import { FC, lazy, Suspense, useEffect, useMemo } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; +import { useShallow } from 'zustand/react/shallow'; import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; import Loader from './components/common/Loader/Loader'; +import { DEFAULT_THEME } from './constants/Appearance.constants'; +import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; +import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider'; import { useApplicationStore } from './hooks/useApplicationStore'; import { getBasePath } from './utils/HistoryUtils'; import i18n from './utils/i18next/LocalUtil'; @@ -30,20 +36,39 @@ const AppRoot: FC = () => { initializeAuthState(); }, [initializeAuthState]); + const { applicationConfig } = useApplicationStore( + useShallow((state) => ({ + applicationConfig: state.applicationConfig, + })) + ); + + const muiTheme = useMemo( + () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), + [applicationConfig?.customTheme] + ); + return (
- - - - - }> - - - - - - + + + + + + + + + }> + + + + + + + + +
); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index c0f21e4f2543..bbbec77609b1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -30,10 +30,6 @@ const AuthenticatedApp = withSuspenseFallback( lazy(() => import('./AuthenticatedApp')) ); -const UnauthenticatedApp = withSuspenseFallback( - lazy(() => import('./UnauthenticatedApp')) -); - const AppContainer = withSuspenseFallback( lazy(() => import('../AppContainer/AppContainer')) ); @@ -172,25 +168,23 @@ const AppRouter = () => { } return ( - - - } path={ROUTES.NOT_FOUND} /> - } path={ROUTES.LOGOUT} /> - } path={ROUTES.UNAUTHORISED} /> - - ) : ( - - ) - } - path={ROUTES.SIGNUP} - /> - } path={ROUTES.AUTH_CALLBACK} /> - } path="*" /> - - + + } path={ROUTES.NOT_FOUND} /> + } path={ROUTES.LOGOUT} /> + } path={ROUTES.UNAUTHORISED} /> + + ) : ( + + ) + } + path={ROUTES.SIGNUP} + /> + } path={ROUTES.AUTH_CALLBACK} /> + } path="*" /> + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx deleted file mode 100644 index 64a0cc88b205..000000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnauthenticatedApp.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2022 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 { FC } from 'react'; -import AntDConfigProvider from '../../context/AntDConfigProvider/AntDConfigProvider'; - -interface UnauthenticatedAppProps { - children: React.ReactNode; -} - -const UnauthenticatedApp: FC = ({ children }) => { - return {children}; -}; - -export default UnauthenticatedApp; From df8ba10ad5b5984aa68bf632cf7f2bd3ea76a38b Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:02:38 +0530 Subject: [PATCH 08/47] fix packaging --- .../src/main/resources/ui/src/AppRoot.tsx | 40 ++----- .../components/AppContainer/AppContainer.tsx | 30 +++++ .../ui/src/components/AppRouter/AppRouter.tsx | 85 +++----------- .../AppRouter/UnAuthenticatedAppRouter.tsx | 46 +++++--- .../Auth/AuthProviders/AuthProvider.tsx | 37 +++--- .../Auth/AuthProviders/BasicAuthProvider.tsx | 5 +- .../LazyAuthProviderWrappers.tsx | 103 +++++++++++++++++ .../resources/ui/src/constants/constants.ts | 10 +- .../ui/src/constants/router.constants.ts | 50 +++++++++ .../resources/ui/src/hoc/withDomainFilter.tsx | 105 ++++++++++++++++++ .../src/main/resources/ui/src/rest/miscAPI.ts | 54 ++++++++- .../src/utils/ApplicationRoutesClassBase.ts | 26 ++--- .../resources/ui/src/utils/CommonUtils.tsx | 13 --- .../resources/ui/src/utils/DomainUtils.tsx | 105 +----------------- .../resources/ui/src/utils/RouterUtils.ts | 4 - .../resources/ui/src/utils/SearchUtils.tsx | 49 -------- .../resources/ui/src/utils/TableUtils.tsx | 15 ++- .../resources/ui/src/utils/UserDataUtils.ts | 23 +++- .../ui/src/utils/WebAnalyticsUtils.ts | 2 +- .../src/main/resources/ui/vite.config.ts | 17 ++- 20 files changed, 476 insertions(+), 343 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index 0f2d7fa94770..253561c0c3e0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -11,24 +11,17 @@ * limitations under the License. */ -import { GlobalStyles, ThemeProvider } from '@mui/material'; -import { createMuiTheme } from '@openmetadata/ui-core-components'; -import { FC, lazy, Suspense, useEffect, useMemo } from 'react'; +import { FC, useEffect } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; -import { useShallow } from 'zustand/react/shallow'; +import App from './App'; import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; -import Loader from './components/common/Loader/Loader'; -import { DEFAULT_THEME } from './constants/Appearance.constants'; import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; -import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider'; import { useApplicationStore } from './hooks/useApplicationStore'; import { getBasePath } from './utils/HistoryUtils'; import i18n from './utils/i18next/LocalUtil'; -const App = lazy(() => import('./App')); - const AppRoot: FC = () => { const { initializeAuthState } = useApplicationStore(); @@ -36,36 +29,17 @@ const AppRoot: FC = () => { initializeAuthState(); }, [initializeAuthState]); - const { applicationConfig } = useApplicationStore( - useShallow((state) => ({ - applicationConfig: state.applicationConfig, - })) - ); - - const muiTheme = useMemo( - () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), - [applicationConfig?.customTheme] - ); - return (
- - - - - - }> - - - - - - + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 34de2e40c0e3..143f63fb7a4b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -12,12 +12,16 @@ */ import { Layout } from 'antd'; import classNames from 'classnames'; +import { isNil } from 'lodash'; import { useCallback, useEffect } from 'react'; +import { useAnalytics } from 'use-analytics'; import { useLimitStore } from '../../context/LimitsProvider/useLimitsStore'; +import { CustomEventTypes } from '../../generated/analytics/webAnalyticEventData'; import { LineageSettings } from '../../generated/configuration/lineageSettings'; import { SettingType } from '../../generated/settings/settings'; import { useCurrentUserPreferences } from '../../hooks/currentUserStore/useCurrentUserStore'; import { useApplicationStore } from '../../hooks/useApplicationStore'; +import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; import { getLimitConfig } from '../../rest/limitsAPI'; import { getSettingsByType } from '../../rest/settingConfigAPI'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; @@ -31,6 +35,8 @@ import './app-container.less'; const { Content } = Layout; const AppContainer = () => { + const location = useCustomLocation(); + const analytics = useAnalytics(); const { currentUser, setAppPreferences, appPreferences } = useApplicationStore(); const { @@ -71,6 +77,30 @@ const AppContainer = () => { } }, [language]); + useEffect(() => { + const { pathname } = location; + + if (pathname !== '/' && !isNil(analytics)) { + analytics.page(); + } + }, [location.pathname, analytics]); + + useEffect(() => { + const handleClickEvent = (event: MouseEvent) => { + const eventValue = + (event.target as HTMLElement)?.textContent || CustomEventTypes.Click; + + if (eventValue && !isNil(analytics)) { + analytics.track(eventValue); + } + }; + + const targetNode = document.body; + targetNode.addEventListener('click', handleClickEvent); + + return () => targetNode.removeEventListener('click', handleClickEvent); + }, [analytics]); + return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index bbbec77609b1..feea5b4e6df2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -11,19 +11,14 @@ * limitations under the License. */ -import { isEmpty, isNil } from 'lodash'; -import { lazy, useCallback, useEffect } from 'react'; +import { isEmpty } from 'lodash'; +import { lazy } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; -import { useAnalytics } from 'use-analytics'; import { useShallow } from 'zustand/react/shallow'; -import { ROUTES } from '../../constants/constants'; -import { CustomEventTypes } from '../../generated/analytics/webAnalyticEventData'; +import { APP_ROUTER_ROUTES } from '../../constants/router.constants'; import { useApplicationStore } from '../../hooks/useApplicationStore'; -import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; import Loader from '../common/Loader/Loader'; -import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; -import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; import withSuspenseFallback from './withSuspenseFallback'; const AuthenticatedApp = withSuspenseFallback( @@ -60,11 +55,9 @@ const SignUpPage = withSuspenseFallback( ); const AppRouter = () => { - const location = useCustomLocation(); const UnAuthenticatedAppRouter = applicationRoutesClass.getUnAuthenticatedRouteElements(); - const analytics = useAnalytics(); const { currentUser, isAuthenticated, @@ -78,43 +71,6 @@ const AppRouter = () => { isAuthenticating: state.isAuthenticating, })) ); - const { plugins = [] } = useApplicationsProvider(); - - useEffect(() => { - const { pathname } = location; - - /** - * Ignore the slash path because we are treating my data as - * default path. - * And check if analytics instance is available - */ - if (pathname !== '/' && !isNil(analytics)) { - // track page view on route change - analytics.page(); - } - }, [location.pathname]); - - const handleClickEvent = useCallback( - (event: MouseEvent) => { - const eventValue = - (event.target as HTMLElement)?.textContent || CustomEventTypes.Click; - /** - * Ignore the click event if the event value is undefined - * And analytics instance is not available - */ - if (eventValue && !isNil(analytics)) { - analytics.track(eventValue); - } - }, - [analytics] - ); - - useEffect(() => { - const targetNode = document.body; - targetNode.addEventListener('click', handleClickEvent); - - return () => targetNode.removeEventListener('click', handleClickEvent); - }, [handleClickEvent]); /** * isApplicationLoading is true when the application is loading in AuthProvider @@ -132,34 +88,23 @@ const AppRouter = () => { return ( - } path={ROUTES.NOT_FOUND} /> - } path={ROUTES.LOGOUT} /> + } path={APP_ROUTER_ROUTES.NOT_FOUND} /> + } path={APP_ROUTER_ROUTES.LOGOUT} /> } - path={ROUTES.UNAUTHORISED} + path={APP_ROUTER_ROUTES.UNAUTHORISED} /> ) : ( - + ) } - path={ROUTES.SIGNUP} + path={APP_ROUTER_ROUTES.SIGNUP} /> - } path={ROUTES.AUTH_CALLBACK} /> - - {plugins?.flatMap((plugin) => { - const routes = plugin.getRoutes?.() || []; - const appRoutes = routes.filter( - (route) => route.position === RoutePosition.APP - ); - - return appRoutes.map((route, idx) => ( - - )); - })} + } path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> } path="*" /> @@ -169,20 +114,20 @@ const AppRouter = () => { return ( - } path={ROUTES.NOT_FOUND} /> - } path={ROUTES.LOGOUT} /> - } path={ROUTES.UNAUTHORISED} /> + } path={APP_ROUTER_ROUTES.NOT_FOUND} /> + } path={APP_ROUTER_ROUTES.LOGOUT} /> + } path={APP_ROUTER_ROUTES.UNAUTHORISED} /> ) : ( - + ) } - path={ROUTES.SIGNUP} + path={APP_ROUTER_ROUTES.SIGNUP} /> - } path={ROUTES.AUTH_CALLBACK} /> + } path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> } path="*" /> ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx index ddefb8d07445..1dc50b4ff6d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/UnAuthenticatedAppRouter.tsx @@ -10,11 +10,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoginCallback } from '@okta/okta-react'; + import { lazy, useMemo } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; -import { ROUTES } from '../../constants/constants'; +import { APP_ROUTER_ROUTES } from '../../constants/router.constants'; import { AuthProvider } from '../../generated/configuration/authenticationConfiguration'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; @@ -42,13 +42,21 @@ const PageNotFound = withSuspenseFallback( ); const AccountActivationConfirmation = withSuspenseFallback( - lazy(() => import('../../pages/SignUp/account-activation-confirmation.component')) + lazy( + () => import('../../pages/SignUp/account-activation-confirmation.component') + ) ); const Auth0Callback = withSuspenseFallback( lazy(() => import('../Auth/AppCallbacks/Auth0Callback/Auth0Callback')) ); +const LoginCallback = withSuspenseFallback( + lazy(() => + import('@okta/okta-react').then((m) => ({ default: m.LoginCallback })) + ) +); + export const UnAuthenticatedAppRouter = () => { const location = useCustomLocation(); const { authConfig, isSigningUp } = useApplicationStore( @@ -78,31 +86,43 @@ export const UnAuthenticatedAppRouter = () => { }, [authConfig?.provider]); if (applicationRoutesClass.isProtectedRoute(location.pathname)) { - return ; + return ; } return ( - } path={ROUTES.SIGNIN} /> + } path={APP_ROUTER_ROUTES.SIGNIN} /> {CallbackComponent && ( - } path={ROUTES.CALLBACK} /> + } + path={APP_ROUTER_ROUTES.CALLBACK} + /> )} {!isSigningUp && ( } - path={ROUTES.HOME} + element={} + path={APP_ROUTER_ROUTES.HOME} /> )} {/* keep this route before any conditional JSX.Element rendering */} - } path={ROUTES.NOT_FOUND} /> + } path={APP_ROUTER_ROUTES.NOT_FOUND} /> {isBasicAuthProvider && ( <> - } path={ROUTES.REGISTER} /> - } path={ROUTES.FORGOT_PASSWORD} /> - } path={ROUTES.RESET_PASSWORD} /> + } + path={APP_ROUTER_ROUTES.REGISTER} + /> + } + path={APP_ROUTER_ROUTES.FORGOT_PASSWORD} + /> + } + path={APP_ROUTER_ROUTES.RESET_PASSWORD} + /> } - path={ROUTES.ACCOUNT_ACTIVATION} + path={APP_ROUTER_ROUTES.ACCOUNT_ACTIVATION} /> )} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index 5cace9a90639..cd1124011645 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -12,13 +12,11 @@ */ import { removeSession } from '@analytics/session-utils'; -import { Auth0Provider } from '@auth0/auth0-react'; import { Configuration, - IPublicClientApplication, + type IPublicClientApplication, PublicClientApplication, } from '@azure/msal-browser'; -import { MsalProvider } from '@azure/msal-react'; import { AxiosError, AxiosRequestHeaders, @@ -41,7 +39,10 @@ import { import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; -import { REDIRECT_PATHNAME, ROUTES } from '../../../constants/constants'; +import { + REDIRECT_PATHNAME, + APP_ROUTER_ROUTES as ROUTES, +} from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; import { @@ -50,6 +51,7 @@ import { } from '../../../generated/configuration/authenticationConfiguration'; import { User } from '../../../generated/entity/teams/user'; import { AuthProvider as AuthProviderEnum } from '../../../generated/settings/settings'; +import { withDomainFilter } from '../../../hoc/withDomainFilter'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import axiosClient from '../../../rest'; @@ -68,7 +70,6 @@ import { prepareUserProfileFromClaims, validateAuthFields, } from '../../../utils/AuthProvider.util'; -import { withDomainFilter } from '../../../utils/DomainUtils'; import { clearOidcToken, getOidcToken, @@ -87,8 +88,12 @@ import { LazyOktaAuthenticator, } from '../AppAuthenticators/LazyAuthenticators'; import { AuthenticatorRef, OidcUser } from './AuthProvider.interface'; -import BasicAuthProvider from './BasicAuthProvider'; -import OktaAuthProvider from './OktaAuthProvider'; +import { + LazyAuth0ProviderWrapper, + LazyBasicAuthProviderWrapper, + LazyMsalProviderWrapper, + LazyOktaAuthProviderWrapper, +} from './LazyAuthProviderWrappers'; interface AuthProviderProps { childComponentType: ComponentType; @@ -632,34 +637,32 @@ export const AuthProvider = ({ case AuthProviderEnum.LDAP: case AuthProviderEnum.Basic: { return ( - + {childElement} - + ); } case AuthProviderEnum.Auth0: { return ( - {childElement} - + ); } case AuthProviderEnum.Okta: { return ( - + {childElement} - + ); } case AuthProviderEnum.Google: @@ -676,11 +679,11 @@ export const AuthProvider = ({ } case AuthProviderEnum.Azure: { return msalInstance ? ( - + {childElement} - + ) : ( ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx index f63de85268d3..b753b30483a5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx @@ -19,7 +19,7 @@ import { HTTP_STATUS_CODE, LOGIN_FAILED_ERROR, } from '../../../constants/Auth.constants'; -import { ROUTES } from '../../../constants/constants'; +import { APP_ROUTER_ROUTES as ROUTES } from '../../../constants/router.constants'; import { PasswordResetRequest } from '../../../generated/auth/passwordResetRequest'; import { RegistrationRequest } from '../../../generated/auth/registrationRequest'; import { @@ -29,7 +29,6 @@ import { logoutUser, resetPassword, } from '../../../rest/auth-API'; -import { getBase64EncodedString } from '../../../utils/CommonUtils'; import { showErrorToast, showInfoToast, @@ -89,7 +88,7 @@ const BasicAuthProvider = ({ children }: BasicAuthProps) => { try { const response = await basicAuthSignIn({ email, - password: getBase64EncodedString(password), + password: btoa(password), }); if (response.accessToken) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx new file mode 100644 index 000000000000..776bdb0b915a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 2025 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 { IPublicClientApplication } from '@azure/msal-browser'; +import { lazy, ReactNode, Suspense } from 'react'; +import Loader from '../../common/Loader/Loader'; + +const Auth0ProviderComponent = lazy(() => + import('@auth0/auth0-react').then((m) => ({ default: m.Auth0Provider })) +); + +const MsalProviderComponent = lazy(() => + import('@azure/msal-react').then((m) => ({ default: m.MsalProvider })) +); + +const OktaAuthProviderComponent = lazy(() => + import('./OktaAuthProvider').then((m) => ({ default: m.OktaAuthProvider })) +); + +const BasicAuthProviderComponent = lazy(() => import('./BasicAuthProvider')); + +interface Auth0ProviderWrapperProps { + clientId: string; + domain: string; + redirectUri: string; + children: ReactNode; +} + +export const LazyAuth0ProviderWrapper = ({ + clientId, + domain, + redirectUri, + children, +}: Auth0ProviderWrapperProps) => { + return ( + }> + + {children} + + + ); +}; + +interface MsalProviderWrapperProps { + instance: IPublicClientApplication; + children: ReactNode; +} + +export const LazyMsalProviderWrapper = ({ + instance, + children, +}: MsalProviderWrapperProps) => { + return ( + }> + + {children} + + + ); +}; + +interface OktaAuthProviderWrapperProps { + children: ReactNode; +} + +export const LazyOktaAuthProviderWrapper = ({ + children, +}: OktaAuthProviderWrapperProps) => { + return ( + }> + {children} + + ); +}; + +interface BasicAuthProviderWrapperProps { + children: ReactNode; +} + +export const LazyBasicAuthProviderWrapper = ({ + children, +}: BasicAuthProviderWrapperProps) => { + return ( + }> + {children} + + ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index cbe68c8a3080..42c02c03c056 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -85,15 +85,7 @@ export const REDIRECT_PATHNAME = 'redirectUrlPath'; export const TERM_ADMIN = 'Admin'; export const TERM_USER = 'User'; export const DISABLED = 'disabled'; -export const imageTypes = { - image: 's96-c', - image192: 's192-c', - image24: 's24-c', - image32: 's32-c', - image48: 's48-c', - image512: 's512-c', - image72: 's72-c', -}; + export const NO_DATA_PLACEHOLDER = '--'; export const PIPE_SYMBOL = '|'; export const NO_DATA = '-'; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts new file mode 100644 index 000000000000..8205928d4951 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2022 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. + */ + +export const APP_ROUTER_ROUTES = { + HOME: '/', + MY_DATA: '/my-data', + NOT_FOUND: '/404', + LOGOUT: '/logout', + UNAUTHORISED: '/unauthorised', + SIGNUP: '/signup', + AUTH_CALLBACK: '/auth-callback', + SIGNIN: '/signin', + FORGOT_PASSWORD: '/forgot-password', + CALLBACK: '/callback', + SILENT_CALLBACK: '/silent-callback', + REGISTER: '/register', + RESET_PASSWORD: '/reset-password', + ACCOUNT_ACTIVATION: '/account-activation', +} as const; + +const UNPROTECTED_ROUTES = [ + APP_ROUTER_ROUTES.SIGNUP, + APP_ROUTER_ROUTES.SIGNIN, + APP_ROUTER_ROUTES.FORGOT_PASSWORD, + APP_ROUTER_ROUTES.CALLBACK, + APP_ROUTER_ROUTES.SILENT_CALLBACK, + APP_ROUTER_ROUTES.REGISTER, + APP_ROUTER_ROUTES.RESET_PASSWORD, + APP_ROUTER_ROUTES.ACCOUNT_ACTIVATION, + APP_ROUTER_ROUTES.HOME, + APP_ROUTER_ROUTES.AUTH_CALLBACK, + APP_ROUTER_ROUTES.NOT_FOUND, + APP_ROUTER_ROUTES.LOGOUT, +]; + +export const isProtectedRoute = (pathname: string): boolean => { + return UNPROTECTED_ROUTES.indexOf(pathname) === -1; +}; + +export const REDIRECT_PATHNAME = 'redirectUrlPath'; diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx new file mode 100644 index 000000000000..db6396e47482 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx @@ -0,0 +1,105 @@ +import { InternalAxiosRequestConfig } from 'axios'; +import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; +import { SearchIndex } from '../enums/search.enum'; +import { useDomainStore } from '../hooks/useDomainStore'; +import { + QueryFieldInterface, + QueryFilterInterface, +} from '../pages/ExplorePage/ExplorePage.interface'; +import { getBasePath } from '../utils/HistoryUtils'; + +export const getPathNameFromWindowLocation = () => { + return window.location.pathname.replace(getBasePath() ?? '', ''); +}; + +export const withDomainFilter = ( + config: InternalAxiosRequestConfig +): InternalAxiosRequestConfig => { + const isGetRequest = config.method === 'get'; + const activeDomain = useDomainStore.getState().activeDomain; + const hasActiveDomain = activeDomain !== DEFAULT_DOMAIN_VALUE; + const currentPath = getPathNameFromWindowLocation(); + const shouldNotIntercept = [ + '/domain', + '/auth/logout', + '/auth/refresh', + ].reduce((prev, curr) => { + return prev || currentPath.startsWith(curr); + }, false); + + if (shouldNotIntercept) { + return config; + } + + if (isGetRequest && hasActiveDomain) { + if (config.url?.includes('/search/query')) { + if (config.params?.index === SearchIndex.TAG) { + return config; + } + + const domainFilterField = + config.params?.index === SearchIndex.DOMAIN + ? 'fullyQualifiedName' + : 'domains.fullyQualifiedName'; + let filter: QueryFilterInterface = { query: { bool: {} } }; + if (config.params?.query_filter) { + try { + const parsed = JSON.parse(config.params.query_filter as string); + filter = parsed?.query ? parsed : { query: { bool: {} } }; + } catch { + filter = { query: { bool: {} } }; + } + } + + let mustArray: QueryFieldInterface[] = []; + const existingMust = filter.query?.bool?.must; + if (Array.isArray(existingMust)) { + mustArray = [...existingMust]; + } else if (existingMust) { + mustArray = [existingMust]; + } + + const { bool: existingBool, ...nonBoolClauses } = filter.query ?? {}; + for (const [key, value] of Object.entries(nonBoolClauses)) { + mustArray.push({ [key]: value } as QueryFieldInterface); + } + + filter.query = { + bool: { + ...existingBool, + must: [ + ...mustArray, + { + bool: { + should: [ + { + term: { + [domainFilterField]: activeDomain, + }, + }, + { + prefix: { + [domainFilterField]: `${activeDomain}.`, + }, + }, + ], + }, + } as QueryFieldInterface, + ], + }, + }; + + config.params = { + ...config.params, + query_filter: JSON.stringify(filter), + }; + } else { + config.params = { + ...config.params, + domain: activeDomain, + }; + } + } + + return config; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/miscAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/miscAPI.ts index 27faf7d1d549..97d29aef00f1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/miscAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/miscAPI.ts @@ -12,8 +12,10 @@ */ import { AxiosResponse } from 'axios'; +import { isEmpty } from 'lodash'; import { Edge } from '../components/Entity/EntityLineage/EntityLineage.interface'; import { ExploreSearchIndex } from '../components/Explore/ExplorePage.interface'; +import { WILD_CARD_CHAR } from '../constants/char.constants'; import { PAGE_SIZE } from '../constants/constants'; import { AsyncDeleteJob } from '../context/AsyncDeleteProvider/AsyncDeleteProvider.interface'; import { SearchIndex } from '../enums/search.enum'; @@ -23,10 +25,58 @@ import { SearchRequest } from '../generated/search/searchRequest'; import { ValidationResponse } from '../generated/system/validationResponse'; import { Paging } from '../generated/type/paging'; import { SearchResponse } from '../interface/search.interface'; -import { getSearchAPIQueryParams } from '../utils/SearchUtils'; -import { escapeESReservedCharacters } from '../utils/StringsUtils'; +import { + escapeESReservedCharacters, + getEncodedFqn, +} from '../utils/StringsUtils'; import APIClient from './index'; +export const getSearchAPIQueryParams = ( + queryString: string, + from: number, + size: number, + filters: string, + sortField: string, + sortOrder: string, + searchIndex: SearchIndex | SearchIndex[], + onlyDeleted = false, + trackTotalHits = false, + wildcard = true +): Record => { + const start = (from - 1) * size; + + const encodedQueryString = queryString + ? getEncodedFqn(escapeESReservedCharacters(queryString)) + : ''; + + const query = + wildcard && encodedQueryString !== WILD_CARD_CHAR + ? `*${encodedQueryString}*` + : encodedQueryString; + + const params: Record = { + q: query + (filters ? ` AND ${filters}` : ''), + from: start, + size, + index: searchIndex, + deleted: onlyDeleted, + }; + + if (!isEmpty(sortField)) { + params.sort_field = sortField; + } + + if (!isEmpty(sortOrder)) { + params.sort_order = sortOrder; + } + + if (trackTotalHits) { + params.track_total_hits = trackTotalHits; + } + + return params; +}; + export const searchData = ( queryString: string, from: number, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts index 1c22cea5d864..7b06796fde80 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts @@ -11,10 +11,13 @@ * limitations under the License. */ -import { FC } from 'react'; -import AuthenticatedAppRouter from '../components/AppRouter/AuthenticatedAppRouter'; +import { FC, lazy } from 'react'; import { UnAuthenticatedAppRouter } from '../components/AppRouter/UnAuthenticatedAppRouter'; -import { ROUTES } from '../constants/constants'; +import { isProtectedRoute } from '../constants/router.constants'; + +const AuthenticatedAppRouter = lazy( + () => import('../components/AppRouter/AuthenticatedAppRouter') +); class ApplicationRoutesClassBase { public getRouteElements(): FC { @@ -26,22 +29,7 @@ class ApplicationRoutesClassBase { } public isProtectedRoute(pathname: string): boolean { - return ( - [ - ROUTES.SIGNUP, - ROUTES.SIGNIN, - ROUTES.FORGOT_PASSWORD, - ROUTES.CALLBACK, - ROUTES.SILENT_CALLBACK, - ROUTES.REGISTER, - ROUTES.RESET_PASSWORD, - ROUTES.ACCOUNT_ACTIVATION, - ROUTES.HOME, - ROUTES.AUTH_CALLBACK, - ROUTES.NOT_FOUND, - ROUTES.LOGOUT, - ].indexOf(pathname) === -1 - ); + return isProtectedRoute(pathname); } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index a1031c9711e3..aea46d5471b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -37,7 +37,6 @@ import { ReactNode } from 'react'; import { Trans } from 'react-i18next'; import Loader from '../components/common/Loader/Loader'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; -import { imageTypes } from '../constants/constants'; import { BASE_COLORS } from '../constants/DataInsight.constants'; import { FEED_COUNT_INITIAL_DATA } from '../constants/entity.constants'; import { VALIDATE_ESCAPE_START_END_REGEX } from '../constants/regex.constants'; @@ -344,18 +343,6 @@ export const requiredField = (label: string, excludeSpace = false) => ( ); -export const getImages = (imageUri: string) => { - const imagesObj: typeof imageTypes = imageTypes; - for (const type in imageTypes) { - imagesObj[type as keyof typeof imageTypes] = imageUri.replace( - 's96-c', - imageTypes[type as keyof typeof imageTypes] - ); - } - - return imagesObj; -}; - export const getServiceLogo = ( serviceType: string, className = '' diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx index 8ffe14e52229..0a01d3ef150e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx @@ -12,8 +12,7 @@ */ import { Tooltip, TooltipTrigger } from '@openmetadata/ui-core-components'; import { InfoCircle } from '@untitledui/icons'; -import { Divider, Space, Tooltip as AntDTooltip, Typography } from 'antd'; -import { InternalAxiosRequestConfig } from 'axios'; +import { Tooltip as AntDTooltip, Divider, Space, Typography } from 'antd'; import classNames from 'classnames'; import { get, isEmpty, isUndefined, noop } from 'lodash'; import { Fragment, ReactNode } from 'react'; @@ -36,20 +35,14 @@ import SubDomainsTable from '../components/Domain/SubDomainsTable/SubDomainsTabl import EntitySummaryPanel from '../components/Explore/EntitySummaryPanel/EntitySummaryPanel.component'; import AssetsTabs from '../components/Glossary/GlossaryTerms/tabs/AssetsTabs.component'; import { AssetsOfEntity } from '../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; -import { - DEFAULT_DOMAIN_VALUE, - DE_ACTIVE_COLOR, - NO_DATA_PLACEHOLDER, -} from '../constants/constants'; +import { DE_ACTIVE_COLOR, NO_DATA_PLACEHOLDER } from '../constants/constants'; import { DOMAIN_TYPE_DATA } from '../constants/Domain.constants'; import { DetailPageWidgetKeys } from '../enums/CustomizeDetailPage.enum'; import { EntityTabs, EntityType } from '../enums/entity.enum'; -import { SearchIndex } from '../enums/search.enum'; import { Domain } from '../generated/entity/domains/domain'; import { Operation } from '../generated/entity/policies/policy'; import { EntityReference } from '../generated/entity/type'; import { PageType } from '../generated/system/ui/page'; -import { useDomainStore } from '../hooks/useDomainStore'; import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface'; import { QueryFieldInterface, @@ -64,99 +57,7 @@ import { getPrioritizedEditPermission, getPrioritizedViewPermission, } from './PermissionsUtils'; -import { getDomainPath, getPathNameFromWindowLocation } from './RouterUtils'; - -export const withDomainFilter = ( - config: InternalAxiosRequestConfig -): InternalAxiosRequestConfig => { - const isGetRequest = config.method === 'get'; - const activeDomain = useDomainStore.getState().activeDomain; - const hasActiveDomain = activeDomain !== DEFAULT_DOMAIN_VALUE; - const currentPath = getPathNameFromWindowLocation(); - const shouldNotIntercept = [ - '/domain', - '/auth/logout', - '/auth/refresh', - ].reduce((prev, curr) => { - return prev || currentPath.startsWith(curr); - }, false); - - if (shouldNotIntercept) { - return config; - } - - if (isGetRequest && hasActiveDomain) { - if (config.url?.includes('/search/query')) { - if (config.params?.index === SearchIndex.TAG) { - return config; - } - - const domainFilterField = - config.params?.index === SearchIndex.DOMAIN - ? 'fullyQualifiedName' - : 'domains.fullyQualifiedName'; - let filter: QueryFilterInterface = { query: { bool: {} } }; - if (config.params?.query_filter) { - try { - const parsed = JSON.parse(config.params.query_filter as string); - filter = parsed?.query ? parsed : { query: { bool: {} } }; - } catch { - filter = { query: { bool: {} } }; - } - } - - let mustArray: QueryFieldInterface[] = []; - const existingMust = filter.query?.bool?.must; - if (Array.isArray(existingMust)) { - mustArray = [...existingMust]; - } else if (existingMust) { - mustArray = [existingMust]; - } - - const { bool: existingBool, ...nonBoolClauses } = filter.query ?? {}; - for (const [key, value] of Object.entries(nonBoolClauses)) { - mustArray.push({ [key]: value } as QueryFieldInterface); - } - - filter.query = { - bool: { - ...existingBool, - must: [ - ...mustArray, - { - bool: { - should: [ - { - term: { - [domainFilterField]: activeDomain, - }, - }, - { - prefix: { - [domainFilterField]: `${activeDomain}.`, - }, - }, - ], - }, - } as QueryFieldInterface, - ], - }, - }; - - config.params = { - ...config.params, - query_filter: JSON.stringify(filter), - }; - } else { - config.params = { - ...config.params, - domain: activeDomain, - }; - } - } - - return config; -}; +import { getDomainPath } from './RouterUtils'; export const getOwner = ( hasPermission: boolean, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts index 9d10f4fdd70f..53f311da6255 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts @@ -50,7 +50,6 @@ import { PipelineType } from '../generated/api/services/ingestionPipelines/creat import { DataQualityPageTabs } from '../pages/DataQuality/DataQualityPage.interface'; import { TestCasePageTabs } from '../pages/IncidentManager/IncidentManager.interface'; import { getPartialNameFromFQN } from './CommonUtils'; -import { getBasePath } from './HistoryUtils'; import { getServiceRouteFromServiceType } from './ServiceUtils'; import { getEncodedFqn } from './StringsUtils'; @@ -687,9 +686,6 @@ export const getNotificationAlertDetailsPath = (fqn: string, tab?: string) => { return path; }; -export const getPathNameFromWindowLocation = () => { - return window.location.pathname.replace(getBasePath() ?? '', ''); -}; export const getTagsDetailsPath = (entityFQN: string) => { let path = ROUTES.TAG_DETAILS; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx index a19fe3045ecd..a1b8747edf65 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx @@ -14,7 +14,6 @@ import { SearchOutlined } from '@ant-design/icons'; import { Button, Typography } from 'antd'; import i18next from 'i18next'; -import { isEmpty } from 'lodash'; import { Bucket } from 'Models'; import { Link } from 'react-router-dom'; import { ReactComponent as GlossaryTermIcon } from '../assets/svg/book.svg'; @@ -33,7 +32,6 @@ import { ReactComponent as IconMlModal } from '../assets/svg/mlmodal.svg'; import { ReactComponent as IconPipeline } from '../assets/svg/pipeline-grey.svg'; import { ReactComponent as IconTag } from '../assets/svg/tag-grey.svg'; import { ReactComponent as IconTopic } from '../assets/svg/topic-grey.svg'; -import { WILD_CARD_CHAR } from '../constants/char.constants'; import { Option, SearchSuggestions, @@ -45,53 +43,6 @@ import { getPartialNameFromTableFQN } from './CommonUtils'; import { ElasticsearchQuery } from './QueryBuilderUtils'; import searchClassBase from './SearchClassBase'; import serviceUtilClassBase from './ServiceUtilClassBase'; -import { escapeESReservedCharacters, getEncodedFqn } from './StringsUtils'; - -export const getSearchAPIQueryParams = ( - queryString: string, - from: number, - size: number, - filters: string, - sortField: string, - sortOrder: string, - searchIndex: SearchIndex | SearchIndex[], - onlyDeleted = false, - trackTotalHits = false, - wildcard = true -): Record => { - const start = (from - 1) * size; - - const encodedQueryString = queryString - ? getEncodedFqn(escapeESReservedCharacters(queryString)) - : ''; - - const query = - wildcard && encodedQueryString !== WILD_CARD_CHAR - ? `*${encodedQueryString}*` - : encodedQueryString; - - const params: Record = { - q: query + (filters ? ` AND ${filters}` : ''), - from: start, - size, - index: searchIndex, - deleted: onlyDeleted, - }; - - if (!isEmpty(sortField)) { - params.sort_field = sortField; - } - - if (!isEmpty(sortOrder)) { - params.sort_order = sortOrder; - } - - if (trackTotalHits) { - params.track_total_hits = trackTotalHits; - } - - return params; -}; // will add back slash "\" before quote in string if present export const getQueryWithSlash = (query: string): string => diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 7826f6a24841..ee5218aa1a50 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -135,10 +135,13 @@ import TabsLabel from '../components/common/TabsLabel/TabsLabel.component'; import { TabProps } from '../components/common/TabsLabel/TabsLabel.interface'; import { GenericTab } from '../components/Customization/GenericTab/GenericTab'; import { CommonWidgets } from '../components/DataAssets/CommonWidgets/CommonWidgets'; -import DataObservabilityTab from '../components/Database/Profiler/DataObservability/DataObservabilityTab'; import SampleDataTableComponent from '../components/Database/SampleDataTable/SampleDataTable.component'; import SchemaTable from '../components/Database/SchemaTable/SchemaTable.component'; import TableQueries from '../components/Database/TableQueries/TableQueries'; + +const DataObservabilityTab = lazy( + () => import('../components/Database/Profiler/DataObservability/DataObservabilityTab') +); import { ContractTab } from '../components/DataContract/ContractTab/ContractTab'; import { useEntityExportModalProvider } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import KnowledgeGraph from '../components/KnowledgeGraph/KnowledgeGraph'; @@ -941,10 +944,12 @@ export const getTableDetailPageBaseTabs = ({ ), key: EntityTabs.PROFILER, children: ( - + }> + + ), }, { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts index 543adbeaf305..b97a632a7573 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts @@ -17,7 +17,6 @@ import { get, isEqual } from 'lodash'; import { OidcUser } from '../components/Auth/AuthProviders/AuthProvider.interface'; import { updateUserDetail } from '../rest/userAPI'; import { User } from './../generated/entity/teams/user'; -import { getImages } from './CommonUtils'; import i18n from './i18next/LocalUtil'; import { getImageWithResolutionAndFallback, @@ -26,6 +25,28 @@ import { import { showErrorToast } from './ToastUtils'; import userClassBase from './UserClassBase'; +export enum EImageTypes { + image = 's96-c', + image192 = 's192-c', + image24 = 's24-c', + image32 = 's32-c', + image48 = 's48-c', + image512 = 's512-c', + image72 = 's72-c', +} + +export const getImages = (imageUri: string) => { + const imagesObj: Record = {} as Record< + EImageTypes, + string + >; + for (const type in EImageTypes) { + imagesObj[type] = imageUri.replace('s96-c', EImageTypes[type]); + } + + return imagesObj; +}; + export const getUserDataFromOidc = ( userData: User, oidcUser: OidcUser diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts index 3c0610832a16..d94aa91c730a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts @@ -26,9 +26,9 @@ import { CustomEventTypes, } from '../generated/analytics/webAnalyticEventType/customEvent'; import { PageViewEvent } from '../generated/analytics/webAnalyticEventType/pageViewEvent'; +import { getPathNameFromWindowLocation } from '../hoc/withDomainFilter'; import { postWebAnalyticEvent } from '../rest/WebAnalyticsAPI'; import { AnalyticsData } from './../components/WebAnalytics/WebAnalytics.interface'; -import { getPathNameFromWindowLocation } from './RouterUtils'; /** * Check if url is valid or not and return the pathname diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index 6a164a29b344..a2a1d95ed24f 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -13,7 +13,7 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; -import path from 'node:path'; +import path from 'path'; import { defineConfig, loadEnv } from 'vite'; import viteCompression from 'vite-plugin-compression'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; @@ -185,7 +185,7 @@ export default defineConfig(({ mode }) => { minify: mode === 'production' ? 'esbuild' : false, cssMinify: 'esbuild', cssCodeSplit: true, - reportCompressedSize: false, + reportCompressedSize: true, chunkSizeWarningLimit: 1500, rollupOptions: { output: { @@ -200,6 +200,19 @@ export default defineConfig(({ mode }) => { return `assets/[name]-[hash][extname]`; }, + manualChunks: (id) => { + if (id.includes('node_modules')) { + if (id.includes('antd')) { + return 'vendor-antd'; + } + if (id.includes('@openmetadata/ui-core-components')) { + return 'vendor-untitled'; + } + if (id.includes('@untitledui/icons')) { + return 'vendor-untitled-icons'; + } + } + }, }, }, }, From bfa39d14136384394ce571f2f50434f25a17d2ae Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:25:27 +0530 Subject: [PATCH 09/47] update imports --- .../src/main/resources/ui/src/App.tsx | 59 +------------------ .../src/main/resources/ui/src/AppRoot.tsx | 55 +++++++++++++++++ .../AntDConfigProvider/AntDConfigProvider.tsx | 4 +- .../ui/src/pages/SignUp/SignUpPage.tsx | 3 +- 4 files changed, 61 insertions(+), 60 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/App.tsx b/openmetadata-ui/src/main/resources/ui/src/App.tsx index dcc0639ae44d..35f6d39c8ce1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/App.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/App.tsx @@ -11,68 +11,11 @@ * limitations under the License. */ -import { isEmpty } from 'lodash'; -import { FC, useEffect } from 'react'; -import { useShallow } from 'zustand/react/shallow'; +import { FC } from 'react'; import AppRouter from './components/AppRouter/AppRouter'; import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider'; -import { useApplicationStore } from './hooks/useApplicationStore'; -import { - getCustomUiThemePreference, - getSystemConfig, -} from './rest/settingConfigAPI'; - -import { getThemeConfig } from './utils/ThemeUtils'; const App: FC = () => { - const { applicationConfig, setApplicationConfig, setRdfEnabled } = - useApplicationStore( - useShallow((state) => ({ - applicationConfig: state.applicationConfig, - setApplicationConfig: state.setApplicationConfig, - setRdfEnabled: state.setRdfEnabled, - })) - ); - - const fetchApplicationConfig = async () => { - try { - const [themeData, systemConfig] = await Promise.all([ - getCustomUiThemePreference(), - getSystemConfig(), - ]); - - setApplicationConfig({ - ...themeData, - customTheme: getThemeConfig(themeData.customTheme), - }); - - setRdfEnabled(systemConfig.rdfEnabled || false); - } catch (error) { - // eslint-disable-next-line no-console - console.error(error); - } - }; - - useEffect(() => { - fetchApplicationConfig(); - }, []); - - useEffect(() => { - const faviconHref = isEmpty( - applicationConfig?.customLogoConfig?.customFaviconUrlPath - ) - ? '/favicon.png' - : applicationConfig?.customLogoConfig?.customFaviconUrlPath ?? - '/favicon.png'; - const link = document.querySelectorAll('link[rel~="icon"]'); - - if (!isEmpty(link)) { - link.forEach((item) => { - item.setAttribute('href', faviconHref); - }); - } - }, [applicationConfig]); - return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index 253561c0c3e0..4f435951a55f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -11,16 +11,23 @@ * limitations under the License. */ +import { isEmpty } from 'lodash'; import { FC, useEffect } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; +import { useShallow } from 'zustand/react/shallow'; import App from './App'; import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; import { useApplicationStore } from './hooks/useApplicationStore'; +import { + getCustomUiThemePreference, + getSystemConfig, +} from './rest/settingConfigAPI'; import { getBasePath } from './utils/HistoryUtils'; import i18n from './utils/i18next/LocalUtil'; +import { getThemeConfig } from './utils/ThemeUtils'; const AppRoot: FC = () => { const { initializeAuthState } = useApplicationStore(); @@ -29,6 +36,54 @@ const AppRoot: FC = () => { initializeAuthState(); }, [initializeAuthState]); + const { applicationConfig, setApplicationConfig, setRdfEnabled } = + useApplicationStore( + useShallow((state) => ({ + applicationConfig: state.applicationConfig, + setApplicationConfig: state.setApplicationConfig, + setRdfEnabled: state.setRdfEnabled, + })) + ); + + const fetchApplicationConfig = async () => { + try { + const [themeData, systemConfig] = await Promise.all([ + getCustomUiThemePreference(), + getSystemConfig(), + ]); + + setApplicationConfig({ + ...themeData, + customTheme: getThemeConfig(themeData.customTheme), + }); + + setRdfEnabled(systemConfig.rdfEnabled || false); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + } + }; + + useEffect(() => { + fetchApplicationConfig(); + }, []); + + useEffect(() => { + const faviconHref = isEmpty( + applicationConfig?.customLogoConfig?.customFaviconUrlPath + ) + ? '/favicon.png' + : applicationConfig?.customLogoConfig?.customFaviconUrlPath ?? + '/favicon.png'; + const link = document.querySelectorAll('link[rel~="icon"]'); + + if (!isEmpty(link)) { + link.forEach((item) => { + item.setAttribute('href', faviconHref); + }); + } + }, [applicationConfig]); + return (
diff --git a/openmetadata-ui/src/main/resources/ui/src/context/AntDConfigProvider/AntDConfigProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/context/AntDConfigProvider/AntDConfigProvider.tsx index b42bb48be7a4..b976ac2644a1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/context/AntDConfigProvider/AntDConfigProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/context/AntDConfigProvider/AntDConfigProvider.tsx @@ -19,7 +19,9 @@ import { generatePalette } from '../../styles/colorPallet'; const AntDConfigProvider: FC<{ children: ReactNode }> = ({ children }) => { const { i18n } = useTranslation(); - const { applicationConfig } = useApplicationStore(); + const applicationConfig = useApplicationStore( + (state) => state.applicationConfig + ); useEffect(() => { const palette = generatePalette( diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx index a81d2a114d2b..e2bf5539bb1c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx @@ -33,8 +33,9 @@ import { setUrlPathnameExpiryAfterRoute, } from '../../utils/AuthProvider.util'; import brandClassBase from '../../utils/BrandData/BrandClassBase'; -import { getImages, Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/CommonUtils'; import { showErrorToast } from '../../utils/ToastUtils'; +import { getImages } from '../../utils/UserDataUtils'; const cookieStorage = new CookieStorage(); From cd6ac88769e0c52e8d71beedea11d20c368eb5dd Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:30:31 +0530 Subject: [PATCH 10/47] improve imports --- .../AlertRecentEventsTab.tsx | 2 +- .../DestinationSelectItem.tsx | 2 +- .../ui/src/components/AppBar/Suggestions.tsx | 2 +- .../AppAuthenticators/MsalAuthenticator.tsx | 2 +- .../ContractSLACard/ContractSLA.component.tsx | 2 +- .../DataInsight/EmptyGraphPlaceholder.tsx | 2 +- .../AddDataQualityTest/TestSuiteIngestion.tsx | 4 +- .../components/TestCaseFormV1.tsx | 4 +- .../DataQualityTab/DataQualityTab.tsx | 5 +- .../TableProfiler/SingleColumnProfile.tsx | 6 +- .../TableProfilerChart/TableProfilerChart.tsx | 2 +- .../SampleDataTable.component.tsx | 6 +- .../SampleDataWithMessages.tsx | 2 +- .../CustomPropertiesSection.tsx | 2 +- .../DataQualityTab/DataQualityTab.tsx | 6 +- .../Explore/ExploreTree/ExploreTree.tsx | 3 +- .../ExploreV1/ExploreV1.component.tsx | 2 +- .../GlossaryTermTab.component.tsx | 2 +- .../components/LineageTable/LineageTable.tsx | 2 +- .../ChangeParentHierarchy.component.tsx | 2 +- .../EntityDeleteModal/EntityDeleteModal.tsx | 2 +- .../CustomizablePageHeader.tsx | 2 +- .../AppInstallVerifyCard.component.tsx | 2 +- .../MarketPlaceAppDetails.component.tsx | 2 +- .../IngestionListTable/IngestionListTable.tsx | 2 +- .../ServiceConfig/ConnectionConfigForm.tsx | 3 +- .../Team/TeamDetails/TeamDetailsV1.tsx | 4 +- .../Team/TeamDetails/TeamHierarchy.tsx | 2 +- .../src/components/UploadFile/UploadFile.tsx | 2 +- .../CustomPropertyTable.tsx | 2 +- .../common/DeleteWidget/DeleteWidgetModal.tsx | 2 +- .../CreateErrorPlaceHolder.tsx | 2 +- .../ErrorPlaceHolderES.tsx | 3 +- .../FilterErrorPlaceHolder.tsx | 2 +- .../PermissionErrorPlaceholder.tsx | 2 +- .../common/TestConnection/TestConnection.tsx | 2 +- .../ui/src/constants/router.constants.ts | 8 +- .../ui/src/hoc/withDomainFilter.test.tsx | 467 ++++++++++++++++++ .../CustomPageSettings/CustomPageSettings.tsx | 6 +- .../CustomizablePage/CustomizablePage.tsx | 2 +- .../ui/src/pages/SignUp/SignUpPage.tsx | 2 +- .../src/pages/UserListPage/UserListPageV1.tsx | 2 +- .../src/pages/UserPage/UserPage.component.tsx | 2 +- .../src/utils/ApplicationRoutesClassBase.ts | 4 +- .../resources/ui/src/utils/CommonUtils.tsx | 18 +- .../ui/src/utils/DomainUtils.test.tsx | 463 ----------------- .../main/resources/ui/src/utils/FeedUtils.tsx | 5 +- .../resources/ui/src/utils/IngestionUtils.tsx | 3 +- .../ui/src/utils/ServiceInsightsTabUtils.tsx | 2 +- .../main/resources/ui/src/utils/TourUtils.tsx | 2 +- .../i18next/{LocalUtil.ts => LocalUtil.tsx} | 18 +- 51 files changed, 548 insertions(+), 552 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx rename openmetadata-ui/src/main/resources/ui/src/utils/i18next/{LocalUtil.ts => LocalUtil.tsx} (88%) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.tsx index 9cba3f503ef2..db464a02b9fc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/AlertDetails/AlertRecentEventsTab/AlertRecentEventsTab.tsx @@ -43,9 +43,9 @@ import { getChangeEventDataFromTypedEvent, getLabelsForEventDetails, } from '../../../../utils/Alerts/AlertsUtil'; -import { Transi18next } from '../../../../utils/CommonUtils'; import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils'; import { getEntityName } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import searchClassBase from '../../../../utils/SearchClassBase'; import { showErrorToast } from '../../../../utils/ToastUtils'; import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.tsx index f98e0fa8fe3a..6d67c03300ba 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.tsx @@ -48,7 +48,7 @@ import { getSubscriptionTypeOptions, normalizeDestinationConfig, } from '../../../../utils/Alerts/AlertsUtil'; -import { Transi18next } from '../../../../utils/CommonUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { checkIfDestinationIsInternal } from '../../../../utils/ObservabilityUtils'; import { DestinationSelectItemProps } from './DestinationSelectItem.interface'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx index 345e182d883e..ba153c33a4b5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx @@ -27,7 +27,7 @@ import { import { useTourProvider } from '../../context/TourProvider/TourProvider'; import { SearchIndex } from '../../enums/search.enum'; import { searchQuery } from '../../rest/searchAPI'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import searchClassBase from '../../utils/SearchClassBase'; import { filterOptionsByIndex, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx index 2e11fcd05858..f3592d314426 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx @@ -29,7 +29,7 @@ import { parseMSALResponse, } from '../../../utils/AuthProvider.util'; import { getPopupSettingLink } from '../../../utils/BrowserUtils'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { showErrorToast } from '../../../utils/ToastUtils'; import Loader from '../../common/Loader/Loader'; import { useAuthProvider } from '../AuthProviders/AuthProvider'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.component.tsx index 5bcb872b67b2..a63fad044937 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.component.tsx @@ -20,8 +20,8 @@ import { ReactComponent as DefaultIcon } from '../../../assets/svg/ic-task.svg'; import { DATA_CONTRACT_SLA } from '../../../constants/DataContract.constants'; import { DataContract } from '../../../generated/entity/data/dataContract'; import { Table } from '../../../generated/entity/data/table'; -import { Transi18next } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/EntityUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider'; import './contract-sla.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/EmptyGraphPlaceholder.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/EmptyGraphPlaceholder.tsx index ff60ae271c23..3d3cf5bf7da5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/EmptyGraphPlaceholder.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/EmptyGraphPlaceholder.tsx @@ -16,7 +16,7 @@ import { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; import { DATA_INSIGHT_DOCS } from '../../constants/docs.constants'; import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../enums/common.enum'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import ErrorPlaceHolder from '../common/ErrorWithPlaceholder/ErrorPlaceHolder'; export const EmptyGraphPlaceholder = ({ icon }: { icon?: ReactElement }) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx index 31a533470738..9083f9868f51 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx @@ -26,8 +26,8 @@ import { useLimitStore } from '../../../context/LimitsProvider/useLimitsStore'; import { FormSubmitType } from '../../../enums/form.enum'; import { IngestionActionMessage } from '../../../enums/ingestion.enum'; import { - CreateIngestionPipeline, FluffyType as ConfigType, + CreateIngestionPipeline, PipelineType, } from '../../../generated/api/services/ingestionPipelines/createIngestionPipeline'; import { @@ -43,9 +43,9 @@ import { import { getNameFromFQN, replaceAllSpacialCharWith_, - Transi18next, } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/EntityUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { getScheduleOptionsFromSchedules } from '../../../utils/SchedularUtils'; import { getIngestionName } from '../../../utils/ServiceUtils'; import { generateUUID } from '../../../utils/StringsUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx index f497157a02da..85b2cb77684f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx @@ -65,8 +65,8 @@ import { SearchIndex } from '../../../../enums/search.enum'; import { ServiceCategory } from '../../../../enums/service.enum'; import { TagSource } from '../../../../generated/api/domains/createDataProduct'; import { - CreateIngestionPipeline, FluffyType as ConfigType, + CreateIngestionPipeline, PipelineType, } from '../../../../generated/api/services/ingestionPipelines/createIngestionPipeline'; import { CreateTestCase } from '../../../../generated/api/tests/createTestCase'; @@ -99,7 +99,6 @@ import { import { filterSelectOptions, replaceAllSpacialCharWith_, - Transi18next, } from '../../../../utils/CommonUtils'; import { convertSearchSourceToTable, @@ -111,6 +110,7 @@ import { generateFormFields, getPopupContainer, } from '../../../../utils/formUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getScheduleOptionsFromSchedules } from '../../../../utils/SchedularUtils'; import { getIngestionName } from '../../../../utils/ServiceUtils'; import { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx index b18a95874e58..869f8b249915 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx @@ -47,12 +47,15 @@ import { TestSuite } from '../../../../generated/tests/testSuite'; import { TestCasePageTabs } from '../../../../pages/IncidentManager/IncidentManager.interface'; import { getListTestCaseIncidentByStateId } from '../../../../rest/incidentManagerAPI'; import { removeTestCaseFromTestSuite } from '../../../../rest/testAPI'; -import { getNameFromFQN, Transi18next } from '../../../../utils/CommonUtils'; import { getColumnNameFromEntityLink, getEntityName, } from '../../../../utils/EntityUtils'; import { getEntityFQN } from '../../../../utils/FeedUtils'; +import { + getNameFromFQN, + Transi18next, +} from '../../../../utils/i18next/LocalUtil'; import { getEntityDetailsPath, getTestCaseDetailPagePath, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/SingleColumnProfile.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/SingleColumnProfile.tsx index e469e58f89ef..2b7d69b97678 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/SingleColumnProfile.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/SingleColumnProfile.tsx @@ -34,11 +34,9 @@ import { import { Table } from '../../../../generated/entity/data/table'; import useCustomLocation from '../../../../hooks/useCustomLocation/useCustomLocation'; import { getColumnProfilerList } from '../../../../rest/tableAPI'; -import { - formatNumberWithComma, - Transi18next, -} from '../../../../utils/CommonUtils'; +import { formatNumberWithComma } from '../../../../utils/CommonUtils'; import documentationLinksClassBase from '../../../../utils/DocumentationLinksClassBase'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { calculateColumnProfilerMetrics, calculateCustomMetrics, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx index 86a46dbd4a0f..1e7559c8effa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerChart/TableProfilerChart.tsx @@ -31,8 +31,8 @@ import { getSystemProfileList, getTableProfilesList, } from '../../../../../rest/tableAPI'; -import { Transi18next } from '../../../../../utils/CommonUtils'; import documentationLinksClassBase from '../../../../../utils/DocumentationLinksClassBase'; +import { Transi18next } from '../../../../../utils/i18next/LocalUtil'; import { calculateCustomMetrics, calculateRowCountMetrics, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataTable/SampleDataTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataTable/SampleDataTable.component.tsx index daa2528cb95c..332ca93d6844 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataTable/SampleDataTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataTable/SampleDataTable.component.tsx @@ -33,11 +33,9 @@ import { deleteSampleDataByTableId, getSampleDataByTableId, } from '../../../rest/tableAPI'; -import { - getEntityDeleteMessage, - Transi18next, -} from '../../../utils/CommonUtils'; +import { getEntityDeleteMessage } from '../../../utils/CommonUtils'; import { downloadFile } from '../../../utils/Export/ExportUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { showErrorToast } from '../../../utils/ToastUtils'; import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import Loader from '../../common/Loader/Loader'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataWithMessages/SampleDataWithMessages.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataWithMessages/SampleDataWithMessages.tsx index ebe6ac527d33..c461fbc5a11c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataWithMessages/SampleDataWithMessages.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SampleDataWithMessages/SampleDataWithMessages.tsx @@ -22,7 +22,7 @@ import { TopicSampleData } from '../../../generated/entity/data/topic'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { getSampleDataBySearchIndexId } from '../../../rest/SearchIndexAPI'; import { getSampleDataByTopicId } from '../../../rest/topicsAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import Loader from '../../common/Loader/Loader'; import MessageCard from './MessageCard'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.tsx index f153f4ee3a40..fe2130b7e7c9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.tsx @@ -18,7 +18,7 @@ import { ReactComponent as AddPlaceHolderIcon } from '../../../../assets/svg/ic- import { CUSTOM_PROPERTIES_DOCS } from '../../../../constants/docs.constants'; import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum'; import { CustomProperty } from '../../../../generated/entity/type'; -import { Transi18next } from '../../../../utils/CommonUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { PropertyValue } from '../../../common/CustomPropertyTable/PropertyValue'; import ErrorPlaceHolderNew from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolderNew'; import Loader from '../../../common/Loader/Loader'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.tsx index da5b16cf07cb..8d11246e13dc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.tsx @@ -33,15 +33,13 @@ import { import { Include } from '../../../../generated/type/include'; import { getListTestCaseIncidentStatus } from '../../../../rest/incidentManagerAPI'; import { getListTestCaseBySearch } from '../../../../rest/testAPI'; -import { - getTableFQNFromColumnFQN, - Transi18next, -} from '../../../../utils/CommonUtils'; +import { getTableFQNFromColumnFQN } from '../../../../utils/CommonUtils'; import { getCurrentMillis, getEpochMillisForPastDays, } from '../../../../utils/date-time/DateTimeUtils'; import { getColumnNameFromEntityLink } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getTestCaseDetailPagePath } from '../../../../utils/RouterUtils'; import { generateEntityLink } from '../../../../utils/TableUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.tsx index eb9418c9e116..4ae1f0cc8699 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.tsx @@ -27,7 +27,7 @@ import { EntityType } from '../../../enums/entity.enum'; import { ExplorePageTabs } from '../../../enums/Explore.enum'; import { SearchIndex } from '../../../enums/search.enum'; import { searchQuery } from '../../../rest/searchAPI'; -import { getCountBadge, Transi18next } from '../../../utils/CommonUtils'; +import { getCountBadge } from '../../../utils/CommonUtils'; import entityUtilClassBase from '../../../utils/EntityUtilClassBase'; import { getPluralizeEntityName } from '../../../utils/EntityUtils'; import { @@ -38,6 +38,7 @@ import { updateTreeData, updateTreeDataWithCounts, } from '../../../utils/ExploreUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import searchClassBase from '../../../utils/SearchClassBase'; import serviceUtilClassBase from '../../../utils/ServiceUtilClassBase'; import { generateUUID } from '../../../utils/StringsUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx index f31d79a9991e..4b8c6a833baa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx @@ -37,12 +37,12 @@ import { import { SIZE, SORT_ORDER } from '../../enums/common.enum'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { getDropDownItems } from '../../utils/AdvancedSearchUtils'; -import { Transi18next } from '../../utils/CommonUtils'; import { highlightEntityNameAndDescription } from '../../utils/EntityUtils'; import { getExploreQueryFilterMust, getSelectedValuesFromQuickFilter, } from '../../utils/ExploreUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getApplicationDetailsPath } from '../../utils/RouterUtils'; import searchClassBase from '../../utils/SearchClassBase'; import FilterErrorPlaceHolder from '../common/ErrorWithPlaceholder/FilterErrorPlaceHolder'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index 359b2b60f0b4..742db91b4855 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -87,7 +87,6 @@ import { patchGlossaryTerm, searchGlossaryTermsPaginated, } from '../../../rest/glossaryAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; import { getBulkEditButton } from '../../../utils/EntityBulkEdit/EntityBulkEditUtils'; import { EntityStatusClass } from '../../../utils/EntityStatusUtils'; import { @@ -101,6 +100,7 @@ import { glossaryTermTableColumnsWidth, permissionForApproveOrReject, } from '../../../utils/GlossaryUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { getGlossaryPath } from '../../../utils/RouterUtils'; import { ownerTableObject } from '../../../utils/TableColumn.util'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.tsx index aee1198174e3..7668dd46b158 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.tsx @@ -54,7 +54,6 @@ import { getLineageDataByFQN, getLineagePagingData, } from '../../rest/lineageAPI'; -import { Transi18next } from '../../utils/CommonUtils'; import { getEntityLinkFromType, getEntityName, @@ -62,6 +61,7 @@ import { } from '../../utils/EntityUtils'; import { getQuickFilterQuery } from '../../utils/ExploreUtils'; import Fqn from '../../utils/Fqn'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getSearchNameEsQuery, LINEAGE_IMPACT_OPTIONS, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.component.tsx index e2c52f9cb79c..970f072db257 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.component.tsx @@ -28,9 +28,9 @@ import { GlossaryTerm, } from '../../../generated/entity/data/glossaryTerm'; import { moveGlossaryTerm } from '../../../rest/glossaryAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; import { EntityStatusClass } from '../../../utils/EntityStatusUtils'; import { getEntityName } from '../../../utils/EntityUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { getGlossaryPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; import Banner from '../../common/Banner/Banner'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx index 7bbc831c4935..42c4c40d3905 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.tsx @@ -15,7 +15,7 @@ import { Button, Input, InputRef, Modal, Typography } from 'antd'; import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import brandClassBase from '../../../utils/BrandData/BrandClassBase'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { EntityDeleteModalProp } from './EntityDeleteModal.interface'; const EntityDeleteModal = ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader.tsx index 15a372d0f68f..9e1217e075d3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader.tsx @@ -24,7 +24,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { PageType } from '../../../../generated/system/ui/page'; import { useFqn } from '../../../../hooks/useFqn'; import { useCustomizeStore } from '../../../../pages/CustomizablePage/CustomizeStore'; -import { Transi18next } from '../../../../utils/CommonUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getPersonaDetailsPath } from '../../../../utils/RouterUtils'; import { UnsavedChangesModal } from '../../../Modals/UnsavedChangesModal/UnsavedChangesModal.component'; import './customizable-page-header.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx index 5f25fddad5ca..768612b3812d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx @@ -27,9 +27,9 @@ import { import { useTranslation } from 'react-i18next'; import { LIGHT_GREEN_COLOR } from '../../../../constants/constants'; import { useApplicationStore } from '../../../../hooks/useApplicationStore'; -import { Transi18next } from '../../../../utils/CommonUtils'; import { getRelativeTime } from '../../../../utils/date-time/DateTimeUtils'; import { getEntityName } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import BrandImage from '../../../common/BrandImage/BrandImage'; import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard'; import AppLogo from '../AppLogo/AppLogo.component'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/MarketPlaceAppDetails/MarketPlaceAppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/MarketPlaceAppDetails/MarketPlaceAppDetails.component.tsx index 42f9b4845568..48d978694ee5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/MarketPlaceAppDetails/MarketPlaceAppDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/MarketPlaceAppDetails/MarketPlaceAppDetails.component.tsx @@ -35,8 +35,8 @@ import { useFqn } from '../../../../hooks/useFqn'; import { getApplicationByName } from '../../../../rest/applicationAPI'; import { getMarketPlaceApplicationByFqn } from '../../../../rest/applicationMarketPlaceAPI'; import brandClassBase from '../../../../utils/BrandData/BrandClassBase'; -import { Transi18next } from '../../../../utils/CommonUtils'; import { getEntityName } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getAppInstallPath } from '../../../../utils/RouterUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; import Loader from '../../../common/Loader/Loader'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.tsx index 03259271c93d..4a974f05ebca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.tsx @@ -40,12 +40,12 @@ import { deleteIngestionPipelineById, getRunHistoryForPipeline, } from '../../../../../rest/ingestionPipelineAPI'; -import { Transi18next } from '../../../../../utils/CommonUtils'; import { getColumnSorter, getEntityName, highlightSearchText, } from '../../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../../utils/i18next/LocalUtil'; import { renderNameField, renderScheduleField, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.tsx index 4a4c7143ae2c..c95b5e11baee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.tsx @@ -30,8 +30,7 @@ import { useApplicationStore } from '../../../../hooks/useApplicationStore'; import { ConfigData } from '../../../../interface/service.interface'; import { getPipelineServiceHostIp } from '../../../../rest/ingestionPipelineAPI'; import brandClassBase from '../../../../utils/BrandData/BrandClassBase'; -import { Transi18next } from '../../../../utils/CommonUtils'; -import i18n from '../../../../utils/i18next/LocalUtil'; +import i18n, { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { formatFormDataForSubmit } from '../../../../utils/JSONSchemaFormUtils'; import { getConnectionSchemas, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx index d15d5b5212b5..9b203ffc3c18 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx @@ -57,8 +57,8 @@ import { SearchIndex } from '../../../../enums/search.enum'; import { OwnerType } from '../../../../enums/user.enum'; import { Team, TeamType } from '../../../../generated/entity/teams/team'; import { - EntityReference as UserTeams, User, + EntityReference as UserTeams, } from '../../../../generated/entity/teams/user'; import { EntityReference } from '../../../../generated/type/entityReference'; import { useAuth } from '../../../../hooks/authHooks'; @@ -68,13 +68,13 @@ import AddAttributeModal from '../../../../pages/RolesPage/AddAttributeModal/Add import { ImportType } from '../../../../pages/TeamsPage/ImportTeamsPage/ImportTeamsPage.interface'; import { searchQuery } from '../../../../rest/searchAPI'; import { exportTeam, restoreTeam } from '../../../../rest/teamsAPI'; -import { Transi18next } from '../../../../utils/CommonUtils'; import { getEntityName } from '../../../../utils/EntityUtils'; import { EXTENSION_POINTS, TabContribution, } from '../../../../utils/ExtensionPointTypes'; import { getSettingPageEntityBreadCrumb } from '../../../../utils/GlobalSettingsUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getSettingsPathWithFqn, getTeamsWithFqnPath, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx index b00f32c531aa..c3cd4f5dc70d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx @@ -26,11 +26,11 @@ import { TabSpecificField } from '../../../../enums/entity.enum'; import { Team } from '../../../../generated/entity/teams/team'; import { Include } from '../../../../generated/type/include'; import { getTeamByName, patchTeamDetail } from '../../../../rest/teamsAPI'; -import { Transi18next } from '../../../../utils/CommonUtils'; import { getEntityName, highlightSearchText, } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getTeamsWithFqnPath } from '../../../../utils/RouterUtils'; import { stringToHTML } from '../../../../utils/StringsUtils'; import { descriptionTableObject } from '../../../../utils/TableColumn.util'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx index 1d675a554d0f..1ceb7eeeaf2c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.tsx @@ -17,7 +17,7 @@ import type { UploadRequestOption } from 'rc-upload/lib/interface'; import { FC, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as ImportIcon } from '../../assets/svg/ic-drag-drop.svg'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { showErrorToast } from '../../utils/ToastUtils'; import Loader from '../common/Loader/Loader'; import './upload-file.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx index a7a389bba00b..194e8ed0d6d8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx @@ -26,13 +26,13 @@ import { DetailPageWidgetKeys } from '../../../enums/CustomizeDetailPage.enum'; import { EntityTabs } from '../../../enums/entity.enum'; import { ChangeDescription, Type } from '../../../generated/entity/type'; import { getTypeByFQN } from '../../../rest/metadataTypeAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; import entityUtilClassBase from '../../../utils/EntityUtilClassBase'; import { getChangedEntityNewValue, getDiffByFieldName, getUpdatedExtensionDiffFields, } from '../../../utils/EntityVersionUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { showErrorToast } from '../../../utils/ToastUtils'; import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider'; import ErrorPlaceHolder from '../ErrorWithPlaceholder/ErrorPlaceHolder'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.tsx index b04ee82469d5..4c7545304bbe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.tsx @@ -36,8 +36,8 @@ import { useAsyncDeleteProvider } from '../../../context/AsyncDeleteProvider/Asy import { EntityType } from '../../../enums/entity.enum'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { deleteEntity } from '../../../rest/miscAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; import deleteWidgetClassBase from '../../../utils/DeleteWidget/DeleteWidgetClassBase'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { useAuthProvider } from '../../Auth/AuthProviders/AuthProvider'; import './delete-widget-modal.style.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/CreateErrorPlaceHolder.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/CreateErrorPlaceHolder.tsx index c18ba89a8db4..b519c13bf53d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/CreateErrorPlaceHolder.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/CreateErrorPlaceHolder.tsx @@ -17,7 +17,7 @@ import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { ReactComponent as AddPlaceHolderIcon } from '../../../assets/svg/add-placeholder.svg'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import PermissionErrorPlaceholder from './PermissionErrorPlaceholder'; import { CreatePlaceholderProps } from './placeholder.interface'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.tsx index 954c49da6b74..f35f26d139d0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.tsx @@ -34,8 +34,7 @@ import { import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useDomainStore } from '../../../hooks/useDomainStore'; import brandClassBase from '../../../utils/BrandData/BrandClassBase'; -import { Transi18next } from '../../../utils/CommonUtils'; -import i18n from '../../../utils/i18next/LocalUtil'; +import i18n, { Transi18next } from '../../../utils/i18next/LocalUtil'; import { useRequiredParams } from '../../../utils/useRequiredParams'; import ErrorPlaceHolder from './ErrorPlaceHolder'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/FilterErrorPlaceHolder.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/FilterErrorPlaceHolder.tsx index 1816c3d6cf2a..70a3661302d7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/FilterErrorPlaceHolder.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/FilterErrorPlaceHolder.tsx @@ -16,7 +16,7 @@ import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { ReactComponent as FilterPlaceHolderIcon } from '../../../assets/svg/no-search-placeholder.svg'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { FilterPlaceholderProps } from './placeholder.interface'; const FilterErrorPlaceHolder = ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/PermissionErrorPlaceholder.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/PermissionErrorPlaceholder.tsx index 0234b2360af8..04bc36cc4077 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/PermissionErrorPlaceholder.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/PermissionErrorPlaceholder.tsx @@ -14,7 +14,7 @@ import { Space, Typography } from 'antd'; import classNames from 'classnames'; import { ReactComponent as NoAccessPlaceHolderIcon } from '../../../assets/svg/add-placeholder.svg'; import { SIZE } from '../../../enums/common.enum'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { PermissionPlaceholderProps } from './placeholder.interface'; const PermissionErrorPlaceholder = ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx index 34526d993cde..d43e840e96ac 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx @@ -50,7 +50,7 @@ import { getWorkflowById, triggerWorkflowById, } from '../../../rest/workflowAPI'; -import { Transi18next } from '../../../utils/CommonUtils'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; import { formatFormDataForSubmit } from '../../../utils/JSONSchemaFormUtils'; import { getServiceType, diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts index 8205928d4951..67c816bd0a64 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts @@ -28,7 +28,7 @@ export const APP_ROUTER_ROUTES = { ACCOUNT_ACTIVATION: '/account-activation', } as const; -const UNPROTECTED_ROUTES = [ +export const UNPROTECTED_ROUTES: Set = new Set([ APP_ROUTER_ROUTES.SIGNUP, APP_ROUTER_ROUTES.SIGNIN, APP_ROUTER_ROUTES.FORGOT_PASSWORD, @@ -41,10 +41,6 @@ const UNPROTECTED_ROUTES = [ APP_ROUTER_ROUTES.AUTH_CALLBACK, APP_ROUTER_ROUTES.NOT_FOUND, APP_ROUTER_ROUTES.LOGOUT, -]; - -export const isProtectedRoute = (pathname: string): boolean => { - return UNPROTECTED_ROUTES.indexOf(pathname) === -1; -}; +]); export const REDIRECT_PATHNAME = 'redirectUrlPath'; diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx new file mode 100644 index 000000000000..8f52ef3a0e10 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx @@ -0,0 +1,467 @@ +import { InternalAxiosRequestConfig } from 'axios'; +import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; +import { SearchIndex } from '../enums/search.enum'; +import { useDomainStore } from '../hooks/useDomainStore'; +import { + getPathNameFromWindowLocation, + withDomainFilter, +} from './withDomainFilter'; + +jest.mock('../hooks/useDomainStore'); + +describe('withDomainFilter', () => { + const mockGetState = jest.fn(); + const mockGetPathName = getPathNameFromWindowLocation as jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + (useDomainStore as unknown as jest.Mock).mockImplementation(() => ({ + getState: mockGetState, + })); + (useDomainStore.getState as jest.Mock) = mockGetState; + }); + + const createMockConfig = ( + method: string = 'get', + url?: string, + params?: Record + ): InternalAxiosRequestConfig => + ({ + method, + url, + params, + headers: {}, + } as InternalAxiosRequestConfig); + + describe('should not intercept requests', () => { + it('should return config unchanged when path starts with /domain', () => { + mockGetPathName.mockReturnValue('/domain/test'); + mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); + + const config = createMockConfig(); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params).toBeUndefined(); + }); + + it('should return config unchanged when path starts with /auth/logout', () => { + mockGetPathName.mockReturnValue('/auth/logout'); + mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); + + const config = createMockConfig(); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params).toBeUndefined(); + }); + + it('should return config unchanged when path starts with /auth/refresh', () => { + mockGetPathName.mockReturnValue('/auth/refresh'); + mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); + + const config = createMockConfig(); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params).toBeUndefined(); + }); + + it('should return config unchanged when method is not GET', () => { + mockGetPathName.mockReturnValue('/api/test'); + mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); + + const config = createMockConfig('post'); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params).toBeUndefined(); + }); + + it('should return config unchanged when activeDomain is DEFAULT_DOMAIN_VALUE', () => { + mockGetPathName.mockReturnValue('/api/test'); + mockGetState.mockReturnValue({ activeDomain: DEFAULT_DOMAIN_VALUE }); + + const config = createMockConfig(); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params).toBeUndefined(); + }); + }); + + describe('regular GET requests', () => { + it('should add domain parameter for regular GET requests with active domain', () => { + mockGetPathName.mockReturnValue('/api/tables'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/api/tables'); + const result = withDomainFilter(config); + + expect(result.params).toEqual({ + domain: 'engineering', + }); + }); + + it('should preserve existing params when adding domain parameter', () => { + mockGetPathName.mockReturnValue('/api/tables'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/api/tables', { + limit: 10, + offset: 0, + }); + const result = withDomainFilter(config); + + expect(result.params).toEqual({ + limit: 10, + offset: 0, + domain: 'engineering', + }); + }); + }); + + describe('search query requests', () => { + it('should add should filter with term and prefix for /search/query with active domain', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + }); + const result = withDomainFilter(config); + + expect(result.params).toHaveProperty('query_filter'); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter).toEqual({ + query: { + bool: { + must: [ + { + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }, + ], + }, + }, + }); + }); + + it('should return config unchanged for TAG index searches', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TAG, + }); + const result = withDomainFilter(config); + + expect(result).toBe(config); + expect(result.params?.query_filter).toBeUndefined(); + }); + + it('should use fullyQualifiedName field for DOMAIN index searches', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.DOMAIN, + }); + const result = withDomainFilter(config); + const queryFilter = JSON.parse(result.params?.query_filter as string); + const shouldClauses = + queryFilter.query.bool.must[queryFilter.query.bool.must.length - 1].bool + .should; + + expect(shouldClauses).toEqual([ + { term: { fullyQualifiedName: 'engineering' } }, + { prefix: { fullyQualifiedName: 'engineering.' } }, + ]); + }); + + it('should preserve existing query_filter and add should filter', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const existingFilter = { + query: { + bool: { + must: [ + { + term: { + entityType: 'table', + }, + }, + ], + }, + }, + }; + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + query_filter: JSON.stringify(existingFilter), + }); + const result = withDomainFilter(config); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter.query.bool.must).toHaveLength(2); + expect(filter.query.bool.must[0]).toEqual({ + term: { + entityType: 'table', + }, + }); + expect(filter.query.bool.must[1]).toEqual({ + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }); + }); + + it('should handle invalid JSON in query_filter gracefully', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + query_filter: 'invalid-json', + }); + const result = withDomainFilter(config); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter).toEqual({ + query: { + bool: { + must: [ + { + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }, + ], + }, + }, + }); + }); + + it('should handle query_filter with empty must array', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const existingFilter = { + query: { + bool: {}, + }, + }; + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + query_filter: JSON.stringify(existingFilter), + }); + const result = withDomainFilter(config); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter.query.bool.must).toHaveLength(1); + expect(filter.query.bool.must[0]).toEqual({ + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }); + }); + + it('should handle empty object query_filter gracefully', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + query_filter: '{}', + }); + const result = withDomainFilter(config); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter).toEqual({ + query: { + bool: { + must: [ + { + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }, + ], + }, + }, + }); + }); + + it('should preserve existing params when adding query_filter', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + limit: 10, + offset: 0, + }); + const result = withDomainFilter(config); + + expect(result.params).toHaveProperty('index', SearchIndex.TABLE); + expect(result.params).toHaveProperty('limit', 10); + expect(result.params).toHaveProperty('offset', 0); + expect(result.params).toHaveProperty('query_filter'); + }); + + it('should preserve non-bool top-level clauses when adding domain filter', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ activeDomain: 'engineering' }); + + const existingFilter = JSON.stringify({ + query: { + term: { 'some.field': 'someValue' }, + bool: { must: [{ term: { 'other.field': 'otherValue' } }] }, + }, + }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + query_filter: existingFilter, + }); + const result = withDomainFilter(config); + + const parsed = JSON.parse(result.params?.query_filter as string); + + expect(parsed.query.bool.must).toContainEqual({ + term: { 'some.field': 'someValue' }, + }); + expect(parsed.query.bool.must).toContainEqual({ + term: { 'other.field': 'otherValue' }, + }); + expect(parsed.query.bool.must).toContainEqual({ + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.', + }, + }, + ], + }, + }); + expect(parsed.query.bool.must).toHaveLength(3); + }); + }); + + describe('nested domain paths', () => { + it('should handle nested domain paths correctly', () => { + mockGetPathName.mockReturnValue('/api/tables'); + mockGetState.mockReturnValue({ + activeDomain: 'engineering.backend.services', + }); + + const config = createMockConfig('get', '/api/tables'); + const result = withDomainFilter(config); + + expect(result.params).toEqual({ + domain: 'engineering.backend.services', + }); + }); + + it('should add should filter with nested domain for search queries', () => { + mockGetPathName.mockReturnValue('/api/search'); + mockGetState.mockReturnValue({ + activeDomain: 'engineering.backend.services', + }); + + const config = createMockConfig('get', '/search/query', { + index: SearchIndex.TABLE, + }); + const result = withDomainFilter(config); + + const filter = JSON.parse(result.params?.query_filter as string); + + expect(filter.query.bool.must[0]).toEqual({ + bool: { + should: [ + { + term: { + 'domains.fullyQualifiedName': 'engineering.backend.services', + }, + }, + { + prefix: { + 'domains.fullyQualifiedName': 'engineering.backend.services.', + }, + }, + ], + }, + }); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.tsx index 9b6739190623..e760cf138c0f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.tsx @@ -37,13 +37,15 @@ import { Paging } from '../../generated/type/paging'; import { usePaging } from '../../hooks/paging/usePaging'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { getAllPersonas } from '../../rest/PersonaAPI'; -import { Transi18next } from '../../utils/CommonUtils'; import { getEntityName } from '../../utils/EntityUtils'; import { getCustomizePagePath, getSettingPageEntityBreadCrumb, } from '../../utils/GlobalSettingsUtils'; -import { translateWithNestedKeys } from '../../utils/i18next/LocalUtil'; +import { + Transi18next, + translateWithNestedKeys, +} from '../../utils/i18next/LocalUtil'; import { getSettingPath } from '../../utils/RouterUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import './custom-page-settings.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx index 60553fbe92c5..6420d336055d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx @@ -43,7 +43,7 @@ import { updateDocument, } from '../../rest/DocStoreAPI'; import { getPersonaByName } from '../../rest/PersonaAPI'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getSettingPath } from '../../utils/RouterUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import { useRequiredParams } from '../../utils/useRequiredParams'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx index e2bf5539bb1c..96313b112fc8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx @@ -33,7 +33,7 @@ import { setUrlPathnameExpiryAfterRoute, } from '../../utils/AuthProvider.util'; import brandClassBase from '../../utils/BrandData/BrandClassBase'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { showErrorToast } from '../../utils/ToastUtils'; import { getImages } from '../../utils/UserDataUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx index 1d0d3190557a..725ec93f5e30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx @@ -51,9 +51,9 @@ import { usePaging } from '../../hooks/paging/usePaging'; import { useTableFilters } from '../../hooks/useTableFilters'; import { searchQuery } from '../../rest/searchAPI'; import { getUsers, restoreUser, UsersQueryParams } from '../../rest/userAPI'; -import { Transi18next } from '../../utils/CommonUtils'; import { getEntityName } from '../../utils/EntityUtils'; import { getSettingPageEntityBreadCrumb } from '../../utils/GlobalSettingsUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getSettingPath } from '../../utils/RouterUtils'; import { getTermQuery } from '../../utils/SearchUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx index 430a32ea829e..c58f8e971e07 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx @@ -29,7 +29,7 @@ import { Include } from '../../generated/type/include'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; import { getUserByName, updateUserDetail } from '../../rest/userAPI'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getTermQuery } from '../../utils/SearchUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts index 7b06796fde80..2ac2f289217d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts @@ -13,7 +13,7 @@ import { FC, lazy } from 'react'; import { UnAuthenticatedAppRouter } from '../components/AppRouter/UnAuthenticatedAppRouter'; -import { isProtectedRoute } from '../constants/router.constants'; +import { UNPROTECTED_ROUTES } from '../constants/router.constants'; const AuthenticatedAppRouter = lazy( () => import('../components/AppRouter/AuthenticatedAppRouter') @@ -29,7 +29,7 @@ class ApplicationRoutesClassBase { } public isProtectedRoute(pathname: string): boolean { - return isProtectedRoute(pathname); + return !UNPROTECTED_ROUTES.has(pathname); } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index aea46d5471b7..a116c7d23472 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -34,7 +34,6 @@ import { RecentlyViewedData, } from 'Models'; import { ReactNode } from 'react'; -import { Trans } from 'react-i18next'; import Loader from '../components/common/Loader/Loader'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { BASE_COLORS } from '../constants/DataInsight.constants'; @@ -51,7 +50,7 @@ import { getFeedCount } from '../rest/feedsAPI'; import brandClassBase from './BrandData/BrandClassBase'; import { getEntityFeedLink } from './EntityUtils'; import Fqn from './Fqn'; -import i18n, { t } from './i18next/LocalUtil'; +import i18n, { t, Transi18next } from './i18next/LocalUtil'; import serviceUtilClassBase from './ServiceUtilClassBase'; import { showErrorToast } from './ToastUtils'; @@ -634,21 +633,6 @@ export const getTrimmedContent = (content: string, limit: number) => { return refinedContent.join(' '); }; -export const Transi18next = ({ - i18nKey, - values, - renderElement, - ...otherProps -}: { - i18nKey: string; - values?: object; - renderElement: ReactNode; -}): JSX.Element => ( - - {renderElement} - -); - export const getEntityDeleteMessage = (entity: string, dependents: string) => { if (dependents) { return t('message.permanently-delete-metadata-and-dependents', { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.test.tsx index c13c9de444e3..643136ae8ea4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.test.tsx @@ -10,18 +10,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { InternalAxiosRequestConfig } from 'axios'; -import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; import { EntityType } from '../enums/entity.enum'; -import { SearchIndex } from '../enums/search.enum'; import { Domain, DomainType } from '../generated/entity/domains/domain'; -import { useDomainStore } from '../hooks/useDomainStore'; import { getQueryFilterToIncludeDomain, isDomainExist, - withDomainFilter, } from '../utils/DomainUtils'; -import { getPathNameFromWindowLocation } from './RouterUtils'; jest.mock('../hooks/useDomainStore'); jest.mock('./RouterUtils'); @@ -260,460 +254,3 @@ describe('isDomainExist', () => { }); }); }); - -describe('withDomainFilter', () => { - const mockGetState = jest.fn(); - const mockGetPathName = getPathNameFromWindowLocation as jest.Mock; - - beforeEach(() => { - jest.clearAllMocks(); - (useDomainStore as unknown as jest.Mock).mockImplementation(() => ({ - getState: mockGetState, - })); - (useDomainStore.getState as jest.Mock) = mockGetState; - }); - - const createMockConfig = ( - method: string = 'get', - url?: string, - params?: Record - ): InternalAxiosRequestConfig => - ({ - method, - url, - params, - headers: {}, - } as InternalAxiosRequestConfig); - - describe('should not intercept requests', () => { - it('should return config unchanged when path starts with /domain', () => { - mockGetPathName.mockReturnValue('/domain/test'); - mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); - - const config = createMockConfig(); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params).toBeUndefined(); - }); - - it('should return config unchanged when path starts with /auth/logout', () => { - mockGetPathName.mockReturnValue('/auth/logout'); - mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); - - const config = createMockConfig(); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params).toBeUndefined(); - }); - - it('should return config unchanged when path starts with /auth/refresh', () => { - mockGetPathName.mockReturnValue('/auth/refresh'); - mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); - - const config = createMockConfig(); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params).toBeUndefined(); - }); - - it('should return config unchanged when method is not GET', () => { - mockGetPathName.mockReturnValue('/api/test'); - mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); - - const config = createMockConfig('post'); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params).toBeUndefined(); - }); - - it('should return config unchanged when activeDomain is DEFAULT_DOMAIN_VALUE', () => { - mockGetPathName.mockReturnValue('/api/test'); - mockGetState.mockReturnValue({ activeDomain: DEFAULT_DOMAIN_VALUE }); - - const config = createMockConfig(); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params).toBeUndefined(); - }); - }); - - describe('regular GET requests', () => { - it('should add domain parameter for regular GET requests with active domain', () => { - mockGetPathName.mockReturnValue('/api/tables'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/api/tables'); - const result = withDomainFilter(config); - - expect(result.params).toEqual({ - domain: 'engineering', - }); - }); - - it('should preserve existing params when adding domain parameter', () => { - mockGetPathName.mockReturnValue('/api/tables'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/api/tables', { - limit: 10, - offset: 0, - }); - const result = withDomainFilter(config); - - expect(result.params).toEqual({ - limit: 10, - offset: 0, - domain: 'engineering', - }); - }); - }); - - describe('search query requests', () => { - it('should add should filter with term and prefix for /search/query with active domain', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - }); - const result = withDomainFilter(config); - - expect(result.params).toHaveProperty('query_filter'); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter).toEqual({ - query: { - bool: { - must: [ - { - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }, - ], - }, - }, - }); - }); - - it('should return config unchanged for TAG index searches', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TAG, - }); - const result = withDomainFilter(config); - - expect(result).toBe(config); - expect(result.params?.query_filter).toBeUndefined(); - }); - - it('should use fullyQualifiedName field for DOMAIN index searches', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.DOMAIN, - }); - const result = withDomainFilter(config); - const queryFilter = JSON.parse(result.params?.query_filter as string); - const shouldClauses = - queryFilter.query.bool.must[queryFilter.query.bool.must.length - 1].bool - .should; - - expect(shouldClauses).toEqual([ - { term: { fullyQualifiedName: 'engineering' } }, - { prefix: { fullyQualifiedName: 'engineering.' } }, - ]); - }); - - it('should preserve existing query_filter and add should filter', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const existingFilter = { - query: { - bool: { - must: [ - { - term: { - entityType: 'table', - }, - }, - ], - }, - }, - }; - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - query_filter: JSON.stringify(existingFilter), - }); - const result = withDomainFilter(config); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter.query.bool.must).toHaveLength(2); - expect(filter.query.bool.must[0]).toEqual({ - term: { - entityType: 'table', - }, - }); - expect(filter.query.bool.must[1]).toEqual({ - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }); - }); - - it('should handle invalid JSON in query_filter gracefully', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - query_filter: 'invalid-json', - }); - const result = withDomainFilter(config); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter).toEqual({ - query: { - bool: { - must: [ - { - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }, - ], - }, - }, - }); - }); - - it('should handle query_filter with empty must array', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const existingFilter = { - query: { - bool: {}, - }, - }; - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - query_filter: JSON.stringify(existingFilter), - }); - const result = withDomainFilter(config); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter.query.bool.must).toHaveLength(1); - expect(filter.query.bool.must[0]).toEqual({ - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }); - }); - - it('should handle empty object query_filter gracefully', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - query_filter: '{}', - }); - const result = withDomainFilter(config); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter).toEqual({ - query: { - bool: { - must: [ - { - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }, - ], - }, - }, - }); - }); - - it('should preserve existing params when adding query_filter', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - limit: 10, - offset: 0, - }); - const result = withDomainFilter(config); - - expect(result.params).toHaveProperty('index', SearchIndex.TABLE); - expect(result.params).toHaveProperty('limit', 10); - expect(result.params).toHaveProperty('offset', 0); - expect(result.params).toHaveProperty('query_filter'); - }); - - it('should preserve non-bool top-level clauses when adding domain filter', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ activeDomain: 'engineering' }); - - const existingFilter = JSON.stringify({ - query: { - term: { 'some.field': 'someValue' }, - bool: { must: [{ term: { 'other.field': 'otherValue' } }] }, - }, - }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - query_filter: existingFilter, - }); - const result = withDomainFilter(config); - - const parsed = JSON.parse(result.params?.query_filter as string); - - expect(parsed.query.bool.must).toContainEqual({ - term: { 'some.field': 'someValue' }, - }); - expect(parsed.query.bool.must).toContainEqual({ - term: { 'other.field': 'otherValue' }, - }); - expect(parsed.query.bool.must).toContainEqual({ - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.', - }, - }, - ], - }, - }); - expect(parsed.query.bool.must).toHaveLength(3); - }); - }); - - describe('nested domain paths', () => { - it('should handle nested domain paths correctly', () => { - mockGetPathName.mockReturnValue('/api/tables'); - mockGetState.mockReturnValue({ - activeDomain: 'engineering.backend.services', - }); - - const config = createMockConfig('get', '/api/tables'); - const result = withDomainFilter(config); - - expect(result.params).toEqual({ - domain: 'engineering.backend.services', - }); - }); - - it('should add should filter with nested domain for search queries', () => { - mockGetPathName.mockReturnValue('/api/search'); - mockGetState.mockReturnValue({ - activeDomain: 'engineering.backend.services', - }); - - const config = createMockConfig('get', '/search/query', { - index: SearchIndex.TABLE, - }); - const result = withDomainFilter(config); - - const filter = JSON.parse(result.params?.query_filter as string); - - expect(filter.query.bool.must[0]).toEqual({ - bool: { - should: [ - { - term: { - 'domains.fullyQualifiedName': 'engineering.backend.services', - }, - }, - { - prefix: { - 'domains.fullyQualifiedName': 'engineering.backend.services.', - }, - }, - ], - }, - }); - }); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx index a2c50628c7ba..0e7091adefa9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx @@ -26,12 +26,12 @@ import { ReactComponent as UpdatedIcon } from '../assets/svg/updated-icon.svg'; import { MentionSuggestionsItem } from '../components/ActivityFeed/FeedEditor/FeedEditor.interface'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { + ENTITY_URL_MAP, EntityField, entityLinkRegEx, EntityRegEx, entityRegex, EntityUrlMapType, - ENTITY_URL_MAP, hashtagRegEx, linkRegEx, mentionRegEx, @@ -62,7 +62,6 @@ import { getPartialNameFromFQN, getPartialNameFromTableFQN, getRandomColor, - Transi18next, } from './CommonUtils'; import { getRelativeCalendar } from './date-time/DateTimeUtils'; import EntityLink from './EntityLink'; @@ -73,7 +72,7 @@ import { getEntityName, } from './EntityUtils'; import Fqn from './Fqn'; -import { t } from './i18next/LocalUtil'; +import { t, Transi18next } from './i18next/LocalUtil'; import { getImageWithResolutionAndFallback, ImageQuality, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/IngestionUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/IngestionUtils.tsx index 4a269eb07ec4..9ada1f5f2789 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/IngestionUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/IngestionUtils.tsx @@ -47,8 +47,7 @@ import { } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; import { SearchSourceAlias } from '../interface/search.interface'; import { DataObj, ServicesType } from '../interface/service.interface'; -import { Transi18next } from './CommonUtils'; -import i18n from './i18next/LocalUtil'; +import i18n, { Transi18next } from './i18next/LocalUtil'; import { getSchemaByWorkflowType } from './IngestionWorkflowUtils'; import { getServiceDetailsPath, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx index f3945a27c9a0..4e239e13d188 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx @@ -42,10 +42,10 @@ import { } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; import { DataInsightCustomChartResult } from '../rest/DataInsightAPI'; import i18n from '../utils/i18next/LocalUtil'; -import { Transi18next } from './CommonUtils'; import documentationLinksClassBase from './DocumentationLinksClassBase'; import { getEntityNameLabel } from './EntityUtils'; import Fqn from './Fqn'; +import { Transi18next } from './i18next/LocalUtil'; import { getEntityIcon } from './TableUtils'; const { t } = i18n; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx index aab82b3ba336..246e025052f1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx @@ -14,7 +14,7 @@ import i18next from 'i18next'; import { EntityTabs } from '../enums/entity.enum'; import { CurrentTourPageType } from '../enums/tour.enum'; -import { Transi18next } from './CommonUtils'; +import { Transi18next } from './i18next/LocalUtil'; interface ArgObject { searchTerm: string; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx similarity index 88% rename from openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts rename to openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index c702fa235fd2..19044e9bc062 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -13,7 +13,8 @@ import i18n, { t as i18nextT } from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; -import { initReactI18next } from 'react-i18next'; +import { ReactNode } from 'react'; +import { initReactI18next, Trans } from 'react-i18next'; import { getInitOptions, languageMap } from './i18nextUtil'; import { SupportedLocales } from './LocalUtil.interface'; @@ -79,4 +80,19 @@ export const translateWithNestedKeys = ( return t(label, translatedParams); }; +export const Transi18next = ({ + i18nKey, + values, + renderElement, + ...otherProps +}: { + i18nKey: string; + values?: object; + renderElement: ReactNode; +}): JSX.Element => ( + + {renderElement} + +); + export default i18n; From fcb911a8c05a94eb8daecdff03dddf0607538aae Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:16:20 +0530 Subject: [PATCH 11/47] run checkstyle --- .../ui/src/components/AppRouter/AppRouter.tsx | 20 +++++-- .../components/AppRouter/AuthenticatedApp.tsx | 52 +++++++++++-------- .../Auth/AuthProviders/AuthProvider.tsx | 4 +- .../AddDataQualityTest/TestSuiteIngestion.tsx | 2 +- .../components/TestCaseFormV1.tsx | 2 +- .../Layout/CarouselLayout/CarouselLayout.tsx | 4 +- .../Team/TeamDetails/TeamDetailsV1.tsx | 2 +- .../ui/src/constants/LoginClassBase.ts | 6 +-- .../ui/src/hoc/withDomainFilter.test.tsx | 12 +++++ .../resources/ui/src/hoc/withDomainFilter.tsx | 12 +++++ .../resources/ui/src/utils/DomainUtils.tsx | 2 +- .../main/resources/ui/src/utils/FeedUtils.tsx | 2 +- .../resources/ui/src/utils/TableUtils.tsx | 11 ++-- 13 files changed, 91 insertions(+), 40 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index feea5b4e6df2..425604af136b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -88,7 +88,10 @@ const AppRouter = () => { return ( - } path={APP_ROUTER_ROUTES.NOT_FOUND} /> + } + path={APP_ROUTER_ROUTES.NOT_FOUND} + /> } path={APP_ROUTER_ROUTES.LOGOUT} /> } @@ -104,7 +107,10 @@ const AppRouter = () => { } path={APP_ROUTER_ROUTES.SIGNUP} /> - } path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> + } + path={APP_ROUTER_ROUTES.AUTH_CALLBACK} + /> } path="*" /> @@ -116,7 +122,10 @@ const AppRouter = () => { } path={APP_ROUTER_ROUTES.NOT_FOUND} /> } path={APP_ROUTER_ROUTES.LOGOUT} /> - } path={APP_ROUTER_ROUTES.UNAUTHORISED} /> + } + path={APP_ROUTER_ROUTES.UNAUTHORISED} + /> { } path={APP_ROUTER_ROUTES.SIGNUP} /> - } path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> + } + path={APP_ROUTER_ROUTES.AUTH_CALLBACK} + /> } path="*" /> ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx index 3479a4712924..221f117ef007 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx @@ -19,8 +19,10 @@ import { } from '@openmetadata/ui-core-components'; import { SnackbarProvider } from 'notistack'; import { FC, useMemo } from 'react'; +import { RouterProvider } from 'react-aria-components'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; +import { useNavigate } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; import { DEFAULT_THEME } from '../../constants/Appearance.constants'; import AirflowStatusProvider from '../../context/AirflowStatusProvider/AirflowStatusProvider'; @@ -35,6 +37,12 @@ import { EntityExportModalProvider } from '../Entity/EntityExportModalProvider/E import ApplicationsProvider from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import WebAnalyticsProvider from '../WebAnalytics/WebAnalyticsProvider'; +const ReactAriaRouterBridge = ({ children }: { children: ReactNode }) => { + const navigate = useNavigate(); + + return {children}; +}; + interface AuthenticatedAppProps { children: React.ReactNode; } @@ -69,27 +77,29 @@ const AuthenticatedApp: FC = ({ children }) => { }} autoHideDuration={6000} maxSnack={3}> - - - - - - - - - - - {children} - - - - - - - - - - + + + + + + + + + + + + {children} + + + + + + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index cd1124011645..2173dab7a9a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -14,8 +14,8 @@ import { removeSession } from '@analytics/session-utils'; import { Configuration, - type IPublicClientApplication, PublicClientApplication, + type IPublicClientApplication, } from '@azure/msal-browser'; import { AxiosError, @@ -40,8 +40,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; import { - REDIRECT_PATHNAME, APP_ROUTER_ROUTES as ROUTES, + REDIRECT_PATHNAME, } from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx index 9083f9868f51..3ec62e8b63af 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/TestSuiteIngestion.tsx @@ -26,8 +26,8 @@ import { useLimitStore } from '../../../context/LimitsProvider/useLimitsStore'; import { FormSubmitType } from '../../../enums/form.enum'; import { IngestionActionMessage } from '../../../enums/ingestion.enum'; import { - FluffyType as ConfigType, CreateIngestionPipeline, + FluffyType as ConfigType, PipelineType, } from '../../../generated/api/services/ingestionPipelines/createIngestionPipeline'; import { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx index 85b2cb77684f..80da4b0d0e98 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.tsx @@ -65,8 +65,8 @@ import { SearchIndex } from '../../../../enums/search.enum'; import { ServiceCategory } from '../../../../enums/service.enum'; import { TagSource } from '../../../../generated/api/domains/createDataProduct'; import { - FluffyType as ConfigType, CreateIngestionPipeline, + FluffyType as ConfigType, PipelineType, } from '../../../../generated/api/services/ingestionPipelines/createIngestionPipeline'; import { CreateTestCase } from '../../../../generated/api/tests/createTestCase'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx index 45c2c245d238..0c8c4c912cc6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx @@ -17,7 +17,9 @@ import { lazy, ReactNode, Suspense } from 'react'; import DocumentTitle from '../../common/DocumentTitle/DocumentTitle'; import './carousel-layout.less'; -const LoginCarousel = lazy(() => import('../../../pages/LoginPage/LoginCarousel')); +const LoginCarousel = lazy( + () => import('../../../pages/LoginPage/LoginCarousel') +); export const CarouselLayout = ({ pageTitle, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx index 9b203ffc3c18..f1761eff1861 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamDetailsV1.tsx @@ -57,8 +57,8 @@ import { SearchIndex } from '../../../../enums/search.enum'; import { OwnerType } from '../../../../enums/user.enum'; import { Team, TeamType } from '../../../../generated/entity/teams/team'; import { - User, EntityReference as UserTeams, + User, } from '../../../../generated/entity/teams/user'; import { EntityReference } from '../../../../generated/type/entityReference'; import { useAuth } from '../../../../hooks/authHooks'; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts index bdf37576584b..4bc6ff07ebda 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts @@ -17,9 +17,9 @@ class LoginClassBase { { title: 'governance', imagePath: () => - import( - '../assets/img/login-screen/governance/governance.png' - ).then((m) => m.default), + import('../assets/img/login-screen/governance/governance.png').then( + (m) => m.default + ), descriptionKey: 'assess-data-reliability-with-data-profiler-lineage', }, { diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx index 8f52ef3a0e10..7a7dedac4a04 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx @@ -1,3 +1,15 @@ +/* + * 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 { InternalAxiosRequestConfig } from 'axios'; import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; import { SearchIndex } from '../enums/search.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx index db6396e47482..a15e3e32e9ed 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx @@ -1,3 +1,15 @@ +/* + * 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 { InternalAxiosRequestConfig } from 'axios'; import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; import { SearchIndex } from '../enums/search.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx index fd0dcf2967e5..2934d81423d2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx @@ -12,7 +12,7 @@ */ import { Tooltip, TooltipTrigger } from '@openmetadata/ui-core-components'; import { InfoCircle } from '@untitledui/icons'; -import { Tooltip as AntDTooltip, Divider, Space, Typography } from 'antd'; +import { Divider, Space, Tooltip as AntDTooltip, Typography } from 'antd'; import classNames from 'classnames'; import { get, isEmpty, isUndefined, noop } from 'lodash'; import { Fragment, ReactNode } from 'react'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx index 0e7091adefa9..40e34a29e593 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx @@ -26,12 +26,12 @@ import { ReactComponent as UpdatedIcon } from '../assets/svg/updated-icon.svg'; import { MentionSuggestionsItem } from '../components/ActivityFeed/FeedEditor/FeedEditor.interface'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { - ENTITY_URL_MAP, EntityField, entityLinkRegEx, EntityRegEx, entityRegex, EntityUrlMapType, + ENTITY_URL_MAP, hashtagRegEx, linkRegEx, mentionRegEx, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 6dfe93a8265d..70ed6e85fe7f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -138,10 +138,6 @@ import { CommonWidgets } from '../components/DataAssets/CommonWidgets/CommonWidg import SampleDataTableComponent from '../components/Database/SampleDataTable/SampleDataTable.component'; import SchemaTable from '../components/Database/SchemaTable/SchemaTable.component'; import TableQueries from '../components/Database/TableQueries/TableQueries'; - -const DataObservabilityTab = lazy( - () => import('../components/Database/Profiler/DataObservability/DataObservabilityTab') -); import { ContractTab } from '../components/DataContract/ContractTab/ContractTab'; import { useEntityExportModalProvider } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import KnowledgeGraph from '../components/KnowledgeGraph/KnowledgeGraph'; @@ -208,6 +204,13 @@ import { TableDetailPageTabProps } from './TableClassBase'; import { TableFieldsInfoCommonEntities } from './TableUtils.interface'; import { extractTopicFields } from './TopicDetailsUtils'; +const DataObservabilityTab = lazy( + () => + import( + '../components/Database/Profiler/DataObservability/DataObservabilityTab' + ) +); + const EntityLineageTab = lazy(() => import('../components/Lineage/EntityLineageTab/EntityLineageTab').then( (module) => ({ default: module.EntityLineageTab }) From 6874953f2e51fcbb8aea9a3d5899a3ea958b37ea Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:15:26 +0530 Subject: [PATCH 12/47] fix build --- .../src/main/resources/ui/src/AppRoot.tsx | 32 ++++-- .../components/AppRouter/AuthenticatedApp.tsx | 105 +++++++----------- .../Auth/AuthProviders/AuthProvider.tsx | 7 +- .../DataQualityTab/DataQualityTab.tsx | 6 +- 4 files changed, 65 insertions(+), 85 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index 4f435951a55f..fb7065674556 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -11,15 +11,19 @@ * limitations under the License. */ +import { GlobalStyles, ThemeProvider } from '@mui/material'; +import { createMuiTheme } from '@openmetadata/ui-core-components'; import { isEmpty } from 'lodash'; -import { FC, useEffect } from 'react'; +import { FC, useEffect, useMemo } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; import App from './App'; import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; +import { DEFAULT_THEME } from './constants/Appearance.constants'; import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; +import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider'; import { useApplicationStore } from './hooks/useApplicationStore'; import { getCustomUiThemePreference, @@ -32,10 +36,6 @@ import { getThemeConfig } from './utils/ThemeUtils'; const AppRoot: FC = () => { const { initializeAuthState } = useApplicationStore(); - useEffect(() => { - initializeAuthState(); - }, [initializeAuthState]); - const { applicationConfig, setApplicationConfig, setRdfEnabled } = useApplicationStore( useShallow((state) => ({ @@ -66,6 +66,7 @@ const AppRoot: FC = () => { useEffect(() => { fetchApplicationConfig(); + initializeAuthState(); }, []); useEffect(() => { @@ -84,17 +85,28 @@ const AppRoot: FC = () => { } }, [applicationConfig]); + const muiTheme = useMemo( + () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), + [applicationConfig?.customTheme] + ); + return (
- - - - - + + + + + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx index 221f117ef007..56fa99440bf1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx @@ -11,28 +11,19 @@ * limitations under the License. */ -import GlobalStyles from '@mui/material/GlobalStyles'; -import { ThemeProvider } from '@mui/material/styles'; -import { - createMuiTheme, - SnackbarContent, -} from '@openmetadata/ui-core-components'; +import { SnackbarContent } from '@openmetadata/ui-core-components'; import { SnackbarProvider } from 'notistack'; -import { FC, useMemo } from 'react'; +import { FC, ReactNode } from 'react'; import { RouterProvider } from 'react-aria-components'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { useNavigate } from 'react-router-dom'; -import { useShallow } from 'zustand/react/shallow'; -import { DEFAULT_THEME } from '../../constants/Appearance.constants'; import AirflowStatusProvider from '../../context/AirflowStatusProvider/AirflowStatusProvider'; import AsyncDeleteProvider from '../../context/AsyncDeleteProvider/AsyncDeleteProvider'; import PermissionProvider from '../../context/PermissionProvider/PermissionProvider'; import RuleEnforcementProvider from '../../context/RuleEnforcementProvider/RuleEnforcementProvider'; import TourProvider from '../../context/TourProvider/TourProvider'; -import { ThemeProvider as UntitledUIThemeProvider } from '../../context/UntitledUIThemeProvider/theme-provider'; import WebSocketProvider from '../../context/WebSocketProvider/WebSocketProvider'; -import { useApplicationStore } from '../../hooks/useApplicationStore'; import { EntityExportModalProvider } from '../Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import ApplicationsProvider from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import WebAnalyticsProvider from '../WebAnalytics/WebAnalyticsProvider'; @@ -48,61 +39,45 @@ interface AuthenticatedAppProps { } const AuthenticatedApp: FC = ({ children }) => { - const { applicationConfig } = useApplicationStore( - useShallow((state) => ({ - applicationConfig: state.applicationConfig, - })) - ); - - const muiTheme = useMemo( - () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), - [applicationConfig?.customTheme] - ); - return ( - - - - - - - - - - - - - - - - {children} - - - - - - - - - - - - - - + + + + + + + + + + + + + {children} + + + + + + + + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index 2173dab7a9a0..82c998f6aa4a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -40,8 +40,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; import { - APP_ROUTER_ROUTES as ROUTES, REDIRECT_PATHNAME, + APP_ROUTER_ROUTES as ROUTES, } from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; @@ -149,7 +149,6 @@ export const AuthProvider = ({ isApplicationLoading, setApplicationLoading, isAuthenticating, - initializeAuthState, } = useApplicationStore(); const tokenService = useRef(TokenService.getInstance()); @@ -330,10 +329,6 @@ export const AuthProvider = ({ } }; - useEffect(() => { - initializeAuthState(); - }, []); - useEffect(() => { if (authenticatorRef.current?.renewIdToken) { tokenService.current.updateRenewToken( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx index 2fb12279be9d..fffd19b7c25e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx @@ -47,15 +47,13 @@ import { TestSuite } from '../../../../generated/tests/testSuite'; import { TestCasePageTabs } from '../../../../pages/IncidentManager/IncidentManager.interface'; import { getListTestCaseIncidentByStateId } from '../../../../rest/incidentManagerAPI'; import { removeTestCaseFromTestSuite } from '../../../../rest/testAPI'; +import { getNameFromFQN } from '../../../../utils/CommonUtils'; import { getColumnNameFromEntityLink, getEntityName, } from '../../../../utils/EntityUtils'; import { getEntityFQN } from '../../../../utils/FeedUtils'; -import { - getNameFromFQN, - Transi18next, -} from '../../../../utils/i18next/LocalUtil'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getEntityDetailsPath, getTestCaseDetailPagePath, From f3849349ba6e23945e896e26ac1b278bf5257c11 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:35:20 +0530 Subject: [PATCH 13/47] fix css imports --- .../Auth/AuthProviders/AuthProvider.tsx | 2 +- .../Explore/AdvanceSearchModal.component.tsx | 1 - .../Widgets/MyTaskWidget/my-task-widget.less | 10 --------- .../QueryBuilderWidget/QueryBuilderWidget.tsx | 21 ++++++++----------- .../ui/src/pages/MyDataPage/my-data.less | 6 ++++++ 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index 82c998f6aa4a..c7bbf296e079 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -40,8 +40,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; import { - REDIRECT_PATHNAME, APP_ROUTER_ROUTES as ROUTES, + REDIRECT_PATHNAME, } from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx index 1c48413543ab..e29185044e39 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/AdvanceSearchModal.component.tsx @@ -13,7 +13,6 @@ import { Builder, Query } from '@react-awesome-query-builder/antd'; import { Button, Modal, Space, Typography } from 'antd'; -import 'antd/dist/antd.css'; import { FunctionComponent } from 'react'; import { useTranslation } from 'react-i18next'; import './advanced-search-modal.less'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/MyTaskWidget/my-task-widget.less b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/MyTaskWidget/my-task-widget.less index eaa06b019f9b..73289d967edb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/MyTaskWidget/my-task-widget.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/MyTaskWidget/my-task-widget.less @@ -115,18 +115,8 @@ } .widget-header-options { - padding: 10px; - height: 40px; min-width: 40px; max-width: 150px; - border-radius: 8px; - border: 1px solid @grey-15; - font-size: 20px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - background-color: @white; &:hover { background-color: @grey-1; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index e919f8060cd0..1e71278e1140 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -11,6 +11,14 @@ * limitations under the License. */ import { InfoCircleOutlined } from '@ant-design/icons'; +import { + Actions, + Builder, + Config, + ImmutableTree, + Utils as QbUtils, + Query, +} from '@react-awesome-query-builder/antd'; import { WidgetProps } from '@rjsf/utils'; import { Alert, @@ -23,19 +31,8 @@ import { Typography, } from 'antd'; import classNames from 'classnames'; -import { useEffect } from 'react'; - -import { - Actions, - Builder, - Config, - ImmutableTree, - Query, - Utils as QbUtils, -} from '@react-awesome-query-builder/antd'; -import 'antd/dist/antd.css'; import { debounce, isEmpty, isUndefined } from 'lodash'; -import { FC, useCallback, useMemo, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { EntityType } from '../../../../../../enums/entity.enum'; import { SearchIndex } from '../../../../../../enums/search.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/my-data.less b/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/my-data.less index dd1ff49ff7e9..7bc860d0f1e7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/my-data.less +++ b/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/my-data.less @@ -70,3 +70,9 @@ scrollbar-color: @grey-300 transparent; } } + +.grid-wrapper { + .grid-container { + margin-top: -220px; + } +} From 0b78f069d635141f24af61c1b426b6498f73edfa Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:59:01 +0530 Subject: [PATCH 14/47] update --- .../src/main/resources/ui/src/AppRoot.tsx | 27 +---- .../components/AppRouter/AuthenticatedApp.tsx | 105 +++++++++++------- .../main/resources/ui/src/rest/searchAPI.ts | 2 +- .../resources/ui/src/utils/SearchUtils.tsx | 48 ++++---- .../resources/ui/src/utils/StringsUtils.ts | 82 +++++++------- 5 files changed, 133 insertions(+), 131 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index fb7065674556..fd614fdd0ad0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -11,19 +11,15 @@ * limitations under the License. */ -import { GlobalStyles, ThemeProvider } from '@mui/material'; -import { createMuiTheme } from '@openmetadata/ui-core-components'; import { isEmpty } from 'lodash'; -import { FC, useEffect, useMemo } from 'react'; +import { FC, useEffect } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; import App from './App'; import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary'; -import { DEFAULT_THEME } from './constants/Appearance.constants'; import AntDConfigProvider from './context/AntDConfigProvider/AntDConfigProvider'; -import { ThemeProvider as UntitledUIThemeProvider } from './context/UntitledUIThemeProvider/theme-provider'; import { useApplicationStore } from './hooks/useApplicationStore'; import { getCustomUiThemePreference, @@ -85,28 +81,17 @@ const AppRoot: FC = () => { } }, [applicationConfig]); - const muiTheme = useMemo( - () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), - [applicationConfig?.customTheme] - ); - return (
- - - - - - - - - - + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx index 56fa99440bf1..e83f2abf2cb9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedApp.tsx @@ -11,22 +11,30 @@ * limitations under the License. */ -import { SnackbarContent } from '@openmetadata/ui-core-components'; +import { GlobalStyles, ThemeProvider } from '@mui/material'; +import { + createMuiTheme, + SnackbarContent, +} from '@openmetadata/ui-core-components'; import { SnackbarProvider } from 'notistack'; -import { FC, ReactNode } from 'react'; +import { FC, ReactNode, useMemo } from 'react'; import { RouterProvider } from 'react-aria-components'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { useNavigate } from 'react-router-dom'; +import { useShallow } from 'zustand/react/shallow'; +import { DEFAULT_THEME } from '../../constants/Appearance.constants'; import AirflowStatusProvider from '../../context/AirflowStatusProvider/AirflowStatusProvider'; import AsyncDeleteProvider from '../../context/AsyncDeleteProvider/AsyncDeleteProvider'; import PermissionProvider from '../../context/PermissionProvider/PermissionProvider'; import RuleEnforcementProvider from '../../context/RuleEnforcementProvider/RuleEnforcementProvider'; import TourProvider from '../../context/TourProvider/TourProvider'; import WebSocketProvider from '../../context/WebSocketProvider/WebSocketProvider'; +import { useApplicationStore } from '../../hooks/useApplicationStore'; import { EntityExportModalProvider } from '../Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import ApplicationsProvider from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import WebAnalyticsProvider from '../WebAnalytics/WebAnalyticsProvider'; +import { ThemeProvider as UntitledUIThemeProvider } from './../../context/UntitledUIThemeProvider/theme-provider'; const ReactAriaRouterBridge = ({ children }: { children: ReactNode }) => { const navigate = useNavigate(); @@ -39,45 +47,62 @@ interface AuthenticatedAppProps { } const AuthenticatedApp: FC = ({ children }) => { + const { applicationConfig } = useApplicationStore( + useShallow((state) => ({ + applicationConfig: state.applicationConfig, + })) + ); + + const muiTheme = useMemo( + () => createMuiTheme(applicationConfig?.customTheme, DEFAULT_THEME), + [applicationConfig?.customTheme] + ); + return ( - - - - - - - - - - - - - {children} - - - - - - - - - - - - + + + + + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts index 232a99ac2260..a40c31700b4d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/searchAPI.ts @@ -23,7 +23,7 @@ import { SearchResponse, } from '../interface/search.interface'; import { omitDeep } from '../utils/APIUtils'; -import { getQueryWithSlash } from '../utils/SearchUtils'; +import { getQueryWithSlash } from '../utils/StringsUtils'; import APIClient from './index'; const getSearchIndexParam: ( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx index a1b8747edf65..a0d3f6f46f17 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx @@ -44,10 +44,6 @@ import { ElasticsearchQuery } from './QueryBuilderUtils'; import searchClassBase from './SearchClassBase'; import serviceUtilClassBase from './ServiceUtilClassBase'; -// will add back slash "\" before quote in string if present -export const getQueryWithSlash = (query: string): string => - query.replace(/["']/g, '\\$&'); - export const getGroupLabel = (index: string) => { let label = ''; let GroupIcon; @@ -224,7 +220,7 @@ export const getSuggestionElement = ( @@ -397,16 +393,14 @@ export const getTermQuery = ( wildcardMustNotQueries?: Record; } ) => { - const termQueries = Object.entries(terms) - .map(([field, value]) => { - const nestedPath = getNestedPath(field); - if (Array.isArray(value)) { - return value.map((v) => wrapTermQuery(field, v, nestedPath)); - } + const termQueries = Object.entries(terms).flatMap(([field, value]) => { + const nestedPath = getNestedPath(field); + if (Array.isArray(value)) { + return value.map((v) => wrapTermQuery(field, v, nestedPath)); + } - return wrapTermQuery(field, value, nestedPath); - }) - .flat(); + return wrapTermQuery(field, value, nestedPath); + }); const wildcardQueries = options?.wildcardTerms ? Object.entries(options.wildcardTerms).map(([field, value]) => ({ @@ -415,16 +409,14 @@ export const getTermQuery = ( : []; const mustNotQueries = options?.mustNotTerms - ? Object.entries(options.mustNotTerms) - .map(([field, value]) => { - const nestedPath = getNestedPath(field); - if (Array.isArray(value)) { - return value.map((v) => wrapTermQuery(field, v, nestedPath)); - } - - return wrapTermQuery(field, value, nestedPath); - }) - .flat() + ? Object.entries(options.mustNotTerms).flatMap(([field, value]) => { + const nestedPath = getNestedPath(field); + if (Array.isArray(value)) { + return value.map((v) => wrapTermQuery(field, v, nestedPath)); + } + + return wrapTermQuery(field, value, nestedPath); + }) : []; const matchQueries = options?.matchTerms @@ -470,15 +462,15 @@ export const getTermQuery = ( // Handle wildcardMustNotQueries const wildcardMustNotQueries = options?.wildcardMustNotQueries - ? Object.entries(options.wildcardMustNotQueries) - .map(([field, value]) => { + ? Object.entries(options.wildcardMustNotQueries).flatMap( + ([field, value]) => { if (Array.isArray(value)) { return value.map((v) => ({ wildcard: { [field]: v } })); } return { wildcard: { [field]: value } }; - }) - .flat() + } + ) : []; const allMustNotQueries = [...mustNotQueries, ...wildcardMustNotQueries]; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts index 20dd50021d26..3771251103b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts @@ -17,9 +17,13 @@ import { get, isString } from 'lodash'; import i18n from './i18next/LocalUtil'; export const stringToSlug = (dataString: string, slugString = '') => { - return dataString.toLowerCase().replace(/ /g, slugString); + return dataString.toLowerCase().replaceAll(' ', slugString); }; +// will add back slash "\" before quote in string if present +export const getQueryWithSlash = (query: string): string => + query.replaceAll(/["']/g, String.raw`\$&`); + /** * Convert a template string into HTML DOM nodes * Same as React.createElement(type, options, children) @@ -66,8 +70,10 @@ export const getJSONFromString = (data: string): string | null => { try { // Format string if possible and return valid JSON return JSON.parse(data); - } catch (e) { - // Invalid JSON, return null + } catch (error) { + // eslint-disable-next-line no-console + console.error('Invalid JSON string:', error); + return null; } }; @@ -87,7 +93,7 @@ export const bytesToSize = (bytes: number) => { } else if (bytes < 0) { return `N/A`; } else { - const i = parseInt( + const i = Number.parseInt( Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10 ); @@ -158,15 +164,6 @@ export const getDecodedFqn = (fqn: string, plusAsSpace = false) => { return uri; }; -/** - * - * @param url - Url to be check - * @returns - True if url is external otherwise false - */ -export const isExternalUrl = (url = '') => { - return /^https?:\/\//.test(url); -}; - /** * * @param a compare value one @@ -189,41 +186,41 @@ export const customServiceComparator = (a: string, b: string): number => { export const replacePlus = (fqn: string) => fqn.replaceAll('+', ' '); export const ES_RESERVED_CHARACTERS: Record = { - '+': '\\+', - '-': '\\-', - '=': '\\=', - '&': '\\&', - '&&': '\\&&', - '||': '\\||', - '>': '\\>', - '<': '\\<', - '!': '\\!', - '(': '\\(', - ')': '\\)', - '{': '\\{', - '}': '\\}', - '[': '\\[', - ']': '\\]', - '^': '\\^', - '"': '\\"', - '~': '\\~', - '*': '\\*', - '?': '\\?', - ':': '\\:', - '\\': '\\\\', - '/': '\\/', + '+': String.raw`\+`, + '-': String.raw`\-`, + '=': String.raw`\=`, + '&': String.raw`\&`, + '&&': String.raw`\&&`, + '||': String.raw`\||`, + '>': String.raw`\>`, + '<': String.raw`\<`, + '!': String.raw`\!`, + '(': String.raw`\(`, + ')': String.raw`\)`, + '{': String.raw`\{`, + '}': String.raw`\}`, + '[': String.raw`\[`, + ']': String.raw`\]`, + '^': String.raw`\^`, + '"': String.raw`\"`, + '~': String.raw`\~`, + '*': String.raw`\*`, + '?': String.raw`\?`, + ':': String.raw`\:`, + '\\': String.raw`\\`, + '/': String.raw`\/`, }; export const escapeESReservedCharacters = (text?: string) => { const reUnescapedHtml = /[\\[\]#+=&|> { return ES_RESERVED_CHARACTERS[char] ?? char; }; return text && reHasUnescapedHtml.test(text) - ? text.replace(reUnescapedHtml, getReplacedChar) + ? text.replaceAll(reUnescapedHtml, getReplacedChar) : text ?? ''; }; @@ -252,6 +249,9 @@ export const formatJsonString = (jsonString: string, indent = '') => { return formattedJson; } catch (error) { + // eslint-disable-next-line no-console + console.error('Invalid JSON string:', error); + // Return the original JSON string if parsing fails return jsonString; } @@ -276,7 +276,7 @@ export const replaceCallback = (character: string) => { * @returns A UUID string */ export const generateUUID = () => { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replaceAll( /[xy]/g, replaceCallback ); @@ -315,7 +315,7 @@ export const jsonToCSV = ( } const escaped = typeof value === 'string' - ? value.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + ? value.replaceAll('\\', '\\\\').replaceAll('"', String.raw`\"`) : value.toString(); // handle quotes in content return `"${escaped}"`; // wrap each field in quotes @@ -350,7 +350,7 @@ export function removeAttachmentsWithoutUrl(htmlString: string): string { doc.querySelectorAll('div[data-type="file-attachment"]'); attachments.forEach((div: HTMLDivElement) => { - const url: string | null = div.getAttribute('data-url'); + const url = div.dataset.url; if (!url) { div.remove(); } From 0ec3718d7bfa21567f7643ba6d05cd0a7ed7243d Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:49:04 +0530 Subject: [PATCH 15/47] improve imports --- .../Auth/AuthProviders/AuthProvider.tsx | 68 +-- .../DomainDetails/DomainDetails.component.tsx | 3 +- .../ui/src/constants/ServiceType.constant.ts | 303 ++++++++++ .../src/constants/ServiceUISchema.constant.ts | 79 +++ .../ui/src/constants/Services.constant.ts | 570 +----------------- .../AddGlossary/AddGlossaryPage.component.tsx | 2 +- .../AddPolicyPage/AddPolicyPage.tsx | 2 +- .../RolesPage/AddRolePage/AddRolePage.tsx | 3 +- .../resources/ui/src/utils/APIServiceUtils.ts | 2 +- .../main/resources/ui/src/utils/APIUtils.ts | 21 +- .../ui/src/utils/AuthProvider.util.ts | 19 +- .../resources/ui/src/utils/CommonUtils.tsx | 18 - .../ui/src/utils/CoverImageUploadUtils.tsx | 2 +- .../ui/src/utils/DashboardServiceUtils.ts | 204 +++---- .../ui/src/utils/DatabaseServiceUtils.tsx | 482 ++++++--------- .../ui/src/utils/DriveServiceUtils.ts | 2 +- .../ui/src/utils/MessagingServiceUtils.ts | 70 +-- .../ui/src/utils/MetadataServiceUtils.ts | 2 +- .../ui/src/utils/MlmodelServiceUtils.ts | 58 +- .../ui/src/utils/OktaCustomStorage.ts | 10 +- .../ui/src/utils/PipelineServiceUtils.ts | 105 +++- .../ui/src/utils/SearchServiceUtils.ts | 46 +- .../ui/src/utils/SecurityServiceUtils.ts | 19 +- .../ui/src/utils/ServiceIconUtils.ts | 168 ++++++ .../ui/src/utils/ServiceUtilClassBase.ts | 242 +------- .../ui/src/utils/StorageServiceUtils.ts | 42 +- .../resources/ui/src/utils/Users.util.tsx | 2 +- 27 files changed, 1122 insertions(+), 1422 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/constants/ServiceType.constant.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/constants/ServiceUISchema.constant.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index c67fcbab44aa..b346eac19aea 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -12,10 +12,9 @@ */ import { removeSession } from '@analytics/session-utils'; -import { +import type { Configuration, - PublicClientApplication, - type IPublicClientApplication, + IPublicClientApplication, } from '@azure/msal-browser'; import { AxiosError, @@ -40,8 +39,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; import { - APP_ROUTER_ROUTES as ROUTES, REDIRECT_PATHNAME, + APP_ROUTER_ROUTES as ROUTES, } from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; @@ -116,7 +115,11 @@ const isEmailVerifyField = 'isEmailVerified'; let requestInterceptor: number | null = null; let responseInterceptor: number | null = null; -let pendingRequests: any[] = []; +let pendingRequests: { + resolve: (value?: unknown) => void; + reject: (reason?: unknown) => void; + config: InternalAxiosRequestConfig; +}[] = []; type AuthContextType = { onLoginHandler: () => void; @@ -383,15 +386,15 @@ export const AuthProvider = ({ } catch (error) { const err = error as AxiosError; if (err?.response?.status === 404) { - if (!authConfig?.enableSelfSignup) { - resetUserDetails(); - navigate(ROUTES.UNAUTHORISED); - showErrorToast(err); - } else { + if (authConfig?.enableSelfSignup) { setNewUserProfile(user.profile); setCurrentUser({} as User); setIsSigningUp(true); navigate(ROUTES.SIGNUP); + } else { + resetUserDetails(); + navigate(ROUTES.UNAUTHORISED); + showErrorToast(err); } } else { // eslint-disable-next-line no-console @@ -431,20 +434,17 @@ export const AuthProvider = ({ configJson: AuthenticationConfiguration ) => { const { provider, ...otherConfigs } = configJson; - switch (provider) { - case AuthProviderEnum.Azure: - { - const instance = new PublicClientApplication( - otherConfigs as unknown as Configuration - ); - - // Need to initialize the instance before setting it - await instance.initialize(); + if (provider === AuthProviderEnum.Azure) { + const AzureBrowser = await import('@azure/msal-browser'); + const { PublicClientApplication } = AzureBrowser; + const instance = new PublicClientApplication( + otherConfigs as unknown as Configuration + ); - setMsalInstance(instance); - } + // Need to initialize the instance before setting it + await instance.initialize(); - break; + setMsalInstance(instance); } }; @@ -463,7 +463,7 @@ export const AuthProvider = ({ } requestInterceptor = axiosClient.interceptors.request.use(async function ( - config: InternalAxiosRequestConfig + config: InternalAxiosRequestConfig ) { // Need to read token from local storage as it might have been updated with refresh const token: string = await getOidcToken(); @@ -503,7 +503,16 @@ export const AuthProvider = ({ handleStoreProtectedRedirectPath(); // If 401 error and refresh is not in progress, trigger the refresh - if (!tokenService.current?.isTokenUpdateInProgress()) { + if (tokenService.current?.isTokenUpdateInProgress()) { + // If refresh is in progress, queue the request + return new Promise((resolve, reject) => { + pendingRequests.push({ + resolve, + reject, + config: error.config, + }); + }); + } else { // Start the refresh process return new Promise((resolve, reject) => { // Add this request to the pending queue @@ -536,15 +545,6 @@ export const AuthProvider = ({ return Promise.reject(error); }); }); - } else { - // If refresh is in progress, queue the request - return new Promise((resolve, reject) => { - pendingRequests.push({ - resolve, - reject, - config: error.config, - }); - }); } } } @@ -644,7 +644,7 @@ export const AuthProvider = ({ + redirectUri={authConfig.callbackUrl?.toString() ?? ''}> {childElement} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx index ebcf55aead72..905a3c122863 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetails/DomainDetails.component.tsx @@ -64,7 +64,7 @@ import { import { addDomains, patchDomains } from '../../../rest/domainAPI'; import { getActiveAnnouncement } from '../../../rest/feedsAPI'; import { searchQuery } from '../../../rest/searchAPI'; -import { getFeedCounts, getIsErrorMatch } from '../../../utils/CommonUtils'; +import { getFeedCounts } from '../../../utils/CommonUtils'; import { createEntityWithCoverImage } from '../../../utils/CoverImageUploadUtils'; import { checkIfExpandViewSupported, @@ -107,6 +107,7 @@ import { useBreadcrumbs } from '../../common/atoms/navigation/useBreadcrumbs'; import { Avatar } from '@openmetadata/ui-core-components'; import { LEARNING_PAGE_IDS } from '../../../constants/Learning.constants'; import { FeedCounts } from '../../../interface/feed.interface'; +import { getIsErrorMatch } from '../../../utils/APIUtils'; import { getEntityAvatarProps } from '../../../utils/IconUtils'; import { withActivityFeed } from '../../AppRouter/withActivityFeed'; import { CoverImage } from '../../common/CoverImage/CoverImage.component'; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/ServiceType.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/ServiceType.constant.ts new file mode 100644 index 000000000000..5a93c8f02c7f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/constants/ServiceType.constant.ts @@ -0,0 +1,303 @@ +/* + * Copyright 2022 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 { map, startCase } from 'lodash'; +import { ServiceTypes, StepperStepType } from 'Models'; +import { EntityType } from '../enums/entity.enum'; +import { ServiceCategory } from '../enums/service.enum'; +import { PipelineType } from '../generated/api/services/ingestionPipelines/createIngestionPipeline'; +import { WorkflowStatus } from '../generated/entity/automations/workflow'; +import { StorageServiceType } from '../generated/entity/data/container'; +import { APIServiceType } from '../generated/entity/services/apiService'; +import { DashboardServiceType } from '../generated/entity/services/dashboardService'; +import { DatabaseServiceType } from '../generated/entity/services/databaseService'; +import { DriveServiceType } from '../generated/entity/services/driveService'; +import { MessagingServiceType } from '../generated/entity/services/messagingService'; +import { MetadataServiceType } from '../generated/entity/services/metadataService'; +import { MlModelServiceType } from '../generated/entity/services/mlmodelService'; +import { PipelineServiceType } from '../generated/entity/services/pipelineService'; +import { SearchServiceType } from '../generated/entity/services/searchService'; +import { Type as SecurityServiceType } from '../generated/entity/services/securityService'; +import { ServiceType } from '../generated/entity/services/serviceType'; + +export const OPEN_METADATA = 'OpenMetadata'; +export const JWT_CONFIG = 'openMetadataJWTClientConfig'; + +export const excludedService = [ + MlModelServiceType.Sklearn, + MetadataServiceType.MetadataES, + MetadataServiceType.OpenMetadata, + PipelineServiceType.Spark, +]; + +export const arrServiceTypes: Array = [ + 'databaseServices', + 'messagingServices', + 'dashboardServices', + 'pipelineServices', + 'mlmodelServices', + 'storageServices', + 'apiServices', + 'securityServices', + 'driveServices', +]; + +export const SERVICE_CATEGORY: { [key: string]: ServiceCategory } = { + databases: ServiceCategory.DATABASE_SERVICES, + messaging: ServiceCategory.MESSAGING_SERVICES, + dashboards: ServiceCategory.DASHBOARD_SERVICES, + pipelines: ServiceCategory.PIPELINE_SERVICES, + mlmodels: ServiceCategory.ML_MODEL_SERVICES, + metadata: ServiceCategory.METADATA_SERVICES, + storages: ServiceCategory.STORAGE_SERVICES, + search: ServiceCategory.SEARCH_SERVICES, + apiServices: ServiceCategory.API_SERVICES, + security: ServiceCategory.SECURITY_SERVICES, + drives: ServiceCategory.DRIVE_SERVICES, +}; + +export const servicesDisplayName: Record< + string, + { key: string; entity: string } +> = { + databaseServices: { key: 'label.entity-service', entity: 'label.database' }, + messagingServices: { key: 'label.entity-service', entity: 'label.messaging' }, + dashboardServices: { key: 'label.entity-service', entity: 'label.dashboard' }, + pipelineServices: { key: 'label.entity-service', entity: 'label.pipeline' }, + mlmodelServices: { key: 'label.entity-service', entity: 'label.ml-model' }, + metadataServices: { key: 'label.entity-service', entity: 'label.metadata' }, + storageServices: { key: 'label.entity-service', entity: 'label.storage' }, + searchServices: { key: 'label.entity-service', entity: 'label.search' }, + dashboardDataModel: { + key: 'label.entity-service', + entity: 'label.data-model', + }, + apiServices: { key: 'label.entity-service', entity: 'label.api-uppercase' }, + securityServices: { key: 'label.entity-service', entity: 'label.security' }, + driveServices: { key: 'label.entity-service', entity: 'label.drive' }, +}; + +export const SERVICE_CATEGORY_OPTIONS = map(ServiceCategory, (value) => ({ + label: startCase(value), + value, +})); + +export const STEPS_FOR_ADD_SERVICE: Array = [ + { + name: 'label.select-field', + nameData: { field: 'label.service-type' }, + step: 1, + }, + { + name: 'label.configure-entity', + nameData: { entity: 'label.service' }, + step: 2, + }, + { + name: 'label.connection-entity', + nameData: { entity: 'label.detail-plural' }, + step: 3, + }, + { + name: 'label.set-default-filters', + step: 4, + }, +]; + +export const STEPS_FOR_EDIT_SERVICE: Array = [ + { + name: 'label.connection-entity', + nameData: { entity: 'label.detail-plural' }, + step: 1, + }, + { + name: 'label.set-default-filters', + step: 2, + }, +]; + +export const SERVICE_DEFAULT_ERROR_MAP = { + serviceType: false, +}; + +export const FETCHING_EXPIRY_TIME = 3 * 60 * 1000; +export const FETCH_INTERVAL = 2000; + +export const WORKFLOW_COMPLETE_STATUS = [ + WorkflowStatus.Failed, + WorkflowStatus.Successful, +]; + +export const TEST_CONNECTION_PROGRESS_PERCENTAGE = { + ZERO: 0, + ONE: 1, + TEN: 10, + TWENTY: 20, + FORTY: 40, + HUNDRED: 100, +}; + +export const SERVICE_TYPE_MAP = { + [ServiceCategory.DASHBOARD_SERVICES]: ServiceType.Dashboard, + [ServiceCategory.DATABASE_SERVICES]: ServiceType.Database, + [ServiceCategory.MESSAGING_SERVICES]: ServiceType.Messaging, + [ServiceCategory.ML_MODEL_SERVICES]: ServiceType.MlModel, + [ServiceCategory.METADATA_SERVICES]: ServiceType.Metadata, + [ServiceCategory.STORAGE_SERVICES]: ServiceType.Storage, + [ServiceCategory.PIPELINE_SERVICES]: ServiceType.Pipeline, + [ServiceCategory.SEARCH_SERVICES]: ServiceType.Search, + [ServiceCategory.API_SERVICES]: ServiceType.API, + [ServiceCategory.SECURITY_SERVICES]: ServiceType.Security, + [ServiceCategory.DRIVE_SERVICES]: ServiceType.Drive, +}; + +export const SERVICE_TYPES_ENUM = { + [ServiceCategory.DASHBOARD_SERVICES]: DashboardServiceType, + [ServiceCategory.DATABASE_SERVICES]: DatabaseServiceType, + [ServiceCategory.MESSAGING_SERVICES]: MessagingServiceType, + [ServiceCategory.ML_MODEL_SERVICES]: MlModelServiceType, + [ServiceCategory.METADATA_SERVICES]: MetadataServiceType, + [ServiceCategory.STORAGE_SERVICES]: StorageServiceType, + [ServiceCategory.PIPELINE_SERVICES]: PipelineServiceType, + [ServiceCategory.SEARCH_SERVICES]: SearchServiceType, + [ServiceCategory.API_SERVICES]: APIServiceType, + [ServiceCategory.SECURITY_SERVICES]: SecurityServiceType, + [ServiceCategory.DRIVE_SERVICES]: DriveServiceType, +}; + +export const BETA_SERVICES = [ + PipelineServiceType.OpenLineage, + PipelineServiceType.Wherescape, + DatabaseServiceType.Cassandra, + MetadataServiceType.AlationSink, + DatabaseServiceType.Cockroach, + SearchServiceType.OpenSearch, + PipelineServiceType.Ssis, + DatabaseServiceType.Ssas, + DashboardServiceType.ThoughtSpot, + SecurityServiceType.Ranger, + DatabaseServiceType.Epic, + DashboardServiceType.Grafana, + DashboardServiceType.Hex, + DatabaseServiceType.ServiceNow, + DatabaseServiceType.Timescale, + DatabaseServiceType.Dremio, + MetadataServiceType.Collibra, + PipelineServiceType.Mulesoft, + DatabaseServiceType.MicrosoftFabric, + PipelineServiceType.MicrosoftFabricPipeline, + DatabaseServiceType.BurstIQ, + DatabaseServiceType.StarRocks, + DriveServiceType.SFTP, + DatabaseServiceType.Informix, + DatabaseServiceType.MicrosoftAccess, +]; + +export const TEST_CONNECTION_INITIAL_MESSAGE = + 'message.test-your-connection-before-creating-service'; + +export const TEST_CONNECTION_SUCCESS_MESSAGE = + 'message.connection-test-successful'; + +export const TEST_CONNECTION_FAILURE_MESSAGE = 'message.connection-test-failed'; + +export const TEST_CONNECTION_TESTING_MESSAGE = + 'message.testing-your-connection-may-take-two-minutes'; + +export const TEST_CONNECTION_WARNING_MESSAGE = + 'message.connection-test-warning'; + +export const ADVANCED_PROPERTIES = [ + 'connectionArguments', + 'connectionOptions', + 'scheme', + 'sampleDataStorageConfig', + 'computeTableMetrics', + 'computeColumnMetrics', + 'includeViews', + 'useStatistics', + 'confidence', + 'samplingMethodType', + 'randomizedSample', + 'sampleDataCount', + 'threadCount', + 'timeoutSeconds', + 'metrics', + 'sslConfig', + 'sslMode', + 'schemaRegistrySSL', + 'consumerConfigSSL', + 'verify', + 'useNonce', + 'disablePkce', + 'maxClockSkew', + 'tokenValidity', + 'maxAge', + 'sessionExpiry', +]; + +export const PIPELINE_SERVICE_PLATFORM = 'Airflow'; + +export const SERVICE_TYPES = [ + EntityType.DATABASE_SERVICE, + EntityType.DASHBOARD_SERVICE, + EntityType.MESSAGING_SERVICE, + EntityType.PIPELINE_SERVICE, + EntityType.MLMODEL_SERVICE, + EntityType.METADATA_SERVICE, + EntityType.STORAGE_SERVICE, + EntityType.SEARCH_SERVICE, + EntityType.API_SERVICE, + EntityType.SECURITY_SERVICE, + EntityType.DRIVE_SERVICE, +]; + +export const EXCLUDE_AUTO_PILOT_SERVICE_TYPES = [EntityType.SECURITY_SERVICE]; + +export const SERVICE_INGESTION_PIPELINE_TYPES = [ + PipelineType.Metadata, + PipelineType.Usage, + PipelineType.Lineage, + PipelineType.Profiler, + PipelineType.AutoClassification, + PipelineType.Dbt, +]; + +export const SERVICE_AUTOPILOT_AGENT_TYPES = [ + PipelineType.Metadata, + PipelineType.Lineage, + PipelineType.Usage, + PipelineType.AutoClassification, + PipelineType.Profiler, +]; + +export const SERVICE_TYPE_WITH_DISPLAY_NAME = new Map([ + [PipelineServiceType.GluePipeline, 'Glue Pipeline'], + [DatabaseServiceType.DomoDatabase, 'Domo Database'], + [DashboardServiceType.DomoDashboard, 'Domo Dashboard'], + [DashboardServiceType.MicroStrategy, 'Micro Strategy'], + [DashboardServiceType.PowerBIReportServer, 'PowerBI Report Server'], + [PipelineServiceType.DatabricksPipeline, 'Databricks Pipeline'], + [PipelineServiceType.DomoPipeline, 'Domo Pipeline'], + [PipelineServiceType.KafkaConnect, 'Kafka Connect'], + [DatabaseServiceType.SapERP, 'SAP ERP'], + [DatabaseServiceType.SapHana, 'SAP HANA'], + [DatabaseServiceType.UnityCatalog, 'Unity Catalog'], + [PipelineServiceType.DataFactory, 'Data Factory'], + [PipelineServiceType.DBTCloud, 'DBT Cloud'], + [PipelineServiceType.OpenLineage, 'Open Lineage'], + [MetadataServiceType.AlationSink, 'Alation Sink'], + [SearchServiceType.ElasticSearch, 'Elasticsearch'], + [DatabaseServiceType.MicrosoftFabric, 'Microsoft Fabric'], + [PipelineServiceType.MicrosoftFabricPipeline, 'Microsoft Fabric Pipeline'], +]); diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/ServiceUISchema.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/ServiceUISchema.constant.ts new file mode 100644 index 000000000000..5bc2a5ddfe85 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/constants/ServiceUISchema.constant.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2022 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 { ServiceNestedConnectionFields } from '../enums/service.enum'; +import { SERVICE_FILTER_PATTERN_FIELDS } from './ServiceConnection.constants'; + +export const DEF_UI_SCHEMA = { + supportsIncrementalMetadataExtraction: { + 'ui:widget': 'hidden', + 'ui:hideError': true, + }, + supportsMetadataExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsSystemProfile: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsDataDiff: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsUsageExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsLineageExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsViewLineageExtraction: { + 'ui:widget': 'hidden', + 'ui:hideError': true, + }, + supportsProfiler: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsDatabase: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsQueryComment: { 'ui:widget': 'hidden', 'ui:hideError': true }, + supportsDBTExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, + type: { 'ui:widget': 'hidden' }, +}; + +export const INGESTION_ELASTIC_SEARCH_WORKFLOW_UI_SCHEMA = { + useSSL: { 'ui:widget': 'hidden', 'ui:hideError': true }, + verifyCerts: { 'ui:widget': 'hidden', 'ui:hideError': true }, + timeout: { 'ui:widget': 'hidden', 'ui:hideError': true }, + caCerts: { 'ui:widget': 'hidden', 'ui:hideError': true }, + useAwsCredentials: { 'ui:widget': 'hidden', 'ui:hideError': true }, + regionName: { 'ui:widget': 'hidden', 'ui:hideError': true }, +}; + +export const INGESTION_WORKFLOW_UI_SCHEMA = { + type: { 'ui:widget': 'hidden', 'ui:hideError': true }, + name: { 'ui:widget': 'hidden', 'ui:hideError': true }, + processingEngine: { 'ui:widget': 'hidden', 'ui:hideError': true }, + 'ui:order': [ + 'rootProcessingEngine', + 'name', + 'displayName', + ...SERVICE_FILTER_PATTERN_FIELDS, + 'enableDebugLog', + '*', + ], +}; + +export const EXCLUDE_INCREMENTAL_EXTRACTION_SUPPORT_UI_SCHEMA = { + incremental: { + 'ui:widget': 'hidden', + 'ui:hideError': true, + }, +}; + +export const COMMON_UI_SCHEMA = { + ...DEF_UI_SCHEMA, + [ServiceNestedConnectionFields.CONNECTION]: { + ...DEF_UI_SCHEMA, + }, + [ServiceNestedConnectionFields.METASTORE_CONNECTION]: { + ...DEF_UI_SCHEMA, + }, + [ServiceNestedConnectionFields.DATABASE_CONNECTION]: { + ...DEF_UI_SCHEMA, + }, +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts index be9db76bcd3a..1afe9943b1b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts @@ -11,557 +11,21 @@ * limitations under the License. */ -import { map, startCase } from 'lodash'; -import { ServiceTypes, StepperStepType } from 'Models'; -import airbyte from '../assets/img/Airbyte.png'; -import airflow from '../assets/img/service-icon-airflow.png'; -import alationsink from '../assets/img/service-icon-alation-sink.png'; -import amazonS3 from '../assets/img/service-icon-amazon-s3.svg'; -import amundsen from '../assets/img/service-icon-amundsen.png'; -import athena from '../assets/img/service-icon-athena.png'; -import atlas from '../assets/img/service-icon-atlas.svg'; -import azuresql from '../assets/img/service-icon-azuresql.png'; -import bigtable from '../assets/img/service-icon-bigtable.png'; -import burstiq from '../assets/img/service-icon-burstiq.png'; -import cassandra from '../assets/img/service-icon-cassandra.png'; -import clickhouse from '../assets/img/service-icon-clickhouse.png'; -import cockroach from '../assets/img/service-icon-cockroach.png'; -import couchbase from '../assets/img/service-icon-couchbase.svg'; -import dagster from '../assets/img/service-icon-dagster.png'; -import databrick from '../assets/img/service-icon-databrick.png'; -import datalake from '../assets/img/service-icon-datalake.png'; -import dbt from '../assets/img/service-icon-dbt.png'; -import deltalake from '../assets/img/service-icon-delta-lake.png'; -import domo from '../assets/img/service-icon-domo.png'; -import doris from '../assets/img/service-icon-doris.png'; -import druid from '../assets/img/service-icon-druid.png'; -import dynamodb from '../assets/img/service-icon-dynamodb.png'; -import exasol from '../assets/img/service-icon-exasol.png'; -import fivetran from '../assets/img/service-icon-fivetran.png'; -import flink from '../assets/img/service-icon-flink.png'; -import gcs from '../assets/img/service-icon-gcs.png'; -import glue from '../assets/img/service-icon-glue.png'; -import grafana from '../assets/img/service-icon-grafana.png'; -import greenplum from '../assets/img/service-icon-greenplum.png'; -import hive from '../assets/img/service-icon-hive.png'; -import ibmdb2 from '../assets/img/service-icon-ibmdb2.png'; -import impala from '../assets/img/service-icon-impala.png'; -import kafka from '../assets/img/service-icon-kafka.png'; -import kinesis from '../assets/img/service-icon-kinesis.png'; -import lightDash from '../assets/img/service-icon-lightdash.png'; -import looker from '../assets/img/service-icon-looker.png'; -import mariadb from '../assets/img/service-icon-mariadb.png'; -import metabase from '../assets/img/service-icon-metabase.png'; -import microstrategy from '../assets/img/service-icon-microstrategy.svg'; -import mode from '../assets/img/service-icon-mode.png'; -import mongodb from '../assets/img/service-icon-mongodb.png'; -import mssql from '../assets/img/service-icon-mssql.png'; -import nifi from '../assets/img/service-icon-nifi.png'; -import openlineage from '../assets/img/service-icon-openlineage.svg'; -import oracle from '../assets/img/service-icon-oracle.png'; -import pinot from '../assets/img/service-icon-pinot.png'; -import postgres from '../assets/img/service-icon-post.png'; -import powerbi from '../assets/img/service-icon-power-bi.png'; -import presto from '../assets/img/service-icon-presto.png'; -import qlikSense from '../assets/img/service-icon-qlik-sense.png'; -import query from '../assets/img/service-icon-query.png'; -import quicksight from '../assets/img/service-icon-quicksight.png'; -import redash from '../assets/img/service-icon-redash.png'; -import redpanda from '../assets/img/service-icon-redpanda.png'; -import redshift from '../assets/img/service-icon-redshift.png'; -import sagemaker from '../assets/img/service-icon-sagemaker.png'; -import salesforce from '../assets/img/service-icon-salesforce.png'; -import sapErp from '../assets/img/service-icon-sap-erp.png'; -import sapHana from '../assets/img/service-icon-sap-hana.png'; -import sas from '../assets/img/service-icon-sas.svg'; -import scikit from '../assets/img/service-icon-scikit.png'; -import sigma from '../assets/img/service-icon-sigma.png'; -import singlestore from '../assets/img/service-icon-singlestore.png'; -import snowflakes from '../assets/img/service-icon-snowflakes.png'; -import spark from '../assets/img/service-icon-spark.png'; -import spline from '../assets/img/service-icon-spline.png'; -import mysql from '../assets/img/service-icon-sql.png'; -import sqlite from '../assets/img/service-icon-sqlite.png'; -import starrocks from '../assets/img/service-icon-starrocks.png'; -import superset from '../assets/img/service-icon-superset.png'; -import synapse from '../assets/img/service-icon-synapse.png'; -import tableau from '../assets/img/service-icon-tableau.png'; -import timescale from '../assets/img/service-icon-timescale.png'; -import trino from '../assets/img/service-icon-trino.png'; -import unitycatalog from '../assets/img/service-icon-unitycatalog.svg'; -import vertica from '../assets/img/service-icon-vertica.png'; -import dashboardDefault from '../assets/svg/dashboard.svg'; -import iconDefaultService from '../assets/svg/default-service-icon.svg'; -import elasticSearch from '../assets/svg/elasticsearch.svg'; -import databaseDefault from '../assets/svg/ic-custom-database.svg'; -import { default as customDriveDefault } from '../assets/svg/ic-custom-drive.svg'; -import mlModelDefault from '../assets/svg/ic-custom-model.svg'; -import searchDefault from '../assets/svg/ic-custom-search.svg'; -import { default as storageDefault } from '../assets/svg/ic-custom-storage.svg'; -import { default as driveDefault } from '../assets/svg/ic-drive-service.svg'; -import restService from '../assets/svg/ic-service-rest-api.svg'; -import logo from '../assets/svg/logo-monogram.svg'; -import openSearch from '../assets/svg/open-search.svg'; -import pipelineDefault from '../assets/svg/pipeline.svg'; -import securitySafe from '../assets/svg/security-safe.svg'; -import googleDrive from '../assets/svg/service-icon-google-drive.svg'; -import hex from '../assets/svg/service-icon-hex.svg'; -import mlflow from '../assets/svg/service-icon-mlflow.svg'; -import pubsub from '../assets/svg/service-icon-pubsub.svg'; -import sftp from '../assets/svg/service-icon-sftp.svg'; -import teradata from '../assets/svg/teradata.svg'; -import topicDefault from '../assets/svg/topic.svg'; -import { EntityType } from '../enums/entity.enum'; -import { - ServiceCategory, - ServiceNestedConnectionFields, -} from '../enums/service.enum'; -import { PipelineType } from '../generated/api/services/ingestionPipelines/createIngestionPipeline'; -import { WorkflowStatus } from '../generated/entity/automations/workflow'; -import { StorageServiceType } from '../generated/entity/data/container'; -import { APIServiceType } from '../generated/entity/services/apiService'; -import { DashboardServiceType } from '../generated/entity/services/dashboardService'; -import { DatabaseServiceType } from '../generated/entity/services/databaseService'; -import { DriveServiceType } from '../generated/entity/services/driveService'; -import { MessagingServiceType } from '../generated/entity/services/messagingService'; -import { MetadataServiceType } from '../generated/entity/services/metadataService'; -import { MlModelServiceType } from '../generated/entity/services/mlmodelService'; -import { PipelineServiceType } from '../generated/entity/services/pipelineService'; -import { SearchServiceType } from '../generated/entity/services/searchService'; -import { Type as SecurityServiceType } from '../generated/entity/services/securityService'; -import { ServiceType } from '../generated/entity/services/serviceType'; -import { SERVICE_FILTER_PATTERN_FIELDS } from './ServiceConnection.constants'; - -export const MYSQL = mysql; -export const SQLITE = sqlite; -export const MSSQL = mssql; -export const REDSHIFT = redshift; -export const BIGQUERY = query; -export const BIGTABLE = bigtable; -export const HEX = hex; -export const HIVE = hive; -export const IMPALA = impala; -export const POSTGRES = postgres; -export const ORACLE = oracle; -export const SNOWFLAKE = snowflakes; -export const ATHENA = athena; -export const PRESTO = presto; -export const TRINO = trino; -export const GLUE = glue; -export const MARIADB = mariadb; -export const VERTICA = vertica; -export const KAFKA = kafka; -export const PUBSUB = pubsub; -export const REDPANDA = redpanda; -export const SUPERSET = superset; -export const SYNAPSE = synapse; -export const LOOKER = looker; -export const MICROSTRATEGY = microstrategy; -export const TABLEAU = tableau; -export const REDASH = redash; -export const METABASE = metabase; -export const AZURESQL = azuresql; -export const CLICKHOUSE = clickhouse; -export const DATABRICK = databrick; -export const UNITYCATALOG = unitycatalog; -export const IBMDB2 = ibmdb2; -export const DORIS = doris; -export const STARROCKS = starrocks; -export const DRUID = druid; -export const DYNAMODB = dynamodb; -export const SIGMA = sigma; -export const SINGLESTORE = singlestore; -export const SALESFORCE = salesforce; -export const MLFLOW = mlflow; -export const SAP_HANA = sapHana; -export const SAP_ERP = sapErp; -export const SCIKIT = scikit; -export const DELTALAKE = deltalake; -export const DEFAULT_SERVICE = iconDefaultService; -export const AIRBYTE = airbyte; -export const PINOT = pinot; -export const DATALAKE = datalake; -export const MODE = mode; -export const DAGSTER = dagster; -export const DBT = dbt; -export const FIVETRAN = fivetran; -export const AMUNDSEN = amundsen; -export const ATLAS = atlas; -export const ALATIONSINK = alationsink; -export const SAS = sas; -export const OPENLINEAGE = openlineage; -export const LOGO = logo; -export const EXASOL = exasol; -export const AIRFLOW = airflow; -export const POWERBI = powerbi; -export const DATABASE_DEFAULT = databaseDefault; -export const TOPIC_DEFAULT = topicDefault; -export const DASHBOARD_DEFAULT = dashboardDefault; -export const PIPELINE_DEFAULT = pipelineDefault; -export const ML_MODEL_DEFAULT = mlModelDefault; -export const CUSTOM_STORAGE_DEFAULT = storageDefault; -export const CUSTOM_DRIVE_DEFAULT = customDriveDefault; -export const DRIVE_DEFAULT = driveDefault; -export const NIFI = nifi; -export const KINESIS = kinesis; -export const QUICKSIGHT = quicksight; -export const DOMO = domo; -export const SAGEMAKER = sagemaker; -export const AMAZON_S3 = amazonS3; -export const GCS = gcs; -export const SPARK = spark; -export const SPLINE = spline; -export const MONGODB = mongodb; -export const CASSANDRA = cassandra; -export const QLIK_SENSE = qlikSense; -export const LIGHT_DASH = lightDash; -export const COUCHBASE = couchbase; -export const GREENPLUM = greenplum; -export const ELASTIC_SEARCH = elasticSearch; -export const OPEN_SEARCH = openSearch; -export const CUSTOM_SEARCH_DEFAULT = searchDefault; -export const TERADATA = teradata; -export const FLINK = flink; -export const REST_SERVICE = restService; -export const COCKROACH = cockroach; -export const SECURITY_DEFAULT = securitySafe; -export const GRAFANA = grafana; -export const GOOGLE_DRIVE = googleDrive; -export const SFTP = sftp; -export const TIMESCALE = timescale; -export const BURSTIQ = burstiq; -export const excludedService = [ - MlModelServiceType.Sklearn, - MetadataServiceType.MetadataES, - MetadataServiceType.OpenMetadata, - PipelineServiceType.Spark, -]; - -export const arrServiceTypes: Array = [ - 'databaseServices', - 'messagingServices', - 'dashboardServices', - 'pipelineServices', - 'mlmodelServices', - 'storageServices', - 'apiServices', - 'securityServices', - 'driveServices', -]; - -export const SERVICE_CATEGORY: { [key: string]: ServiceCategory } = { - databases: ServiceCategory.DATABASE_SERVICES, - messaging: ServiceCategory.MESSAGING_SERVICES, - dashboards: ServiceCategory.DASHBOARD_SERVICES, - pipelines: ServiceCategory.PIPELINE_SERVICES, - mlmodels: ServiceCategory.ML_MODEL_SERVICES, - metadata: ServiceCategory.METADATA_SERVICES, - storages: ServiceCategory.STORAGE_SERVICES, - search: ServiceCategory.SEARCH_SERVICES, - apiServices: ServiceCategory.API_SERVICES, - security: ServiceCategory.SECURITY_SERVICES, - drives: ServiceCategory.DRIVE_SERVICES, -}; - -export const servicesDisplayName: Record< - string, - { key: string; entity: string } -> = { - databaseServices: { key: 'label.entity-service', entity: 'label.database' }, - messagingServices: { key: 'label.entity-service', entity: 'label.messaging' }, - dashboardServices: { key: 'label.entity-service', entity: 'label.dashboard' }, - pipelineServices: { key: 'label.entity-service', entity: 'label.pipeline' }, - mlmodelServices: { key: 'label.entity-service', entity: 'label.ml-model' }, - metadataServices: { key: 'label.entity-service', entity: 'label.metadata' }, - storageServices: { key: 'label.entity-service', entity: 'label.storage' }, - searchServices: { key: 'label.entity-service', entity: 'label.search' }, - dashboardDataModel: { - key: 'label.entity-service', - entity: 'label.data-model', - }, - apiServices: { key: 'label.entity-service', entity: 'label.api-uppercase' }, - securityServices: { key: 'label.entity-service', entity: 'label.security' }, - driveServices: { key: 'label.entity-service', entity: 'label.drive' }, -}; - -export const DEF_UI_SCHEMA = { - supportsIncrementalMetadataExtraction: { - 'ui:widget': 'hidden', - 'ui:hideError': true, - }, - supportsMetadataExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsSystemProfile: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsDataDiff: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsUsageExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsLineageExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsViewLineageExtraction: { - 'ui:widget': 'hidden', - 'ui:hideError': true, - }, - supportsProfiler: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsDatabase: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsQueryComment: { 'ui:widget': 'hidden', 'ui:hideError': true }, - supportsDBTExtraction: { 'ui:widget': 'hidden', 'ui:hideError': true }, - type: { 'ui:widget': 'hidden' }, -}; - -export const INGESTION_ELASTIC_SEARCH_WORKFLOW_UI_SCHEMA = { - useSSL: { 'ui:widget': 'hidden', 'ui:hideError': true }, - verifyCerts: { 'ui:widget': 'hidden', 'ui:hideError': true }, - timeout: { 'ui:widget': 'hidden', 'ui:hideError': true }, - caCerts: { 'ui:widget': 'hidden', 'ui:hideError': true }, - useAwsCredentials: { 'ui:widget': 'hidden', 'ui:hideError': true }, - regionName: { 'ui:widget': 'hidden', 'ui:hideError': true }, -}; - -export const INGESTION_WORKFLOW_UI_SCHEMA = { - type: { 'ui:widget': 'hidden', 'ui:hideError': true }, - name: { 'ui:widget': 'hidden', 'ui:hideError': true }, - processingEngine: { 'ui:widget': 'hidden', 'ui:hideError': true }, - 'ui:order': [ - 'rootProcessingEngine', - 'name', - 'displayName', - ...SERVICE_FILTER_PATTERN_FIELDS, - 'enableDebugLog', - '*', - ], -}; - -export const EXCLUDE_INCREMENTAL_EXTRACTION_SUPPORT_UI_SCHEMA = { - incremental: { - 'ui:widget': 'hidden', - 'ui:hideError': true, - }, -}; - -export const COMMON_UI_SCHEMA = { - ...DEF_UI_SCHEMA, - [ServiceNestedConnectionFields.CONNECTION]: { - ...DEF_UI_SCHEMA, - }, - [ServiceNestedConnectionFields.METASTORE_CONNECTION]: { - ...DEF_UI_SCHEMA, - }, - [ServiceNestedConnectionFields.DATABASE_CONNECTION]: { - ...DEF_UI_SCHEMA, - }, -}; - -export const OPEN_METADATA = 'OpenMetadata'; -export const JWT_CONFIG = 'openMetadataJWTClientConfig'; - -export const SERVICE_CATEGORY_OPTIONS = map(ServiceCategory, (value) => ({ - label: startCase(value), - value, -})); - -export const STEPS_FOR_ADD_SERVICE: Array = [ - { - name: 'label.select-field', - nameData: { field: 'label.service-type' }, - step: 1, - }, - { - name: 'label.configure-entity', - nameData: { entity: 'label.service' }, - step: 2, - }, - { - name: 'label.connection-entity', - nameData: { entity: 'label.detail-plural' }, - step: 3, - }, - { - name: 'label.set-default-filters', - step: 4, - }, -]; - -export const STEPS_FOR_EDIT_SERVICE: Array = [ - { - name: 'label.connection-entity', - nameData: { entity: 'label.detail-plural' }, - step: 1, - }, - { - name: 'label.set-default-filters', - step: 2, - }, -]; - -export const SERVICE_DEFAULT_ERROR_MAP = { - serviceType: false, -}; -// 3 minutes timeout to wait for test connection status -// Increasing it temporarily while we investigate test connection delays -// @pmbrull -export const FETCHING_EXPIRY_TIME = 3 * 60 * 1000; -export const FETCH_INTERVAL = 2000; -export const WORKFLOW_COMPLETE_STATUS = [ - WorkflowStatus.Failed, - WorkflowStatus.Successful, -]; -export const TEST_CONNECTION_PROGRESS_PERCENTAGE = { - ZERO: 0, - ONE: 1, - TEN: 10, - TWENTY: 20, - FORTY: 40, - HUNDRED: 100, -}; - -export const SERVICE_TYPE_MAP = { - [ServiceCategory.DASHBOARD_SERVICES]: ServiceType.Dashboard, - [ServiceCategory.DATABASE_SERVICES]: ServiceType.Database, - [ServiceCategory.MESSAGING_SERVICES]: ServiceType.Messaging, - [ServiceCategory.ML_MODEL_SERVICES]: ServiceType.MlModel, - [ServiceCategory.METADATA_SERVICES]: ServiceType.Metadata, - [ServiceCategory.STORAGE_SERVICES]: ServiceType.Storage, - [ServiceCategory.PIPELINE_SERVICES]: ServiceType.Pipeline, - [ServiceCategory.SEARCH_SERVICES]: ServiceType.Search, - [ServiceCategory.API_SERVICES]: ServiceType.API, - [ServiceCategory.SECURITY_SERVICES]: ServiceType.Security, - [ServiceCategory.DRIVE_SERVICES]: ServiceType.Drive, -}; - -export const SERVICE_TYPES_ENUM = { - [ServiceCategory.DASHBOARD_SERVICES]: DashboardServiceType, - [ServiceCategory.DATABASE_SERVICES]: DatabaseServiceType, - [ServiceCategory.MESSAGING_SERVICES]: MessagingServiceType, - [ServiceCategory.ML_MODEL_SERVICES]: MlModelServiceType, - [ServiceCategory.METADATA_SERVICES]: MetadataServiceType, - [ServiceCategory.STORAGE_SERVICES]: StorageServiceType, - [ServiceCategory.PIPELINE_SERVICES]: PipelineServiceType, - [ServiceCategory.SEARCH_SERVICES]: SearchServiceType, - [ServiceCategory.API_SERVICES]: APIServiceType, - [ServiceCategory.SECURITY_SERVICES]: SecurityServiceType, - [ServiceCategory.DRIVE_SERVICES]: DriveServiceType, -}; - -export const BETA_SERVICES = [ - PipelineServiceType.OpenLineage, - PipelineServiceType.Wherescape, - DatabaseServiceType.Cassandra, - MetadataServiceType.AlationSink, - DatabaseServiceType.Cockroach, - SearchServiceType.OpenSearch, - PipelineServiceType.Ssis, - DatabaseServiceType.Ssas, - DashboardServiceType.ThoughtSpot, - SecurityServiceType.Ranger, - DatabaseServiceType.Epic, - DashboardServiceType.Grafana, - DashboardServiceType.Hex, - DatabaseServiceType.ServiceNow, - DatabaseServiceType.Timescale, - DatabaseServiceType.Dremio, - MetadataServiceType.Collibra, - PipelineServiceType.Mulesoft, - DatabaseServiceType.MicrosoftFabric, - PipelineServiceType.MicrosoftFabricPipeline, - DatabaseServiceType.BurstIQ, - DatabaseServiceType.StarRocks, - DriveServiceType.SFTP, - DatabaseServiceType.Informix, - DatabaseServiceType.MicrosoftAccess, -]; - -export const TEST_CONNECTION_INITIAL_MESSAGE = - 'message.test-your-connection-before-creating-service'; - -export const TEST_CONNECTION_SUCCESS_MESSAGE = - 'message.connection-test-successful'; - -export const TEST_CONNECTION_FAILURE_MESSAGE = 'message.connection-test-failed'; - -export const TEST_CONNECTION_TESTING_MESSAGE = - 'message.testing-your-connection-may-take-two-minutes'; - -export const TEST_CONNECTION_WARNING_MESSAGE = - 'message.connection-test-warning'; - -export const ADVANCED_PROPERTIES = [ - 'connectionArguments', - 'connectionOptions', - 'scheme', - 'sampleDataStorageConfig', - 'computeTableMetrics', - 'computeColumnMetrics', - 'includeViews', - 'useStatistics', - 'confidence', - 'samplingMethodType', - 'randomizedSample', - 'sampleDataCount', - 'threadCount', - 'timeoutSeconds', - 'metrics', - 'sslConfig', - 'sslMode', - 'schemaRegistrySSL', - 'consumerConfigSSL', - 'verify', - 'useNonce', - 'disablePkce', - 'maxClockSkew', - 'tokenValidity', - 'maxAge', - 'sessionExpiry', -]; - -export const PIPELINE_SERVICE_PLATFORM = 'Airflow'; - -export const SERVICE_TYPES = [ - EntityType.DATABASE_SERVICE, - EntityType.DASHBOARD_SERVICE, - EntityType.MESSAGING_SERVICE, - EntityType.PIPELINE_SERVICE, - EntityType.MLMODEL_SERVICE, - EntityType.METADATA_SERVICE, - EntityType.STORAGE_SERVICE, - EntityType.SEARCH_SERVICE, - EntityType.API_SERVICE, - EntityType.SECURITY_SERVICE, - EntityType.DRIVE_SERVICE, -]; - -export const EXCLUDE_AUTO_PILOT_SERVICE_TYPES = [EntityType.SECURITY_SERVICE]; - -export const SERVICE_INGESTION_PIPELINE_TYPES = [ - PipelineType.Metadata, - PipelineType.Usage, - PipelineType.Lineage, - PipelineType.Profiler, - PipelineType.AutoClassification, - PipelineType.Dbt, -]; - -export const SERVICE_AUTOPILOT_AGENT_TYPES = [ - PipelineType.Metadata, - PipelineType.Lineage, - PipelineType.Usage, - PipelineType.AutoClassification, - PipelineType.Profiler, -]; +/** + * @deprecated This file previously contained 93 eager image imports that bloated the bundle. + * + * All exports have been moved to modular files. Update your imports: + * + * UI Schemas: + * - import { COMMON_UI_SCHEMA, DEF_UI_SCHEMA } from './ServiceUISchema.constant'; + * + * Service Types & Constants: + * - import { SERVICE_TYPES, BETA_SERVICES } from './ServiceType.constant'; + * + * Service Icons (lazy-loaded): + * - import { getServiceIcon } from '../utils/ServiceIconUtils'; + * - const icon = await getServiceIcon('mysql'); + */ -export const SERVICE_TYPE_WITH_DISPLAY_NAME = new Map([ - [PipelineServiceType.GluePipeline, 'Glue Pipeline'], - [DatabaseServiceType.DomoDatabase, 'Domo Database'], - [DashboardServiceType.DomoDashboard, 'Domo Dashboard'], - [DashboardServiceType.MicroStrategy, 'Micro Strategy'], - [DashboardServiceType.PowerBIReportServer, 'PowerBI Report Server'], - [PipelineServiceType.DatabricksPipeline, 'Databricks Pipeline'], - [PipelineServiceType.DomoPipeline, 'Domo Pipeline'], - [PipelineServiceType.KafkaConnect, 'Kafka Connect'], - [DatabaseServiceType.SapERP, 'SAP ERP'], - [DatabaseServiceType.SapHana, 'SAP HANA'], - [DatabaseServiceType.UnityCatalog, 'Unity Catalog'], - [PipelineServiceType.DataFactory, 'Data Factory'], - [PipelineServiceType.DBTCloud, 'DBT Cloud'], - [PipelineServiceType.OpenLineage, 'Open Lineage'], - [MetadataServiceType.AlationSink, 'Alation Sink'], - [SearchServiceType.ElasticSearch, 'Elasticsearch'], - [DatabaseServiceType.MicrosoftFabric, 'Microsoft Fabric'], - [PipelineServiceType.MicrosoftFabricPipeline, 'Microsoft Fabric Pipeline'], -]); +export * from './ServiceUISchema.constant'; +export * from './ServiceType.constant'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddGlossary/AddGlossaryPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddGlossary/AddGlossaryPage.component.tsx index b484e2d08d35..1b24770558b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AddGlossary/AddGlossaryPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddGlossary/AddGlossaryPage.component.tsx @@ -30,7 +30,7 @@ import { CreateGlossary } from '../../generated/api/data/createGlossary'; import { Operation } from '../../generated/entity/policies/policy'; import { withPageLayout } from '../../hoc/withPageLayout'; import { addGlossaries } from '../../rest/glossaryAPI'; -import { getIsErrorMatch } from '../../utils/CommonUtils'; +import { getIsErrorMatch } from '../../utils/APIUtils'; import { checkPermission } from '../../utils/PermissionsUtils'; import { getGlossaryPath } from '../../utils/RouterUtils'; import { getClassifications, getTaglist } from '../../utils/TagsUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx index dde5c4a1caf8..de344d089090 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx @@ -31,8 +31,8 @@ import { import { withPageLayout } from '../../../hoc/withPageLayout'; import { FieldProp, FieldTypes } from '../../../interface/FormUtils.interface'; import { addPolicy } from '../../../rest/rolesAPIV1'; +import { getIsErrorMatch } from '../../../utils/APIUtils'; import brandClassBase from '../../../utils/BrandData/BrandClassBase'; -import { getIsErrorMatch } from '../../../utils/CommonUtils'; import { getField } from '../../../utils/formUtils'; import { translateWithNestedKeys } from '../../../utils/i18next/LocalUtil'; import { getPath, getPolicyWithFqnPath } from '../../../utils/RouterUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.tsx index e26b8a73295d..7252d82c9674 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.tsx @@ -28,8 +28,8 @@ import { Policy } from '../../../generated/entity/policies/policy'; import { withPageLayout } from '../../../hoc/withPageLayout'; import { FieldProp, FieldTypes } from '../../../interface/FormUtils.interface'; import { addRole, getPolicies } from '../../../rest/rolesAPIV1'; +import { getIsErrorMatch } from '../../../utils/APIUtils'; import brandClassBase from '../../../utils/BrandData/BrandClassBase'; -import { getIsErrorMatch } from '../../../utils/CommonUtils'; import { getField } from '../../../utils/formUtils'; import { translateWithNestedKeys } from '../../../utils/i18next/LocalUtil'; import { getPath, getRoleWithFqnPath } from '../../../utils/RouterUtils'; @@ -70,7 +70,6 @@ const AddRolePage = () => { const data = { name: trim(name), description, - // TODO the policies should be names instead of ID policies: selectedPolicies.map((policy) => policy), }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts index 9e6a2b6b2067..59c4884becc1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts @@ -11,7 +11,7 @@ * limitations under the License. */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { APIServiceType } from '../generated/entity/services/apiService'; import restConnection from '../jsons/connectionSchemas/connections/api/restConnection.json'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts index c2ec3b9cd0a3..e808654dcdef 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts @@ -11,7 +11,8 @@ * limitations under the License. */ -import { isArray, isObject, transform } from 'lodash'; +import { AxiosError } from 'axios'; +import { get, isArray, isObject, transform } from 'lodash'; import { SearchIndex } from '../enums/search.enum'; import { DataProduct } from '../generated/entity/domains/dataProduct'; import { Domain } from '../generated/entity/domains/domain'; @@ -112,3 +113,21 @@ export const omitDeep = ( } }); }; + +export const getIsErrorMatch = (error: AxiosError, key: string): boolean => { + let errorMessage = ''; + + if (error) { + errorMessage = get(error, 'response.data.message', ''); + if (!errorMessage) { + // if error text is undefined or null or empty, try responseMessage in data + errorMessage = get(error, 'response.data.responseMessage', ''); + } + if (!errorMessage) { + errorMessage = get(error, 'response.data', '') as string; + errorMessage = typeof errorMessage === 'string' ? errorMessage : ''; + } + } + + return errorMessage.includes(key); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts index ae6dd95cc1c6..83d9edcec97f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts @@ -11,12 +11,7 @@ * limitations under the License. */ -import { - AuthenticationResult, - BrowserCacheLocation, - Configuration, - PopupRequest, -} from '@azure/msal-browser'; +import type { AuthenticationResult, Configuration } from '@azure/msal-browser'; import { CookieStorage } from 'cookie-storage'; import jwtDecode, { JwtPayload } from 'jwt-decode'; import { first, get, isEmpty, isNil } from 'lodash'; @@ -188,7 +183,7 @@ export const getAuthConfig = ( postLogoutRedirectUri: '/', }, cache: { - cacheLocation: BrowserCacheLocation.LocalStorage, + cacheLocation: 'localStorage', }, provider, enableSelfSignup, @@ -207,7 +202,7 @@ export const getAuthConfig = ( postLogoutRedirectUri: '/', }, cache: { - cacheLocation: BrowserCacheLocation.LocalStorage, + cacheLocation: 'localStorage', }, provider, clientType, @@ -222,9 +217,9 @@ export const getAuthConfig = ( }; // Add here scopes for id token to be used at MS Identity Platform endpoints. -export const msalLoginRequest: PopupRequest = { +export const msalLoginRequest = { scopes: ['openid', 'profile', 'email', 'offline_access'], -}; +} as const; export const getNameFromEmail = (email: string) => { if (new RegExp(EMAIL_REG_EX).exec(email)) { @@ -279,8 +274,8 @@ export const extractNameFromUserProfile = (user: UserProfile): string => { return user.name.trim(); } - const givenName = get(user, 'given_name', ''); - const familyName = get(user, 'family_name', ''); + const givenName: string = get(user, 'given_name', ''); + const familyName: string = get(user, 'family_name', ''); if (givenName && familyName) { return `${givenName.trim()} ${familyName.trim()}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index a116c7d23472..0ffebc873e1e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -674,24 +674,6 @@ export const reducerWithoutAction = (state: S, action: A) => { */ export const getBase64EncodedString = (text: string): string => btoa(text); -export const getIsErrorMatch = (error: AxiosError, key: string): boolean => { - let errorMessage = ''; - - if (error) { - errorMessage = get(error, 'response.data.message', ''); - if (!errorMessage) { - // if error text is undefined or null or empty, try responseMessage in data - errorMessage = get(error, 'response.data.responseMessage', ''); - } - if (!errorMessage) { - errorMessage = get(error, 'response.data', '') as string; - errorMessage = typeof errorMessage === 'string' ? errorMessage : ''; - } - } - - return errorMessage.includes(key); -}; - /** * @param color hex have color code * @param opacity take opacity how much to reduce it diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CoverImageUploadUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CoverImageUploadUtils.tsx index d7adcb088186..2888c29274aa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CoverImageUploadUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CoverImageUploadUtils.tsx @@ -18,7 +18,7 @@ import imageClassBase from '../components/BlockEditor/Extensions/image/ImageClas import { CoverImageFileValue } from '../components/common/CoverImageUpload/CoverImageUpload.interface'; import { ERROR_MESSAGE } from '../constants/constants'; import { EntityType } from '../enums/entity.enum'; -import { getIsErrorMatch } from './CommonUtils'; +import { getIsErrorMatch } from './APIUtils'; import { showNotistackError, showNotistackSuccess, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts index 5c71999498c6..c2064f130671 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts @@ -12,29 +12,89 @@ */ import { cloneDeep, isEmpty, isUndefined } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { DashboardConnection, DashboardServiceType, } from '../generated/entity/services/dashboardService'; -import customDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/customDashboardConnection.json'; -import domoDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/domoDashboardConnection.json'; -import grafanaConnection from '../jsons/connectionSchemas/connections/dashboard/grafanaConnection.json'; -import hexConnection from '../jsons/connectionSchemas/connections/dashboard/hexConnection.json'; -import lightdashConnection from '../jsons/connectionSchemas/connections/dashboard/lightdashConnection.json'; -import lookerConnection from '../jsons/connectionSchemas/connections/dashboard/lookerConnection.json'; -import metabaseConnection from '../jsons/connectionSchemas/connections/dashboard/metabaseConnection.json'; -import microStrategyConnection from '../jsons/connectionSchemas/connections/dashboard/microStrategyConnection.json'; -import modeConnection from '../jsons/connectionSchemas/connections/dashboard/modeConnection.json'; -import powerBIConnection from '../jsons/connectionSchemas/connections/dashboard/powerBIConnection.json'; -import qlikcloudConnection from '../jsons/connectionSchemas/connections/dashboard/qlikCloudConnection.json'; -import qliksenseConnection from '../jsons/connectionSchemas/connections/dashboard/qlikSenseConnection.json'; -import quicksightConnection from '../jsons/connectionSchemas/connections/dashboard/quickSightConnection.json'; -import redashConnection from '../jsons/connectionSchemas/connections/dashboard/redashConnection.json'; -import sigmaConnection from '../jsons/connectionSchemas/connections/dashboard/sigmaConnection.json'; -import ssrsConnection from '../jsons/connectionSchemas/connections/dashboard/ssrsConnection.json'; -import supersetConnection from '../jsons/connectionSchemas/connections/dashboard/supersetConnection.json'; -import tableauConnection from '../jsons/connectionSchemas/connections/dashboard/tableauConnection.json'; + +const DASHBOARD_CONNECTION_SCHEMAS: Record< + DashboardServiceType, + () => Promise<{ default: Record }> +> = { + [DashboardServiceType.Looker]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/lookerConnection.json' + ), + [DashboardServiceType.Metabase]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/metabaseConnection.json' + ), + [DashboardServiceType.Mode]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/modeConnection.json' + ), + [DashboardServiceType.PowerBI]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/powerBIConnection.json' + ), + [DashboardServiceType.Redash]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/redashConnection.json' + ), + [DashboardServiceType.Superset]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/supersetConnection.json' + ), + [DashboardServiceType.Sigma]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/sigmaConnection.json' + ), + [DashboardServiceType.Tableau]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/tableauConnection.json' + ), + [DashboardServiceType.DomoDashboard]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/domoDashboardConnection.json' + ), + [DashboardServiceType.CustomDashboard]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/customDashboardConnection.json' + ), + [DashboardServiceType.QuickSight]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/quickSightConnection.json' + ), + [DashboardServiceType.QlikSense]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/qlikSenseConnection.json' + ), + [DashboardServiceType.QlikCloud]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/qlikCloudConnection.json' + ), + [DashboardServiceType.Lightdash]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/lightdashConnection.json' + ), + [DashboardServiceType.MicroStrategy]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/microStrategyConnection.json' + ), + [DashboardServiceType.Grafana]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/grafanaConnection.json' + ), + [DashboardServiceType.Hex]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/hexConnection.json' + ), + [DashboardServiceType.Ssrs]: () => + import( + '../jsons/connectionSchemas/connections/dashboard/ssrsConnection.json' + ), +}; export const getDashboardURL = (config: DashboardConnection['config']) => { return !isUndefined(config) && !isEmpty(config.hostPort) @@ -42,109 +102,15 @@ export const getDashboardURL = (config: DashboardConnection['config']) => { : '--'; }; -export const getDashboardConfig = (type: DashboardServiceType) => { - let schema = {}; +export const getDashboardConfig = async (type: DashboardServiceType) => { const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { - case DashboardServiceType.Looker: { - schema = lookerConnection; - - break; - } - case DashboardServiceType.Metabase: { - schema = metabaseConnection; - - break; - } - case DashboardServiceType.Mode: { - schema = modeConnection; - - break; - } - case DashboardServiceType.PowerBI: { - schema = powerBIConnection; - - break; - } - case DashboardServiceType.Redash: { - schema = redashConnection; - - break; - } - case DashboardServiceType.Superset: { - schema = supersetConnection; - - break; - } - case DashboardServiceType.Sigma: { - schema = sigmaConnection; - - break; - } - case DashboardServiceType.Tableau: { - schema = tableauConnection; - - break; - } - case DashboardServiceType.DomoDashboard: { - schema = domoDashboardConnection; - - break; - } - case DashboardServiceType.CustomDashboard: { - schema = customDashboardConnection; - - break; - } - - case DashboardServiceType.QuickSight: { - schema = quicksightConnection; - - break; - } + const loaderFn = DASHBOARD_CONNECTION_SCHEMAS[type]; - case DashboardServiceType.QlikSense: { - schema = qliksenseConnection; - - break; - } - - case DashboardServiceType.QlikCloud: { - schema = qlikcloudConnection; - - break; - } - - case DashboardServiceType.Lightdash: { - schema = lightdashConnection; - - break; - } - - case DashboardServiceType.MicroStrategy: { - schema = microStrategyConnection; - - break; - } - - case DashboardServiceType.Grafana: { - schema = grafanaConnection; - - break; - } - - case DashboardServiceType.Hex: { - schema = hexConnection; - - break; - } - - case DashboardServiceType.Ssrs: { - schema = ssrsConnection; - - break; - } + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx index 13e7835b7271..6c4eec8b4411 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx @@ -18,302 +18,214 @@ import { ReactComponent as ImportIcon } from '../assets/svg/ic-import.svg'; import { ManageButtonItemLabel } from '../components/common/ManageButtonContentItem/ManageButtonContentItem.component'; import { useEntityExportModalProvider } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import { ExportTypes } from '../constants/Export.constants'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { OperationPermission } from '../context/PermissionProvider/PermissionProvider.interface'; import { EntityType } from '../enums/entity.enum'; import { DatabaseServiceType } from '../generated/entity/services/databaseService'; -import athenaConnection from '../jsons/connectionSchemas/connections/database/athenaConnection.json'; -import azureSQLConnection from '../jsons/connectionSchemas/connections/database/azureSQLConnection.json'; -import bigQueryConnection from '../jsons/connectionSchemas/connections/database/bigQueryConnection.json'; -import bigTableConnection from '../jsons/connectionSchemas/connections/database/bigTableConnection.json'; -import burstiqConnection from '../jsons/connectionSchemas/connections/database/burstIQConnection.json'; -import cassandraConnection from '../jsons/connectionSchemas/connections/database/cassandraConnection.json'; -import clickhouseConnection from '../jsons/connectionSchemas/connections/database/clickhouseConnection.json'; -import cockroachConnection from '../jsons/connectionSchemas/connections/database/cockroachConnection.json'; -import couchbaseConnection from '../jsons/connectionSchemas/connections/database/couchbaseConnection.json'; -import customDatabaseConnection from '../jsons/connectionSchemas/connections/database/customDatabaseConnection.json'; -import databricksConnection from '../jsons/connectionSchemas/connections/database/databricksConnection.json'; -import DatalakeConnection from '../jsons/connectionSchemas/connections/database/datalakeConnection.json'; -import db2Connection from '../jsons/connectionSchemas/connections/database/db2Connection.json'; -import deltaLakeConnection from '../jsons/connectionSchemas/connections/database/deltaLakeConnection.json'; -import domoDatabaseConnection from '../jsons/connectionSchemas/connections/database/domoDatabaseConnection.json'; -import dorisConnection from '../jsons/connectionSchemas/connections/database/dorisConnection.json'; -import druidConnection from '../jsons/connectionSchemas/connections/database/druidConnection.json'; -import dynamoDBConnection from '../jsons/connectionSchemas/connections/database/dynamoDBConnection.json'; -import exasolConnection from '../jsons/connectionSchemas/connections/database/exasolConnection.json'; -import glueConnection from '../jsons/connectionSchemas/connections/database/glueConnection.json'; -import greenplumConnection from '../jsons/connectionSchemas/connections/database/greenplumConnection.json'; -import hiveConnection from '../jsons/connectionSchemas/connections/database/hiveConnection.json'; -import impalaConnection from '../jsons/connectionSchemas/connections/database/impalaConnection.json'; -import mariaDBConnection from '../jsons/connectionSchemas/connections/database/mariaDBConnection.json'; -import mongoDBConnection from '../jsons/connectionSchemas/connections/database/mongoDBConnection.json'; -import mssqlConnection from '../jsons/connectionSchemas/connections/database/mssqlConnection.json'; -import mysqlConnection from '../jsons/connectionSchemas/connections/database/mysqlConnection.json'; -import oracleConnection from '../jsons/connectionSchemas/connections/database/oracleConnection.json'; -import pinotConnection from '../jsons/connectionSchemas/connections/database/pinotDBConnection.json'; -import postgresConnection from '../jsons/connectionSchemas/connections/database/postgresConnection.json'; -import prestoConnection from '../jsons/connectionSchemas/connections/database/prestoConnection.json'; -import redshiftConnection from '../jsons/connectionSchemas/connections/database/redshiftConnection.json'; -import salesforceConnection from '../jsons/connectionSchemas/connections/database/salesforceConnection.json'; -import sapErpConnection from '../jsons/connectionSchemas/connections/database/sapErpConnection.json'; -import sapHanaConnection from '../jsons/connectionSchemas/connections/database/sapHanaConnection.json'; -import sasConnection from '../jsons/connectionSchemas/connections/database/sasConnection.json'; -import singleStoreConnection from '../jsons/connectionSchemas/connections/database/singleStoreConnection.json'; -import snowflakeConnection from '../jsons/connectionSchemas/connections/database/snowflakeConnection.json'; -import sqliteConnection from '../jsons/connectionSchemas/connections/database/sqliteConnection.json'; -import starrocksConnection from '../jsons/connectionSchemas/connections/database/starrocksConnection.json'; -import synapseConnection from '../jsons/connectionSchemas/connections/database/synapseConnection.json'; -import teradataConnection from '../jsons/connectionSchemas/connections/database/teradataConnection.json'; -import timescaleConnection from '../jsons/connectionSchemas/connections/database/timescaleConnection.json'; -import trinoConnection from '../jsons/connectionSchemas/connections/database/trinoConnection.json'; -import unityCatalogConnection from '../jsons/connectionSchemas/connections/database/unityCatalogConnection.json'; -import verticaConnection from '../jsons/connectionSchemas/connections/database/verticaConnection.json'; import { exportDatabaseServiceDetailsInCSV } from '../rest/serviceAPI'; import { getEntityImportPath } from './EntityUtils'; import { t } from './i18next/LocalUtil'; -export const getDatabaseConfig = (type: DatabaseServiceType) => { - let schema = {}; - const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type as unknown as DatabaseServiceType) { - case DatabaseServiceType.Athena: { - schema = athenaConnection; - - break; - } - case DatabaseServiceType.AzureSQL: { - schema = azureSQLConnection; - - break; - } - case DatabaseServiceType.BigQuery: { - schema = bigQueryConnection; - - break; - } - case DatabaseServiceType.BigTable: { - schema = bigTableConnection; - - break; - } - case DatabaseServiceType.Clickhouse: { - schema = clickhouseConnection; - - break; - } - case DatabaseServiceType.Cockroach: { - schema = cockroachConnection; - - break; - } - case DatabaseServiceType.Databricks: { - schema = databricksConnection; - - break; - } - case DatabaseServiceType.Datalake: { - schema = DatalakeConnection; - - break; - } - case DatabaseServiceType.Db2: { - schema = db2Connection; - - break; - } - case DatabaseServiceType.DeltaLake: { - schema = deltaLakeConnection; - - break; - } - case DatabaseServiceType.Doris: { - schema = dorisConnection; - - break; - } - case DatabaseServiceType.StarRocks: { - schema = starrocksConnection; - - break; - } - case DatabaseServiceType.Druid: { - schema = druidConnection; - - break; - } - - case DatabaseServiceType.DynamoDB: { - schema = dynamoDBConnection; - - break; - } - case DatabaseServiceType.Exasol: { - schema = exasolConnection; - - break; - } - case DatabaseServiceType.Glue: { - schema = glueConnection; - - break; - } - case DatabaseServiceType.Hive: { - schema = hiveConnection; - - break; - } - case DatabaseServiceType.Impala: { - schema = impalaConnection; - - break; - } - case DatabaseServiceType.MariaDB: { - schema = mariaDBConnection; - - break; - } - case DatabaseServiceType.Mssql: { - schema = mssqlConnection; - - break; - } - case DatabaseServiceType.Mysql: { - schema = mysqlConnection; - - break; - } - case DatabaseServiceType.Oracle: { - schema = oracleConnection; - - break; - } - case DatabaseServiceType.Postgres: { - schema = postgresConnection; - - break; - } - case DatabaseServiceType.Presto: { - schema = prestoConnection; - - break; - } - case DatabaseServiceType.Redshift: { - schema = redshiftConnection; - - break; - } - case DatabaseServiceType.Salesforce: { - schema = salesforceConnection; - - break; - } - case DatabaseServiceType.SingleStore: { - schema = singleStoreConnection; - - break; - } - case DatabaseServiceType.Snowflake: { - schema = snowflakeConnection; - - break; - } - case DatabaseServiceType.SQLite: { - schema = sqliteConnection; - - break; - } - case DatabaseServiceType.Synapse: { - schema = synapseConnection; - - break; - } - case DatabaseServiceType.Trino: { - schema = trinoConnection; - - break; - } - case DatabaseServiceType.Vertica: { - schema = verticaConnection; - - break; - } - case DatabaseServiceType.CustomDatabase: { - schema = customDatabaseConnection; - - break; - } - case DatabaseServiceType.DomoDatabase: { - schema = domoDatabaseConnection; - - break; - } - case DatabaseServiceType.SapHana: { - schema = sapHanaConnection; - - break; - } - case DatabaseServiceType.SapERP: { - schema = sapErpConnection; - - break; - } - case DatabaseServiceType.MongoDB: { - schema = mongoDBConnection; - - break; - } - case DatabaseServiceType.Cassandra: { - schema = cassandraConnection; - - break; - } - case DatabaseServiceType.Couchbase: { - schema = couchbaseConnection; - - break; - } - case DatabaseServiceType.PinotDB: { - schema = pinotConnection; - - break; - } - case DatabaseServiceType.Greenplum: { - schema = greenplumConnection; - - break; - } - case DatabaseServiceType.UnityCatalog: { - schema = unityCatalogConnection; - - break; - } - case DatabaseServiceType.SAS: { - schema = sasConnection; - - break; - } - case DatabaseServiceType.Teradata: { - schema = teradataConnection; - - break; - } - case DatabaseServiceType.Timescale: { - schema = timescaleConnection; - - break; - } - case DatabaseServiceType.BurstIQ: { - schema = burstiqConnection; +const DATABASE_CONNECTION_SCHEMAS: Record< + DatabaseServiceType, + () => Promise<{ default: Record }> +> = { + [DatabaseServiceType.Athena]: () => + import( + '../jsons/connectionSchemas/connections/database/athenaConnection.json' + ), + [DatabaseServiceType.AzureSQL]: () => + import( + '../jsons/connectionSchemas/connections/database/azureSQLConnection.json' + ), + [DatabaseServiceType.BigQuery]: () => + import( + '../jsons/connectionSchemas/connections/database/bigQueryConnection.json' + ), + [DatabaseServiceType.BigTable]: () => + import( + '../jsons/connectionSchemas/connections/database/bigTableConnection.json' + ), + [DatabaseServiceType.Clickhouse]: () => + import( + '../jsons/connectionSchemas/connections/database/clickhouseConnection.json' + ), + [DatabaseServiceType.Cockroach]: () => + import( + '../jsons/connectionSchemas/connections/database/cockroachConnection.json' + ), + [DatabaseServiceType.CustomDatabase]: () => + import( + '../jsons/connectionSchemas/connections/database/customDatabaseConnection.json' + ), + [DatabaseServiceType.Databricks]: () => + import( + '../jsons/connectionSchemas/connections/database/databricksConnection.json' + ), + [DatabaseServiceType.Datalake]: () => + import( + '../jsons/connectionSchemas/connections/database/datalakeConnection.json' + ), + [DatabaseServiceType.Db2]: () => + import( + '../jsons/connectionSchemas/connections/database/db2Connection.json' + ), + [DatabaseServiceType.DeltaLake]: () => + import( + '../jsons/connectionSchemas/connections/database/deltaLakeConnection.json' + ), + [DatabaseServiceType.Doris]: () => + import( + '../jsons/connectionSchemas/connections/database/dorisConnection.json' + ), + [DatabaseServiceType.Druid]: () => + import( + '../jsons/connectionSchemas/connections/database/druidConnection.json' + ), + [DatabaseServiceType.DynamoDB]: () => + import( + '../jsons/connectionSchemas/connections/database/dynamoDBConnection.json' + ), + [DatabaseServiceType.Glue]: () => + import( + '../jsons/connectionSchemas/connections/database/glueConnection.json' + ), + [DatabaseServiceType.Hive]: () => + import( + '../jsons/connectionSchemas/connections/database/hiveConnection.json' + ), + [DatabaseServiceType.Impala]: () => + import( + '../jsons/connectionSchemas/connections/database/impalaConnection.json' + ), + [DatabaseServiceType.MariaDB]: () => + import( + '../jsons/connectionSchemas/connections/database/mariaDBConnection.json' + ), + [DatabaseServiceType.Mssql]: () => + import( + '../jsons/connectionSchemas/connections/database/mssqlConnection.json' + ), + [DatabaseServiceType.Mysql]: () => + import( + '../jsons/connectionSchemas/connections/database/mysqlConnection.json' + ), + [DatabaseServiceType.Oracle]: () => + import( + '../jsons/connectionSchemas/connections/database/oracleConnection.json' + ), + [DatabaseServiceType.PinotDB]: () => + import( + '../jsons/connectionSchemas/connections/database/pinotDBConnection.json' + ), + [DatabaseServiceType.Postgres]: () => + import( + '../jsons/connectionSchemas/connections/database/postgresConnection.json' + ), + [DatabaseServiceType.Presto]: () => + import( + '../jsons/connectionSchemas/connections/database/prestoConnection.json' + ), + [DatabaseServiceType.Redshift]: () => + import( + '../jsons/connectionSchemas/connections/database/redshiftConnection.json' + ), + [DatabaseServiceType.Salesforce]: () => + import( + '../jsons/connectionSchemas/connections/database/salesforceConnection.json' + ), + [DatabaseServiceType.SAPHana]: () => + import( + '../jsons/connectionSchemas/connections/database/sapHanaConnection.json' + ), + [DatabaseServiceType.SingleStore]: () => + import( + '../jsons/connectionSchemas/connections/database/singleStoreConnection.json' + ), + [DatabaseServiceType.Snowflake]: () => + import( + '../jsons/connectionSchemas/connections/database/snowflakeConnection.json' + ), + [DatabaseServiceType.SQLite]: () => + import( + '../jsons/connectionSchemas/connections/database/sqliteConnection.json' + ), + [DatabaseServiceType.Synapse]: () => + import( + '../jsons/connectionSchemas/connections/database/synapseConnection.json' + ), + [DatabaseServiceType.Teradata]: () => + import( + '../jsons/connectionSchemas/connections/database/teradataConnection.json' + ), + [DatabaseServiceType.Trino]: () => + import( + '../jsons/connectionSchemas/connections/database/trinoConnection.json' + ), + [DatabaseServiceType.UnityCatalog]: () => + import( + '../jsons/connectionSchemas/connections/database/unityCatalogConnection.json' + ), + [DatabaseServiceType.Vertica]: () => + import( + '../jsons/connectionSchemas/connections/database/verticaConnection.json' + ), + [DatabaseServiceType.MongoDB]: () => + import( + '../jsons/connectionSchemas/connections/database/mongoDBConnection.json' + ), + [DatabaseServiceType.Couchbase]: () => + import( + '../jsons/connectionSchemas/connections/database/couchbaseConnection.json' + ), + [DatabaseServiceType.Greenplum]: () => + import( + '../jsons/connectionSchemas/connections/database/greenplumConnection.json' + ), + [DatabaseServiceType.DomoDatabase]: () => + import( + '../jsons/connectionSchemas/connections/database/domoDatabaseConnection.json' + ), + [DatabaseServiceType.Cassandra]: () => + import( + '../jsons/connectionSchemas/connections/database/cassandraConnection.json' + ), + [DatabaseServiceType.Exasol]: () => + import( + '../jsons/connectionSchemas/connections/database/exasolConnection.json' + ), + [DatabaseServiceType.SapErp]: () => + import( + '../jsons/connectionSchemas/connections/database/sapErpConnection.json' + ), + [DatabaseServiceType.Sas]: () => + import( + '../jsons/connectionSchemas/connections/database/sasConnection.json' + ), + [DatabaseServiceType.StarRocks]: () => + import( + '../jsons/connectionSchemas/connections/database/starrocksConnection.json' + ), + [DatabaseServiceType.Timescale]: () => + import( + '../jsons/connectionSchemas/connections/database/timescaleConnection.json' + ), + [DatabaseServiceType.BurstIQ]: () => + import( + '../jsons/connectionSchemas/connections/database/burstIQConnection.json' + ), +}; - break; - } - default: { - schema = {}; +export const getDatabaseConfig = async (type: DatabaseServiceType) => { + const uiSchema = { ...COMMON_UI_SCHEMA }; + const loaderFn = DATABASE_CONNECTION_SCHEMAS[type]; - break; - } + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.ts index dcdc1b20def6..d66f9e87c768 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.ts @@ -12,7 +12,7 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { DriveServiceType } from '../generated/entity/services/driveService'; import customDriveConnection from '../jsons/connectionSchemas/connections/drive/customDriveConnection.json'; import googleDriveConnection from '../jsons/connectionSchemas/connections/drive/googleDriveConnection.json'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts index c435fa95110a..3a20b5bfdeb0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts @@ -12,21 +12,41 @@ */ import { cloneDeep, isUndefined } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { MessagingConnection, MessagingServiceType, } from '../generated/entity/services/messagingService'; -import customMessagingConnection from '../jsons/connectionSchemas/connections/messaging/customMessagingConnection.json'; -import kafkaConnection from '../jsons/connectionSchemas/connections/messaging/kafkaConnection.json'; -import kinesisConnection from '../jsons/connectionSchemas/connections/messaging/kinesisConnection.json'; -import pubSubConnection from '../jsons/connectionSchemas/connections/messaging/pubSubConnection.json'; -import redpandaConnection from '../jsons/connectionSchemas/connections/messaging/redpandaConnection.json'; + +const MESSAGING_CONNECTION_SCHEMAS: Record< + MessagingServiceType, + () => Promise<{ default: Record }> +> = { + [MessagingServiceType.Kafka]: () => + import( + '../jsons/connectionSchemas/connections/messaging/kafkaConnection.json' + ), + [MessagingServiceType.Redpanda]: () => + import( + '../jsons/connectionSchemas/connections/messaging/redpandaConnection.json' + ), + [MessagingServiceType.CustomMessaging]: () => + import( + '../jsons/connectionSchemas/connections/messaging/customMessagingConnection.json' + ), + [MessagingServiceType.Kinesis]: () => + import( + '../jsons/connectionSchemas/connections/messaging/kinesisConnection.json' + ), + [MessagingServiceType.PubSub]: () => + import( + '../jsons/connectionSchemas/connections/messaging/pubSubConnection.json' + ), +}; export const getBrokers = (config: MessagingConnection['config']) => { let retVal: string | undefined; - // Change it to switch case if more than 1 conditions arise if (config?.type === MessagingServiceType.Kafka) { retVal = config.bootstrapServers; } @@ -34,39 +54,15 @@ export const getBrokers = (config: MessagingConnection['config']) => { return !isUndefined(retVal) ? retVal : '--'; }; -export const getMessagingConfig = (type: MessagingServiceType) => { - let schema = {}; +export const getMessagingConfig = async (type: MessagingServiceType) => { const uiSchema = { ...COMMON_UI_SCHEMA }; + const loaderFn = MESSAGING_CONNECTION_SCHEMAS[type]; - switch (type) { - case MessagingServiceType.Kafka: - schema = kafkaConnection; - - break; - - case MessagingServiceType.Redpanda: - schema = redpandaConnection; - - break; - - case MessagingServiceType.CustomMessaging: - schema = customMessagingConnection; - - break; - - case MessagingServiceType.Kinesis: - schema = kinesisConnection; - - break; - - case MessagingServiceType.PubSub: - schema = pubSubConnection; - - break; - - default: - break; + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MetadataServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MetadataServiceUtils.ts index e20dd14cdc03..d1aa2a63a098 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MetadataServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MetadataServiceUtils.ts @@ -12,7 +12,7 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { MetadataServiceType } from '../generated/entity/services/metadataService'; import alationSinkConnection from '../jsons/connectionSchemas/connections/metadata/alationSinkConnection.json'; import amundsenConnection from '../jsons/connectionSchemas/connections/metadata/amundsenConnection.json'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts index 3b3f1e9ea748..e0ea1e7584d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts @@ -12,38 +12,42 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { MlModelServiceType } from '../generated/entity/services/mlmodelService'; -import customMlModelConnection from '../jsons/connectionSchemas/connections/mlmodel/customMlModelConnection.json'; -import mlflowConnection from '../jsons/connectionSchemas/connections/mlmodel/mlflowConnection.json'; -import segamakerConnection from '../jsons/connectionSchemas/connections/mlmodel/sageMakerConnection.json'; -import sklearnConnection from '../jsons/connectionSchemas/connections/mlmodel/sklearnConnection.json'; -export const getMlmodelConfig = (type: MlModelServiceType) => { - let schema = {}; - const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { - case MlModelServiceType.Mlflow: { - schema = mlflowConnection; - - break; - } - case MlModelServiceType.Sklearn: { - schema = sklearnConnection; - - break; - } - case MlModelServiceType.CustomMlModel: { - schema = customMlModelConnection; +const MLMODEL_CONNECTION_SCHEMAS: Record< + MlModelServiceType, + () => Promise<{ default: Record }> +> = { + [MlModelServiceType.Mlflow]: () => + import( + '../jsons/connectionSchemas/connections/mlmodel/mlflowConnection.json' + ), + [MlModelServiceType.Sklearn]: () => + import( + '../jsons/connectionSchemas/connections/mlmodel/sklearnConnection.json' + ), + [MlModelServiceType.CustomMlModel]: () => + import( + '../jsons/connectionSchemas/connections/mlmodel/customMlModelConnection.json' + ), + [MlModelServiceType.SageMaker]: () => + import( + '../jsons/connectionSchemas/connections/mlmodel/sageMakerConnection.json' + ), +}; - break; - } - case MlModelServiceType.SageMaker: { - schema = segamakerConnection; +export const getMlmodelConfig = async (type: MlModelServiceType) => { + const uiSchema = { ...COMMON_UI_SCHEMA }; + const loaderFn = MLMODEL_CONNECTION_SCHEMAS[type] + ? MLMODEL_CONNECTION_SCHEMAS[type] + : null; - break; - } + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/OktaCustomStorage.ts b/openmetadata-ui/src/main/resources/ui/src/utils/OktaCustomStorage.ts index ffe838858ffc..f63481b108ad 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/OktaCustomStorage.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/OktaCustomStorage.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { StorageProvider } from '@okta/okta-auth-js'; +import type { StorageProvider } from '@okta/okta-auth-js'; import { swTokenStorage } from './SwTokenStorage'; import { isServiceWorkerAvailable } from './SwTokenStorageUtils'; @@ -19,8 +19,8 @@ const OKTA_TOKENS_KEY = 'okta_tokens'; export class OktaCustomStorage implements StorageProvider { private memoryCache: Record = {}; - private isServiceWorkerAvailable: boolean; - private initPromise: Promise; + private readonly isServiceWorkerAvailable: boolean; + private readonly initPromise: Promise; constructor() { this.isServiceWorkerAvailable = isServiceWorkerAvailable(); @@ -66,9 +66,9 @@ export class OktaCustomStorage implements StorageProvider { return this.memoryCache[key] || null; } - async setItem(key: string, value: string): Promise { + setItem(key: string, value: string) { this.memoryCache[key] = value; - await this.persistToStorage(); + this.persistToStorage(); } removeItem(key: string): void { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts index cfa7e7c42b21..81227fd068c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts @@ -12,95 +12,138 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { PipelineServiceType } from '../generated/entity/services/pipelineService'; -import airbyteConnection from '../jsons/connectionSchemas/connections/pipeline/airbyteConnection.json'; -import airflowConnection from '../jsons/connectionSchemas/connections/pipeline/airflowConnection.json'; -import customPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/customPipelineConnection.json'; -import dagsterConnection from '../jsons/connectionSchemas/connections/pipeline/dagsterConnection.json'; -import databricksPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/databricksPipelineConnection.json'; -import dbtCloudConnection from '../jsons/connectionSchemas/connections/pipeline/dbtCloudConnection.json'; -import domoPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/domoPipelineConnection.json'; -import fivetranConnection from '../jsons/connectionSchemas/connections/pipeline/fivetranConnection.json'; -import flinkConnection from '../jsons/connectionSchemas/connections/pipeline/flinkConnection.json'; -import gluePipelineConnection from '../jsons/connectionSchemas/connections/pipeline/gluePipelineConnection.json'; -import KafkaConnectConnection from '../jsons/connectionSchemas/connections/pipeline/kafkaConnectConnection.json'; -import nifiConnection from '../jsons/connectionSchemas/connections/pipeline/nifiConnection.json'; -import openLineageConnection from '../jsons/connectionSchemas/connections/pipeline/openLineageConnection.json'; -import splineConnection from '../jsons/connectionSchemas/connections/pipeline/splineConnection.json'; - -export const getPipelineConfig = (type: PipelineServiceType) => { + +export const getPipelineConfig = async (type: PipelineServiceType) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; + switch (type) { case PipelineServiceType.Airbyte: { - schema = airbyteConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/airbyteConnection.json' + ) + ).default; break; } case PipelineServiceType.Airflow: { - schema = airflowConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/airflowConnection.json' + ) + ).default; break; } case PipelineServiceType.GluePipeline: { - schema = gluePipelineConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/gluePipelineConnection.json' + ) + ).default; break; } case PipelineServiceType.KafkaConnect: { - schema = KafkaConnectConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/kafkaConnectConnection.json' + ) + ).default; break; } case PipelineServiceType.Fivetran: { - schema = fivetranConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/fivetranConnection.json' + ) + ).default; break; } case PipelineServiceType.Dagster: { - schema = dagsterConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/dagsterConnection.json' + ) + ).default; break; } case PipelineServiceType.DBTCloud: { - schema = dbtCloudConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/dbtCloudConnection.json' + ) + ).default; break; } case PipelineServiceType.Nifi: { - schema = nifiConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/nifiConnection.json' + ) + ).default; break; } case PipelineServiceType.DomoPipeline: { - schema = domoPipelineConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/domoPipelineConnection.json' + ) + ).default; break; } case PipelineServiceType.CustomPipeline: { - schema = customPipelineConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/customPipelineConnection.json' + ) + ).default; break; } case PipelineServiceType.DatabricksPipeline: { - schema = databricksPipelineConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/databricksPipelineConnection.json' + ) + ).default; break; } case PipelineServiceType.Spline: { - schema = splineConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/splineConnection.json' + ) + ).default; break; } case PipelineServiceType.OpenLineage: { - schema = openLineageConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/openLineageConnection.json' + ) + ).default; break; } case PipelineServiceType.Flink: { - schema = flinkConnection; + schema = ( + await import( + '../jsons/connectionSchemas/connections/pipeline/flinkConnection.json' + ) + ).default; break; } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts index 0cc1a24d4987..b13715c22c3d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts @@ -12,32 +12,36 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { SearchServiceType } from '../generated/entity/services/searchService'; -import customSearchConnection from '../jsons/connectionSchemas/connections/search/customSearchConnection.json'; -import elasticSearchConnection from '../jsons/connectionSchemas/connections/search/elasticSearchConnection.json'; -import openSearchConnection from '../jsons/connectionSchemas/connections/search/openSearchConnection.json'; -export const getSearchServiceConfig = (type: SearchServiceType) => { - let schema = {}; - const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { - case SearchServiceType.ElasticSearch: { - schema = elasticSearchConnection; - - break; - } - case SearchServiceType.OpenSearch: { - schema = openSearchConnection; +const SEARCH_CONNECTION_SCHEMAS: Record< + SearchServiceType, + () => Promise<{ default: Record }> +> = { + [SearchServiceType.ElasticSearch]: () => + import( + '../jsons/connectionSchemas/connections/search/elasticSearchConnection.json' + ), + [SearchServiceType.OpenSearch]: () => + import( + '../jsons/connectionSchemas/connections/search/openSearchConnection.json' + ), + [SearchServiceType.CustomSearch]: () => + import( + '../jsons/connectionSchemas/connections/search/customSearchConnection.json' + ), +}; - break; - } - case SearchServiceType.CustomSearch: { - schema = customSearchConnection; +export const getSearchServiceConfig = async (type: SearchServiceType) => { + const uiSchema = { ...COMMON_UI_SCHEMA }; + const loaderFn = SEARCH_CONNECTION_SCHEMAS[type]; - break; - } + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts index b1694cd07925..22a0f695560f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts @@ -12,22 +12,19 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { Type } from '../generated/entity/services/securityService'; -export const getSecurityConfig = (type: Type) => { +export const getSecurityConfig = async (type: Type) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { - case Type.Ranger: { - // eslint-disable-next-line @typescript-eslint/no-require-imports - schema = require('../jsons/connectionSchemas/connections/security/rangerConnection.json'); - - break; - } - default: - break; + if (type === Type.Ranger) { + schema = ( + await import( + '../jsons/connectionSchemas/connections/security/rangerConnection.json' + ) + ).default; } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts new file mode 100644 index 000000000000..118f6a4a3f10 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -0,0 +1,168 @@ +/* + * Copyright 2022 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. + */ + +const SERVICE_ICON_LOADERS: Record Promise<{ default: string }>> = + { + // Database services + mysql: () => import('../assets/img/service-icon-sql.png'), + sqlite: () => import('../assets/img/service-icon-sqlite.png'), + mssql: () => import('../assets/img/service-icon-mssql.png'), + redshift: () => import('../assets/img/service-icon-redshift.png'), + bigquery: () => import('../assets/img/service-icon-query.png'), + bigtable: () => import('../assets/img/service-icon-bigtable.png'), + hive: () => import('../assets/img/service-icon-hive.png'), + impala: () => import('../assets/img/service-icon-impala.png'), + postgres: () => import('../assets/img/service-icon-post.png'), + oracle: () => import('../assets/img/service-icon-oracle.png'), + snowflake: () => import('../assets/img/service-icon-snowflakes.png'), + athena: () => import('../assets/img/service-icon-athena.png'), + presto: () => import('../assets/img/service-icon-presto.png'), + trino: () => import('../assets/img/service-icon-trino.png'), + glue: () => import('../assets/img/service-icon-glue.png'), + mariadb: () => import('../assets/img/service-icon-mariadb.png'), + vertica: () => import('../assets/img/service-icon-vertica.png'), + azuresql: () => import('../assets/img/service-icon-azuresql.png'), + clickhouse: () => import('../assets/img/service-icon-clickhouse.png'), + databrick: () => import('../assets/img/service-icon-databrick.png'), + unitycatalog: () => import('../assets/img/service-icon-unitycatalog.svg'), + ibmdb2: () => import('../assets/img/service-icon-ibmdb2.png'), + doris: () => import('../assets/img/service-icon-doris.png'), + starrocks: () => import('../assets/img/service-icon-starrocks.png'), + druid: () => import('../assets/img/service-icon-druid.png'), + dynamodb: () => import('../assets/img/service-icon-dynamodb.png'), + singlestore: () => import('../assets/img/service-icon-singlestore.png'), + salesforce: () => import('../assets/img/service-icon-salesforce.png'), + saphana: () => import('../assets/img/service-icon-sap-hana.png'), + saperp: () => import('../assets/img/service-icon-sap-erp.png'), + deltalake: () => import('../assets/img/service-icon-delta-lake.png'), + pinot: () => import('../assets/img/service-icon-pinot.png'), + datalake: () => import('../assets/img/service-icon-datalake.png'), + exasol: () => import('../assets/img/service-icon-exasol.png'), + mongodb: () => import('../assets/img/service-icon-mongodb.png'), + cassandra: () => import('../assets/img/service-icon-cassandra.png'), + couchbase: () => import('../assets/img/service-icon-couchbase.svg'), + greenplum: () => import('../assets/img/service-icon-greenplum.png'), + teradata: () => import('../assets/svg/teradata.svg'), + cockroach: () => import('../assets/img/service-icon-cockroach.png'), + timescale: () => import('../assets/img/service-icon-timescale.png'), + burstiq: () => import('../assets/img/service-icon-burstiq.png'), + sas: () => import('../assets/img/service-icon-sas.svg'), + + // Messaging services + kafka: () => import('../assets/img/service-icon-kafka.png'), + pubsub: () => import('../assets/svg/service-icon-pubsub.svg'), + redpanda: () => import('../assets/img/service-icon-redpanda.png'), + kinesis: () => import('../assets/img/service-icon-kinesis.png'), + + // Dashboard services + superset: () => import('../assets/img/service-icon-superset.png'), + looker: () => import('../assets/img/service-icon-looker.png'), + tableau: () => import('../assets/img/service-icon-tableau.png'), + redash: () => import('../assets/img/service-icon-redash.png'), + metabase: () => import('../assets/img/service-icon-metabase.png'), + powerbi: () => import('../assets/img/service-icon-power-bi.png'), + sigma: () => import('../assets/img/service-icon-sigma.png'), + mode: () => import('../assets/img/service-icon-mode.png'), + domo: () => import('../assets/img/service-icon-domo.png'), + quicksight: () => import('../assets/img/service-icon-quicksight.png'), + qliksense: () => import('../assets/img/service-icon-qlik-sense.png'), + lightdash: () => import('../assets/img/service-icon-lightdash.png'), + microstrategy: () => import('../assets/img/service-icon-microstrategy.svg'), + grafana: () => import('../assets/img/service-icon-grafana.png'), + hex: () => import('../assets/svg/service-icon-hex.svg'), + + // Pipeline services + airflow: () => import('../assets/img/service-icon-airflow.png'), + airbyte: () => import('../assets/img/Airbyte.png'), + dagster: () => import('../assets/img/service-icon-dagster.png'), + dbt: () => import('../assets/img/service-icon-dbt.png'), + fivetran: () => import('../assets/img/service-icon-fivetran.png'), + nifi: () => import('../assets/img/service-icon-nifi.png'), + spark: () => import('../assets/img/service-icon-spark.png'), + spline: () => import('../assets/img/service-icon-spline.png'), + flink: () => import('../assets/img/service-icon-flink.png'), + openlineage: () => import('../assets/img/service-icon-openlineage.svg'), + + // ML Model services + mlflow: () => import('../assets/svg/service-icon-mlflow.svg'), + scikit: () => import('../assets/img/service-icon-scikit.png'), + sagemaker: () => import('../assets/img/service-icon-sagemaker.png'), + + // Storage services + amazons3: () => import('../assets/img/service-icon-amazon-s3.svg'), + gcs: () => import('../assets/img/service-icon-gcs.png'), + + // Search services + elasticsearch: () => import('../assets/svg/elasticsearch.svg'), + opensearch: () => import('../assets/svg/open-search.svg'), + + // Metadata services + amundsen: () => import('../assets/img/service-icon-amundsen.png'), + atlas: () => import('../assets/img/service-icon-atlas.svg'), + alationsink: () => import('../assets/img/service-icon-alation-sink.png'), + + // Drive services + googledrive: () => import('../assets/svg/service-icon-google-drive.svg'), + sftp: () => import('../assets/svg/service-icon-sftp.svg'), + + // Default icons + defaultservice: () => import('../assets/svg/default-service-icon.svg'), + databasedefault: () => import('../assets/svg/ic-custom-database.svg'), + topicdefault: () => import('../assets/svg/topic.svg'), + dashboarddefault: () => import('../assets/svg/dashboard.svg'), + pipelinedefault: () => import('../assets/svg/pipeline.svg'), + mlmodeldefault: () => import('../assets/svg/ic-custom-model.svg'), + storagedefault: () => import('../assets/svg/ic-custom-storage.svg'), + drivedefault: () => import('../assets/svg/ic-drive-service.svg'), + customdrivedefault: () => import('../assets/svg/ic-custom-drive.svg'), + searchdefault: () => import('../assets/svg/ic-custom-search.svg'), + securitydefault: () => import('../assets/svg/security-safe.svg'), + restservice: () => import('../assets/svg/ic-service-rest-api.svg'), + logo: () => import('../assets/svg/logo-monogram.svg'), + synapse: () => import('../assets/img/service-icon-synapse.png'), + }; + +const iconCache = new Map(); + +export const getServiceIcon = async (iconKey: string): Promise => { + const normalizedKey = iconKey.toLowerCase().replace(/[_-]/g, ''); + + if (iconCache.has(normalizedKey)) { + return iconCache.get(normalizedKey) as unknown as string; + } + + const loader = SERVICE_ICON_LOADERS[normalizedKey]; + + if (!loader) { + const defaultIcon = await SERVICE_ICON_LOADERS.defaultservice(); + + return defaultIcon.default; + } + + const icon = await loader(); + iconCache.set(normalizedKey, icon.default); + + return icon.default; +}; + +export const getServiceIconSync = (iconKey: string): string | null => { + const normalizedKey = iconKey.toLowerCase().replace(/[_-]/g, ''); + + return iconCache.get(normalizedKey) ?? null; +}; + +export const preloadServiceIcons = async ( + iconKeys: string[] +): Promise => { + await Promise.all(iconKeys.map((key) => getServiceIcon(key))); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index 944e82ed99b9..f47fdb7a360b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -25,103 +25,6 @@ import AgentsStatusWidget from '../components/ServiceInsights/AgentsStatusWidget import PlatformInsightsWidget from '../components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget'; import TotalDataAssetsWidget from '../components/ServiceInsights/TotalDataAssetsWidget/TotalDataAssetsWidget'; import MetadataAgentsWidget from '../components/Settings/Services/Ingestion/MetadataAgentsWidget/MetadataAgentsWidget'; -import { - AIRBYTE, - AIRFLOW, - ALATIONSINK, - AMAZON_S3, - AMUNDSEN, - ATHENA, - ATLAS, - AZURESQL, - BIGQUERY, - BIGTABLE, - BURSTIQ, - CASSANDRA, - CLICKHOUSE, - COCKROACH, - COUCHBASE, - CUSTOM_DRIVE_DEFAULT, - CUSTOM_SEARCH_DEFAULT, - CUSTOM_STORAGE_DEFAULT, - DAGSTER, - DASHBOARD_DEFAULT, - DATABASE_DEFAULT, - DATABRICK, - DATALAKE, - DBT, - DEFAULT_SERVICE, - DELTALAKE, - DOMO, - DORIS, - DRUID, - DYNAMODB, - ELASTIC_SEARCH, - EXASOL, - FIVETRAN, - FLINK, - GCS, - GLUE, - GOOGLE_DRIVE, - GRAFANA, - GREENPLUM, - HEX, - HIVE, - IBMDB2, - IMPALA, - KAFKA, - KINESIS, - LIGHT_DASH, - LOGO, - LOOKER, - MARIADB, - METABASE, - MICROSTRATEGY, - MLFLOW, - ML_MODEL_DEFAULT, - MODE, - MONGODB, - MSSQL, - MYSQL, - NIFI, - OPENLINEAGE, - OPEN_SEARCH, - ORACLE, - PINOT, - PIPELINE_DEFAULT, - POSTGRES, - POWERBI, - PRESTO, - QLIK_SENSE, - QUICKSIGHT, - REDASH, - REDPANDA, - REDSHIFT, - REST_SERVICE, - SAGEMAKER, - SALESFORCE, - SAP_ERP, - SAP_HANA, - SAS, - SCIKIT, - SFTP, - SIGMA, - SINGLESTORE, - SNOWFLAKE, - SPARK, - SPLINE, - SQLITE, - STARROCKS, - SUPERSET, - SYNAPSE, - TABLEAU, - TERADATA, - TIMESCALE, - TOPIC_DEFAULT, - TRINO, - UNITYCATALOG, - VERTICA, -} from '../constants/Services.constant'; import { EntityType } from '../enums/entity.enum'; import { ExplorePageTabs } from '../enums/Explore.enum'; import { @@ -168,6 +71,7 @@ import { getMlmodelConfig } from './MlmodelServiceUtils'; import { getPipelineConfig } from './PipelineServiceUtils'; import { getSearchServiceConfig } from './SearchServiceUtils'; import { getSecurityConfig } from './SecurityServiceUtils'; +import { getServiceIcon } from './ServiceIconUtils'; import { getSearchIndexFromService, getTestConnectionName, @@ -413,148 +317,12 @@ class ServiceUtilClassBase { return EntityType.TABLE; } - private readonly serviceLogoMap = new Map([ - [this.DatabaseServiceTypeSmallCase.CustomDatabase, DATABASE_DEFAULT], - [this.DatabaseServiceTypeSmallCase.Mysql, MYSQL], - [this.DatabaseServiceTypeSmallCase.Redshift, REDSHIFT], - [this.DatabaseServiceTypeSmallCase.BigQuery, BIGQUERY], - [this.DatabaseServiceTypeSmallCase.BigTable, BIGTABLE], - [this.DatabaseServiceTypeSmallCase.Hive, HIVE], - [this.DatabaseServiceTypeSmallCase.Impala, IMPALA], - [this.DatabaseServiceTypeSmallCase.Postgres, POSTGRES], - [this.DatabaseServiceTypeSmallCase.Oracle, ORACLE], - [this.DatabaseServiceTypeSmallCase.Snowflake, SNOWFLAKE], - [this.DatabaseServiceTypeSmallCase.Mssql, MSSQL], - [this.DatabaseServiceTypeSmallCase.Athena, ATHENA], - [this.DatabaseServiceTypeSmallCase.Presto, PRESTO], - [this.DatabaseServiceTypeSmallCase.Trino, TRINO], - [this.DatabaseServiceTypeSmallCase.Glue, GLUE], - [this.DatabaseServiceTypeSmallCase.DomoDatabase, DOMO], - [this.DatabaseServiceTypeSmallCase.MariaDB, MARIADB], - [this.DatabaseServiceTypeSmallCase.Vertica, VERTICA], - [this.DatabaseServiceTypeSmallCase.AzureSQL, AZURESQL], - [this.DatabaseServiceTypeSmallCase.Clickhouse, CLICKHOUSE], - [this.DatabaseServiceTypeSmallCase.Databricks, DATABRICK], - [this.DatabaseServiceTypeSmallCase.UnityCatalog, UNITYCATALOG], - [this.DatabaseServiceTypeSmallCase.Db2, IBMDB2], - [this.DatabaseServiceTypeSmallCase.Doris, DORIS], - [this.DatabaseServiceTypeSmallCase.StarRocks, STARROCKS], - [this.DatabaseServiceTypeSmallCase.Druid, DRUID], - [this.DatabaseServiceTypeSmallCase.DynamoDB, DYNAMODB], - [this.DatabaseServiceTypeSmallCase.Exasol, EXASOL], - [this.DatabaseServiceTypeSmallCase.SingleStore, SINGLESTORE], - [this.DatabaseServiceTypeSmallCase.SQLite, SQLITE], - [this.DatabaseServiceTypeSmallCase.Salesforce, SALESFORCE], - [this.DatabaseServiceTypeSmallCase.SapHana, SAP_HANA], - [this.DatabaseServiceTypeSmallCase.SapERP, SAP_ERP], - [this.DatabaseServiceTypeSmallCase.DeltaLake, DELTALAKE], - [this.DatabaseServiceTypeSmallCase.PinotDB, PINOT], - [this.DatabaseServiceTypeSmallCase.Datalake, DATALAKE], - [this.DatabaseServiceTypeSmallCase.MongoDB, MONGODB], - [this.DatabaseServiceTypeSmallCase.Cassandra, CASSANDRA], - [this.DatabaseServiceTypeSmallCase.SAS, SAS], - [this.DatabaseServiceTypeSmallCase.Couchbase, COUCHBASE], - [this.DatabaseServiceTypeSmallCase.Cockroach, COCKROACH], - [this.DatabaseServiceTypeSmallCase.Greenplum, GREENPLUM], - [this.DatabaseServiceTypeSmallCase.Teradata, TERADATA], - [this.DatabaseServiceTypeSmallCase.Synapse, SYNAPSE], - [this.DatabaseServiceTypeSmallCase.BurstIQ, BURSTIQ], - [this.DatabaseServiceTypeSmallCase.Timescale, TIMESCALE], - [this.MessagingServiceTypeSmallCase.CustomMessaging, TOPIC_DEFAULT], - [this.MessagingServiceTypeSmallCase.Kafka, KAFKA], - [this.MessagingServiceTypeSmallCase.Redpanda, REDPANDA], - [this.MessagingServiceTypeSmallCase.Kinesis, KINESIS], - [this.DashboardServiceTypeSmallCase.CustomDashboard, DASHBOARD_DEFAULT], - [this.DashboardServiceTypeSmallCase.Superset, SUPERSET], - [this.DashboardServiceTypeSmallCase.Looker, LOOKER], - [this.DashboardServiceTypeSmallCase.Tableau, TABLEAU], - [this.DashboardServiceTypeSmallCase.Hex, HEX], - [this.DashboardServiceTypeSmallCase.Redash, REDASH], - [this.DashboardServiceTypeSmallCase.Metabase, METABASE], - [this.DashboardServiceTypeSmallCase.PowerBI, POWERBI], - [this.DashboardServiceTypeSmallCase.QuickSight, QUICKSIGHT], - [this.DashboardServiceTypeSmallCase.DomoDashboard, DOMO], - [this.DashboardServiceTypeSmallCase.Mode, MODE], - [this.DashboardServiceTypeSmallCase.QlikSense, QLIK_SENSE], - [this.DashboardServiceTypeSmallCase.QlikCloud, QLIK_SENSE], - [this.DashboardServiceTypeSmallCase.Lightdash, LIGHT_DASH], - [this.DashboardServiceTypeSmallCase.Sigma, SIGMA], - [this.DashboardServiceTypeSmallCase.MicroStrategy, MICROSTRATEGY], - [this.DashboardServiceTypeSmallCase.Grafana, GRAFANA], - [this.PipelineServiceTypeSmallCase.CustomPipeline, PIPELINE_DEFAULT], - [this.PipelineServiceTypeSmallCase.Airflow, AIRFLOW], - [this.PipelineServiceTypeSmallCase.Airbyte, AIRBYTE], - [this.PipelineServiceTypeSmallCase.Dagster, DAGSTER], - [this.PipelineServiceTypeSmallCase.Fivetran, FIVETRAN], - [this.PipelineServiceTypeSmallCase.DBTCloud, DBT], - [this.PipelineServiceTypeSmallCase.GluePipeline, GLUE], - [this.PipelineServiceTypeSmallCase.KafkaConnect, KAFKA], - [this.PipelineServiceTypeSmallCase.Spark, SPARK], - [this.PipelineServiceTypeSmallCase.Spline, SPLINE], - [this.PipelineServiceTypeSmallCase.Nifi, NIFI], - [this.PipelineServiceTypeSmallCase.DomoPipeline, DOMO], - [this.PipelineServiceTypeSmallCase.DatabricksPipeline, DATABRICK], - [this.PipelineServiceTypeSmallCase.OpenLineage, OPENLINEAGE], - [this.PipelineServiceTypeSmallCase.Flink, FLINK], - [this.MlModelServiceTypeSmallCase.CustomMlModel, ML_MODEL_DEFAULT], - [this.MlModelServiceTypeSmallCase.Mlflow, MLFLOW], - [this.MlModelServiceTypeSmallCase.Sklearn, SCIKIT], - [this.MlModelServiceTypeSmallCase.SageMaker, SAGEMAKER], - [this.MetadataServiceTypeSmallCase.Amundsen, AMUNDSEN], - [this.MetadataServiceTypeSmallCase.Atlas, ATLAS], - [this.MetadataServiceTypeSmallCase.AlationSink, ALATIONSINK], - [this.MetadataServiceTypeSmallCase.OpenMetadata, LOGO], - [this.StorageServiceTypeSmallCase.CustomStorage, CUSTOM_STORAGE_DEFAULT], - [this.StorageServiceTypeSmallCase.S3, AMAZON_S3], - [this.StorageServiceTypeSmallCase.Gcs, GCS], - [this.SearchServiceTypeSmallCase.CustomSearch, CUSTOM_SEARCH_DEFAULT], - [this.SearchServiceTypeSmallCase.ElasticSearch, ELASTIC_SEARCH], - [this.SearchServiceTypeSmallCase.OpenSearch, OPEN_SEARCH], - [this.ApiServiceTypeSmallCase.REST, REST_SERVICE], - [this.DriveServiceTypeSmallCase.CustomDrive, CUSTOM_DRIVE_DEFAULT], - [this.DriveServiceTypeSmallCase.GoogleDrive, GOOGLE_DRIVE], - [this.DriveServiceTypeSmallCase.SFTP, SFTP], - ]); - - private getDefaultLogoForServiceType(type: string): string { - const serviceTypes = this.getSupportedServiceFromList(); - - if (serviceTypes.messagingServices.includes(type)) { - return TOPIC_DEFAULT; - } - if (serviceTypes.dashboardServices.includes(type)) { - return DASHBOARD_DEFAULT; - } - if (serviceTypes.pipelineServices.includes(type)) { - return PIPELINE_DEFAULT; - } - if (serviceTypes.databaseServices.includes(type)) { - return DATABASE_DEFAULT; - } - if (serviceTypes.mlmodelServices.includes(type)) { - return ML_MODEL_DEFAULT; - } - if (serviceTypes.storageServices.includes(type)) { - return CUSTOM_STORAGE_DEFAULT; - } - if (serviceTypes.searchServices.includes(type)) { - return CUSTOM_SEARCH_DEFAULT; - } - if (serviceTypes.securityServices.includes(type)) { - return DEFAULT_SERVICE; - } - if (serviceTypes.driveServices.includes(type)) { - return CUSTOM_DRIVE_DEFAULT; - } - - return DEFAULT_SERVICE; - } - - public getServiceLogo(type: string): string { + async getServiceLogo(type: string): Promise { const lowerType = toLower(type); - const logo = this.serviceLogoMap.get(lowerType); - return logo ?? this.getDefaultLogoForServiceType(type); + const icon = await getServiceIcon(lowerType); + + return icon; } public getServiceTypeLogo(searchSource: { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts index 9ccb4e64c529..e3933b1a4884 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts @@ -12,32 +12,32 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { StorageServiceType } from '../generated/entity/services/storageService'; -import customConnection from '../jsons/connectionSchemas/connections/storage/customStorageConnection.json'; -import gcsConnection from '../jsons/connectionSchemas/connections/storage/gcsConnection.json'; -import s3Connection from '../jsons/connectionSchemas/connections/storage/s3Connection.json'; -export const getStorageConfig = (type: StorageServiceType) => { - let schema = {}; - const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type as unknown as StorageServiceType) { - case StorageServiceType.S3: { - schema = s3Connection; - - break; - } - case StorageServiceType.Gcs: { - schema = gcsConnection; +const STORAGE_CONNECTION_SCHEMAS: Record< + StorageServiceType, + () => Promise<{ default: Record }> +> = { + [StorageServiceType.S3]: () => + import('../jsons/connectionSchemas/connections/storage/s3Connection.json'), + [StorageServiceType.Gcs]: () => + import('../jsons/connectionSchemas/connections/storage/gcsConnection.json'), + [StorageServiceType.CustomStorage]: () => + import( + '../jsons/connectionSchemas/connections/storage/customStorageConnection.json' + ), +}; - break; - } - case StorageServiceType.CustomStorage: { - schema = customConnection; +export const getStorageConfig = async (type: StorageServiceType) => { + const uiSchema = { ...COMMON_UI_SCHEMA }; + const loaderFn = STORAGE_CONNECTION_SCHEMAS[type]; - break; - } + if (!loaderFn) { + return cloneDeep({ schema: {}, uiSchema }); } + const schema = (await loaderFn()).default; + return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Users.util.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/Users.util.tsx index 0b53be5fa593..b0af24655ed0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Users.util.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Users.util.tsx @@ -27,7 +27,7 @@ import { } from '../constants/constants'; import { MASKED_EMAIL } from '../constants/User.constants'; import { EntityReference, User } from '../generated/entity/teams/user'; -import { getIsErrorMatch } from './CommonUtils'; +import { getIsErrorMatch } from './APIUtils'; import { getEntityName } from './EntityUtils'; import { t } from './i18next/LocalUtil'; import { LIST_CAP } from './PermissionsUtils'; From 69c53c538e987cb9533c02f0c69108dc7939257a Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:11:34 +0530 Subject: [PATCH 16/47] lazy load local options --- .../components/AppContainer/AppContainer.tsx | 5 +- .../MarketplaceNavBar.component.tsx | 5 +- .../ui/src/components/NavBar/NavBar.tsx | 8 +- .../ui/src/utils/i18next/i18nextUtil.ts | 78 ++++++++++--------- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index e645ec455618..903968f59ab0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -27,6 +27,7 @@ import { getLimitConfig } from '../../rest/limitsAPI'; import { getSettingsByType } from '../../rest/settingConfigAPI'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; import i18n from '../../utils/i18next/LocalUtil'; +import { loadLocale } from '../../utils/i18next/i18nextUtil'; import { isNewLayoutRoute } from '../../utils/LayoutUtils'; import AppSidebar from '../AppSidebar/AppSidebar.component'; import { LimitBanner } from '../common/LimitBanner/LimitBanner'; @@ -94,7 +95,9 @@ const AppContainer = () => { useEffect(() => { if (language) { - i18n.changeLanguage(language); + loadLocale(language).then(() => { + i18n.changeLanguage(language); + }); } }, [language]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 8454dd3ad87c..44738a848603 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -57,7 +57,7 @@ import { getEntityType, prepareFeedLink, } from '../../../utils/FeedUtils'; -import { languageSelectOptions } from '../../../utils/i18next/i18nextUtil'; +import { languageSelectOptions, loadLocale } from '../../../utils/i18next/i18nextUtil'; import { SupportedLocales } from '../../../utils/i18next/LocalUtil.interface'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; @@ -363,7 +363,8 @@ const MarketplaceNavBar = () => { fetchOMVersion(); }, []); - const handleLanguageChange = useCallback(({ key }: MenuInfo) => { + const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { + await loadLocale(key); i18next.changeLanguage(key); setPreference({ language: key as SupportedLocales }); navigate(0); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 4659927d9249..f3b5aab06d53 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -72,7 +72,10 @@ import { getEntityType, prepareFeedLink, } from '../../utils/FeedUtils'; -import { languageSelectOptions } from '../../utils/i18next/i18nextUtil'; +import { + languageSelectOptions, + loadLocale, +} from '../../utils/i18next/i18nextUtil'; import { SupportedLocales } from '../../utils/i18next/LocalUtil.interface'; import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; @@ -438,7 +441,8 @@ const NavBar = () => { [activeDomainEntityRef, activeDomain, t] ); - const handleLanguageChange = useCallback(({ key }: MenuInfo) => { + const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { + await loadLocale(key); i18next.changeLanguage(key); setPreference({ language: key as SupportedLocales }); navigate(0); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts index 7d6d527ee82c..2680b554663b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts @@ -13,27 +13,51 @@ import i18next, { InitOptions } from 'i18next'; import { map, upperCase } from 'lodash'; -import arSA from '../../locale/languages/ar-sa.json'; -import deDe from '../../locale/languages/de-de.json'; import enUS from '../../locale/languages/en-us.json'; -import esES from '../../locale/languages/es-es.json'; -import frFR from '../../locale/languages/fr-fr.json'; -import glES from '../../locale/languages/gl-es.json'; -import heHE from '../../locale/languages/he-he.json'; -import jaJP from '../../locale/languages/ja-jp.json'; -import koKR from '../../locale/languages/ko-kr.json'; -import mrIN from '../../locale/languages/mr-in.json'; -import nlNL from '../../locale/languages/nl-nl.json'; -import prPR from '../../locale/languages/pr-pr.json'; -import ptBR from '../../locale/languages/pt-br.json'; -import ptPT from '../../locale/languages/pt-pt.json'; -import ruRU from '../../locale/languages/ru-ru.json'; -import thTH from '../../locale/languages/th-th.json'; -import trTR from '../../locale/languages/tr-tr.json'; -import zhCN from '../../locale/languages/zh-cn.json'; -import zhTW from '../../locale/languages/zh-tw.json'; import { SupportedLocales } from './LocalUtil.interface'; +const LOCALE_LOADERS: Record< + string, + () => Promise<{ default: Record }> +> = { + 'en-US': () => import('../../locale/languages/en-us.json'), + 'ko-KR': () => import('../../locale/languages/ko-kr.json'), + 'fr-FR': () => import('../../locale/languages/fr-fr.json'), + 'zh-CN': () => import('../../locale/languages/zh-cn.json'), + 'zh-TW': () => import('../../locale/languages/zh-tw.json'), + 'ja-JP': () => import('../../locale/languages/ja-jp.json'), + 'pt-BR': () => import('../../locale/languages/pt-br.json'), + 'pt-PT': () => import('../../locale/languages/pt-pt.json'), + 'es-ES': () => import('../../locale/languages/es-es.json'), + 'gl-ES': () => import('../../locale/languages/gl-es.json'), + 'ru-RU': () => import('../../locale/languages/ru-ru.json'), + 'de-DE': () => import('../../locale/languages/de-de.json'), + 'he-HE': () => import('../../locale/languages/he-he.json'), + 'nl-NL': () => import('../../locale/languages/nl-nl.json'), + 'pr-PR': () => import('../../locale/languages/pr-pr.json'), + 'th-TH': () => import('../../locale/languages/th-th.json'), + 'mr-IN': () => import('../../locale/languages/mr-in.json'), + 'tr-TR': () => import('../../locale/languages/tr-tr.json'), + 'ar-SA': () => import('../../locale/languages/ar-sa.json'), +}; + +const loadedLocales = new Set(['en-US']); + +export const loadLocale = async (locale: string): Promise => { + if (loadedLocales.has(locale)) { + return; + } + + const loader = LOCALE_LOADERS[locale]; + if (!loader) { + return; + } + + const translations = await loader(); + i18next.addResourceBundle(locale, 'translation', translations.default, true); + loadedLocales.add(locale); +}; + export const languageSelectOptions = map(SupportedLocales, (value, key) => ({ label: `${key} - ${upperCase(value.split('-')[0])}`, key: value, @@ -45,24 +69,6 @@ export const getInitOptions = (): InitOptions => { supportedLngs: Object.values(SupportedLocales), resources: { 'en-US': { translation: enUS }, - 'ko-KR': { translation: koKR }, - 'fr-FR': { translation: frFR }, - 'zh-CN': { translation: zhCN }, - 'zh-TW': { translation: zhTW }, - 'ja-JP': { translation: jaJP }, - 'pt-BR': { translation: ptBR }, - 'pt-PT': { translation: ptPT }, - 'es-ES': { translation: esES }, - 'gl-ES': { translation: glES }, - 'ru-RU': { translation: ruRU }, - 'de-DE': { translation: deDe }, - 'he-HE': { translation: heHE }, - 'nl-NL': { translation: nlNL }, - 'pr-PR': { translation: prPR }, - 'th-TH': { translation: thTH }, - 'mr-IN': { translation: mrIN }, - 'tr-TR': { translation: trTR }, - 'ar-SA': { translation: arSA }, }, fallbackLng: ['en-US'], detection: { From 2a605991ff30ce2cda7f38df4dbc0d8069a8c897 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:13:57 +0530 Subject: [PATCH 17/47] refactor entityUtils --- .../DataAssetSummaryPanelV1.test.tsx | 31 +- .../DataAssetSummaryPanelV1.tsx | 27 +- .../EntitySummaryPanel.test.tsx | 1 - .../utils/DataAssetSummaryPanelUtils.test.tsx | 58 + .../src/utils/DataAssetSummaryPanelUtils.tsx | 1411 ++++++++++++++++ .../resources/ui/src/utils/EntityUtils.tsx | 1412 +---------------- 6 files changed, 1502 insertions(+), 1438 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.test.tsx index f4caff39344e..86bf3891c017 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.test.tsx @@ -12,7 +12,6 @@ */ import { - act, cleanup, fireEvent, render, @@ -20,6 +19,7 @@ import { waitFor, } from '@testing-library/react'; import { AxiosError } from 'axios'; +import { act } from 'react'; import { useTranslation } from 'react-i18next'; import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; import { useTourProvider } from '../../context/TourProvider/TourProvider'; @@ -30,16 +30,18 @@ import { patchDashboardDetails } from '../../rest/dashboardAPI'; import { getListTestCaseIncidentStatus } from '../../rest/incidentManagerAPI'; import { patchTableDetails } from '../../rest/tableAPI'; import { listTestCases } from '../../rest/testAPI'; +import { getEntityOverview } from '../../utils/DataAssetSummaryPanelUtils'; import { getCurrentMillis, getEpochMillisForPastDays, } from '../../utils/date-time/DateTimeUtils'; -import { getEntityOverview } from '../../utils/EntityUtils'; import { generateEntityLink } from '../../utils/TableUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import { DataAssetSummaryPanelV1 } from './DataAssetSummaryPanelV1'; import { DataAssetSummaryPanelProps } from './DataAssetSummaryPanelV1.interface'; +type DataAssetType = DataAssetSummaryPanelProps['dataAsset']; + // Mock TableUtils first to ensure getTierTags is available jest.mock('../../utils/TableUtils', () => { const mockGetTierTags = jest.fn(() => null); @@ -88,7 +90,7 @@ jest.mock('../../utils/ToastUtils', () => ({ showSuccessToast: jest.fn(), })); -jest.mock('../../utils/EntityUtils', () => { +jest.mock('../../utils/DataAssetSummaryPanelUtils', () => { const mockGetEntityOverview = jest.fn(() => []); return { @@ -194,9 +196,7 @@ jest.mock('../common/DescriptionSection/DescriptionSection', () => {
@@ -206,10 +206,10 @@ jest.mock('../common/DescriptionSection/DescriptionSection', () => { jest.mock('../common/OverviewSection/OverviewSection', () => { return jest.fn().mockImplementation(({ entityInfoV1 }) => (
- {(entityInfoV1 || []).map((item: any, index: number) => ( + {(entityInfoV1 || []).map((item: { name: string; value: string }) => (
+ key={item.name + item.value}> {item.name} {item.value}
))} @@ -221,8 +221,8 @@ jest.mock('../common/DataQualitySection/DataQualitySection', () => { return jest.fn().mockImplementation(({ tests, totalTests }) => (
{totalTests}
- {tests.map((test: any, index: number) => ( -
+ {tests.map((test: { type: string; count: number }) => ( +
{test.type}: {test.count}
))} @@ -382,7 +382,7 @@ describe('DataAssetSummaryPanelV1', () => { const mockOnDescriptionUpdate = jest.fn(); const defaultProps: DataAssetSummaryPanelProps = { - dataAsset: mockDataAsset as any, + dataAsset: mockDataAsset as unknown as DataAssetType, entityType: EntityType.TABLE, isLoading: false, onOwnerUpdate: mockOnOwnerUpdate, @@ -422,10 +422,7 @@ describe('DataAssetSummaryPanelV1', () => { { name: 'Queries', value: 250, visible: ['explore'] }, { name: 'Incidents', - value: - (additionalInfo && additionalInfo.incidentCount) !== undefined - ? additionalInfo.incidentCount - : 0, + value: additionalInfo?.incidentCount ?? 0, visible: ['explore'], }, ] @@ -596,7 +593,7 @@ describe('DataAssetSummaryPanelV1', () => { dataAsset: { ...mockDataAsset, deleted: true, - } as any, + } as unknown as DataAssetType, }; await act(async () => { @@ -904,7 +901,7 @@ describe('DataAssetSummaryPanelV1', () => { name: 'new-table', displayName: 'New Table', fullyQualifiedName: 'new.fqn', - } as any; + } as unknown as DataAssetType; await act(async () => { rerender( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.tsx index 9d80e5599d68..2418bc723128 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.tsx @@ -10,29 +10,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { AxiosError } from 'axios'; +import { Operation } from 'fast-json-patch'; import { isEmpty } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { ENTITY_PATH } from '../../constants/constants'; +import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant'; import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; import { OperationPermission, ResourceEntity, } from '../../context/PermissionProvider/PermissionProvider.interface'; import { useTourProvider } from '../../context/TourProvider/TourProvider'; -import { - getCurrentMillis, - getEpochMillisForPastDays, -} from '../../utils/date-time/DateTimeUtils'; -import { - DRAWER_NAVIGATION_OPTIONS, - getEntityOverview, - hasLineageTab, -} from '../../utils/EntityUtils'; - -import { AxiosError } from 'axios'; -import { Operation } from 'fast-json-patch'; -import { ENTITY_PATH } from '../../constants/constants'; -import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant'; import { EntityType } from '../../enums/entity.enum'; import { EntityReference } from '../../generated/entity/type'; import { TagLabel, TestCaseStatus } from '../../generated/tests/testCase'; @@ -40,7 +30,16 @@ import { TagSource } from '../../generated/type/tagLabel'; import { getListTestCaseIncidentStatus } from '../../rest/incidentManagerAPI'; import { updateTableColumn } from '../../rest/tableAPI'; import { listTestCases } from '../../rest/testAPI'; +import { getEntityOverview } from '../../utils/DataAssetSummaryPanelUtils'; +import { + getCurrentMillis, + getEpochMillisForPastDays, +} from '../../utils/date-time/DateTimeUtils'; import entityUtilClassBase from '../../utils/EntityUtilClassBase'; +import { + DRAWER_NAVIGATION_OPTIONS, + hasLineageTab, +} from '../../utils/EntityUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { generateEntityLink, getTierTags } from '../../utils/TableUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx index 249909833fb9..2191e89c0fe8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx @@ -89,7 +89,6 @@ jest.mock('../../../utils/EntityUtils', () => { return { getEntityLinkFromType: jest.fn().mockImplementation(() => 'link'), getEntityName: jest.fn().mockImplementation(() => 'displayName'), - getEntityOverview: jest.fn().mockImplementation(() => []), hasLineageTab: jest.fn((entityType) => LINEAGE_TABS_SET.has(entityType)), hasSchemaTab: jest.fn((entityType) => SCHEMA_TABS_SET.has(entityType)), hasCustomPropertiesTab: jest.fn((entityType) => diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx new file mode 100644 index 000000000000..ee8aad5e160e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx @@ -0,0 +1,58 @@ +import { ExplorePageTabs } from '../enums/Explore.enum'; +import { MOCK_CHART_DATA } from '../mocks/Chart.mock'; +import { MOCK_TABLE, MOCK_TIER_DATA } from '../mocks/TableData.mock'; +import { getEntityOverview } from './DataAssetSummaryPanelUtils'; +import { getTierTags } from './TableUtils'; + +describe('getEntityOverview', () => { + it('should call getChartOverview and get ChartData if ExplorePageTabs is charts', () => { + const result = JSON.stringify( + getEntityOverview(ExplorePageTabs.CHARTS, { + ...MOCK_CHART_DATA, + dataProducts: [], + }) + ); + + expect(result).toContain('label.owner-plural'); + expect(result).toContain('label.chart'); + expect(result).toContain('label.url-uppercase'); + expect(result).toContain('Are you an ethnic minority in your city?'); + expect(result).toContain( + `http://localhost:8088/superset/explore/?form_data=%7B%22slice_id%22%3A%20127%7D` + ); + expect(result).toContain('label.service'); + expect(result).toContain('sample_superset'); + expect(result).toContain('Other'); + expect(result).toContain('label.service-type'); + expect(result).toContain('Superset'); + }); + + it('should call getChartOverview and get TableData if ExplorePageTabs is table', () => { + const result = JSON.stringify( + getEntityOverview(ExplorePageTabs.TABLES, { + ...MOCK_TABLE, + tags: [MOCK_TIER_DATA], + dataProducts: [], + }) + ); + + expect(result).toContain('label.owner-plural'); + expect(result).toContain('label.type'); + expect(result).toContain('label.service'); + expect(result).toContain('label.database'); + expect(result).toContain('label.schema'); + expect(result).toContain('label.tier'); + expect(result).toContain('label.usage'); + expect(result).toContain('label.query-plural'); + expect(result).toContain('label.column-plural'); + expect(result).toContain('label.row-plural'); + expect(getTierTags).toHaveBeenCalledWith([MOCK_TIER_DATA]); + expect(result).toContain('Regular'); + expect(result).toContain('sample_data'); + expect(result).toContain('ecommerce_db'); + expect(result).toContain('shopify'); + expect(result).toContain('0th'); + expect(result).toContain('4'); + expect(result).toContain('14567'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx new file mode 100644 index 000000000000..fad91619bd57 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx @@ -0,0 +1,1411 @@ +import i18next from 'i18next'; +import { isEmpty, isNil, isObject, isUndefined } from 'lodash'; +import { DomainLabel } from '../components/common/DomainLabel/DomainLabel.component'; +import { OwnerLabel } from '../components/common/OwnerLabel/OwnerLabel.component'; +import QueryCount from '../components/common/QueryCount/QueryCount.component'; +import { DataAssetSummaryPanelProps } from '../components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.interface'; +import { ProfilerTabPath } from '../components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; +import { EntityServiceUnion } from '../components/Explore/ExplorePage.interface'; +import TagsV1 from '../components/Tag/TagsV1/TagsV1.component'; +import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; +import { NO_DATA } from '../constants/constants'; +import { TAG_START_WITH } from '../constants/Tag.constants'; +import { EntityTabs, EntityType, FqnPart } from '../enums/entity.enum'; +import { ExplorePageTabs } from '../enums/Explore.enum'; +import { ServiceCategory } from '../enums/service.enum'; +import { APICollection } from '../generated/entity/data/apiCollection'; +import { APIEndpoint } from '../generated/entity/data/apiEndpoint'; +import { Chart } from '../generated/entity/data/chart'; +import { Container } from '../generated/entity/data/container'; +import { Dashboard } from '../generated/entity/data/dashboard'; +import { DashboardDataModel } from '../generated/entity/data/dashboardDataModel'; +import { Database } from '../generated/entity/data/database'; +import { DatabaseSchema } from '../generated/entity/data/databaseSchema'; +import { Directory } from '../generated/entity/data/directory'; +import { File } from '../generated/entity/data/file'; +import { Metric } from '../generated/entity/data/metric'; +import { Mlmodel } from '../generated/entity/data/mlmodel'; +import { SearchIndex } from '../generated/entity/data/searchIndex'; +import { Spreadsheet } from '../generated/entity/data/spreadsheet'; +import { + StoredProcedure, + StoredProcedureCodeObject, +} from '../generated/entity/data/storedProcedure'; +import { Table, TableType, TagLabel } from '../generated/entity/data/table'; +import { Topic } from '../generated/entity/data/topic'; +import { Worksheet } from '../generated/entity/data/worksheet'; + +import { Pipeline } from '../generated/entity/data/pipeline'; +import { EntityReference } from '../generated/entity/type'; +import { UsageDetails } from '../generated/type/usageDetails'; +import { + formatNumberWithComma, + getPartialNameFromTableFQN, +} from './CommonUtils'; +import { DRAWER_NAVIGATION_OPTIONS, getEntityName } from './EntityUtils'; +import { BasicEntityOverviewInfo } from './EntityUtils.interface'; +import { getEntityDetailsPath, getServiceDetailsPath } from './RouterUtils'; +import { bytesToSize, stringToHTML } from './StringsUtils'; +import { getTierTags, getUsagePercentile } from './TableUtils'; + +interface ColumnSearchResult { + dataType?: string; + dataTypeDisplay?: string; + constraint?: string; + table?: { + name?: string; + displayName?: string; + fullyQualifiedName?: string; + }; + service?: { + name?: string; + displayName?: string; + fullyQualifiedName?: string; + type?: string; + }; + database?: { + name?: string; + displayName?: string; + fullyQualifiedName?: string; + }; + databaseSchema?: { + name?: string; + displayName?: string; + fullyQualifiedName?: string; + }; + owners?: EntityReference[]; + domains?: EntityReference[]; +} + +const entityTierRenderer = (tier?: TagLabel) => { + return tier ? ( + + ) : ( + NO_DATA + ); +}; + +const getUsageData = (usageSummary: UsageDetails | undefined) => + isNil(usageSummary?.weeklyStats?.percentileRank) + ? NO_DATA + : getUsagePercentile(usageSummary?.weeklyStats?.percentileRank ?? 0); + +const getTableFieldsFromTableDetails = (tableDetails: Table) => { + const { + fullyQualifiedName, + owners, + tags, + usageSummary, + profile, + columns, + tableType, + service, + database, + databaseSchema, + domains, + } = tableDetails; + const [serviceName, databaseName, schemaName] = getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database, FqnPart.Schema], + FQN_SEPARATOR_CHAR + ).split(FQN_SEPARATOR_CHAR); + + const serviceDisplayName = getEntityName(service) || serviceName; + const databaseDisplayName = getEntityName(database) || databaseName; + const schemaDisplayName = getEntityName(databaseSchema) || schemaName; + + const tier = getTierTags(tags ?? []); + + return { + fullyQualifiedName, + owners, + service: serviceDisplayName, + database: databaseDisplayName, + schema: schemaDisplayName, + tier, + usage: getUsageData(usageSummary), + profile, + columns, + tableType, + domains, + }; +}; + +const getCommonOverview = ( + { + owners, + domains, + }: { + owners?: EntityReference[]; + domains?: EntityReference[]; + }, + showOwner = true +) => { + return [ + ...(showOwner + ? [ + { + name: i18next.t('label.owner-plural'), + value: ( + + ), + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + ] + : []), + { + name: i18next.t('label.domain-plural'), + value: ( + + ), + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + ]; +}; + +const getTableOverview = ( + tableDetails: Table, + additionalInfo?: Record +) => { + const { + fullyQualifiedName, + owners, + profile, + columns, + tableType, + service, + database, + schema, + tier, + usage, + domains, + } = getTableFieldsFromTableDetails(tableDetails); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.type'), + value: tableType ?? TableType.Regular, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: service || NO_DATA, + url: getServiceDetailsPath(service, ServiceCategory.DATABASE_SERVICES), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.database'), + value: database || NO_DATA, + url: getEntityDetailsPath( + EntityType.DATABASE, + getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database], + FQN_SEPARATOR_CHAR + ) + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.schema'), + value: schema || NO_DATA, + url: getEntityDetailsPath( + EntityType.DATABASE_SCHEMA, + getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database, FqnPart.Schema], + FQN_SEPARATOR_CHAR + ) + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.usage'), + value: usage || NO_DATA, + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.query-plural'), + value: , + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.column-plural'), + value: columns ? columns.length : NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.row-plural'), + value: + !isUndefined(profile) && profile?.rowCount + ? formatNumberWithComma(profile.rowCount) + : NO_DATA, + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.incident-plural'), + value: additionalInfo?.incidentCount ?? 0, + isLink: true, + linkProps: { + pathname: getEntityDetailsPath( + EntityType.TABLE, + fullyQualifiedName ?? '', + EntityTabs.PROFILER, + ProfilerTabPath.INCIDENTS + ), + }, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + return overview; +}; + +const getTopicOverview = (topicDetails: Topic) => { + const { + domains, + partitions, + replicationFactor, + retentionSize, + cleanupPolicies, + maximumMessageSize, + messageSchema, + } = topicDetails; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ domains, owners: topicDetails.owners }), + { + name: i18next.t('label.partition-plural'), + value: partitions ?? NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.replication-factor'), + value: replicationFactor, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.retention-size'), + value: bytesToSize(retentionSize ?? 0), + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.clean-up-policy-plural'), + value: cleanupPolicies ? cleanupPolicies.join(', ') : NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.max-message-size'), + value: bytesToSize(maximumMessageSize ?? 0), + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.schema-type'), + value: messageSchema?.schemaType ?? NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + return overview; +}; + +const getPipelineOverview = (pipelineDetails: Pipeline) => { + const { owners, tags, sourceUrl, service, displayName, domains } = + pipelineDetails; + const tier = getTierTags(tags ?? []); + const serviceDisplayName = getEntityName(service); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: `${i18next.t('label.pipeline')} ${i18next.t( + 'label.url-uppercase' + )}`, + dataTestId: 'pipeline-url-label', + value: stringToHTML(displayName ?? '') || NO_DATA, + url: sourceUrl, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: serviceDisplayName || NO_DATA, + url: getServiceDetailsPath( + service?.name ?? '', + ServiceCategory.PIPELINE_SERVICES + ), + isLink: true, + isExternal: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + ]; + + return overview; +}; + +const getDashboardOverview = (dashboardDetails: Dashboard) => { + const { owners, tags, sourceUrl, service, displayName, project, domains } = + dashboardDetails; + const tier = getTierTags(tags ?? []); + const serviceDisplayName = getEntityName(service); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: `${i18next.t('label.dashboard')} ${i18next.t( + 'label.url-uppercase' + )}`, + value: stringToHTML(displayName ?? '') || NO_DATA, + url: sourceUrl, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: serviceDisplayName || NO_DATA, + url: getServiceDetailsPath( + service?.name ?? '', + ServiceCategory.DASHBOARD_SERVICES + ), + isExternal: false, + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + isExternal: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.project'), + value: project ?? NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + ]; + + return overview; +}; + +export const getSearchIndexOverview = (searchIndexDetails: SearchIndex) => { + const { owners, tags, service, domains } = searchIndexDetails; + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + isExternal: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName ?? NO_DATA, + url: getServiceDetailsPath( + service?.name ?? '', + ServiceCategory.SEARCH_SERVICES + ), + isExternal: false, + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + ]; + + return overview; +}; + +const getMlModelOverview = (mlModelDetails: Mlmodel) => { + const { algorithm, target, server, dashboard, owners, domains } = + mlModelDetails; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.algorithm'), + value: algorithm || NO_DATA, + url: '', + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.target'), + value: target ?? NO_DATA, + url: '', + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.server'), + value: server ?? NO_DATA, + url: server, + isLink: Boolean(server), + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.dashboard'), + value: getEntityName(dashboard) || NO_DATA, + url: getEntityDetailsPath( + EntityType.DASHBOARD, + dashboard?.fullyQualifiedName ?? '' + ), + isLink: true, + isExternal: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + return overview; +}; + +const getContainerOverview = (containerDetails: Container) => { + const { numberOfObjects, serviceType, dataModel, owners, domains } = + containerDetails; + + const visible = [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ]; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.object-plural'), + value: numberOfObjects, + isLink: false, + visible, + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible, + }, + { + name: i18next.t('label.column-plural'), + value: + !isUndefined(dataModel) && dataModel.columns + ? dataModel.columns.length + : NO_DATA, + isLink: false, + visible, + }, + ]; + + return overview; +}; + +const getChartOverview = (chartDetails: Chart) => { + const { + owners, + sourceUrl, + chartType, + service, + serviceType, + displayName, + domains, + } = chartDetails; + const serviceDisplayName = getEntityName(service); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: `${i18next.t('label.chart')} ${i18next.t('label.url-uppercase')}`, + value: stringToHTML(displayName ?? '') || NO_DATA, + url: sourceUrl, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: serviceDisplayName || NO_DATA, + url: getServiceDetailsPath( + service?.name ?? '', + ServiceCategory.DASHBOARD_SERVICES + ), + isExternal: false, + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.chart-type'), + value: chartType ?? NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.service-type'), + value: serviceType ?? NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + ]; + + return overview; +}; + +const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { + const { + owners, + tags, + service, + domains, + displayName, + dataModelType, + fullyQualifiedName, + } = dataModelDetails; + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: `${i18next.t('label.data-model')} ${i18next.t( + 'label.url-uppercase' + )}`, + value: stringToHTML(displayName ?? '') || NO_DATA, + url: getEntityDetailsPath( + EntityType.DASHBOARD_DATA_MODEL, + fullyQualifiedName ?? '' + ), + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName ?? NO_DATA, + url: getServiceDetailsPath( + service?.name ?? '', + ServiceCategory.DASHBOARD_SERVICES + ), + isExternal: false, + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + isExternal: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.data-model-type'), + value: dataModelType, + isLink: false, + isExternal: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + return overview; +}; + +const getStoredProcedureOverview = ( + storedProcedureDetails: StoredProcedure +) => { + const { fullyQualifiedName, owners, tags, domains, storedProcedureCode } = + storedProcedureDetails; + const [service, database, schema] = getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database, FqnPart.Schema], + FQN_SEPARATOR_CHAR + ).split(FQN_SEPARATOR_CHAR); + + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.service'), + value: service || NO_DATA, + url: getServiceDetailsPath(service, ServiceCategory.DATABASE_SERVICES), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.database'), + value: database || NO_DATA, + url: getEntityDetailsPath( + EntityType.DATABASE, + getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database], + FQN_SEPARATOR_CHAR + ) + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.schema'), + value: schema || NO_DATA, + url: getEntityDetailsPath( + EntityType.DATABASE_SCHEMA, + getPartialNameFromTableFQN( + fullyQualifiedName ?? '', + [FqnPart.Service, FqnPart.Database, FqnPart.Schema], + FQN_SEPARATOR_CHAR + ) + ), + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ...(isObject(storedProcedureCode) + ? [ + { + name: i18next.t('label.language'), + value: + (storedProcedureCode as StoredProcedureCodeObject).language ?? + NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ] + : []), + ]; + + return overview; +}; + +const getDatabaseOverview = (databaseDetails: Database) => { + const { owners, service, domains, tags, usageSummary } = databaseDetails; + + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + { + name: i18next.t('label.owner-plural'), + value: , + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ...getCommonOverview({ domains }, false), + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName || NO_DATA, + url: getServiceDetailsPath( + service?.fullyQualifiedName ?? '', + ServiceCategory.DATABASE_SERVICES + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + + { + name: i18next.t('label.usage'), + value: getUsageData(usageSummary), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ]; + + return overview; +}; + +const getDatabaseSchemaOverview = (databaseSchemaDetails: DatabaseSchema) => { + const { owners, service, tags, domains, usageSummary, database } = + databaseSchemaDetails; + + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + { + name: i18next.t('label.owner-plural'), + value: , + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ...getCommonOverview({ domains }, false), + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName ?? NO_DATA, + url: getServiceDetailsPath( + service?.fullyQualifiedName ?? '', + ServiceCategory.DATABASE_SERVICES + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.database'), + value: database?.fullyQualifiedName ?? NO_DATA, + url: getEntityDetailsPath( + EntityType.DATABASE, + database?.fullyQualifiedName ?? '' + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.usage'), + value: getUsageData(usageSummary), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ]; + + return overview; +}; + +const getEntityServiceOverview = (serviceDetails: EntityServiceUnion) => { + const { owners, domains, tags, serviceType } = serviceDetails; + + const tier = getTierTags(tags ?? []); + + const overview: BasicEntityOverviewInfo[] = [ + { + name: i18next.t('label.owner-plural'), + value: , + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ...getCommonOverview({ domains }, false), + { + name: i18next.t('label.tier'), + value: entityTierRenderer(tier), + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ]; + + return overview; +}; + +const getApiCollectionOverview = (apiCollection: APICollection) => { + if (isNil(apiCollection) || isEmpty(apiCollection)) { + return []; + } + + const { service, domains } = apiCollection; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ domains }, false), + { + name: i18next.t('label.endpoint-url'), + value: apiCollection.endpointURL || NO_DATA, + url: apiCollection.endpointURL, + isLink: true, + isExternal: true, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName ?? NO_DATA, + url: getServiceDetailsPath( + service?.fullyQualifiedName ?? '', + ServiceCategory.API_SERVICES + ), + isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.explore], + }, + ]; + + return overview; +}; +const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { + if (isNil(apiEndpoint) || isEmpty(apiEndpoint)) { + return []; + } + const { service, apiCollection, domains } = apiEndpoint; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ domains }, false), + { + name: i18next.t('label.endpoint-url'), + value: apiEndpoint.endpointURL || NO_DATA, + url: apiEndpoint.endpointURL, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.api-collection'), + value: apiEndpoint.apiCollection?.fullyQualifiedName ?? '', + url: getEntityDetailsPath( + EntityType.API_COLLECTION, + apiCollection?.fullyQualifiedName ?? '' + ), + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.service'), + value: service?.fullyQualifiedName ?? '', + url: getServiceDetailsPath( + service?.fullyQualifiedName ?? '', + ServiceCategory.API_SERVICES + ), + isLink: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.request-method'), + value: apiEndpoint.requestMethod || NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + ]; + + return overview; +}; +const getMetricOverview = (metric: Metric) => { + if (isNil(metric) || isEmpty(metric)) { + return []; + } + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ domains: metric.domains }, false), + { + name: i18next.t('label.metric-type'), + value: metric.metricType || NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.unit-of-measurement'), + value: metric.unitOfMeasurement || NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + { + name: i18next.t('label.granularity'), + value: metric.granularity || NO_DATA, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.explore, + DRAWER_NAVIGATION_OPTIONS.lineage, + ], + }, + ]; + + return overview; +}; + +const getDirectoryOverview = (directoryDetails: Directory) => { + const { + numberOfSubDirectories, + numberOfFiles, + serviceType, + owners, + domains, + } = directoryDetails; + + const visible = [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ]; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.directory-plural'), + value: numberOfSubDirectories ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.file-plural'), + value: numberOfFiles ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible, + }, + ]; + + return overview; +}; + +const getFileOverview = (fileDetails: File) => { + const { fileExtension, fileType, fileVersion, serviceType, owners, domains } = + fileDetails; + + const visible = [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ]; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.file-extension'), + value: fileExtension ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.file-type'), + value: fileType ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.file-version'), + value: fileVersion ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible, + }, + ]; + + return overview; +}; + +const getSpreadsheetOverview = (spreadsheetDetails: Spreadsheet) => { + const { fileVersion, serviceType, owners, domains } = spreadsheetDetails; + + const visible = [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ]; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.file-version'), + value: fileVersion ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible, + }, + ]; + + return overview; +}; + +const getWorksheetOverview = (worksheetDetails: Worksheet) => { + const { columnCount, rowCount, serviceType, owners, domains } = + worksheetDetails; + + const visible = [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ]; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.column-plural'), + value: columnCount ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.row-plural'), + value: rowCount ?? NO_DATA, + isLink: false, + visible, + }, + { + name: i18next.t('label.service-type'), + value: serviceType, + isLink: false, + visible, + }, + ]; + + return overview; +}; + +const getColumnOverview = ( + columnDetails: ColumnSearchResult +): BasicEntityOverviewInfo[] => { + const { + dataType, + dataTypeDisplay, + constraint, + table, + service, + database, + databaseSchema, + owners, + domains, + } = columnDetails; + + const overview: BasicEntityOverviewInfo[] = [ + ...getCommonOverview({ owners, domains }), + { + name: i18next.t('label.data-type'), + value: dataTypeDisplay || dataType || '--', + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.table'), + value: table?.displayName || table?.name || '--', + url: table?.fullyQualifiedName + ? getEntityDetailsPath(EntityType.TABLE, table.fullyQualifiedName) + : undefined, + isLink: !!table?.fullyQualifiedName, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.service'), + value: service?.displayName || service?.name || '--', + url: service?.fullyQualifiedName + ? getServiceDetailsPath(service.fullyQualifiedName, service.type || '') + : undefined, + isLink: !!service?.fullyQualifiedName, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.database'), + value: database?.displayName || database?.name || '--', + url: database?.fullyQualifiedName + ? getEntityDetailsPath(EntityType.DATABASE, database.fullyQualifiedName) + : undefined, + isLink: !!database?.fullyQualifiedName, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.schema'), + value: databaseSchema?.displayName || databaseSchema?.name || '--', + url: databaseSchema?.fullyQualifiedName + ? getEntityDetailsPath( + EntityType.DATABASE_SCHEMA, + databaseSchema.fullyQualifiedName + ) + : undefined, + isLink: !!databaseSchema?.fullyQualifiedName, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + if (constraint) { + overview.push({ + name: i18next.t('label.constraint'), + value: constraint, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }); + } + + return overview; +}; + +export const getEntityOverview = ( + type: string, + entityDetail: DataAssetSummaryPanelProps['dataAsset'], + additionalInfo?: Record +): Array => { + switch (type) { + case ExplorePageTabs.TABLES: + case EntityType.TABLE: { + return getTableOverview(entityDetail as Table, additionalInfo); + } + + case ExplorePageTabs.COLUMNS: + case EntityType.TABLE_COLUMN: { + return getColumnOverview(entityDetail as unknown as ColumnSearchResult); + } + + case ExplorePageTabs.TOPICS: + case EntityType.TOPIC: { + return getTopicOverview(entityDetail as Topic); + } + + case ExplorePageTabs.PIPELINES: + case EntityType.PIPELINE: { + return getPipelineOverview(entityDetail as Pipeline); + } + + case ExplorePageTabs.DASHBOARDS: + case EntityType.DASHBOARD: { + return getDashboardOverview(entityDetail as Dashboard); + } + + case ExplorePageTabs.SEARCH_INDEX: + case EntityType.SEARCH_INDEX: { + return getSearchIndexOverview(entityDetail as SearchIndex); + } + + case ExplorePageTabs.MLMODELS: + case EntityType.MLMODEL: { + return getMlModelOverview(entityDetail as Mlmodel); + } + case ExplorePageTabs.CONTAINERS: + case EntityType.CONTAINER: { + return getContainerOverview(entityDetail as Container); + } + case ExplorePageTabs.CHARTS: + case EntityType.CHART: { + return getChartOverview(entityDetail as Chart); + } + + case ExplorePageTabs.DASHBOARD_DATA_MODEL: + case EntityType.DASHBOARD_DATA_MODEL: { + return getDataModelOverview(entityDetail as DashboardDataModel); + } + + case ExplorePageTabs.STORED_PROCEDURE: + case EntityType.STORED_PROCEDURE: { + return getStoredProcedureOverview(entityDetail as StoredProcedure); + } + + case ExplorePageTabs.DATABASE: + case EntityType.DATABASE: { + return getDatabaseOverview(entityDetail as Database); + } + + case ExplorePageTabs.DATABASE_SCHEMA: + case EntityType.DATABASE_SCHEMA: { + return getDatabaseSchemaOverview(entityDetail as DatabaseSchema); + } + + case ExplorePageTabs.API_COLLECTION: + case EntityType.API_COLLECTION: { + return getApiCollectionOverview(entityDetail as APICollection); + } + + case ExplorePageTabs.API_ENDPOINT: + case EntityType.API_ENDPOINT: { + return getApiEndpointOverview(entityDetail as APIEndpoint); + } + + case ExplorePageTabs.METRIC: + case EntityType.METRIC: { + return getMetricOverview(entityDetail as Metric); + } + + case ExplorePageTabs.DIRECTORIES: + case EntityType.DIRECTORY: { + return getDirectoryOverview(entityDetail as Directory); + } + + case ExplorePageTabs.FILES: + case EntityType.FILE: { + return getFileOverview(entityDetail as unknown as File); + } + + case ExplorePageTabs.SPREADSHEETS: + case EntityType.SPREADSHEET: { + return getSpreadsheetOverview(entityDetail as Spreadsheet); + } + + case ExplorePageTabs.WORKSHEETS: + case EntityType.WORKSHEET: { + return getWorksheetOverview(entityDetail as Worksheet); + } + + case ExplorePageTabs.DATABASE_SERVICE: + case ExplorePageTabs.MESSAGING_SERVICE: + case ExplorePageTabs.DASHBOARD_SERVICE: + case ExplorePageTabs.ML_MODEL_SERVICE: + case ExplorePageTabs.PIPELINE_SERVICE: + case ExplorePageTabs.SEARCH_INDEX_SERVICE: + case ExplorePageTabs.API_SERVICE: + case EntityType.DATABASE_SERVICE: + case EntityType.MESSAGING_SERVICE: + case EntityType.DASHBOARD_SERVICE: + case EntityType.MLMODEL_SERVICE: + case EntityType.PIPELINE_SERVICE: + case EntityType.SEARCH_SERVICE: + case EntityType.API_SERVICE: { + return getEntityServiceOverview(entityDetail as EntityServiceUnion); + } + + default: + return []; + } +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx index 3a4e23fedf9a..3626bd85197e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -13,44 +13,27 @@ import { Popover, Space, Typography } from 'antd'; import i18next, { t } from 'i18next'; -import { - isEmpty, - isNil, - isObject, - isUndefined, - lowerCase, - startCase, -} from 'lodash'; +import { isEmpty, isUndefined, lowerCase, startCase } from 'lodash'; import { EntityDetailUnion } from 'Models'; import { Fragment } from 'react'; import { Link } from 'react-router-dom'; import { Node } from 'reactflow'; -import { DomainLabel } from '../components/common/DomainLabel/DomainLabel.component'; -import { OwnerLabel } from '../components/common/OwnerLabel/OwnerLabel.component'; -import QueryCount from '../components/common/QueryCount/QueryCount.component'; import { TitleLink } from '../components/common/TitleBreadcrumb/TitleBreadcrumb.interface'; import { DataAssetsWithoutServiceField } from '../components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface'; -import { DataAssetSummaryPanelProps } from '../components/DataAssetSummaryPanelV1/DataAssetSummaryPanelV1.interface'; -import { ProfilerTabPath } from '../components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; import { QueryVoteType } from '../components/Database/TableQueries/TableQueries.interface'; import { CUSTOM_PROPERTIES_TABS_SET, LINEAGE_TABS_SET, SCHEMA_TABS_SET, } from '../components/Entity/EntityRightPanel/EntityRightPanelVerticalNav.constants'; -import { - EntityServiceUnion, - EntityWithServices, -} from '../components/Explore/ExplorePage.interface'; +import { EntityWithServices } from '../components/Explore/ExplorePage.interface'; import { SearchedDataProps, SourceType, } from '../components/SearchedData/SearchedData.interface'; -import TagsV1 from '../components/Tag/TagsV1/TagsV1.component'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { DEFAULT_DOMAIN_VALUE, - NO_DATA, PLACEHOLDER_ROUTE_ENTITY_TYPE, PLACEHOLDER_ROUTE_FQN, ROUTES, @@ -59,14 +42,12 @@ import { GlobalSettingOptions, GlobalSettingsMenuCategory, } from '../constants/GlobalSettings.constants'; -import { TAG_START_WITH } from '../constants/Tag.constants'; import { EntityLineageNodeType, EntityTabs, EntityType, FqnPart, } from '../enums/entity.enum'; -import { ExplorePageTabs } from '../enums/Explore.enum'; import { ServiceCategory, ServiceCategoryPlural } from '../enums/service.enum'; import { Kpi } from '../generated/dataInsight/kpi/kpi'; import { Classification } from '../generated/entity/classification/classification'; @@ -75,32 +56,23 @@ import { APICollection } from '../generated/entity/data/apiCollection'; import { APIEndpoint } from '../generated/entity/data/apiEndpoint'; import { Chart } from '../generated/entity/data/chart'; import { Container } from '../generated/entity/data/container'; -import { Dashboard } from '../generated/entity/data/dashboard'; import { DashboardDataModel } from '../generated/entity/data/dashboardDataModel'; import { Database } from '../generated/entity/data/database'; import { DatabaseSchema } from '../generated/entity/data/databaseSchema'; import { Directory } from '../generated/entity/data/directory'; import { File } from '../generated/entity/data/file'; import { GlossaryTerm } from '../generated/entity/data/glossaryTerm'; -import { Metric } from '../generated/entity/data/metric'; -import { Mlmodel } from '../generated/entity/data/mlmodel'; -import { Pipeline } from '../generated/entity/data/pipeline'; import { SearchIndex as SearchIndexAsset, - SearchIndex as SearchIndexEntity, SearchIndexField, } from '../generated/entity/data/searchIndex'; import { Spreadsheet } from '../generated/entity/data/spreadsheet'; -import { - StoredProcedure, - StoredProcedureCodeObject, -} from '../generated/entity/data/storedProcedure'; +import { StoredProcedure } from '../generated/entity/data/storedProcedure'; import { Column, ColumnJoins, JoinedWith, Table, - TableType, } from '../generated/entity/data/table'; import { Topic } from '../generated/entity/data/topic'; import { Worksheet } from '../generated/entity/data/worksheet'; @@ -113,7 +85,6 @@ import { import { TestCase, TestSuite } from '../generated/tests/testCase'; import { EntityReference } from '../generated/type/entityUsage'; import { TagLabel } from '../generated/type/tagLabel'; -import { UsageDetails } from '../generated/type/usageDetails'; import { Votes } from '../generated/type/votes'; import { DataInsightTabs } from '../interface/data-insight.interface'; import { @@ -122,13 +93,11 @@ import { } from '../interface/search.interface'; import { DataQualityPageTabs } from '../pages/DataQuality/DataQualityPage.interface'; import { - formatNumberWithComma, getPartialNameFromTableFQN, getTableFQNFromColumnFQN, } from './CommonUtils'; import { getDataInsightPathWithFqn } from './DataInsightUtils'; import EntityLink from './EntityLink'; -import { BasicEntityOverviewInfo } from './EntityUtils.interface'; import Fqn from './Fqn'; import { getApplicationDetailsPath, @@ -155,13 +124,8 @@ import { getTestCaseDetailPagePath, } from './RouterUtils'; import { getServiceRouteFromServiceType } from './ServiceUtils'; -import { bytesToSize, getEncodedFqn, stringToHTML } from './StringsUtils'; -import { - getDataTypeString, - getTagsWithoutTier, - getTierTags, - getUsagePercentile, -} from './TableUtils'; +import { getEncodedFqn } from './StringsUtils'; +import { getDataTypeString, getTagsWithoutTier } from './TableUtils'; import { getTableTags } from './TagsUtils'; export enum DRAWER_NAVIGATION_OPTIONS { @@ -239,1370 +203,6 @@ export const getEntityTags = ( } }; -const entityTierRenderer = (tier?: TagLabel) => { - return tier ? ( - - ) : ( - NO_DATA - ); -}; - -const getUsageData = (usageSummary: UsageDetails | undefined) => - !isNil(usageSummary?.weeklyStats?.percentileRank) - ? getUsagePercentile(usageSummary?.weeklyStats?.percentileRank ?? 0) - : NO_DATA; - -const getTableFieldsFromTableDetails = (tableDetails: Table) => { - const { - fullyQualifiedName, - owners, - tags, - usageSummary, - profile, - columns, - tableType, - service, - database, - databaseSchema, - domains, - } = tableDetails; - const [serviceName, databaseName, schemaName] = getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database, FqnPart.Schema], - FQN_SEPARATOR_CHAR - ).split(FQN_SEPARATOR_CHAR); - - const serviceDisplayName = getEntityName(service) || serviceName; - const databaseDisplayName = getEntityName(database) || databaseName; - const schemaDisplayName = getEntityName(databaseSchema) || schemaName; - - const tier = getTierTags(tags ?? []); - - return { - fullyQualifiedName, - owners, - service: serviceDisplayName, - database: databaseDisplayName, - schema: schemaDisplayName, - tier, - usage: getUsageData(usageSummary), - profile, - columns, - tableType, - domains, - }; -}; - -const getCommonOverview = ( - { - owners, - domains, - }: { - owners?: EntityReference[]; - domains?: EntityReference[]; - }, - showOwner = true -) => { - return [ - ...(showOwner - ? [ - { - name: i18next.t('label.owner-plural'), - value: ( - - ), - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - ] - : []), - { - name: i18next.t('label.domain-plural'), - value: ( - - ), - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - ]; -}; - -interface ColumnSearchResult { - dataType?: string; - dataTypeDisplay?: string; - constraint?: string; - table?: { - name?: string; - displayName?: string; - fullyQualifiedName?: string; - }; - service?: { - name?: string; - displayName?: string; - fullyQualifiedName?: string; - type?: string; - }; - database?: { - name?: string; - displayName?: string; - fullyQualifiedName?: string; - }; - databaseSchema?: { - name?: string; - displayName?: string; - fullyQualifiedName?: string; - }; - owners?: EntityReference[]; - domains?: EntityReference[]; -} - -const getColumnOverview = ( - columnDetails: ColumnSearchResult -): BasicEntityOverviewInfo[] => { - const { - dataType, - dataTypeDisplay, - constraint, - table, - service, - database, - databaseSchema, - owners, - domains, - } = columnDetails; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.data-type'), - value: dataTypeDisplay || dataType || '--', - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.table'), - value: table?.displayName || table?.name || '--', - url: table?.fullyQualifiedName - ? getEntityDetailsPath(EntityType.TABLE, table.fullyQualifiedName) - : undefined, - isLink: !!table?.fullyQualifiedName, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: service?.displayName || service?.name || '--', - url: service?.fullyQualifiedName - ? getServiceDetailsPath(service.fullyQualifiedName, service.type || '') - : undefined, - isLink: !!service?.fullyQualifiedName, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.database'), - value: database?.displayName || database?.name || '--', - url: database?.fullyQualifiedName - ? getEntityDetailsPath(EntityType.DATABASE, database.fullyQualifiedName) - : undefined, - isLink: !!database?.fullyQualifiedName, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.schema'), - value: databaseSchema?.displayName || databaseSchema?.name || '--', - url: databaseSchema?.fullyQualifiedName - ? getEntityDetailsPath( - EntityType.DATABASE_SCHEMA, - databaseSchema.fullyQualifiedName - ) - : undefined, - isLink: !!databaseSchema?.fullyQualifiedName, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ]; - - if (constraint) { - overview.push({ - name: i18next.t('label.constraint'), - value: constraint, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }); - } - - return overview; -}; - -const getTableOverview = ( - tableDetails: Table, - additionalInfo?: Record -) => { - const { - fullyQualifiedName, - owners, - profile, - columns, - tableType, - service, - database, - schema, - tier, - usage, - domains, - } = getTableFieldsFromTableDetails(tableDetails); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.type'), - value: tableType ?? TableType.Regular, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: service || NO_DATA, - url: getServiceDetailsPath(service, ServiceCategory.DATABASE_SERVICES), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.database'), - value: database || NO_DATA, - url: getEntityDetailsPath( - EntityType.DATABASE, - getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database], - FQN_SEPARATOR_CHAR - ) - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.schema'), - value: schema || NO_DATA, - url: getEntityDetailsPath( - EntityType.DATABASE_SCHEMA, - getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database, FqnPart.Schema], - FQN_SEPARATOR_CHAR - ) - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.usage'), - value: usage || NO_DATA, - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.query-plural'), - value: , - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.column-plural'), - value: columns ? columns.length : NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.row-plural'), - value: - !isUndefined(profile) && profile?.rowCount - ? formatNumberWithComma(profile.rowCount) - : NO_DATA, - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.incident-plural'), - value: additionalInfo?.incidentCount ?? 0, - isLink: true, - linkProps: { - pathname: getEntityDetailsPath( - EntityType.TABLE, - fullyQualifiedName ?? '', - EntityTabs.PROFILER, - ProfilerTabPath.INCIDENTS - ), - }, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ]; - - return overview; -}; - -const getTopicOverview = (topicDetails: Topic) => { - const { - domains, - partitions, - replicationFactor, - retentionSize, - cleanupPolicies, - maximumMessageSize, - messageSchema, - } = topicDetails; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ domains, owners: topicDetails.owners }), - { - name: i18next.t('label.partition-plural'), - value: partitions ?? NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.replication-factor'), - value: replicationFactor, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.retention-size'), - value: bytesToSize(retentionSize ?? 0), - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.clean-up-policy-plural'), - value: cleanupPolicies ? cleanupPolicies.join(', ') : NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.max-message-size'), - value: bytesToSize(maximumMessageSize ?? 0), - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.schema-type'), - value: messageSchema?.schemaType ?? NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ]; - - return overview; -}; - -const getPipelineOverview = (pipelineDetails: Pipeline) => { - const { owners, tags, sourceUrl, service, displayName, domains } = - pipelineDetails; - const tier = getTierTags(tags ?? []); - const serviceDisplayName = getEntityName(service); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: `${i18next.t('label.pipeline')} ${i18next.t( - 'label.url-uppercase' - )}`, - dataTestId: 'pipeline-url-label', - value: stringToHTML(displayName ?? '') || NO_DATA, - url: sourceUrl, - isLink: true, - isExternal: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: serviceDisplayName || NO_DATA, - url: getServiceDetailsPath( - service?.name ?? '', - ServiceCategory.PIPELINE_SERVICES - ), - isLink: true, - isExternal: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - ]; - - return overview; -}; - -const getDashboardOverview = (dashboardDetails: Dashboard) => { - const { owners, tags, sourceUrl, service, displayName, project, domains } = - dashboardDetails; - const tier = getTierTags(tags ?? []); - const serviceDisplayName = getEntityName(service); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: `${i18next.t('label.dashboard')} ${i18next.t( - 'label.url-uppercase' - )}`, - value: stringToHTML(displayName ?? '') || NO_DATA, - url: sourceUrl, - isLink: true, - isExternal: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: serviceDisplayName || NO_DATA, - url: getServiceDetailsPath( - service?.name ?? '', - ServiceCategory.DASHBOARD_SERVICES - ), - isExternal: false, - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - isExternal: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.project'), - value: project ?? NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - ]; - - return overview; -}; - -export const getSearchIndexOverview = ( - searchIndexDetails: SearchIndexEntity -) => { - const { owners, tags, service, domains } = searchIndexDetails; - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - isExternal: false, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName ?? NO_DATA, - url: getServiceDetailsPath( - service?.name ?? '', - ServiceCategory.SEARCH_SERVICES - ), - isExternal: false, - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - ]; - - return overview; -}; - -const getMlModelOverview = (mlModelDetails: Mlmodel) => { - const { algorithm, target, server, dashboard, owners, domains } = - mlModelDetails; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.algorithm'), - value: algorithm || NO_DATA, - url: '', - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.target'), - value: target ?? NO_DATA, - url: '', - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.server'), - value: server ?? NO_DATA, - url: server, - isLink: Boolean(server), - isExternal: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.dashboard'), - value: getEntityName(dashboard) || NO_DATA, - url: getEntityDetailsPath( - EntityType.DASHBOARD, - dashboard?.fullyQualifiedName ?? '' - ), - isLink: true, - isExternal: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ]; - - return overview; -}; - -const getContainerOverview = (containerDetails: Container) => { - const { numberOfObjects, serviceType, dataModel, owners, domains } = - containerDetails; - - const visible = [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ]; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.object-plural'), - value: numberOfObjects, - isLink: false, - visible, - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible, - }, - { - name: i18next.t('label.column-plural'), - value: - !isUndefined(dataModel) && dataModel.columns - ? dataModel.columns.length - : NO_DATA, - isLink: false, - visible, - }, - ]; - - return overview; -}; - -const getChartOverview = (chartDetails: Chart) => { - const { - owners, - sourceUrl, - chartType, - service, - serviceType, - displayName, - domains, - } = chartDetails; - const serviceDisplayName = getEntityName(service); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: `${i18next.t('label.chart')} ${i18next.t('label.url-uppercase')}`, - value: stringToHTML(displayName ?? '') || NO_DATA, - url: sourceUrl, - isLink: true, - isExternal: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: serviceDisplayName || NO_DATA, - url: getServiceDetailsPath( - service?.name ?? '', - ServiceCategory.DASHBOARD_SERVICES - ), - isExternal: false, - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.chart-type'), - value: chartType ?? NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.service-type'), - value: serviceType ?? NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - ]; - - return overview; -}; - -const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { - const { - owners, - tags, - service, - domains, - displayName, - dataModelType, - fullyQualifiedName, - } = dataModelDetails; - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: `${i18next.t('label.data-model')} ${i18next.t( - 'label.url-uppercase' - )}`, - value: stringToHTML(displayName ?? '') || NO_DATA, - url: getEntityDetailsPath( - EntityType.DASHBOARD_DATA_MODEL, - fullyQualifiedName ?? '' - ), - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName ?? NO_DATA, - url: getServiceDetailsPath( - service?.name ?? '', - ServiceCategory.DASHBOARD_SERVICES - ), - isExternal: false, - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - isExternal: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.data-model-type'), - value: dataModelType, - isLink: false, - isExternal: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ]; - - return overview; -}; - -const getStoredProcedureOverview = ( - storedProcedureDetails: StoredProcedure -) => { - const { fullyQualifiedName, owners, tags, domains, storedProcedureCode } = - storedProcedureDetails; - const [service, database, schema] = getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database, FqnPart.Schema], - FQN_SEPARATOR_CHAR - ).split(FQN_SEPARATOR_CHAR); - - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.service'), - value: service || NO_DATA, - url: getServiceDetailsPath(service, ServiceCategory.DATABASE_SERVICES), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.database'), - value: database || NO_DATA, - url: getEntityDetailsPath( - EntityType.DATABASE, - getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database], - FQN_SEPARATOR_CHAR - ) - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.lineage], - }, - { - name: i18next.t('label.schema'), - value: schema || NO_DATA, - url: getEntityDetailsPath( - EntityType.DATABASE_SCHEMA, - getPartialNameFromTableFQN( - fullyQualifiedName ?? '', - [FqnPart.Service, FqnPart.Database, FqnPart.Schema], - FQN_SEPARATOR_CHAR - ) - ), - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ...(isObject(storedProcedureCode) - ? [ - { - name: i18next.t('label.language'), - value: - (storedProcedureCode as StoredProcedureCodeObject).language ?? - NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ], - }, - ] - : []), - ]; - - return overview; -}; - -const getDatabaseOverview = (databaseDetails: Database) => { - const { owners, service, domains, tags, usageSummary } = databaseDetails; - - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - { - name: i18next.t('label.owner-plural'), - value: , - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ...getCommonOverview({ domains }, false), - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName || NO_DATA, - url: getServiceDetailsPath( - service?.fullyQualifiedName ?? '', - ServiceCategory.DATABASE_SERVICES - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - - { - name: i18next.t('label.usage'), - value: getUsageData(usageSummary), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ]; - - return overview; -}; - -const getDatabaseSchemaOverview = (databaseSchemaDetails: DatabaseSchema) => { - const { owners, service, tags, domains, usageSummary, database } = - databaseSchemaDetails; - - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - { - name: i18next.t('label.owner-plural'), - value: , - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ...getCommonOverview({ domains }, false), - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName ?? NO_DATA, - url: getServiceDetailsPath( - service?.fullyQualifiedName ?? '', - ServiceCategory.DATABASE_SERVICES - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.database'), - value: database?.fullyQualifiedName ?? NO_DATA, - url: getEntityDetailsPath( - EntityType.DATABASE, - database?.fullyQualifiedName ?? '' - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.usage'), - value: getUsageData(usageSummary), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ]; - - return overview; -}; - -const getEntityServiceOverview = (serviceDetails: EntityServiceUnion) => { - const { owners, domains, tags, serviceType } = serviceDetails; - - const tier = getTierTags(tags ?? []); - - const overview: BasicEntityOverviewInfo[] = [ - { - name: i18next.t('label.owner-plural'), - value: , - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ...getCommonOverview({ domains }, false), - { - name: i18next.t('label.tier'), - value: entityTierRenderer(tier), - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ]; - - return overview; -}; - -const getApiCollectionOverview = (apiCollection: APICollection) => { - if (isNil(apiCollection) || isEmpty(apiCollection)) { - return []; - } - - const { service, domains } = apiCollection; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ domains }, false), - { - name: i18next.t('label.endpoint-url'), - value: apiCollection.endpointURL || NO_DATA, - url: apiCollection.endpointURL, - isLink: true, - isExternal: true, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName ?? NO_DATA, - url: getServiceDetailsPath( - service?.fullyQualifiedName ?? '', - ServiceCategory.API_SERVICES - ), - isLink: true, - visible: [DRAWER_NAVIGATION_OPTIONS.explore], - }, - ]; - - return overview; -}; -const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { - if (isNil(apiEndpoint) || isEmpty(apiEndpoint)) { - return []; - } - const { service, apiCollection, domains } = apiEndpoint; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ domains }, false), - { - name: i18next.t('label.endpoint-url'), - value: apiEndpoint.endpointURL || NO_DATA, - url: apiEndpoint.endpointURL, - isLink: true, - isExternal: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.api-collection'), - value: apiEndpoint.apiCollection?.fullyQualifiedName ?? '', - url: getEntityDetailsPath( - EntityType.API_COLLECTION, - apiCollection?.fullyQualifiedName ?? '' - ), - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.service'), - value: service?.fullyQualifiedName ?? '', - url: getServiceDetailsPath( - service?.fullyQualifiedName ?? '', - ServiceCategory.API_SERVICES - ), - isLink: true, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.request-method'), - value: apiEndpoint.requestMethod || NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - ]; - - return overview; -}; -const getMetricOverview = (metric: Metric) => { - if (isNil(metric) || isEmpty(metric)) { - return []; - } - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ domains: metric.domains }, false), - { - name: i18next.t('label.metric-type'), - value: metric.metricType || NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.unit-of-measurement'), - value: metric.unitOfMeasurement || NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - { - name: i18next.t('label.granularity'), - value: metric.granularity || NO_DATA, - isLink: false, - visible: [ - DRAWER_NAVIGATION_OPTIONS.explore, - DRAWER_NAVIGATION_OPTIONS.lineage, - ], - }, - ]; - - return overview; -}; - -const getDirectoryOverview = (directoryDetails: Directory) => { - const { - numberOfSubDirectories, - numberOfFiles, - serviceType, - owners, - domains, - } = directoryDetails; - - const visible = [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ]; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.directory-plural'), - value: numberOfSubDirectories ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.file-plural'), - value: numberOfFiles ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible, - }, - ]; - - return overview; -}; - -const getFileOverview = (fileDetails: File) => { - const { fileExtension, fileType, fileVersion, serviceType, owners, domains } = - fileDetails; - - const visible = [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ]; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.file-extension'), - value: fileExtension ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.file-type'), - value: fileType ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.file-version'), - value: fileVersion ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible, - }, - ]; - - return overview; -}; - -const getSpreadsheetOverview = (spreadsheetDetails: Spreadsheet) => { - const { fileVersion, serviceType, owners, domains } = spreadsheetDetails; - - const visible = [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ]; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.file-version'), - value: fileVersion ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible, - }, - ]; - - return overview; -}; - -const getWorksheetOverview = (worksheetDetails: Worksheet) => { - const { columnCount, rowCount, serviceType, owners, domains } = - worksheetDetails; - - const visible = [ - DRAWER_NAVIGATION_OPTIONS.lineage, - DRAWER_NAVIGATION_OPTIONS.explore, - ]; - - const overview: BasicEntityOverviewInfo[] = [ - ...getCommonOverview({ owners, domains }), - { - name: i18next.t('label.column-plural'), - value: columnCount ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.row-plural'), - value: rowCount ?? NO_DATA, - isLink: false, - visible, - }, - { - name: i18next.t('label.service-type'), - value: serviceType, - isLink: false, - visible, - }, - ]; - - return overview; -}; - -export const getEntityOverview = ( - type: string, - entityDetail: DataAssetSummaryPanelProps['dataAsset'], - additionalInfo?: Record -): Array => { - switch (type) { - case ExplorePageTabs.TABLES: - case EntityType.TABLE: { - return getTableOverview(entityDetail as Table, additionalInfo); - } - - case ExplorePageTabs.COLUMNS: - case EntityType.TABLE_COLUMN: { - return getColumnOverview(entityDetail as unknown as ColumnSearchResult); - } - - case ExplorePageTabs.TOPICS: - case EntityType.TOPIC: { - return getTopicOverview(entityDetail as Topic); - } - - case ExplorePageTabs.PIPELINES: - case EntityType.PIPELINE: { - return getPipelineOverview(entityDetail as Pipeline); - } - - case ExplorePageTabs.DASHBOARDS: - case EntityType.DASHBOARD: { - return getDashboardOverview(entityDetail as Dashboard); - } - - case ExplorePageTabs.SEARCH_INDEX: - case EntityType.SEARCH_INDEX: { - return getSearchIndexOverview(entityDetail as SearchIndexEntity); - } - - case ExplorePageTabs.MLMODELS: - case EntityType.MLMODEL: { - return getMlModelOverview(entityDetail as Mlmodel); - } - case ExplorePageTabs.CONTAINERS: - case EntityType.CONTAINER: { - return getContainerOverview(entityDetail as Container); - } - case ExplorePageTabs.CHARTS: - case EntityType.CHART: { - return getChartOverview(entityDetail as Chart); - } - - case ExplorePageTabs.DASHBOARD_DATA_MODEL: - case EntityType.DASHBOARD_DATA_MODEL: { - return getDataModelOverview(entityDetail as DashboardDataModel); - } - - case ExplorePageTabs.STORED_PROCEDURE: - case EntityType.STORED_PROCEDURE: { - return getStoredProcedureOverview(entityDetail as StoredProcedure); - } - - case ExplorePageTabs.DATABASE: - case EntityType.DATABASE: { - return getDatabaseOverview(entityDetail as Database); - } - - case ExplorePageTabs.DATABASE_SCHEMA: - case EntityType.DATABASE_SCHEMA: { - return getDatabaseSchemaOverview(entityDetail as DatabaseSchema); - } - - case ExplorePageTabs.API_COLLECTION: - case EntityType.API_COLLECTION: { - return getApiCollectionOverview(entityDetail as APICollection); - } - - case ExplorePageTabs.API_ENDPOINT: - case EntityType.API_ENDPOINT: { - return getApiEndpointOverview(entityDetail as APIEndpoint); - } - - case ExplorePageTabs.METRIC: - case EntityType.METRIC: { - return getMetricOverview(entityDetail as Metric); - } - - case ExplorePageTabs.DIRECTORIES: - case EntityType.DIRECTORY: { - return getDirectoryOverview(entityDetail as Directory); - } - - case ExplorePageTabs.FILES: - case EntityType.FILE: { - return getFileOverview(entityDetail as File); - } - - case ExplorePageTabs.SPREADSHEETS: - case EntityType.SPREADSHEET: { - return getSpreadsheetOverview(entityDetail as Spreadsheet); - } - - case ExplorePageTabs.WORKSHEETS: - case EntityType.WORKSHEET: { - return getWorksheetOverview(entityDetail as Worksheet); - } - - case ExplorePageTabs.DATABASE_SERVICE: - case ExplorePageTabs.MESSAGING_SERVICE: - case ExplorePageTabs.DASHBOARD_SERVICE: - case ExplorePageTabs.ML_MODEL_SERVICE: - case ExplorePageTabs.PIPELINE_SERVICE: - case ExplorePageTabs.SEARCH_INDEX_SERVICE: - case ExplorePageTabs.API_SERVICE: - case EntityType.DATABASE_SERVICE: - case EntityType.MESSAGING_SERVICE: - case EntityType.DASHBOARD_SERVICE: - case EntityType.MLMODEL_SERVICE: - case EntityType.PIPELINE_SERVICE: - case EntityType.SEARCH_SERVICE: - case EntityType.API_SERVICE: { - return getEntityServiceOverview(entityDetail as EntityServiceUnion); - } - - default: - return []; - } -}; - export const ENTITY_LINK_SEPARATOR = '::'; export const getEntityFeedLink = ( @@ -1687,7 +287,7 @@ export const checkIfJoinsAvailable = ( return ( joins && Boolean(joins.length) && - Boolean(joins.find((join) => join.columnName === columnName)) + Boolean(joins.some((join) => join.columnName === columnName)) ); }; From 2de630f1e64b7346fee24fbbeb13b44aea548cfe Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:32:41 +0530 Subject: [PATCH 18/47] update --- .../DestinationSelectItem.test.tsx | 8 +- .../components/AppBar/Suggestions.test.tsx | 26 +++--- .../components/AppContainer/AppContainer.tsx | 2 +- .../Auth/AuthProviders/AuthProvider.test.tsx | 5 +- .../Auth/AuthProviders/AuthProvider.tsx | 2 +- .../LazyAuthProviderWrappers.tsx | 45 +++++----- .../ContractSLACard/ContractSLA.test.tsx | 10 ++- .../MarketplaceNavBar.component.tsx | 5 +- .../components/TestCaseFormV1.test.tsx | 24 ------ .../CustomPropertiesSection.test.tsx | 36 ++++---- .../DataQualityTab/DataQualityTab.test.tsx | 2 +- .../Explore/ExploreTree/ExploreTree.test.tsx | 6 -- .../components/ExploreV1/ExploreV1.test.tsx | 11 --- .../Layout/CarouselLayout/CarouselLayout.tsx | 11 ++- .../LineageTable/LineageTable.test.tsx | 1 - .../ChangeParentHierarchy.test.tsx | 2 +- .../EntityDeleteModal.test.tsx | 26 ++---- .../ui/src/components/NavBar/NavBar.test.tsx | 9 +- .../IngestionListTable.test.tsx | 3 +- .../ConnectionConfigForm.test.tsx | 18 ++-- .../Team/TeamDetails/TeamHierarchy.tsx | 2 +- .../components/UploadFile/UploadFile.test.tsx | 14 +--- .../DeleteWidget/DeleteWidgetModal.test.tsx | 11 ++- .../ErrorPlaceHolderES.test.tsx | 12 +++ .../QueryBuilderWidget.test.tsx | 4 +- .../QueryBuilderWidget/QueryBuilderWidget.tsx | 2 +- .../TestConnection/TestConnection.test.tsx | 3 +- .../ui/src/constants/Services.constant.ts | 2 +- .../ui/src/hoc/withDomainFilter.test.tsx | 82 +++++++++++++------ .../resources/ui/src/hoc/withDomainFilter.tsx | 7 +- .../CustomPageSettings.test.tsx | 4 - .../CustomizablePage.test.tsx | 3 +- .../pages/LoginPage/LoginCarousel.test.tsx | 3 +- .../AddRolePage/AddRolePage.test.tsx | 3 - .../ui/src/pages/SignUp/SignUpPage.test.tsx | 16 ++-- .../src/main/resources/ui/src/setupTests.js | 22 +++++ .../resources/ui/src/utils/APIServiceUtils.ts | 10 +-- .../utils/DataAssetSummaryPanelUtils.test.tsx | 12 +++ .../src/utils/DataAssetSummaryPanelUtils.tsx | 12 +++ .../ui/src/utils/EntityUtils.test.tsx | 54 ------------ .../resources/ui/src/utils/HistoryUtils.ts | 6 +- .../resources/ui/src/utils/LocationUtils.ts | 16 ++++ .../resources/ui/src/utils/UserDataUtils.ts | 3 +- 43 files changed, 276 insertions(+), 279 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx index e7cc33e3430c..b292ded8900a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx @@ -59,10 +59,16 @@ jest.mock('antd', () => { }; }); -jest.mock('../../../../utils/CommonUtils', () => ({ +jest.mock('../../../../utils/i18next/LocalUtil', () => ({ Transi18next: jest.fn().mockImplementation(({ i18nKey }) => { return {i18nKey}; }), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, + t: jest.fn().mockImplementation((key) => key), + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); describe('DestinationSelectItem component', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx index a783bf0343ee..ae59290f5985 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx @@ -19,9 +19,6 @@ import Suggestions from './Suggestions'; // Mock dependencies jest.mock('../../rest/searchAPI'); -jest.mock('react-i18next', () => ({ - useTranslation: jest.fn(), -})); jest.mock('../../context/TourProvider/TourProvider'); jest.mock('../../utils/SearchUtils', () => ({ filterOptionsByIndex: jest.fn((options, index) => { @@ -39,12 +36,16 @@ jest.mock('../../utils/SearchUtils', () => ({ jest.mock('../../utils/SearchClassBase', () => ({ getEntitiesSuggestions: jest.fn(() => []), })); -jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: ({ i18nKey, values }: { i18nKey: string; values: any }) => ( - - {i18nKey} {values?.keyword || ''} - - ), +jest.mock('../../utils/i18next/LocalUtil', () => ({ + Transi18next: jest.fn().mockImplementation(({ i18nKey }) => { + return {i18nKey}; + }), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, + t: jest.fn().mockImplementation((key) => key), + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); // Mock location.search for the component @@ -56,7 +57,6 @@ Object.defineProperty(window, 'location', { }); const mockSearchQuery = searchQuery as jest.Mock; -const mockUseTranslation = useTranslation as jest.Mock; const mockUseTourProvider = useTourProvider as jest.Mock; const defaultProps = { @@ -71,15 +71,11 @@ const defaultProps = { describe('Suggestions Component', () => { beforeEach(() => { jest.clearAllMocks(); - mockUseTranslation.mockReturnValue({ - t: jest.fn((key: string) => key), - i18n: { language: 'en' }, - } as any); mockUseTourProvider.mockReturnValue({ isTourOpen: false, updateTourPage: jest.fn(), updateTourSearch: jest.fn(), - } as any); + }); }); describe('AI Query Suggestions', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 903968f59ab0..50f4251be5b8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -26,8 +26,8 @@ import { useLineageStore } from '../../hooks/useLineageStore'; import { getLimitConfig } from '../../rest/limitsAPI'; import { getSettingsByType } from '../../rest/settingConfigAPI'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; -import i18n from '../../utils/i18next/LocalUtil'; import { loadLocale } from '../../utils/i18next/i18nextUtil'; +import i18n from '../../utils/i18next/LocalUtil'; import { isNewLayoutRoute } from '../../utils/LayoutUtils'; import AppSidebar from '../AppSidebar/AppSidebar.component'; import { LimitBanner } from '../common/LimitBanner/LimitBanner'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx index 9d998a62a804..6e079e3d76ee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx @@ -100,6 +100,7 @@ jest.mock('../../../hooks/useApplicationStore', () => ({ isApplicationLoading: false, setApplicationLoading: jest.fn(), initializeAuthState: jest.fn(), + isAuthenticating: false, authConfig: { provider: AuthProviderProps.Basic, providerName: 'Basic', @@ -131,7 +132,7 @@ describe('Test auth provider', () => { ); - const logoutButton = screen.getByTestId('logout-button'); + const logoutButton = await screen.findByTestId('logout-button'); expect(logoutButton).toBeInTheDocument(); }); @@ -151,7 +152,7 @@ describe('Test auth provider', () => { ); - const logoutButton = screen.getByTestId('logout-button'); + const logoutButton = await screen.findByTestId('logout-button'); expect(logoutButton).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index b346eac19aea..168c6efb8e53 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -39,8 +39,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UN_AUTHORIZED_EXCLUDED_PATHS } from '../../../constants/Auth.constants'; import { - REDIRECT_PATHNAME, APP_ROUTER_ROUTES as ROUTES, + REDIRECT_PATHNAME, } from '../../../constants/router.constants'; import { ClientErrors } from '../../../enums/Axios.enum'; import { TabSpecificField } from '../../../enums/entity.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx index 776bdb0b915a..3e3cd1b6dd5a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx @@ -11,23 +11,32 @@ * limitations under the License. */ -import { IPublicClientApplication } from '@azure/msal-browser'; +import type { IPublicClientApplication } from '@azure/msal-browser'; import { lazy, ReactNode, Suspense } from 'react'; +import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; import Loader from '../../common/Loader/Loader'; -const Auth0ProviderComponent = lazy(() => - import('@auth0/auth0-react').then((m) => ({ default: m.Auth0Provider })) +const Auth0ProviderComponent = withSuspenseFallback( + lazy(() => + import('@auth0/auth0-react').then((m) => ({ default: m.Auth0Provider })) + ) ); -const MsalProviderComponent = lazy(() => - import('@azure/msal-react').then((m) => ({ default: m.MsalProvider })) +const MsalProviderComponent = withSuspenseFallback( + lazy(() => + import('@azure/msal-react').then((m) => ({ default: m.MsalProvider })) + ) ); -const OktaAuthProviderComponent = lazy(() => - import('./OktaAuthProvider').then((m) => ({ default: m.OktaAuthProvider })) +const OktaAuthProviderComponent = withSuspenseFallback( + lazy(() => + import('./OktaAuthProvider').then((m) => ({ default: m.OktaAuthProvider })) + ) ); -const BasicAuthProviderComponent = lazy(() => import('./BasicAuthProvider')); +const BasicAuthProviderComponent = withSuspenseFallback( + lazy(() => import('./BasicAuthProvider')) +); interface Auth0ProviderWrapperProps { clientId: string; @@ -66,11 +75,9 @@ export const LazyMsalProviderWrapper = ({ children, }: MsalProviderWrapperProps) => { return ( - }> - - {children} - - + + {children} + ); }; @@ -81,11 +88,7 @@ interface OktaAuthProviderWrapperProps { export const LazyOktaAuthProviderWrapper = ({ children, }: OktaAuthProviderWrapperProps) => { - return ( - }> - {children} - - ); + return {children}; }; interface BasicAuthProviderWrapperProps { @@ -95,9 +98,5 @@ interface BasicAuthProviderWrapperProps { export const LazyBasicAuthProviderWrapper = ({ children, }: BasicAuthProviderWrapperProps) => { - return ( - }> - {children} - - ); + return {children}; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx index 26ca45c264ae..e089aae78d29 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx @@ -22,12 +22,18 @@ import { MOCK_DATA_CONTRACT } from '../../../mocks/DataContract.mock'; import { mockTableData } from '../../../mocks/TableVersion.mock'; import ContractSLA from './ContractSLA.component'; -jest.mock('../../../utils/CommonUtils', () => ({ - Transi18next: ({ i18nKey, values }: any) => ( +jest.mock('../../../utils/i18next/LocalUtil', () => ({ + Transi18next: ({ i18nKey, values }: {i18nKey: string, values: Record}) => ( {i18nKey} - {values?.label}: {values?.data} ), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, + t: jest.fn().mockImplementation((key) => key), + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); jest.mock('../../../assets/svg/ic-check-circle-2.svg', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 44738a848603..03d6134c7e07 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -57,7 +57,10 @@ import { getEntityType, prepareFeedLink, } from '../../../utils/FeedUtils'; -import { languageSelectOptions, loadLocale } from '../../../utils/i18next/i18nextUtil'; +import { + languageSelectOptions, + loadLocale, +} from '../../../utils/i18next/i18nextUtil'; import { SupportedLocales } from '../../../utils/i18next/LocalUtil.interface'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.test.tsx index 3c2f82b43401..af05395fbdcd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/TestCaseFormV1.test.tsx @@ -285,30 +285,6 @@ jest.mock( )) ); -// Mock translations -jest.mock('react-i18next', () => ({ - useTranslation: jest.fn(() => ({ - t: (key: string, params?: Record) => { - if (key.includes('.') && params) { - return key.replace( - /\{\{(\w+)\}\}/g, - (_, paramKey) => params[paramKey] || '' - ); - } - - return key; - }, - })), - Trans: jest.fn(({ children, i18nKey }) => { - // Simple mock for Trans component that renders children - if (typeof children === 'string') { - return children; - } - - return children || i18nKey; - }), -})); - jest.mock('@openmetadata/ui-core-components', () => ({ Alert: ({ title, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx index e771ba0fd73a..29ea629ed7b8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx @@ -32,17 +32,17 @@ jest.mock('react-router-dom', () => ({ )), })); -// Mock Transi18next component -jest.mock('../../../../utils/CommonUtils', () => ({ - Transi18next: jest - .fn() - .mockImplementation(({ i18nKey, renderElement, values }) => ( -
- {i18nKey} - {values?.entity} - {values?.docs} - {renderElement} -
- )), -})); +// // Mock Transi18next component +// jest.mock('../../../../utils/CommonUtils', () => ({ +// Transi18next: jest +// .fn() +// .mockImplementation(({ i18nKey, renderElement, values }) => ( +//
+// {i18nKey} - {values?.entity} - {values?.docs} +// {renderElement} +//
+// )), +// })); // Mock Loader component jest.mock('../../../common/Loader/Loader', () => { @@ -272,11 +272,8 @@ describe('CustomPropertiesSection', () => { expect(errorPlaceholder).toBeInTheDocument(); expect(errorPlaceholder).toHaveAttribute('data-type', 'PERMISSION'); - - const transComponent = screen.getByTestId('trans-component'); - - expect(transComponent).toBeInTheDocument(); - expect(transComponent).toHaveTextContent('message.no-access-placeholder'); + + expect(errorPlaceholder).toHaveTextContent('message.no-access-placeholder'); expect(screen.queryByTestId('search-bar')).not.toBeInTheDocument(); expect(screen.queryByTestId('property-name')).not.toBeInTheDocument(); @@ -296,11 +293,8 @@ describe('CustomPropertiesSection', () => { expect(errorPlaceholder).toBeInTheDocument(); expect(errorPlaceholder).toHaveAttribute('data-type', 'CUSTOM'); - - const transComponent = screen.getByTestId('trans-component'); - - expect(transComponent).toBeInTheDocument(); - expect(transComponent).toHaveTextContent( + + expect(errorPlaceholder).toHaveTextContent( 'message.no-custom-properties-entity' ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.test.tsx index b92573748be8..b753447d5ee9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DataQualityTab/DataQualityTab.test.tsx @@ -236,7 +236,7 @@ jest.mock('../../../../utils/EntityUtils', () => ({ if (entityLink.includes('::columns::')) { const parts = entityLink.split('::columns::'); - return parts[parts.length - 1]; + return parts.at(-1); } return null; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.test.tsx index aa9ef2473a89..e5dbb069d0f3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.test.tsx @@ -19,12 +19,6 @@ jest.mock('react-router-dom', () => ({ }), })); -jest.mock('react-i18next', () => ({ - useTranslation: jest.fn().mockReturnValue({ - t: jest.fn().mockImplementation((key) => key), - }), -})); - describe('ExploreTree', () => { it('renders the correct tree nodes', async () => { const { getByText, queryByTestId } = render( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx index 92d4ad6cdb57..f049102838d2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.test.tsx @@ -161,17 +161,6 @@ jest.mock('react-i18next', () => ({ Trans: ({ children }: { children: React.ReactNode }) => <>{children}, })); -jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: jest - .fn() - .mockImplementation(({ i18nKey, renderElement, values }) => ( -
- {i18nKey} {values && JSON.stringify(values)} - {renderElement} -
- )), -})); - jest.mock('../../utils/AdvancedSearchUtils', () => ({ getDropDownItems: jest.fn().mockReturnValue([]), })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx index 0c8c4c912cc6..efae472360f8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Layout/CarouselLayout/CarouselLayout.tsx @@ -13,12 +13,13 @@ import { Col, Grid, Layout, Row } from 'antd'; import { Content } from 'antd/lib/layout/layout'; import classNames from 'classnames'; -import { lazy, ReactNode, Suspense } from 'react'; +import { lazy, ReactNode } from 'react'; +import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; import DocumentTitle from '../../common/DocumentTitle/DocumentTitle'; import './carousel-layout.less'; -const LoginCarousel = lazy( - () => import('../../../pages/LoginPage/LoginCarousel') +const LoginCarousel = withSuspenseFallback( + lazy(() => import('../../../pages/LoginPage/LoginCarousel')) ); export const CarouselLayout = ({ @@ -47,9 +48,7 @@ export const CarouselLayout = ({ 'form-carousel-container', carouselClassName )}> - }> - - +
)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.test.tsx index a194e42337e7..136a0a296cd6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/LineageTable/LineageTable.test.tsx @@ -75,7 +75,6 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: ({ i18nKey }: { i18nKey: string }) => i18nKey, getPartialNameFromTableFQN: jest .fn() .mockImplementation((fqn: string) => fqn), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx index 6fe3b74d3e05..495c931aa41c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx @@ -12,7 +12,6 @@ */ import { - act, findByRole, fireEvent, render, @@ -22,6 +21,7 @@ import userEvent from '@testing-library/user-event'; import { PageType } from '../../../generated/system/ui/page'; import { mockedGlossaryTerms } from '../../../mocks/Glossary.mock'; import ChangeParent from './ChangeParentHierarchy.component'; +import { act } from 'react'; const mockOnCancel = jest.fn(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx index 565a492e3020..dc114512ef00 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx @@ -11,10 +11,11 @@ * limitations under the License. */ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import * as CommonUtils from '../../../utils/CommonUtils'; import EntityDeleteModal from './EntityDeleteModal'; +import { Transi18next } from '../../../utils/i18next/LocalUtil'; +import { act } from 'react'; const onCancel = jest.fn(); const onConfirm = jest.fn(); @@ -35,13 +36,6 @@ jest.mock('../../../utils/BrandData/BrandClassBase', () => ({ }, })); -jest.mock('react-i18next', () => ({ - Trans: jest.fn().mockImplementation(() =>
Trans
), - useTranslation: () => ({ - t: (key: string) => key, - }), -})); - describe('Test EntityDelete Modal Component', () => { it('Should render component', async () => { await act(async () => { @@ -141,17 +135,7 @@ describe('Test EntityDelete Modal Component', () => { }); it('should render with correct brandName (OpenMetadata or Collate)', async () => { - // Mock Transi18next to actually render interpolated values - const mockTransi18next = jest.fn(({ values }) => ( -
- {values?.entityName && `Entity: ${values.entityName}`} - {values?.brandName && ` Brand: ${values.brandName}`} -
- )); - - jest - .spyOn(CommonUtils, 'Transi18next') - .mockImplementation(mockTransi18next); + await act(async () => { render(, { @@ -168,7 +152,7 @@ describe('Test EntityDelete Modal Component', () => { expect(bodyText.textContent).not.toContain('{{brandName}}'); // Verify Transi18next was called with brandName parameter - expect(mockTransi18next).toHaveBeenCalledWith( + expect(Transi18next).toHaveBeenCalledWith( expect.objectContaining({ i18nKey: 'message.permanently-delete-metadata', values: expect.objectContaining({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx index 619f0fc33e4b..89285f6dc116 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { LAST_VERSION_FETCH_TIME_KEY, ONE_HOUR_MS, @@ -18,6 +18,7 @@ import { import { getVersion } from '../../rest/miscAPI'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import NavBarComponent from './NavBar'; +import { act } from 'react'; // Place these at the very top of your test file, before any imports! const mockGetItem = jest.fn(); @@ -213,6 +214,11 @@ jest.mock('./PopupAlertClassBase', () => ({ }, })); +jest.mock('../../utils/i18next/i18nextUtil', () => ({ + languageSelectOptions: [], + loadLocale: jest.fn() +})) + describe('Test NavBar Component', () => { it('Should render NavBar component', async () => { render(); @@ -287,6 +293,7 @@ describe('handleDocumentVisibilityChange one hour threshold', () => { jest.resetModules(); jest.clearAllMocks(); global.Date.now = jest.fn(); + mockUseCustomLocation.pathname = '/' }); afterEach(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx index 2c8ea78134d1..cf8f0986dba9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import { usePermissionProvider } from '../../../../../context/PermissionProvider/PermissionProvider'; @@ -26,6 +26,7 @@ import { getRunHistoryForPipeline, } from '../../../../../rest/ingestionPipelineAPI'; import IngestionListTable from './IngestionListTable'; +import { act } from 'react'; const mockGetEntityPermissionByFqn = jest.fn(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx index 84c5b9402ae3..76ed3603e402 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx @@ -10,13 +10,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { act, fireEvent, render, screen } from '@testing-library/react'; -import { forwardRef } from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { act, forwardRef } from 'react'; import { LOADING_STATE } from '../../../../enums/common.enum'; import { ServiceCategory } from '../../../../enums/service.enum'; import { MOCK_ATHENA_SERVICE } from '../../../../mocks/Service.mock'; import { getPipelineServiceHostIp } from '../../../../rest/ingestionPipelineAPI'; -import * as CommonUtils from '../../../../utils/CommonUtils'; +import * as LocalUtils from '../../../../utils/i18next/LocalUtil'; import { formatFormDataForSubmit } from '../../../../utils/JSONSchemaFormUtils'; import { getConnectionSchemas } from '../../../../utils/ServiceConnectionUtils'; import ConnectionConfigForm from './ConnectionConfigForm'; @@ -140,10 +140,6 @@ jest.mock('../../../common/AirflowMessageBanner/AirflowMessageBanner', () => { ); }); -jest.mock('../../../../utils/CommonUtils', () => ({ - Transi18next: jest.fn().mockReturnValue('message.airflow-host-ip-address'), -})); - jest.mock('../../../../utils/BrandData/BrandClassBase', () => ({ __esModule: true, default: { @@ -216,6 +212,12 @@ const mockProps = { }; describe('ServiceConfig', () => { + beforeEach(() => { + jest + .spyOn(LocalUtils, 'Transi18next') + .mockImplementation(() => <>message.airflow-host-ip-address); + }) + it('should render Service Config', async () => { render(); @@ -306,7 +308,7 @@ describe('ServiceConfig', () => { )); jest - .spyOn(CommonUtils, 'Transi18next') + .spyOn(LocalUtils, 'Transi18next') .mockImplementation(mockTransi18next); await act(async () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx index 9c7a9a9f3af7..1843a1405a3c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx @@ -25,8 +25,8 @@ import { TabSpecificField } from '../../../../enums/entity.enum'; import { Team } from '../../../../generated/entity/teams/team'; import { Include } from '../../../../generated/type/include'; import { getTeamByName, patchTeamDetail } from '../../../../rest/teamsAPI'; -import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { getEntityName } from '../../../../utils/EntityUtils'; +import { Transi18next } from '../../../../utils/i18next/LocalUtil'; import { descriptionTableObject } from '../../../../utils/TableColumn.util'; import { getTableExpandableConfig } from '../../../../utils/TableUtils'; import { isDropRestricted } from '../../../../utils/TeamUtils'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx index d2e5117f1a3a..39e61c001487 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UploadFile/UploadFile.test.tsx @@ -28,12 +28,6 @@ jest.mock('../../utils/ToastUtils', () => ({ showErrorToast: jest.fn(), })); -jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: jest - .fn() - .mockReturnValue('message.drag-and-drop-or-browse-csv-files-here'), -})); - describe('UploadFile Component', () => { const defaultProps: UploadFileProps = { fileType: '.csv', @@ -44,13 +38,13 @@ describe('UploadFile Component', () => { jest.clearAllMocks(); }); - it('should render the upload component with correct props', () => { + it('should render the upload component with correct props', async () => { render(); expect(screen.getByTestId('upload-file-widget')).toBeInTheDocument(); expect(screen.getByTestId('import-icon')).toBeInTheDocument(); expect( - screen.getByText('message.drag-and-drop-or-browse-csv-files-here') + await screen.findByText(/message.drag-and-drop-or-browse-csv-files-here/) ).toBeInTheDocument(); }); @@ -204,11 +198,11 @@ describe('UploadFile Component', () => { }); }); - it('should render browse text correctly', () => { + it('should render browse text correctly', async () => { render(); expect( - screen.getByText('message.drag-and-drop-or-browse-csv-files-here') + await screen.findByText(/message.drag-and-drop-or-browse-csv-files-here/) ).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx index 6bf00eb563c7..66875e341a52 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx @@ -17,6 +17,7 @@ import { EntityType } from '../../../enums/entity.enum'; import { mockUserData } from '../../../mocks/MyDataPage.mock'; import { DeleteWidgetModalProps } from './DeleteWidget.interface'; import DeleteWidgetModal from './DeleteWidgetModal'; +import { ReactNode } from 'react'; const mockProps: DeleteWidgetModalProps = { visible: true, @@ -36,10 +37,6 @@ const mockPropsUser: DeleteWidgetModalProps = { const mockOnLogoutHandler = jest.fn(); -jest.mock('lodash', () => ({ - ...jest.requireActual('lodash'), - startCase: jest.fn(), -})); jest.mock('../../../rest/miscAPI', () => ({ deleteEntity: jest.fn().mockImplementation(() => @@ -65,6 +62,12 @@ jest.mock('../../../utils/ToastUtils', () => ({ showSuccessToast: jest.fn(), })); +jest.mock('../../../utils/i18next/LocalUtil', () => ({ + Transi18next: ({ children }: { children: ReactNode }) => children, + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), + t: jest.fn().mockImplementation((key: string) => key), +})); + describe('Test DeleteWidgetV1 Component', () => { it('Component should render properly', async () => { render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx index 32b21017c789..eea00ff62300 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx @@ -41,6 +41,18 @@ jest.mock('../../../utils/BrandData/BrandClassBase', () => ({ }, })); +jest.mock('../../../utils/i18next/LocalUtil', () => ({ + Transi18next: jest.fn().mockImplementation(({ i18nKey }) => { + return {i18nKey}; + }), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, + t: jest.fn().mockImplementation((key) => key), + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), +})); + const mockErrorMessage = 'An exception with message [Elasticsearch exception [type=index_not_found_exception, reason=no such index [test_search_index]]] was thrown while processing request.'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.test.tsx index ba1e4995f9b7..b15cb8a2f644 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.test.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AntdConfig, BasicConfig } from '@react-awesome-query-builder/antd'; +import { AntdConfig } from '@react-awesome-query-builder/antd'; import { Registry } from '@rjsf/utils'; import { render, screen } from '@testing-library/react'; import React from 'react'; @@ -19,7 +19,7 @@ import QueryBuilderWidget from './QueryBuilderWidget'; const mockOnFocus = jest.fn(); const mockOnBlur = jest.fn(); const mockOnChange = jest.fn(); -const baseConfig = AntdConfig as BasicConfig; +const baseConfig = AntdConfig; jest.mock( '../../../../../Explore/AdvanceSearchProvider/AdvanceSearchProvider.component', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index 1e71278e1140..ea361ba56d19 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -16,8 +16,8 @@ import { Builder, Config, ImmutableTree, - Utils as QbUtils, Query, + Utils as QbUtils, } from '@react-awesome-query-builder/antd'; import { WidgetProps } from '@rjsf/utils'; import { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx index 061422086808..86b210571a14 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx @@ -11,7 +11,6 @@ * limitations under the License. */ import { - act, fireEvent, render, screen, @@ -36,6 +35,7 @@ import { TEST_CONNECTION_DEFINITION, WORKFLOW_DETAILS, } from './TestConnection.mock'; +import { act } from 'react'; const mockonValidateFormRequiredFields = jest.fn(); @@ -96,6 +96,7 @@ jest.mock( }) ); + jest.useFakeTimers(); describe('Test Connection Component', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts index 1afe9943b1b9..3feb917bc017 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts @@ -27,5 +27,5 @@ * - const icon = await getServiceIcon('mysql'); */ -export * from './ServiceUISchema.constant'; export * from './ServiceType.constant'; +export * from './ServiceUISchema.constant'; diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx index 7a7dedac4a04..b31cde692332 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.test.tsx @@ -14,16 +14,16 @@ import { InternalAxiosRequestConfig } from 'axios'; import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; import { SearchIndex } from '../enums/search.enum'; import { useDomainStore } from '../hooks/useDomainStore'; -import { - getPathNameFromWindowLocation, - withDomainFilter, -} from './withDomainFilter'; +import { getPathNameFromWindowLocation } from '../utils/LocationUtils'; +import { withDomainFilter } from './withDomainFilter'; jest.mock('../hooks/useDomainStore'); +jest.mock('../utils/LocationUtils', () => ({ + getPathNameFromWindowLocation: jest.fn(), +})); describe('withDomainFilter', () => { const mockGetState = jest.fn(); - const mockGetPathName = getPathNameFromWindowLocation as jest.Mock; beforeEach(() => { jest.clearAllMocks(); @@ -47,7 +47,9 @@ describe('withDomainFilter', () => { describe('should not intercept requests', () => { it('should return config unchanged when path starts with /domain', () => { - mockGetPathName.mockReturnValue('/domain/test'); + (getPathNameFromWindowLocation as jest.Mock).mockImplementationOnce( + () => '/domain/test' + ); mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); const config = createMockConfig(); @@ -58,7 +60,9 @@ describe('withDomainFilter', () => { }); it('should return config unchanged when path starts with /auth/logout', () => { - mockGetPathName.mockReturnValue('/auth/logout'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/auth/logout' + ); mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); const config = createMockConfig(); @@ -69,7 +73,9 @@ describe('withDomainFilter', () => { }); it('should return config unchanged when path starts with /auth/refresh', () => { - mockGetPathName.mockReturnValue('/auth/refresh'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/auth/refresh' + ); mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); const config = createMockConfig(); @@ -80,7 +86,9 @@ describe('withDomainFilter', () => { }); it('should return config unchanged when method is not GET', () => { - mockGetPathName.mockReturnValue('/api/test'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/test' + ); mockGetState.mockReturnValue({ activeDomain: 'testDomain' }); const config = createMockConfig('post'); @@ -91,7 +99,9 @@ describe('withDomainFilter', () => { }); it('should return config unchanged when activeDomain is DEFAULT_DOMAIN_VALUE', () => { - mockGetPathName.mockReturnValue('/api/test'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/test' + ); mockGetState.mockReturnValue({ activeDomain: DEFAULT_DOMAIN_VALUE }); const config = createMockConfig(); @@ -104,7 +114,9 @@ describe('withDomainFilter', () => { describe('regular GET requests', () => { it('should add domain parameter for regular GET requests with active domain', () => { - mockGetPathName.mockReturnValue('/api/tables'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/tables' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/api/tables'); @@ -116,7 +128,9 @@ describe('withDomainFilter', () => { }); it('should preserve existing params when adding domain parameter', () => { - mockGetPathName.mockReturnValue('/api/tables'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/tables' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/api/tables', { @@ -135,7 +149,9 @@ describe('withDomainFilter', () => { describe('search query requests', () => { it('should add should filter with term and prefix for /search/query with active domain', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -174,7 +190,9 @@ describe('withDomainFilter', () => { }); it('should return config unchanged for TAG index searches', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -187,7 +205,9 @@ describe('withDomainFilter', () => { }); it('should use fullyQualifiedName field for DOMAIN index searches', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -206,7 +226,9 @@ describe('withDomainFilter', () => { }); it('should preserve existing query_filter and add should filter', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const existingFilter = { @@ -256,7 +278,9 @@ describe('withDomainFilter', () => { }); it('should handle invalid JSON in query_filter gracefully', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -294,7 +318,9 @@ describe('withDomainFilter', () => { }); it('should handle query_filter with empty must array', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const existingFilter = { @@ -331,7 +357,9 @@ describe('withDomainFilter', () => { }); it('should handle empty object query_filter gracefully', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -369,7 +397,9 @@ describe('withDomainFilter', () => { }); it('should preserve existing params when adding query_filter', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const config = createMockConfig('get', '/search/query', { @@ -386,7 +416,9 @@ describe('withDomainFilter', () => { }); it('should preserve non-bool top-level clauses when adding domain filter', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering' }); const existingFilter = JSON.stringify({ @@ -432,7 +464,9 @@ describe('withDomainFilter', () => { describe('nested domain paths', () => { it('should handle nested domain paths correctly', () => { - mockGetPathName.mockReturnValue('/api/tables'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/tables' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering.backend.services', }); @@ -446,7 +480,9 @@ describe('withDomainFilter', () => { }); it('should add should filter with nested domain for search queries', () => { - mockGetPathName.mockReturnValue('/api/search'); + (getPathNameFromWindowLocation as jest.Mock).mockReturnValueOnce( + '/api/search' + ); mockGetState.mockReturnValue({ activeDomain: 'engineering.backend.services', }); diff --git a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx index a15e3e32e9ed..dac6cbbd787b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/hoc/withDomainFilter.tsx @@ -18,11 +18,7 @@ import { QueryFieldInterface, QueryFilterInterface, } from '../pages/ExplorePage/ExplorePage.interface'; -import { getBasePath } from '../utils/HistoryUtils'; - -export const getPathNameFromWindowLocation = () => { - return window.location.pathname.replace(getBasePath() ?? '', ''); -}; +import { getPathNameFromWindowLocation } from '../utils/LocationUtils'; export const withDomainFilter = ( config: InternalAxiosRequestConfig @@ -31,6 +27,7 @@ export const withDomainFilter = ( const activeDomain = useDomainStore.getState().activeDomain; const hasActiveDomain = activeDomain !== DEFAULT_DOMAIN_VALUE; const currentPath = getPathNameFromWindowLocation(); + const shouldNotIntercept = [ '/domain', '/auth/logout', diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.test.tsx index a7f754061674..1c966dd1527e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPageSettings/CustomPageSettings.test.tsx @@ -62,10 +62,6 @@ jest.mock('../../components/common/NextPrevious/NextPrevious', () => { return jest.fn().mockImplementation(() =>
NextPrevious
); }); -jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: jest.fn().mockReturnValue(
Transi18next
), -})); - jest.mock('../../components/PageHeader/PageHeader.component', () => { return jest.fn().mockImplementation(({ children, data }) => (
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx index d0c47476f3ae..2bb9186b9a4e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { useParams } from 'react-router-dom'; import { Page, PageType } from '../../generated/system/ui/page'; import { @@ -25,6 +25,7 @@ import { getDocumentByFQN } from '../../rest/DocStoreAPI'; import { getPersonaByName } from '../../rest/PersonaAPI'; import { CustomizablePage } from './CustomizablePage'; import { WidgetConfig } from './CustomizablePage.interface'; +import { act } from 'react'; jest.mock( '../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder', diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx index b245860f0635..66c217dfbad3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx @@ -11,10 +11,11 @@ * limitations under the License. */ -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import loginClassBase from '../../constants/LoginClassBase'; import LoginCarousel from './LoginCarousel'; +import { act } from 'react'; const LOGIN_SLIDE = loginClassBase.getLoginCarouselContent(); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.test.tsx index adcf94566158..48fd6bfa7fb4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddRolePage/AddRolePage.test.tsx @@ -66,9 +66,6 @@ jest.mock('../../../components/common/ResizablePanels/ResizablePanels', () => )) ); -jest.mock('../../../utils/CommonUtils', () => ({ - getIsErrorMatch: jest.fn(), -})); jest.mock('../../../utils/BrandData/BrandClassBase', () => ({ __esModule: true, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx index d887a748be17..ffeb009d58b2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx @@ -11,11 +11,12 @@ * limitations under the License. */ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { createUser } from '../../rest/userAPI'; -import { getImages } from '../../utils/CommonUtils'; import { mockChangedFormData, mockCreateUser } from './mocks/SignupData.mock'; import SignUp from './SignUpPage'; +import { getImages } from '../../utils/UserDataUtils'; +import { act } from 'react'; let letExpectedUserName = { name: 'sample123', @@ -50,13 +51,12 @@ jest.mock('../../utils/ToastUtils', () => ({ showErrorToast: jest.fn().mockImplementation(() => mockShowErrorToast), })); -jest.mock('../../utils/CommonUtils', () => ({ +jest.mock('../../utils/UserDataUtils', () => ({ getImages: jest .fn() .mockResolvedValue( 'https://lh3.googleusercontent.com/a/ALm5wu0HwEPhAbyRha16cUHrEum-zxTDzj6KZiqYsT5Y=s96-c' ), - Transi18next: jest.fn().mockReturnValue('text'), })); jest.mock('../../utils/AuthProvider.util', () => ({ @@ -84,7 +84,7 @@ describe('SignUp page', () => { const emailInput = screen.getByTestId('email-input'); const selectTeamLabel = screen.getByTestId('select-team-label'); const createButton = screen.getByTestId('create-button'); - const loadingContent = await screen.queryByTestId('loading-content'); + const loadingContent = screen.queryByTestId('loading-content'); const submitButton = screen.getByTestId('create-button'); expect(logo).toBeInTheDocument(); @@ -111,11 +111,11 @@ describe('SignUp page', () => { const form = screen.getByTestId('create-user-form'); const fullNameInput = screen.getByTestId( 'full-name-input' - ) as HTMLInputElement; + ); const userNameInput = screen.getByTestId( 'username-input' - ) as HTMLInputElement; - const emailInput = screen.getByTestId('email-input') as HTMLInputElement; + ) ; + const emailInput = screen.getByTestId('email-input'); const submitButton = screen.getByTestId('create-button'); expect(form).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index fa4c15921153..936624b11508 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -228,3 +228,25 @@ jest.mock('@mui/material', () => { styled, }; }); + + +jest.mock('./utils/i18next/LocalUtil', () => { + const React = require('react'); + + return ({ + Transi18next: jest.fn().mockImplementation(({ i18nKey, renderElement, values }) => { + const valueArr = Object.values(values ?? {}) + + return React.createElement('div', i18nKey, + [i18nKey, renderElement, valueArr]); + }), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, + t: jest.fn().mockImplementation((key) => key), + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), + translateWithNestedKeys: jest.fn().mockImplementation((key, params) => { + return params ? `${key}_${JSON.stringify(params)}` : key; + }), +})}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts index 59c4884becc1..9a0c1581c729 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts @@ -18,14 +18,8 @@ import restConnection from '../jsons/connectionSchemas/connections/api/restConne export const getAPIConfig = (type: APIServiceType) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { - case APIServiceType.REST: - schema = restConnection; - - break; - - default: - break; + if (type === APIServiceType.REST) { + schema = restConnection; } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx index ee8aad5e160e..bab18e5878ee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx @@ -1,3 +1,15 @@ +/* + * 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 { ExplorePageTabs } from '../enums/Explore.enum'; import { MOCK_CHART_DATA } from '../mocks/Chart.mock'; import { MOCK_TABLE, MOCK_TIER_DATA } from '../mocks/TableData.mock'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx index fad91619bd57..d0be44d62eae 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx @@ -1,3 +1,15 @@ +/* + * 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 i18next from 'i18next'; import { isEmpty, isNil, isObject, isUndefined } from 'lodash'; import { DomainLabel } from '../components/common/DomainLabel/DomainLabel.component'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx index 599f427825e8..42f38217ac1b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx @@ -26,7 +26,6 @@ import { getDomainDisplayName, getEntityBreadcrumbs, getEntityLinkFromType, - getEntityOverview, hasCustomPropertiesTab, hasLineageTab, hasSchemaTab, @@ -218,59 +217,6 @@ describe('EntityUtils unit tests', () => { }); }); - describe('getEntityOverview', () => { - it('should call getChartOverview and get ChartData if ExplorePageTabs is charts', () => { - const result = JSON.stringify( - getEntityOverview(ExplorePageTabs.CHARTS, { - ...MOCK_CHART_DATA, - dataProducts: [], - }) - ); - - expect(result).toContain('label.owner-plural'); - expect(result).toContain('label.chart'); - expect(result).toContain('label.url-uppercase'); - expect(result).toContain('Are you an ethnic minority in your city?'); - expect(result).toContain( - `http://localhost:8088/superset/explore/?form_data=%7B%22slice_id%22%3A%20127%7D` - ); - expect(result).toContain('label.service'); - expect(result).toContain('sample_superset'); - expect(result).toContain('Other'); - expect(result).toContain('label.service-type'); - expect(result).toContain('Superset'); - }); - - it('should call getChartOverview and get TableData if ExplorePageTabs is table', () => { - const result = JSON.stringify( - getEntityOverview(ExplorePageTabs.TABLES, { - ...MOCK_TABLE, - tags: [MOCK_TIER_DATA], - dataProducts: [], - }) - ); - - expect(result).toContain('label.owner-plural'); - expect(result).toContain('label.type'); - expect(result).toContain('label.service'); - expect(result).toContain('label.database'); - expect(result).toContain('label.schema'); - expect(result).toContain('label.tier'); - expect(result).toContain('label.usage'); - expect(result).toContain('label.query-plural'); - expect(result).toContain('label.column-plural'); - expect(result).toContain('label.row-plural'); - expect(getTierTags).toHaveBeenCalledWith([MOCK_TIER_DATA]); - expect(result).toContain('Regular'); - expect(result).toContain('sample_data'); - expect(result).toContain('ecommerce_db'); - expect(result).toContain('shopify'); - expect(result).toContain('0th'); - expect(result).toContain('4'); - expect(result).toContain('14567'); - }); - }); - describe('getColumnSorter', () => { type TestType = { name: string }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/HistoryUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/HistoryUtils.ts index 53130b85b4d7..174ae3ce877b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/HistoryUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/HistoryUtils.ts @@ -11,7 +11,7 @@ * limitations under the License. */ export const getBasePath = () => { - return window.BASE_PATH !== '${basePath}' - ? window.BASE_PATH?.slice(0, -1) ?? '' - : ''; + return globalThis.BASE_PATH === '${basePath}' + ? '' + : globalThis.BASE_PATH?.slice(0, -1) ?? ''; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts new file mode 100644 index 000000000000..26ae263eee30 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export const getPathNameFromWindowLocation = () => { + return window.location.pathname.replace(getBasePath() ?? '', ''); +}; \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts index b97a632a7573..90c689e45e4d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts @@ -40,7 +40,8 @@ export const getImages = (imageUri: string) => { EImageTypes, string >; - for (const type in EImageTypes) { + const keys = Object.keys(EImageTypes); + for (const type of keys) { imagesObj[type] = imageUri.replace('s96-c', EImageTypes[type]); } From 1414cec28cbcde0016cafe7b39ec706addbdc041 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:29:59 +0530 Subject: [PATCH 19/47] update --- .../resources/ui/src/utils/APIServiceUtils.ts | 4 +- .../ui/src/utils/DashboardServiceUtils.ts | 202 +++++--- .../ui/src/utils/DatabaseServiceUtils.tsx | 480 +++++++++++------- .../ui/src/utils/MessagingServiceUtils.ts | 70 +-- .../ui/src/utils/MlmodelServiceUtils.ts | 56 +- .../ui/src/utils/PipelineServiceUtils.ts | 103 ++-- .../ui/src/utils/SearchServiceUtils.ts | 44 +- .../ui/src/utils/SecurityServiceUtils.ts | 9 +- .../ui/src/utils/ServiceIconUtils.ts | 343 ++++++++----- .../ui/src/utils/ServiceUtilClassBase.ts | 10 +- .../ui/src/utils/StorageServiceUtils.ts | 45 +- 11 files changed, 764 insertions(+), 602 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts index 9a0c1581c729..99f58dbf27cd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts @@ -11,15 +11,15 @@ * limitations under the License. */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { APIServiceType } from '../generated/entity/services/apiService'; import restConnection from '../jsons/connectionSchemas/connections/api/restConnection.json'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; export const getAPIConfig = (type: APIServiceType) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; if (type === APIServiceType.REST) { - schema = restConnection; + schema = restConnection; } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts index c2064f130671..8a1135061852 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardServiceUtils.ts @@ -17,84 +17,24 @@ import { DashboardConnection, DashboardServiceType, } from '../generated/entity/services/dashboardService'; - -const DASHBOARD_CONNECTION_SCHEMAS: Record< - DashboardServiceType, - () => Promise<{ default: Record }> -> = { - [DashboardServiceType.Looker]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/lookerConnection.json' - ), - [DashboardServiceType.Metabase]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/metabaseConnection.json' - ), - [DashboardServiceType.Mode]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/modeConnection.json' - ), - [DashboardServiceType.PowerBI]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/powerBIConnection.json' - ), - [DashboardServiceType.Redash]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/redashConnection.json' - ), - [DashboardServiceType.Superset]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/supersetConnection.json' - ), - [DashboardServiceType.Sigma]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/sigmaConnection.json' - ), - [DashboardServiceType.Tableau]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/tableauConnection.json' - ), - [DashboardServiceType.DomoDashboard]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/domoDashboardConnection.json' - ), - [DashboardServiceType.CustomDashboard]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/customDashboardConnection.json' - ), - [DashboardServiceType.QuickSight]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/quickSightConnection.json' - ), - [DashboardServiceType.QlikSense]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/qlikSenseConnection.json' - ), - [DashboardServiceType.QlikCloud]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/qlikCloudConnection.json' - ), - [DashboardServiceType.Lightdash]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/lightdashConnection.json' - ), - [DashboardServiceType.MicroStrategy]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/microStrategyConnection.json' - ), - [DashboardServiceType.Grafana]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/grafanaConnection.json' - ), - [DashboardServiceType.Hex]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/hexConnection.json' - ), - [DashboardServiceType.Ssrs]: () => - import( - '../jsons/connectionSchemas/connections/dashboard/ssrsConnection.json' - ), -}; +import customDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/customDashboardConnection.json'; +import domoDashboardConnection from '../jsons/connectionSchemas/connections/dashboard/domoDashboardConnection.json'; +import grafanaConnection from '../jsons/connectionSchemas/connections/dashboard/grafanaConnection.json'; +import hexConnection from '../jsons/connectionSchemas/connections/dashboard/hexConnection.json'; +import lightdashConnection from '../jsons/connectionSchemas/connections/dashboard/lightdashConnection.json'; +import lookerConnection from '../jsons/connectionSchemas/connections/dashboard/lookerConnection.json'; +import metabaseConnection from '../jsons/connectionSchemas/connections/dashboard/metabaseConnection.json'; +import microStrategyConnection from '../jsons/connectionSchemas/connections/dashboard/microStrategyConnection.json'; +import modeConnection from '../jsons/connectionSchemas/connections/dashboard/modeConnection.json'; +import powerBIConnection from '../jsons/connectionSchemas/connections/dashboard/powerBIConnection.json'; +import qlikcloudConnection from '../jsons/connectionSchemas/connections/dashboard/qlikCloudConnection.json'; +import qliksenseConnection from '../jsons/connectionSchemas/connections/dashboard/qlikSenseConnection.json'; +import quicksightConnection from '../jsons/connectionSchemas/connections/dashboard/quickSightConnection.json'; +import redashConnection from '../jsons/connectionSchemas/connections/dashboard/redashConnection.json'; +import sigmaConnection from '../jsons/connectionSchemas/connections/dashboard/sigmaConnection.json'; +import ssrsConnection from '../jsons/connectionSchemas/connections/dashboard/ssrsConnection.json'; +import supersetConnection from '../jsons/connectionSchemas/connections/dashboard/supersetConnection.json'; +import tableauConnection from '../jsons/connectionSchemas/connections/dashboard/tableauConnection.json'; export const getDashboardURL = (config: DashboardConnection['config']) => { return !isUndefined(config) && !isEmpty(config.hostPort) @@ -102,15 +42,109 @@ export const getDashboardURL = (config: DashboardConnection['config']) => { : '--'; }; -export const getDashboardConfig = async (type: DashboardServiceType) => { +export const getDashboardConfig = (type: DashboardServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = DASHBOARD_CONNECTION_SCHEMAS[type]; + switch (type) { + case DashboardServiceType.Looker: { + schema = lookerConnection; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + break; + } + case DashboardServiceType.Metabase: { + schema = metabaseConnection; + + break; + } + case DashboardServiceType.Mode: { + schema = modeConnection; + + break; + } + case DashboardServiceType.PowerBI: { + schema = powerBIConnection; + + break; + } + case DashboardServiceType.Redash: { + schema = redashConnection; + + break; + } + case DashboardServiceType.Superset: { + schema = supersetConnection; + + break; + } + case DashboardServiceType.Sigma: { + schema = sigmaConnection; + + break; + } + case DashboardServiceType.Tableau: { + schema = tableauConnection; + + break; + } + case DashboardServiceType.DomoDashboard: { + schema = domoDashboardConnection; + + break; + } + case DashboardServiceType.CustomDashboard: { + schema = customDashboardConnection; + + break; + } + + case DashboardServiceType.QuickSight: { + schema = quicksightConnection; + + break; + } - const schema = (await loaderFn()).default; + case DashboardServiceType.QlikSense: { + schema = qliksenseConnection; + + break; + } + + case DashboardServiceType.QlikCloud: { + schema = qlikcloudConnection; + + break; + } + + case DashboardServiceType.Lightdash: { + schema = lightdashConnection; + + break; + } + + case DashboardServiceType.MicroStrategy: { + schema = microStrategyConnection; + + break; + } + + case DashboardServiceType.Grafana: { + schema = grafanaConnection; + + break; + } + + case DashboardServiceType.Hex: { + schema = hexConnection; + + break; + } + + case DashboardServiceType.Ssrs: { + schema = ssrsConnection; + + break; + } + } return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx index 6c4eec8b4411..7090a6d18d3d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DatabaseServiceUtils.tsx @@ -22,209 +22,297 @@ import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { OperationPermission } from '../context/PermissionProvider/PermissionProvider.interface'; import { EntityType } from '../enums/entity.enum'; import { DatabaseServiceType } from '../generated/entity/services/databaseService'; +import athenaConnection from '../jsons/connectionSchemas/connections/database/athenaConnection.json'; +import azureSQLConnection from '../jsons/connectionSchemas/connections/database/azureSQLConnection.json'; +import bigQueryConnection from '../jsons/connectionSchemas/connections/database/bigQueryConnection.json'; +import bigTableConnection from '../jsons/connectionSchemas/connections/database/bigTableConnection.json'; +import burstiqConnection from '../jsons/connectionSchemas/connections/database/burstIQConnection.json'; +import cassandraConnection from '../jsons/connectionSchemas/connections/database/cassandraConnection.json'; +import clickhouseConnection from '../jsons/connectionSchemas/connections/database/clickhouseConnection.json'; +import cockroachConnection from '../jsons/connectionSchemas/connections/database/cockroachConnection.json'; +import couchbaseConnection from '../jsons/connectionSchemas/connections/database/couchbaseConnection.json'; +import customDatabaseConnection from '../jsons/connectionSchemas/connections/database/customDatabaseConnection.json'; +import databricksConnection from '../jsons/connectionSchemas/connections/database/databricksConnection.json'; +import DatalakeConnection from '../jsons/connectionSchemas/connections/database/datalakeConnection.json'; +import db2Connection from '../jsons/connectionSchemas/connections/database/db2Connection.json'; +import deltaLakeConnection from '../jsons/connectionSchemas/connections/database/deltaLakeConnection.json'; +import domoDatabaseConnection from '../jsons/connectionSchemas/connections/database/domoDatabaseConnection.json'; +import dorisConnection from '../jsons/connectionSchemas/connections/database/dorisConnection.json'; +import druidConnection from '../jsons/connectionSchemas/connections/database/druidConnection.json'; +import dynamoDBConnection from '../jsons/connectionSchemas/connections/database/dynamoDBConnection.json'; +import exasolConnection from '../jsons/connectionSchemas/connections/database/exasolConnection.json'; +import glueConnection from '../jsons/connectionSchemas/connections/database/glueConnection.json'; +import greenplumConnection from '../jsons/connectionSchemas/connections/database/greenplumConnection.json'; +import hiveConnection from '../jsons/connectionSchemas/connections/database/hiveConnection.json'; +import impalaConnection from '../jsons/connectionSchemas/connections/database/impalaConnection.json'; +import mariaDBConnection from '../jsons/connectionSchemas/connections/database/mariaDBConnection.json'; +import mongoDBConnection from '../jsons/connectionSchemas/connections/database/mongoDBConnection.json'; +import mssqlConnection from '../jsons/connectionSchemas/connections/database/mssqlConnection.json'; +import mysqlConnection from '../jsons/connectionSchemas/connections/database/mysqlConnection.json'; +import oracleConnection from '../jsons/connectionSchemas/connections/database/oracleConnection.json'; +import pinotConnection from '../jsons/connectionSchemas/connections/database/pinotDBConnection.json'; +import postgresConnection from '../jsons/connectionSchemas/connections/database/postgresConnection.json'; +import prestoConnection from '../jsons/connectionSchemas/connections/database/prestoConnection.json'; +import redshiftConnection from '../jsons/connectionSchemas/connections/database/redshiftConnection.json'; +import salesforceConnection from '../jsons/connectionSchemas/connections/database/salesforceConnection.json'; +import sapErpConnection from '../jsons/connectionSchemas/connections/database/sapErpConnection.json'; +import sapHanaConnection from '../jsons/connectionSchemas/connections/database/sapHanaConnection.json'; +import sasConnection from '../jsons/connectionSchemas/connections/database/sasConnection.json'; +import singleStoreConnection from '../jsons/connectionSchemas/connections/database/singleStoreConnection.json'; +import snowflakeConnection from '../jsons/connectionSchemas/connections/database/snowflakeConnection.json'; +import sqliteConnection from '../jsons/connectionSchemas/connections/database/sqliteConnection.json'; +import starrocksConnection from '../jsons/connectionSchemas/connections/database/starrocksConnection.json'; +import synapseConnection from '../jsons/connectionSchemas/connections/database/synapseConnection.json'; +import teradataConnection from '../jsons/connectionSchemas/connections/database/teradataConnection.json'; +import timescaleConnection from '../jsons/connectionSchemas/connections/database/timescaleConnection.json'; +import trinoConnection from '../jsons/connectionSchemas/connections/database/trinoConnection.json'; +import unityCatalogConnection from '../jsons/connectionSchemas/connections/database/unityCatalogConnection.json'; +import verticaConnection from '../jsons/connectionSchemas/connections/database/verticaConnection.json'; import { exportDatabaseServiceDetailsInCSV } from '../rest/serviceAPI'; import { getEntityImportPath } from './EntityUtils'; import { t } from './i18next/LocalUtil'; -const DATABASE_CONNECTION_SCHEMAS: Record< - DatabaseServiceType, - () => Promise<{ default: Record }> -> = { - [DatabaseServiceType.Athena]: () => - import( - '../jsons/connectionSchemas/connections/database/athenaConnection.json' - ), - [DatabaseServiceType.AzureSQL]: () => - import( - '../jsons/connectionSchemas/connections/database/azureSQLConnection.json' - ), - [DatabaseServiceType.BigQuery]: () => - import( - '../jsons/connectionSchemas/connections/database/bigQueryConnection.json' - ), - [DatabaseServiceType.BigTable]: () => - import( - '../jsons/connectionSchemas/connections/database/bigTableConnection.json' - ), - [DatabaseServiceType.Clickhouse]: () => - import( - '../jsons/connectionSchemas/connections/database/clickhouseConnection.json' - ), - [DatabaseServiceType.Cockroach]: () => - import( - '../jsons/connectionSchemas/connections/database/cockroachConnection.json' - ), - [DatabaseServiceType.CustomDatabase]: () => - import( - '../jsons/connectionSchemas/connections/database/customDatabaseConnection.json' - ), - [DatabaseServiceType.Databricks]: () => - import( - '../jsons/connectionSchemas/connections/database/databricksConnection.json' - ), - [DatabaseServiceType.Datalake]: () => - import( - '../jsons/connectionSchemas/connections/database/datalakeConnection.json' - ), - [DatabaseServiceType.Db2]: () => - import( - '../jsons/connectionSchemas/connections/database/db2Connection.json' - ), - [DatabaseServiceType.DeltaLake]: () => - import( - '../jsons/connectionSchemas/connections/database/deltaLakeConnection.json' - ), - [DatabaseServiceType.Doris]: () => - import( - '../jsons/connectionSchemas/connections/database/dorisConnection.json' - ), - [DatabaseServiceType.Druid]: () => - import( - '../jsons/connectionSchemas/connections/database/druidConnection.json' - ), - [DatabaseServiceType.DynamoDB]: () => - import( - '../jsons/connectionSchemas/connections/database/dynamoDBConnection.json' - ), - [DatabaseServiceType.Glue]: () => - import( - '../jsons/connectionSchemas/connections/database/glueConnection.json' - ), - [DatabaseServiceType.Hive]: () => - import( - '../jsons/connectionSchemas/connections/database/hiveConnection.json' - ), - [DatabaseServiceType.Impala]: () => - import( - '../jsons/connectionSchemas/connections/database/impalaConnection.json' - ), - [DatabaseServiceType.MariaDB]: () => - import( - '../jsons/connectionSchemas/connections/database/mariaDBConnection.json' - ), - [DatabaseServiceType.Mssql]: () => - import( - '../jsons/connectionSchemas/connections/database/mssqlConnection.json' - ), - [DatabaseServiceType.Mysql]: () => - import( - '../jsons/connectionSchemas/connections/database/mysqlConnection.json' - ), - [DatabaseServiceType.Oracle]: () => - import( - '../jsons/connectionSchemas/connections/database/oracleConnection.json' - ), - [DatabaseServiceType.PinotDB]: () => - import( - '../jsons/connectionSchemas/connections/database/pinotDBConnection.json' - ), - [DatabaseServiceType.Postgres]: () => - import( - '../jsons/connectionSchemas/connections/database/postgresConnection.json' - ), - [DatabaseServiceType.Presto]: () => - import( - '../jsons/connectionSchemas/connections/database/prestoConnection.json' - ), - [DatabaseServiceType.Redshift]: () => - import( - '../jsons/connectionSchemas/connections/database/redshiftConnection.json' - ), - [DatabaseServiceType.Salesforce]: () => - import( - '../jsons/connectionSchemas/connections/database/salesforceConnection.json' - ), - [DatabaseServiceType.SAPHana]: () => - import( - '../jsons/connectionSchemas/connections/database/sapHanaConnection.json' - ), - [DatabaseServiceType.SingleStore]: () => - import( - '../jsons/connectionSchemas/connections/database/singleStoreConnection.json' - ), - [DatabaseServiceType.Snowflake]: () => - import( - '../jsons/connectionSchemas/connections/database/snowflakeConnection.json' - ), - [DatabaseServiceType.SQLite]: () => - import( - '../jsons/connectionSchemas/connections/database/sqliteConnection.json' - ), - [DatabaseServiceType.Synapse]: () => - import( - '../jsons/connectionSchemas/connections/database/synapseConnection.json' - ), - [DatabaseServiceType.Teradata]: () => - import( - '../jsons/connectionSchemas/connections/database/teradataConnection.json' - ), - [DatabaseServiceType.Trino]: () => - import( - '../jsons/connectionSchemas/connections/database/trinoConnection.json' - ), - [DatabaseServiceType.UnityCatalog]: () => - import( - '../jsons/connectionSchemas/connections/database/unityCatalogConnection.json' - ), - [DatabaseServiceType.Vertica]: () => - import( - '../jsons/connectionSchemas/connections/database/verticaConnection.json' - ), - [DatabaseServiceType.MongoDB]: () => - import( - '../jsons/connectionSchemas/connections/database/mongoDBConnection.json' - ), - [DatabaseServiceType.Couchbase]: () => - import( - '../jsons/connectionSchemas/connections/database/couchbaseConnection.json' - ), - [DatabaseServiceType.Greenplum]: () => - import( - '../jsons/connectionSchemas/connections/database/greenplumConnection.json' - ), - [DatabaseServiceType.DomoDatabase]: () => - import( - '../jsons/connectionSchemas/connections/database/domoDatabaseConnection.json' - ), - [DatabaseServiceType.Cassandra]: () => - import( - '../jsons/connectionSchemas/connections/database/cassandraConnection.json' - ), - [DatabaseServiceType.Exasol]: () => - import( - '../jsons/connectionSchemas/connections/database/exasolConnection.json' - ), - [DatabaseServiceType.SapErp]: () => - import( - '../jsons/connectionSchemas/connections/database/sapErpConnection.json' - ), - [DatabaseServiceType.Sas]: () => - import( - '../jsons/connectionSchemas/connections/database/sasConnection.json' - ), - [DatabaseServiceType.StarRocks]: () => - import( - '../jsons/connectionSchemas/connections/database/starrocksConnection.json' - ), - [DatabaseServiceType.Timescale]: () => - import( - '../jsons/connectionSchemas/connections/database/timescaleConnection.json' - ), - [DatabaseServiceType.BurstIQ]: () => - import( - '../jsons/connectionSchemas/connections/database/burstIQConnection.json' - ), -}; - -export const getDatabaseConfig = async (type: DatabaseServiceType) => { +export const getDatabaseConfig = (type: DatabaseServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = DATABASE_CONNECTION_SCHEMAS[type]; + switch (type as unknown as DatabaseServiceType) { + case DatabaseServiceType.Athena: { + schema = athenaConnection; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + break; + } + case DatabaseServiceType.AzureSQL: { + schema = azureSQLConnection; + + break; + } + case DatabaseServiceType.BigQuery: { + schema = bigQueryConnection; + + break; + } + case DatabaseServiceType.BigTable: { + schema = bigTableConnection; + + break; + } + case DatabaseServiceType.Clickhouse: { + schema = clickhouseConnection; + + break; + } + case DatabaseServiceType.Cockroach: { + schema = cockroachConnection; + + break; + } + case DatabaseServiceType.Databricks: { + schema = databricksConnection; + + break; + } + case DatabaseServiceType.Datalake: { + schema = DatalakeConnection; + + break; + } + case DatabaseServiceType.Db2: { + schema = db2Connection; + + break; + } + case DatabaseServiceType.DeltaLake: { + schema = deltaLakeConnection; + + break; + } + case DatabaseServiceType.Doris: { + schema = dorisConnection; + + break; + } + case DatabaseServiceType.StarRocks: { + schema = starrocksConnection; + + break; + } + case DatabaseServiceType.Druid: { + schema = druidConnection; + + break; + } + + case DatabaseServiceType.DynamoDB: { + schema = dynamoDBConnection; + + break; + } + case DatabaseServiceType.Exasol: { + schema = exasolConnection; + + break; + } + case DatabaseServiceType.Glue: { + schema = glueConnection; + + break; + } + case DatabaseServiceType.Hive: { + schema = hiveConnection; + + break; + } + case DatabaseServiceType.Impala: { + schema = impalaConnection; + + break; + } + case DatabaseServiceType.MariaDB: { + schema = mariaDBConnection; + + break; + } + case DatabaseServiceType.Mssql: { + schema = mssqlConnection; + + break; + } + case DatabaseServiceType.Mysql: { + schema = mysqlConnection; + + break; + } + case DatabaseServiceType.Oracle: { + schema = oracleConnection; + + break; + } + case DatabaseServiceType.Postgres: { + schema = postgresConnection; - const schema = (await loaderFn()).default; + break; + } + case DatabaseServiceType.Presto: { + schema = prestoConnection; + + break; + } + case DatabaseServiceType.Redshift: { + schema = redshiftConnection; + + break; + } + case DatabaseServiceType.Salesforce: { + schema = salesforceConnection; + + break; + } + case DatabaseServiceType.SingleStore: { + schema = singleStoreConnection; + + break; + } + case DatabaseServiceType.Snowflake: { + schema = snowflakeConnection; + + break; + } + case DatabaseServiceType.SQLite: { + schema = sqliteConnection; + + break; + } + case DatabaseServiceType.Synapse: { + schema = synapseConnection; + + break; + } + case DatabaseServiceType.Trino: { + schema = trinoConnection; + + break; + } + case DatabaseServiceType.Vertica: { + schema = verticaConnection; + + break; + } + case DatabaseServiceType.CustomDatabase: { + schema = customDatabaseConnection; + + break; + } + case DatabaseServiceType.DomoDatabase: { + schema = domoDatabaseConnection; + + break; + } + case DatabaseServiceType.SapHana: { + schema = sapHanaConnection; + + break; + } + case DatabaseServiceType.SapERP: { + schema = sapErpConnection; + + break; + } + case DatabaseServiceType.MongoDB: { + schema = mongoDBConnection; + + break; + } + case DatabaseServiceType.Cassandra: { + schema = cassandraConnection; + + break; + } + case DatabaseServiceType.Couchbase: { + schema = couchbaseConnection; + + break; + } + case DatabaseServiceType.PinotDB: { + schema = pinotConnection; + + break; + } + case DatabaseServiceType.Greenplum: { + schema = greenplumConnection; + + break; + } + case DatabaseServiceType.UnityCatalog: { + schema = unityCatalogConnection; + + break; + } + case DatabaseServiceType.SAS: { + schema = sasConnection; + + break; + } + case DatabaseServiceType.Teradata: { + schema = teradataConnection; + + break; + } + case DatabaseServiceType.Timescale: { + schema = timescaleConnection; + + break; + } + case DatabaseServiceType.BurstIQ: { + schema = burstiqConnection; + + break; + } + default: { + schema = {}; + + break; + } + } return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts index 3a20b5bfdeb0..dced426db1f3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MessagingServiceUtils.ts @@ -17,52 +17,56 @@ import { MessagingConnection, MessagingServiceType, } from '../generated/entity/services/messagingService'; - -const MESSAGING_CONNECTION_SCHEMAS: Record< - MessagingServiceType, - () => Promise<{ default: Record }> -> = { - [MessagingServiceType.Kafka]: () => - import( - '../jsons/connectionSchemas/connections/messaging/kafkaConnection.json' - ), - [MessagingServiceType.Redpanda]: () => - import( - '../jsons/connectionSchemas/connections/messaging/redpandaConnection.json' - ), - [MessagingServiceType.CustomMessaging]: () => - import( - '../jsons/connectionSchemas/connections/messaging/customMessagingConnection.json' - ), - [MessagingServiceType.Kinesis]: () => - import( - '../jsons/connectionSchemas/connections/messaging/kinesisConnection.json' - ), - [MessagingServiceType.PubSub]: () => - import( - '../jsons/connectionSchemas/connections/messaging/pubSubConnection.json' - ), -}; +import customMessagingConnection from '../jsons/connectionSchemas/connections/messaging/customMessagingConnection.json'; +import kafkaConnection from '../jsons/connectionSchemas/connections/messaging/kafkaConnection.json'; +import kinesisConnection from '../jsons/connectionSchemas/connections/messaging/kinesisConnection.json'; +import pubSubConnection from '../jsons/connectionSchemas/connections/messaging/pubSubConnection.json'; +import redpandaConnection from '../jsons/connectionSchemas/connections/messaging/redpandaConnection.json'; export const getBrokers = (config: MessagingConnection['config']) => { let retVal: string | undefined; + // Change it to switch case if more than 1 conditions arise if (config?.type === MessagingServiceType.Kafka) { retVal = config.bootstrapServers; } - return !isUndefined(retVal) ? retVal : '--'; + return isUndefined(retVal) ? '--' : retVal; }; -export const getMessagingConfig = async (type: MessagingServiceType) => { +export const getMessagingConfig = (type: MessagingServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = MESSAGING_CONNECTION_SCHEMAS[type]; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + switch (type) { + case MessagingServiceType.Kafka: + schema = kafkaConnection; + + break; + + case MessagingServiceType.Redpanda: + schema = redpandaConnection; + + break; - const schema = (await loaderFn()).default; + case MessagingServiceType.CustomMessaging: + schema = customMessagingConnection; + + break; + + case MessagingServiceType.Kinesis: + schema = kinesisConnection; + + break; + + case MessagingServiceType.PubSub: + schema = pubSubConnection; + + break; + + default: + break; + } return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts index e0ea1e7584d1..7a9f59498b66 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MlmodelServiceUtils.ts @@ -14,40 +14,36 @@ import { cloneDeep } from 'lodash'; import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { MlModelServiceType } from '../generated/entity/services/mlmodelService'; +import customMlModelConnection from '../jsons/connectionSchemas/connections/mlmodel/customMlModelConnection.json'; +import mlflowConnection from '../jsons/connectionSchemas/connections/mlmodel/mlflowConnection.json'; +import segamakerConnection from '../jsons/connectionSchemas/connections/mlmodel/sageMakerConnection.json'; +import sklearnConnection from '../jsons/connectionSchemas/connections/mlmodel/sklearnConnection.json'; -const MLMODEL_CONNECTION_SCHEMAS: Record< - MlModelServiceType, - () => Promise<{ default: Record }> -> = { - [MlModelServiceType.Mlflow]: () => - import( - '../jsons/connectionSchemas/connections/mlmodel/mlflowConnection.json' - ), - [MlModelServiceType.Sklearn]: () => - import( - '../jsons/connectionSchemas/connections/mlmodel/sklearnConnection.json' - ), - [MlModelServiceType.CustomMlModel]: () => - import( - '../jsons/connectionSchemas/connections/mlmodel/customMlModelConnection.json' - ), - [MlModelServiceType.SageMaker]: () => - import( - '../jsons/connectionSchemas/connections/mlmodel/sageMakerConnection.json' - ), -}; - -export const getMlmodelConfig = async (type: MlModelServiceType) => { +export const getMlmodelConfig = (type: MlModelServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = MLMODEL_CONNECTION_SCHEMAS[type] - ? MLMODEL_CONNECTION_SCHEMAS[type] - : null; + switch (type) { + case MlModelServiceType.Mlflow: { + schema = mlflowConnection; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + break; + } + case MlModelServiceType.Sklearn: { + schema = sklearnConnection; - const schema = (await loaderFn()).default; + break; + } + case MlModelServiceType.CustomMlModel: { + schema = customMlModelConnection; + + break; + } + case MlModelServiceType.SageMaker: { + schema = segamakerConnection; + + break; + } + } return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts index 81227fd068c7..5b36af83834a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineServiceUtils.ts @@ -14,136 +14,93 @@ import { cloneDeep } from 'lodash'; import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { PipelineServiceType } from '../generated/entity/services/pipelineService'; - -export const getPipelineConfig = async (type: PipelineServiceType) => { +import airbyteConnection from '../jsons/connectionSchemas/connections/pipeline/airbyteConnection.json'; +import airflowConnection from '../jsons/connectionSchemas/connections/pipeline/airflowConnection.json'; +import customPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/customPipelineConnection.json'; +import dagsterConnection from '../jsons/connectionSchemas/connections/pipeline/dagsterConnection.json'; +import databricksPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/databricksPipelineConnection.json'; +import dbtCloudConnection from '../jsons/connectionSchemas/connections/pipeline/dbtCloudConnection.json'; +import domoPipelineConnection from '../jsons/connectionSchemas/connections/pipeline/domoPipelineConnection.json'; +import fivetranConnection from '../jsons/connectionSchemas/connections/pipeline/fivetranConnection.json'; +import flinkConnection from '../jsons/connectionSchemas/connections/pipeline/flinkConnection.json'; +import gluePipelineConnection from '../jsons/connectionSchemas/connections/pipeline/gluePipelineConnection.json'; +import KafkaConnectConnection from '../jsons/connectionSchemas/connections/pipeline/kafkaConnectConnection.json'; +import nifiConnection from '../jsons/connectionSchemas/connections/pipeline/nifiConnection.json'; +import openLineageConnection from '../jsons/connectionSchemas/connections/pipeline/openLineageConnection.json'; +import splineConnection from '../jsons/connectionSchemas/connections/pipeline/splineConnection.json'; + +export const getPipelineConfig = (type: PipelineServiceType) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - switch (type) { case PipelineServiceType.Airbyte: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/airbyteConnection.json' - ) - ).default; + schema = airbyteConnection; break; } case PipelineServiceType.Airflow: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/airflowConnection.json' - ) - ).default; + schema = airflowConnection; break; } case PipelineServiceType.GluePipeline: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/gluePipelineConnection.json' - ) - ).default; + schema = gluePipelineConnection; break; } case PipelineServiceType.KafkaConnect: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/kafkaConnectConnection.json' - ) - ).default; + schema = KafkaConnectConnection; break; } case PipelineServiceType.Fivetran: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/fivetranConnection.json' - ) - ).default; + schema = fivetranConnection; break; } case PipelineServiceType.Dagster: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/dagsterConnection.json' - ) - ).default; + schema = dagsterConnection; break; } case PipelineServiceType.DBTCloud: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/dbtCloudConnection.json' - ) - ).default; + schema = dbtCloudConnection; break; } case PipelineServiceType.Nifi: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/nifiConnection.json' - ) - ).default; + schema = nifiConnection; break; } case PipelineServiceType.DomoPipeline: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/domoPipelineConnection.json' - ) - ).default; + schema = domoPipelineConnection; break; } case PipelineServiceType.CustomPipeline: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/customPipelineConnection.json' - ) - ).default; + schema = customPipelineConnection; break; } case PipelineServiceType.DatabricksPipeline: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/databricksPipelineConnection.json' - ) - ).default; + schema = databricksPipelineConnection; break; } case PipelineServiceType.Spline: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/splineConnection.json' - ) - ).default; + schema = splineConnection; break; } case PipelineServiceType.OpenLineage: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/openLineageConnection.json' - ) - ).default; + schema = openLineageConnection; break; } case PipelineServiceType.Flink: { - schema = ( - await import( - '../jsons/connectionSchemas/connections/pipeline/flinkConnection.json' - ) - ).default; + schema = flinkConnection; break; } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts index b13715c22c3d..c10d3bbc018d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchServiceUtils.ts @@ -14,34 +14,30 @@ import { cloneDeep } from 'lodash'; import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { SearchServiceType } from '../generated/entity/services/searchService'; +import customSearchConnection from '../jsons/connectionSchemas/connections/search/customSearchConnection.json'; +import elasticSearchConnection from '../jsons/connectionSchemas/connections/search/elasticSearchConnection.json'; +import openSearchConnection from '../jsons/connectionSchemas/connections/search/openSearchConnection.json'; -const SEARCH_CONNECTION_SCHEMAS: Record< - SearchServiceType, - () => Promise<{ default: Record }> -> = { - [SearchServiceType.ElasticSearch]: () => - import( - '../jsons/connectionSchemas/connections/search/elasticSearchConnection.json' - ), - [SearchServiceType.OpenSearch]: () => - import( - '../jsons/connectionSchemas/connections/search/openSearchConnection.json' - ), - [SearchServiceType.CustomSearch]: () => - import( - '../jsons/connectionSchemas/connections/search/customSearchConnection.json' - ), -}; - -export const getSearchServiceConfig = async (type: SearchServiceType) => { +export const getSearchServiceConfig = (type: SearchServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = SEARCH_CONNECTION_SCHEMAS[type]; + switch (type) { + case SearchServiceType.ElasticSearch: { + schema = elasticSearchConnection; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + break; + } + case SearchServiceType.OpenSearch: { + schema = openSearchConnection; - const schema = (await loaderFn()).default; + break; + } + case SearchServiceType.CustomSearch: { + schema = customSearchConnection; + + break; + } + } return cloneDeep({ schema, uiSchema }); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts index 22a0f695560f..38df67e0a24c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SecurityServiceUtils.ts @@ -14,17 +14,14 @@ import { cloneDeep } from 'lodash'; import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { Type } from '../generated/entity/services/securityService'; +import rangerConnection from '../jsons/connectionSchemas/connections/security/rangerConnection.json'; -export const getSecurityConfig = async (type: Type) => { +export const getSecurityConfig = (type: Type) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; if (type === Type.Ranger) { - schema = ( - await import( - '../jsons/connectionSchemas/connections/security/rangerConnection.json' - ) - ).default; + schema = rangerConnection; } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index 118f6a4a3f10..bef009c30e69 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -11,158 +11,249 @@ * limitations under the License. */ -const SERVICE_ICON_LOADERS: Record Promise<{ default: string }>> = - { - // Database services - mysql: () => import('../assets/img/service-icon-sql.png'), - sqlite: () => import('../assets/img/service-icon-sqlite.png'), - mssql: () => import('../assets/img/service-icon-mssql.png'), - redshift: () => import('../assets/img/service-icon-redshift.png'), - bigquery: () => import('../assets/img/service-icon-query.png'), - bigtable: () => import('../assets/img/service-icon-bigtable.png'), - hive: () => import('../assets/img/service-icon-hive.png'), - impala: () => import('../assets/img/service-icon-impala.png'), - postgres: () => import('../assets/img/service-icon-post.png'), - oracle: () => import('../assets/img/service-icon-oracle.png'), - snowflake: () => import('../assets/img/service-icon-snowflakes.png'), - athena: () => import('../assets/img/service-icon-athena.png'), - presto: () => import('../assets/img/service-icon-presto.png'), - trino: () => import('../assets/img/service-icon-trino.png'), - glue: () => import('../assets/img/service-icon-glue.png'), - mariadb: () => import('../assets/img/service-icon-mariadb.png'), - vertica: () => import('../assets/img/service-icon-vertica.png'), - azuresql: () => import('../assets/img/service-icon-azuresql.png'), - clickhouse: () => import('../assets/img/service-icon-clickhouse.png'), - databrick: () => import('../assets/img/service-icon-databrick.png'), - unitycatalog: () => import('../assets/img/service-icon-unitycatalog.svg'), - ibmdb2: () => import('../assets/img/service-icon-ibmdb2.png'), - doris: () => import('../assets/img/service-icon-doris.png'), - starrocks: () => import('../assets/img/service-icon-starrocks.png'), - druid: () => import('../assets/img/service-icon-druid.png'), - dynamodb: () => import('../assets/img/service-icon-dynamodb.png'), - singlestore: () => import('../assets/img/service-icon-singlestore.png'), - salesforce: () => import('../assets/img/service-icon-salesforce.png'), - saphana: () => import('../assets/img/service-icon-sap-hana.png'), - saperp: () => import('../assets/img/service-icon-sap-erp.png'), - deltalake: () => import('../assets/img/service-icon-delta-lake.png'), - pinot: () => import('../assets/img/service-icon-pinot.png'), - datalake: () => import('../assets/img/service-icon-datalake.png'), - exasol: () => import('../assets/img/service-icon-exasol.png'), - mongodb: () => import('../assets/img/service-icon-mongodb.png'), - cassandra: () => import('../assets/img/service-icon-cassandra.png'), - couchbase: () => import('../assets/img/service-icon-couchbase.svg'), - greenplum: () => import('../assets/img/service-icon-greenplum.png'), - teradata: () => import('../assets/svg/teradata.svg'), - cockroach: () => import('../assets/img/service-icon-cockroach.png'), - timescale: () => import('../assets/img/service-icon-timescale.png'), - burstiq: () => import('../assets/img/service-icon-burstiq.png'), - sas: () => import('../assets/img/service-icon-sas.svg'), +import mysql from '../assets/img/service-icon-sql.png'; +import sqlite from '../assets/img/service-icon-sqlite.png'; +import mssql from '../assets/img/service-icon-mssql.png'; +import redshift from '../assets/img/service-icon-redshift.png'; +import bigquery from '../assets/img/service-icon-query.png'; +import bigtable from '../assets/img/service-icon-bigtable.png'; +import hive from '../assets/img/service-icon-hive.png'; +import impala from '../assets/img/service-icon-impala.png'; +import postgres from '../assets/img/service-icon-post.png'; +import oracle from '../assets/img/service-icon-oracle.png'; +import snowflake from '../assets/img/service-icon-snowflakes.png'; +import athena from '../assets/img/service-icon-athena.png'; +import presto from '../assets/img/service-icon-presto.png'; +import trino from '../assets/img/service-icon-trino.png'; +import glue from '../assets/img/service-icon-glue.png'; +import mariadb from '../assets/img/service-icon-mariadb.png'; +import vertica from '../assets/img/service-icon-vertica.png'; +import azuresql from '../assets/img/service-icon-azuresql.png'; +import clickhouse from '../assets/img/service-icon-clickhouse.png'; +import databrick from '../assets/img/service-icon-databrick.png'; +import unitycatalog from '../assets/img/service-icon-unitycatalog.svg'; +import ibmdb2 from '../assets/img/service-icon-ibmdb2.png'; +import doris from '../assets/img/service-icon-doris.png'; +import starrocks from '../assets/img/service-icon-starrocks.png'; +import druid from '../assets/img/service-icon-druid.png'; +import dynamodb from '../assets/img/service-icon-dynamodb.png'; +import singlestore from '../assets/img/service-icon-singlestore.png'; +import salesforce from '../assets/img/service-icon-salesforce.png'; +import saphana from '../assets/img/service-icon-sap-hana.png'; +import saperp from '../assets/img/service-icon-sap-erp.png'; +import deltalake from '../assets/img/service-icon-delta-lake.png'; +import pinot from '../assets/img/service-icon-pinot.png'; +import datalake from '../assets/img/service-icon-datalake.png'; +import exasol from '../assets/img/service-icon-exasol.png'; +import mongodb from '../assets/img/service-icon-mongodb.png'; +import cassandra from '../assets/img/service-icon-cassandra.png'; +import couchbase from '../assets/img/service-icon-couchbase.svg'; +import greenplum from '../assets/img/service-icon-greenplum.png'; +import teradata from '../assets/svg/teradata.svg'; +import cockroach from '../assets/img/service-icon-cockroach.png'; +import timescale from '../assets/img/service-icon-timescale.png'; +import burstiq from '../assets/img/service-icon-burstiq.png'; +import sas from '../assets/img/service-icon-sas.svg'; // Messaging services - kafka: () => import('../assets/img/service-icon-kafka.png'), - pubsub: () => import('../assets/svg/service-icon-pubsub.svg'), - redpanda: () => import('../assets/img/service-icon-redpanda.png'), - kinesis: () => import('../assets/img/service-icon-kinesis.png'), +import kafka from '../assets/img/service-icon-kafka.png'; +import pubsub from '../assets/svg/service-icon-pubsub.svg'; +import redpanda from '../assets/img/service-icon-redpanda.png'; +import kinesis from '../assets/img/service-icon-kinesis.png'; // Dashboard services - superset: () => import('../assets/img/service-icon-superset.png'), - looker: () => import('../assets/img/service-icon-looker.png'), - tableau: () => import('../assets/img/service-icon-tableau.png'), - redash: () => import('../assets/img/service-icon-redash.png'), - metabase: () => import('../assets/img/service-icon-metabase.png'), - powerbi: () => import('../assets/img/service-icon-power-bi.png'), - sigma: () => import('../assets/img/service-icon-sigma.png'), - mode: () => import('../assets/img/service-icon-mode.png'), - domo: () => import('../assets/img/service-icon-domo.png'), - quicksight: () => import('../assets/img/service-icon-quicksight.png'), - qliksense: () => import('../assets/img/service-icon-qlik-sense.png'), - lightdash: () => import('../assets/img/service-icon-lightdash.png'), - microstrategy: () => import('../assets/img/service-icon-microstrategy.svg'), - grafana: () => import('../assets/img/service-icon-grafana.png'), - hex: () => import('../assets/svg/service-icon-hex.svg'), +import superset from '../assets/img/service-icon-superset.png'; +import looker from '../assets/img/service-icon-looker.png'; +import tableau from '../assets/img/service-icon-tableau.png'; +import redash from '../assets/img/service-icon-redash.png'; +import metabase from '../assets/img/service-icon-metabase.png'; +import powerbi from '../assets/img/service-icon-power-bi.png'; +import sigma from '../assets/img/service-icon-sigma.png'; +import mode from '../assets/img/service-icon-mode.png'; +import domo from '../assets/img/service-icon-domo.png'; +import quicksight from '../assets/img/service-icon-quicksight.png'; +import qliksense from '../assets/img/service-icon-qlik-sense.png'; +import lightdash from '../assets/img/service-icon-lightdash.png'; +import microstrategy from '../assets/img/service-icon-microstrategy.svg'; +import grafana from '../assets/img/service-icon-grafana.png'; +import hex from '../assets/svg/service-icon-hex.svg'; // Pipeline services - airflow: () => import('../assets/img/service-icon-airflow.png'), - airbyte: () => import('../assets/img/Airbyte.png'), - dagster: () => import('../assets/img/service-icon-dagster.png'), - dbt: () => import('../assets/img/service-icon-dbt.png'), - fivetran: () => import('../assets/img/service-icon-fivetran.png'), - nifi: () => import('../assets/img/service-icon-nifi.png'), - spark: () => import('../assets/img/service-icon-spark.png'), - spline: () => import('../assets/img/service-icon-spline.png'), - flink: () => import('../assets/img/service-icon-flink.png'), - openlineage: () => import('../assets/img/service-icon-openlineage.svg'), +import airflow from '../assets/img/service-icon-airflow.png'; +import airbyte from '../assets/img/Airbyte.png'; +import dagster from '../assets/img/service-icon-dagster.png'; +import dbt from '../assets/img/service-icon-dbt.png'; +import fivetran from '../assets/img/service-icon-fivetran.png'; +import nifi from '../assets/img/service-icon-nifi.png'; +import spark from '../assets/img/service-icon-spark.png'; +import spline from '../assets/img/service-icon-spline.png'; +import flink from '../assets/img/service-icon-flink.png'; +import openlineage from '../assets/img/service-icon-openlineage.svg'; // ML Model services - mlflow: () => import('../assets/svg/service-icon-mlflow.svg'), - scikit: () => import('../assets/img/service-icon-scikit.png'), - sagemaker: () => import('../assets/img/service-icon-sagemaker.png'), +import mlflow from '../assets/svg/service-icon-mlflow.svg'; +import scikit from '../assets/img/service-icon-scikit.png'; +import sagemaker from '../assets/img/service-icon-sagemaker.png'; // Storage services - amazons3: () => import('../assets/img/service-icon-amazon-s3.svg'), - gcs: () => import('../assets/img/service-icon-gcs.png'), +import amazons3 from '../assets/img/service-icon-amazon-s3.svg'; +import gcs from '../assets/img/service-icon-gcs.png'; // Search services - elasticsearch: () => import('../assets/svg/elasticsearch.svg'), - opensearch: () => import('../assets/svg/open-search.svg'), +import elasticsearch from '../assets/svg/elasticsearch.svg'; +import opensearch from '../assets/svg/open-search.svg'; // Metadata services - amundsen: () => import('../assets/img/service-icon-amundsen.png'), - atlas: () => import('../assets/img/service-icon-atlas.svg'), - alationsink: () => import('../assets/img/service-icon-alation-sink.png'), +import amundsen from '../assets/img/service-icon-amundsen.png'; +import atlas from '../assets/img/service-icon-atlas.svg'; +import alationsink from '../assets/img/service-icon-alation-sink.png'; // Drive services - googledrive: () => import('../assets/svg/service-icon-google-drive.svg'), - sftp: () => import('../assets/svg/service-icon-sftp.svg'), +import googledrive from '../assets/svg/service-icon-google-drive.svg'; +import sftp from '../assets/svg/service-icon-sftp.svg'; // Default icons - defaultservice: () => import('../assets/svg/default-service-icon.svg'), - databasedefault: () => import('../assets/svg/ic-custom-database.svg'), - topicdefault: () => import('../assets/svg/topic.svg'), - dashboarddefault: () => import('../assets/svg/dashboard.svg'), - pipelinedefault: () => import('../assets/svg/pipeline.svg'), - mlmodeldefault: () => import('../assets/svg/ic-custom-model.svg'), - storagedefault: () => import('../assets/svg/ic-custom-storage.svg'), - drivedefault: () => import('../assets/svg/ic-drive-service.svg'), - customdrivedefault: () => import('../assets/svg/ic-custom-drive.svg'), - searchdefault: () => import('../assets/svg/ic-custom-search.svg'), - securitydefault: () => import('../assets/svg/security-safe.svg'), - restservice: () => import('../assets/svg/ic-service-rest-api.svg'), - logo: () => import('../assets/svg/logo-monogram.svg'), - synapse: () => import('../assets/img/service-icon-synapse.png'), - }; +import defaultservice from '../assets/svg/default-service-icon.svg'; +import databasedefault from '../assets/svg/ic-custom-database.svg'; +import topicdefault from '../assets/svg/topic.svg'; +import dashboarddefault from '../assets/svg/dashboard.svg'; +import pipelinedefault from '../assets/svg/pipeline.svg'; +import mlmodeldefault from '../assets/svg/ic-custom-model.svg'; +import storagedefault from '../assets/svg/ic-custom-storage.svg'; +import drivedefault from '../assets/svg/ic-drive-service.svg'; +import customdrivedefault from '../assets/svg/ic-custom-drive.svg'; +import searchdefault from '../assets/svg/ic-custom-search.svg'; +import securitydefault from '../assets/svg/security-safe.svg'; +import restservice from '../assets/svg/ic-service-rest-api.svg'; +import logo from '../assets/svg/logo-monogram.svg'; +import synapse from '../assets/img/service-icon-synapse.png'; + + +const SERVICE_ICON_LOADERS: Record = + { + // Database services + mysql: mysql, + sqlite: sqlite, + mssql: mssql, + redshift: redshift, + bigquery: bigquery, + bigtable: bigtable, + hive: hive, + impala: impala, + postgres: postgres, + oracle: oracle, + snowflake: snowflake, + athena: athena, + presto: presto, + trino: trino, + glue: glue, + mariadb: mariadb, + vertica: vertica, + azuresql: azuresql, + clickhouse: clickhouse, + databrick: databrick, + unitycatalog: unitycatalog, + ibmdb2: ibmdb2, + doris: doris, + starrocks: starrocks, + druid: druid, + dynamodb: dynamodb, + singlestore: singlestore, + salesforce: salesforce, + saphana: saphana, + saperp: saperp, + deltalake: deltalake, + pinot: pinot, + datalake: datalake, + exasol: exasol, + mongodb: mongodb, + cassandra: cassandra, + couchbase: couchbase, + greenplum: greenplum, + teradata: teradata, + cockroach: cockroach, + timescale: timescale, + burstiq: burstiq, + sas: sas, + + // Messaging services + kafka: kafka, + pubsub: pubsub, + redpanda: redpanda, + kinesis: kinesis, -const iconCache = new Map(); + // Dashboard services + superset: superset, + looker: looker, + tableau: tableau, + redash: redash, + metabase: metabase, + powerbi: powerbi, + sigma: sigma, + mode: mode, + domo: domo, + quicksight: quicksight, + qliksense: qliksense, + lightdash: lightdash, + microstrategy: microstrategy, + grafana: grafana, + hex: hex, -export const getServiceIcon = async (iconKey: string): Promise => { - const normalizedKey = iconKey.toLowerCase().replace(/[_-]/g, ''); + // Pipeline services + airflow: airflow, + airbyte: airbyte, + dagster: dagster, + dbt: dbt, + fivetran: fivetran, + nifi: nifi, + spark: spark, + spline: spline, + flink: flink, + openlineage: openlineage, - if (iconCache.has(normalizedKey)) { - return iconCache.get(normalizedKey) as unknown as string; - } + // ML Model services + mlflow: mlflow, + scikit: scikit, + sagemaker: sagemaker, - const loader = SERVICE_ICON_LOADERS[normalizedKey]; + // Storage services + amazons3: amazons3, + gcs: gcs, - if (!loader) { - const defaultIcon = await SERVICE_ICON_LOADERS.defaultservice(); + // Search services + elasticsearch: elasticsearch, + opensearch: opensearch, - return defaultIcon.default; - } + // Metadata services + amundsen: amundsen, + atlas: atlas, + alationsink: alationsink, - const icon = await loader(); - iconCache.set(normalizedKey, icon.default); + // Drive services + googledrive: googledrive, + sftp: sftp, - return icon.default; -}; + // Default icons + defaultservice: defaultservice, + databasedefault: databasedefault, + topicdefault: topicdefault, + dashboarddefault: dashboarddefault, + pipelinedefault: pipelinedefault, + mlmodeldefault: mlmodeldefault, + storagedefault: storagedefault, + drivedefault: drivedefault, + customdrivedefault: customdrivedefault, + searchdefault: searchdefault, + securitydefault: securitydefault, + restservice: restservice, + logo: logo, + synapse: synapse, + }; -export const getServiceIconSync = (iconKey: string): string | null => { - const normalizedKey = iconKey.toLowerCase().replace(/[_-]/g, ''); - return iconCache.get(normalizedKey) ?? null; -}; +export const getServiceIcon = (iconKey: string): string => { + const normalizedKey = iconKey.toLowerCase().replaceAll(/[_-]/g, ''); + const icon = SERVICE_ICON_LOADERS[normalizedKey] ?? defaultservice; -export const preloadServiceIcons = async ( - iconKeys: string[] -): Promise => { - await Promise.all(iconKeys.map((key) => getServiceIcon(key))); -}; + return icon; +}; \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index f47fdb7a360b..6b35aaa61760 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -12,7 +12,7 @@ */ import { ObjectFieldTemplatePropertyType } from '@rjsf/utils'; -import { get, isEmpty, toLower } from 'lodash'; +import { get, isEmpty } from 'lodash'; import { ServiceTypes } from 'Models'; import GlossaryIcon from '../assets/svg/book.svg'; import ChartIcon from '../assets/svg/chart.svg'; @@ -317,12 +317,8 @@ class ServiceUtilClassBase { return EntityType.TABLE; } - async getServiceLogo(type: string): Promise { - const lowerType = toLower(type); - - const icon = await getServiceIcon(lowerType); - - return icon; + public getServiceLogo(type: string) { + return getServiceIcon(type); } public getServiceTypeLogo(searchSource: { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts index e3933b1a4884..1c62f61e0ef5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StorageServiceUtils.ts @@ -12,32 +12,35 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; +import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; import { StorageServiceType } from '../generated/entity/services/storageService'; +import customConnection from '../jsons/connectionSchemas/connections/storage/customStorageConnection.json'; +import gcsConnection from '../jsons/connectionSchemas/connections/storage/gcsConnection.json'; +import s3Connection from '../jsons/connectionSchemas/connections/storage/s3Connection.json'; -const STORAGE_CONNECTION_SCHEMAS: Record< - StorageServiceType, - () => Promise<{ default: Record }> -> = { - [StorageServiceType.S3]: () => - import('../jsons/connectionSchemas/connections/storage/s3Connection.json'), - [StorageServiceType.Gcs]: () => - import('../jsons/connectionSchemas/connections/storage/gcsConnection.json'), - [StorageServiceType.CustomStorage]: () => - import( - '../jsons/connectionSchemas/connections/storage/customStorageConnection.json' - ), -}; - -export const getStorageConfig = async (type: StorageServiceType) => { +export const getStorageConfig = (type: StorageServiceType) => { + let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; - const loaderFn = STORAGE_CONNECTION_SCHEMAS[type]; + switch (type) { + case StorageServiceType.S3: { + schema = s3Connection; - if (!loaderFn) { - return cloneDeep({ schema: {}, uiSchema }); - } + break; + } + case StorageServiceType.Gcs: { + schema = gcsConnection; - const schema = (await loaderFn()).default; + break; + } + case StorageServiceType.CustomStorage: { + schema = customConnection; + + break; + } + + default: + break; + } return cloneDeep({ schema, uiSchema }); }; From 203e3e08dda45dddf094e76aff50b069e03158a9 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:10:38 +0530 Subject: [PATCH 20/47] fix failures --- .../AppAuthenticators/LazyAuthenticators.tsx | 49 +++++++++---------- .../MsalAuthenticator.test.tsx | 15 +++--- .../Auth/AuthProviders/AuthProvider.test.tsx | 2 +- .../Auth/AuthProviders/AuthProvider.tsx | 4 ++ .../LazyAuthProviderWrappers.tsx | 16 +++--- .../CustomPropertiesSection.test.tsx | 12 ----- .../CustomPropertyTable.test.tsx | 2 +- .../ui/src/constants/LoginClassBase.ts | 25 ++++------ .../src/main/resources/ui/src/index.tsx | 2 +- .../ui/src/pages/LoginPage/LoginCarousel.tsx | 26 +--------- .../utils/DataAssetSummaryPanelUtils.test.tsx | 12 +++++ .../ui/src/utils/EntityUtils.test.tsx | 4 -- .../resources/ui/src/utils/LocationUtils.ts | 6 ++- .../resources/ui/src/utils/TableUtils.tsx | 26 ++++++---- .../resources/ui/src/utils/UserDataUtils.ts | 31 ++++++------ .../ui/src/utils/WebAnalyticsUtils.ts | 9 ++-- 16 files changed, 112 insertions(+), 129 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx index 6b6df88f312c..b778bf10b3f0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx @@ -11,28 +11,29 @@ * limitations under the License. */ -import { forwardRef, lazy, ReactNode, Suspense } from 'react'; -import Loader from '../../common/Loader/Loader'; +import { type ComponentType, forwardRef, lazy, ReactNode } from 'react'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; +import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; +import type { WebStorageStateStore } from 'oidc-client'; -const Auth0Authenticator = lazy(() => import('./Auth0Authenticator')); -const BasicAuthAuthenticator = lazy(() => import('./BasicAuthAuthenticator')); -const MsalAuthenticator = lazy(() => import('./MsalAuthenticator')); -const OidcAuthenticator = lazy(() => import('./OidcAuthenticator')); -const OktaAuthenticator = lazy(() => import('./OktaAuthenticator')); -const GenericAuthenticator = lazy(() => +const Auth0Authenticator = withSuspenseFallback(lazy(() => import('./Auth0Authenticator'))); +const BasicAuthAuthenticator = withSuspenseFallback(lazy(() => import('./BasicAuthAuthenticator'))); +const MsalAuthenticator = withSuspenseFallback(lazy(() => import('./MsalAuthenticator'))); +const OidcAuthenticator = withSuspenseFallback(lazy(() => import('./OidcAuthenticator'))); +const OktaAuthenticator = withSuspenseFallback(lazy(() => import('./OktaAuthenticator'))); +const GenericAuthenticator = withSuspenseFallback(lazy(() => import('./GenericAuthenticator').then((m) => ({ default: m.GenericAuthenticator, })) -); +)); export const LazyAuth0Authenticator = forwardRef< AuthenticatorRef, { children: ReactNode } >((props, ref) => ( - }> + - + )); LazyAuth0Authenticator.displayName = 'LazyAuth0Authenticator'; @@ -41,9 +42,9 @@ export const LazyBasicAuthAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } >((props, ref) => ( - }> + - + )); LazyBasicAuthAuthenticator.displayName = 'LazyBasicAuthAuthenticator'; @@ -52,9 +53,9 @@ export const LazyMsalAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } >((props, ref) => ( - }> + - + )); LazyMsalAuthenticator.displayName = 'LazyMsalAuthenticator'; @@ -63,15 +64,13 @@ export const LazyOidcAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - childComponentType: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - userConfig: any; + childComponentType: ComponentType; + userConfig: Record; } >((props, ref) => ( - }> + - + )); LazyOidcAuthenticator.displayName = 'LazyOidcAuthenticator'; @@ -80,9 +79,9 @@ export const LazyOktaAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } >((props, ref) => ( - }> + - + )); LazyOktaAuthenticator.displayName = 'LazyOktaAuthenticator'; @@ -91,9 +90,9 @@ export const LazyGenericAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } >((props, ref) => ( - }> + - + )); LazyGenericAuthenticator.displayName = 'LazyGenericAuthenticator'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx index 1bf1ec7475c9..caaaf037fd0a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx @@ -15,10 +15,11 @@ import { InteractionStatus, } from '@azure/msal-browser'; import { useMsal } from '@azure/msal-react'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { msalLoginRequest } from '../../../utils/AuthProvider.util'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; import MsalAuthenticator from './MsalAuthenticator'; +import { act } from 'react'; // Mock MSAL hooks and utilities jest.mock('@azure/msal-react', () => ({ @@ -75,11 +76,11 @@ describe('MsalAuthenticator', () => { it('should handle login in iframe using popup', async () => { // Mock window.self !== window.top for iframe detection - Object.defineProperty(window, 'self', { + Object.defineProperty(globalThis, 'self', { value: { location: {} }, writable: true, }); - Object.defineProperty(window, 'top', { + Object.defineProperty(globalThis, 'top', { value: { location: {} }, writable: true, }); @@ -105,12 +106,12 @@ describe('MsalAuthenticator', () => { it('should handle login in normal window using redirect', async () => { // Mock window.self === window.top for normal window detection - Object.defineProperty(window, 'self', { - value: window, + Object.defineProperty(globalThis, 'self', { + value: globalThis, writable: true, }); - Object.defineProperty(window, 'top', { - value: window, + Object.defineProperty(globalThis, 'top', { + value: globalThis, writable: true, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx index 6e079e3d76ee..becce8ebb152 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx @@ -25,7 +25,7 @@ const localStorageMock = { clear: jest.fn(), }; -Object.defineProperty(window, 'localStorage', { +Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index 168c6efb8e53..1a506b2ab317 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -611,6 +611,7 @@ export const AuthProvider = ({ }; const getProtectedApp = () => { + // Show loader if application is loading or authenticating const childElement = isApplicationLoading || isAuthenticating ? ( @@ -618,6 +619,7 @@ export const AuthProvider = ({ children ); + // Handling for SAML moved to GenericAuthenticator if ( clientType === ClientType.Confidential || authConfig?.provider === AuthProviderEnum.Saml @@ -642,6 +644,8 @@ export const AuthProvider = ({ case AuthProviderEnum.Auth0: { return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx index 3e3cd1b6dd5a..448c5ef36623 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx @@ -12,9 +12,9 @@ */ import type { IPublicClientApplication } from '@azure/msal-browser'; -import { lazy, ReactNode, Suspense } from 'react'; +import { lazy, ReactNode } from 'react'; import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; -import Loader from '../../common/Loader/Loader'; +import { CacheLocation } from '@auth0/auth0-react'; const Auth0ProviderComponent = withSuspenseFallback( lazy(() => @@ -43,6 +43,8 @@ interface Auth0ProviderWrapperProps { domain: string; redirectUri: string; children: ReactNode; + useRefreshTokens: boolean + cacheLocation?: CacheLocation } export const LazyAuth0ProviderWrapper = ({ @@ -50,18 +52,18 @@ export const LazyAuth0ProviderWrapper = ({ domain, redirectUri, children, + useRefreshTokens, + cacheLocation }: Auth0ProviderWrapperProps) => { return ( - }> + redirectUri={redirectUri} + useRefreshTokens={useRefreshTokens}> {children} - ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx index 29ea629ed7b8..55714d8da0a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx @@ -32,18 +32,6 @@ jest.mock('react-router-dom', () => ({ )), })); -// // Mock Transi18next component -// jest.mock('../../../../utils/CommonUtils', () => ({ -// Transi18next: jest -// .fn() -// .mockImplementation(({ i18nKey, renderElement, values }) => ( -//
-// {i18nKey} - {values?.entity} - {values?.docs} -// {renderElement} -//
-// )), -// })); - // Mock Loader component jest.mock('../../../common/Loader/Loader', () => { return jest.fn().mockImplementation(({ size }) => ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx index bef41340f168..d07518f1a2ed 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx @@ -12,7 +12,6 @@ */ import { - act, render, screen, waitForElementToBeRemoved, @@ -21,6 +20,7 @@ import { EntityType } from '../../../enums/entity.enum'; import { Table } from '../../../generated/entity/data/table'; import { getTypeByFQN } from '../../../rest/metadataTypeAPI'; import { CustomPropertyTable } from './CustomPropertyTable'; +import { act } from 'react'; const mockCustomProperties = [ { diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts index 4bc6ff07ebda..add5ef1bbcc1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/LoginClassBase.ts @@ -11,40 +11,33 @@ * limitations under the License. */ +import dataCollaborationImg from '../assets/img/login-screen/data-collaboration/data-collaboration.png'; +import discoveryImg from '../assets/img/login-screen/discovery/data-discovery.png'; +import governanceImg from '../assets/img/login-screen/governance/governance.png'; +import observabilityImg from '../assets/img/login-screen/observability/data-observability.png'; + class LoginClassBase { public getLoginCarouselContent() { const carouselContent = [ { title: 'governance', - imagePath: () => - import('../assets/img/login-screen/governance/governance.png').then( - (m) => m.default - ), + image: governanceImg, descriptionKey: 'assess-data-reliability-with-data-profiler-lineage', }, { title: 'data-collaboration', - imagePath: () => - import( - '../assets/img/login-screen/data-collaboration/data-collaboration.png' - ).then((m) => m.default), + image: dataCollaborationImg, descriptionKey: 'deeply-understand-table-relations-message', }, { title: 'data-observability', - imagePath: () => - import( - '../assets/img/login-screen/observability/data-observability.png' - ).then((m) => m.default), + image: observabilityImg, descriptionKey: 'discover-your-data-and-unlock-the-value-of-data-assets', }, { title: 'data-discovery', - imagePath: () => - import( - '../assets/img/login-screen/discovery/data-discovery.png' - ).then((m) => m.default), + image: discoveryImg, descriptionKey: 'enables-end-to-end-metadata-management', }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/index.tsx b/openmetadata-ui/src/main/resources/ui/src/index.tsx index 611c3a989354..aa68632d0c79 100644 --- a/openmetadata-ui/src/main/resources/ui/src/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/index.tsx @@ -29,7 +29,7 @@ root.render( ); -if ('serviceWorker' in navigator && 'indexedDB' in window) { +if ('serviceWorker' in navigator && 'indexedDB' in globalThis) { window.addEventListener('load', () => { const basePath = getBasePath(); const serviceWorkerPath = basePath diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx index 0ef6b11ccbde..a6a9623918f4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.tsx @@ -13,30 +13,15 @@ import { Carousel, Typography } from 'antd'; import { uniqueId } from 'lodash'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import loginClassBase from '../../constants/LoginClassBase'; const LoginCarousel = () => { const [currentIndex, setCurrentIndex] = useState(0); - const [loadedImages, setLoadedImages] = useState>({}); const carouselContent = loginClassBase.getLoginCarouselContent(); const { t } = useTranslation(); - useEffect(() => { - const loadImage = async (index: number) => { - if (!loadedImages[index] && carouselContent[index]) { - const imageSrc = await carouselContent[index].imagePath(); - setLoadedImages((prev) => ({ ...prev, [index]: imageSrc })); - } - }; - - loadImage(currentIndex); - - const nextIndex = (currentIndex + 1) % carouselContent.length; - loadImage(nextIndex); - }, [currentIndex, carouselContent, loadedImages]); - return ( {

- {loadedImages[idx] && ( - slider - )} + slider
))} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx index bab18e5878ee..b88b170e9556 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx @@ -16,6 +16,18 @@ import { MOCK_TABLE, MOCK_TIER_DATA } from '../mocks/TableData.mock'; import { getEntityOverview } from './DataAssetSummaryPanelUtils'; import { getTierTags } from './TableUtils'; +jest.mock('./TableUtils', () => ({ + getTierTags: jest.fn(), + getUsagePercentile: jest.fn().mockImplementation((value) => value + 'th'), +})); + + +jest.mock('./CommonUtils', () => ({ + getPartialNameFromTableFQN: jest.fn().mockImplementation((value) => value), + getTableFQNFromColumnFQN: jest.fn().mockImplementation((value) => value), + formatNumberWithComma: jest.fn().mockImplementation((value) => value), +})); + describe('getEntityOverview', () => { it('should call getChartOverview and get ChartData if ExplorePageTabs is charts', () => { const result = JSON.stringify( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx index 42f38217ac1b..4304ee07d104 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx @@ -14,11 +14,8 @@ import { render } from '@testing-library/react'; import { startCase } from 'lodash'; import { DEFAULT_DOMAIN_VALUE } from '../constants/constants'; import { EntityTabs, EntityType } from '../enums/entity.enum'; -import { ExplorePageTabs } from '../enums/Explore.enum'; import { ServiceCategory } from '../enums/service.enum'; import { TestSuite } from '../generated/tests/testCase'; -import { MOCK_CHART_DATA } from '../mocks/Chart.mock'; -import { MOCK_TABLE, MOCK_TIER_DATA } from '../mocks/TableData.mock'; import { columnSorter, getBreadcrumbForTestSuite, @@ -54,7 +51,6 @@ import { getSettingPath, } from './RouterUtils'; import { getServiceRouteFromServiceType } from './ServiceUtils'; -import { getTierTags } from './TableUtils'; jest.mock('../constants/constants', () => ({ DEFAULT_DOMAIN_VALUE: 'All Domains', diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts index 26ae263eee30..1187695bc719 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts @@ -11,6 +11,8 @@ * limitations under the License. */ +import { getBasePath } from "./HistoryUtils"; + export const getPathNameFromWindowLocation = () => { - return window.location.pathname.replace(getBasePath() ?? '', ''); -}; \ No newline at end of file + return globalThis.location.pathname.replace(getBasePath() ?? '', ''); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 70ed6e85fe7f..4a795bfe3075 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -135,12 +135,8 @@ import TabsLabel from '../components/common/TabsLabel/TabsLabel.component'; import { TabProps } from '../components/common/TabsLabel/TabsLabel.interface'; import { GenericTab } from '../components/Customization/GenericTab/GenericTab'; import { CommonWidgets } from '../components/DataAssets/CommonWidgets/CommonWidgets'; -import SampleDataTableComponent from '../components/Database/SampleDataTable/SampleDataTable.component'; import SchemaTable from '../components/Database/SchemaTable/SchemaTable.component'; -import TableQueries from '../components/Database/TableQueries/TableQueries'; -import { ContractTab } from '../components/DataContract/ContractTab/ContractTab'; import { useEntityExportModalProvider } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; -import KnowledgeGraph from '../components/KnowledgeGraph/KnowledgeGraph'; import { SourceType } from '../components/SearchedData/SearchedData.interface'; import { NON_SERVICE_TYPE_ASSETS } from '../constants/Assets.constants'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; @@ -182,7 +178,6 @@ import { } from '../pages/TableDetailsPageV1/FrequentlyJoinedTables/FrequentlyJoinedTables.component'; import { PartitionedKeys } from '../pages/TableDetailsPageV1/PartitionedKeys/PartitionedKeys.component'; import ConstraintIcon from '../pages/TableDetailsPageV1/TableConstraints/ConstraintIcon'; -import TableConstraints from '../pages/TableDetailsPageV1/TableConstraints/TableConstraints'; import { exportTableDetailsInCSV } from '../rest/tableAPI'; import { extractApiEndpointFields } from './APIEndpoints/APIEndpointUtils'; import { @@ -203,19 +198,32 @@ import { ordinalize } from './StringsUtils'; import { TableDetailPageTabProps } from './TableClassBase'; import { TableFieldsInfoCommonEntities } from './TableUtils.interface'; import { extractTopicFields } from './TopicDetailsUtils'; +import withSuspenseFallback from '../components/AppRouter/withSuspenseFallback'; -const DataObservabilityTab = lazy( +const SampleDataTableComponent = withSuspenseFallback(lazy(() => import('../components/Database/SampleDataTable/SampleDataTable.component'))); + +const TableQueries = withSuspenseFallback(lazy(() => import('../components/Database/TableQueries/TableQueries'))); + +const ContractTab = withSuspenseFallback(lazy(() => import('../components/DataContract/ContractTab/ContractTab').then( + (module) => ({ default: module.ContractTab }) +))); + +const DataObservabilityTab = withSuspenseFallback(lazy( () => import( '../components/Database/Profiler/DataObservability/DataObservabilityTab' ) -); +)); -const EntityLineageTab = lazy(() => +const EntityLineageTab = withSuspenseFallback(lazy(() => import('../components/Lineage/EntityLineageTab/EntityLineageTab').then( (module) => ({ default: module.EntityLineageTab }) ) -); +)); + +const TableConstraints = withSuspenseFallback(lazy(() => import('../pages/TableDetailsPageV1/TableConstraints/TableConstraints'))); + +const KnowledgeGraph = withSuspenseFallback(lazy(() => import('../components/KnowledgeGraph/KnowledgeGraph'))); export const getUsagePercentile = (pctRank: number, isLiteral = false) => { const percentile = Math.round(pctRank * 10) / 10; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts index 90c689e45e4d..61bd4488d49e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts @@ -25,24 +25,23 @@ import { import { showErrorToast } from './ToastUtils'; import userClassBase from './UserClassBase'; -export enum EImageTypes { - image = 's96-c', - image192 = 's192-c', - image24 = 's24-c', - image32 = 's32-c', - image48 = 's48-c', - image512 = 's512-c', - image72 = 's72-c', +export const imageTypes = { + image: 's96-c', + image192: 's192-c', + image24: 's24-c', + image32: 's32-c', + image48: 's48-c', + image512: 's512-c', + image72: 's72-c', } export const getImages = (imageUri: string) => { - const imagesObj: Record = {} as Record< - EImageTypes, - string - >; - const keys = Object.keys(EImageTypes); - for (const type of keys) { - imagesObj[type] = imageUri.replace('s96-c', EImageTypes[type]); + const imagesObj: typeof imageTypes = imageTypes; + for (const type in imageTypes) { + imagesObj[type as keyof typeof imageTypes] = imageUri.replace( + 's96-c', + imageTypes[type as keyof typeof imageTypes] + ); } return imagesObj; @@ -57,7 +56,7 @@ export const getUserDataFromOidc = ( : undefined; const profileEmail = oidcUser.profile.email; const email = - profileEmail && profileEmail.indexOf('@') !== -1 + profileEmail?.includes('@') ? profileEmail : userData.email; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts index d94aa91c730a..e17a1ff714b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts @@ -26,9 +26,9 @@ import { CustomEventTypes, } from '../generated/analytics/webAnalyticEventType/customEvent'; import { PageViewEvent } from '../generated/analytics/webAnalyticEventType/pageViewEvent'; -import { getPathNameFromWindowLocation } from '../hoc/withDomainFilter'; import { postWebAnalyticEvent } from '../rest/WebAnalyticsAPI'; import { AnalyticsData } from './../components/WebAnalytics/WebAnalytics.interface'; +import { getPathNameFromWindowLocation } from './LocationUtils'; /** * Check if url is valid or not and return the pathname @@ -77,7 +77,8 @@ const handlePostAnalytic = async ( // collect the event data await postWebAnalyticEvent(webAnalyticEventData); } catch (error) { - // silently ignore the error + // eslint-disable-next-line no-console + console.error('Error tracking web analytic event:', error); } }; @@ -92,7 +93,7 @@ export const trackPageView = (pageData: AnalyticsData, userId?: string) => { const { payload } = pageData; - const { location, navigator, performance, document } = window; + const { location, navigator, performance, document } = globalThis; const { hostname } = location; const pageLoadTime = getPageLoadTime(performance); @@ -134,7 +135,7 @@ export const trackCustomEvent = (eventData: AnalyticsData, userId?: string) => { const { payload } = eventData; const { meta, event: eventValue } = payload; - const { location } = window; + const { location } = globalThis; // timestamp for the current event const timestamp = meta.ts; From 52333ac911e403db2192b78e9aeef4e6d0502783 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:13:34 +0530 Subject: [PATCH 21/47] apply checkstyle --- .../components/AppBar/Suggestions.test.tsx | 1 - .../AppAuthenticators/LazyAuthenticators.tsx | 74 ++-- .../MsalAuthenticator.test.tsx | 2 +- .../LazyAuthProviderWrappers.tsx | 24 +- .../ContractSLACard/ContractSLA.test.tsx | 8 +- .../CustomPropertiesSection.test.tsx | 8 +- .../ChangeParentHierarchy.test.tsx | 9 +- .../EntityDeleteModal.test.tsx | 8 +- .../ui/src/components/NavBar/NavBar.test.tsx | 10 +- .../IngestionListTable.test.tsx | 2 +- .../ConnectionConfigForm.test.tsx | 6 +- .../CustomPropertyTable.test.tsx | 2 +- .../DeleteWidget/DeleteWidgetModal.test.tsx | 3 +- .../TestConnection/TestConnection.test.tsx | 3 +- .../CustomizablePage.test.tsx | 4 +- .../pages/LoginPage/LoginCarousel.test.tsx | 2 +- .../ui/src/pages/SignUp/SignUpPage.test.tsx | 12 +- .../src/main/resources/ui/src/setupTests.js | 39 +- .../resources/ui/src/utils/APIServiceUtils.ts | 4 +- .../utils/DataAssetSummaryPanelUtils.test.tsx | 1 - .../resources/ui/src/utils/LocationUtils.ts | 2 +- .../ui/src/utils/ServiceIconUtils.ts | 359 +++++++++--------- .../resources/ui/src/utils/TableUtils.tsx | 58 ++- .../resources/ui/src/utils/UserDataUtils.ts | 7 +- 24 files changed, 324 insertions(+), 324 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx index ae59290f5985..dec2ecf91242 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx @@ -11,7 +11,6 @@ * limitations under the License. */ import { fireEvent, render, screen } from '@testing-library/react'; -import { useTranslation } from 'react-i18next'; import { useTourProvider } from '../../context/TourProvider/TourProvider'; import { SearchIndex } from '../../enums/search.enum'; import { searchQuery } from '../../rest/searchAPI'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx index b778bf10b3f0..ac7630e1c281 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx @@ -11,52 +11,52 @@ * limitations under the License. */ -import { type ComponentType, forwardRef, lazy, ReactNode } from 'react'; -import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; -import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; import type { WebStorageStateStore } from 'oidc-client'; +import { forwardRef, lazy, ReactNode, type ComponentType } from 'react'; +import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; +import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; -const Auth0Authenticator = withSuspenseFallback(lazy(() => import('./Auth0Authenticator'))); -const BasicAuthAuthenticator = withSuspenseFallback(lazy(() => import('./BasicAuthAuthenticator'))); -const MsalAuthenticator = withSuspenseFallback(lazy(() => import('./MsalAuthenticator'))); -const OidcAuthenticator = withSuspenseFallback(lazy(() => import('./OidcAuthenticator'))); -const OktaAuthenticator = withSuspenseFallback(lazy(() => import('./OktaAuthenticator'))); -const GenericAuthenticator = withSuspenseFallback(lazy(() => - import('./GenericAuthenticator').then((m) => ({ - default: m.GenericAuthenticator, - })) -)); +const Auth0Authenticator = withSuspenseFallback( + lazy(() => import('./Auth0Authenticator')) +); +const BasicAuthAuthenticator = withSuspenseFallback( + lazy(() => import('./BasicAuthAuthenticator')) +); +const MsalAuthenticator = withSuspenseFallback( + lazy(() => import('./MsalAuthenticator')) +); +const OidcAuthenticator = withSuspenseFallback( + lazy(() => import('./OidcAuthenticator')) +); +const OktaAuthenticator = withSuspenseFallback( + lazy(() => import('./OktaAuthenticator')) +); +const GenericAuthenticator = withSuspenseFallback( + lazy(() => + import('./GenericAuthenticator').then((m) => ({ + default: m.GenericAuthenticator, + })) + ) +); export const LazyAuth0Authenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyAuth0Authenticator.displayName = 'LazyAuth0Authenticator'; export const LazyBasicAuthAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyBasicAuthAuthenticator.displayName = 'LazyBasicAuthAuthenticator'; export const LazyMsalAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyMsalAuthenticator.displayName = 'LazyMsalAuthenticator'; @@ -67,32 +67,20 @@ export const LazyOidcAuthenticator = forwardRef< childComponentType: ComponentType; userConfig: Record; } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyOidcAuthenticator.displayName = 'LazyOidcAuthenticator'; export const LazyOktaAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyOktaAuthenticator.displayName = 'LazyOktaAuthenticator'; export const LazyGenericAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ( - - - -)); +>((props, ref) => ); LazyGenericAuthenticator.displayName = 'LazyGenericAuthenticator'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx index caaaf037fd0a..22db40cc0aff 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx @@ -16,10 +16,10 @@ import { } from '@azure/msal-browser'; import { useMsal } from '@azure/msal-react'; import { render, screen } from '@testing-library/react'; +import { act } from 'react'; import { msalLoginRequest } from '../../../utils/AuthProvider.util'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; import MsalAuthenticator from './MsalAuthenticator'; -import { act } from 'react'; // Mock MSAL hooks and utilities jest.mock('@azure/msal-react', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx index 448c5ef36623..6a047313dcc2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/LazyAuthProviderWrappers.tsx @@ -11,10 +11,10 @@ * limitations under the License. */ +import { CacheLocation } from '@auth0/auth0-react'; import type { IPublicClientApplication } from '@azure/msal-browser'; import { lazy, ReactNode } from 'react'; import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; -import { CacheLocation } from '@auth0/auth0-react'; const Auth0ProviderComponent = withSuspenseFallback( lazy(() => @@ -43,8 +43,8 @@ interface Auth0ProviderWrapperProps { domain: string; redirectUri: string; children: ReactNode; - useRefreshTokens: boolean - cacheLocation?: CacheLocation + useRefreshTokens: boolean; + cacheLocation?: CacheLocation; } export const LazyAuth0ProviderWrapper = ({ @@ -53,17 +53,17 @@ export const LazyAuth0ProviderWrapper = ({ redirectUri, children, useRefreshTokens, - cacheLocation + cacheLocation, }: Auth0ProviderWrapperProps) => { return ( - - {children} - + + {children} + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx index e089aae78d29..19a064379e5e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx @@ -23,7 +23,13 @@ import { mockTableData } from '../../../mocks/TableVersion.mock'; import ContractSLA from './ContractSLA.component'; jest.mock('../../../utils/i18next/LocalUtil', () => ({ - Transi18next: ({ i18nKey, values }: {i18nKey: string, values: Record}) => ( + Transi18next: ({ + i18nKey, + values, + }: { + i18nKey: string; + values: Record; + }) => ( {i18nKey} - {values?.label}: {values?.data} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx index 55714d8da0a0..0ae9421248d3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/CustomPropertiesSection/CustomPropertiesSection.test.tsx @@ -260,8 +260,10 @@ describe('CustomPropertiesSection', () => { expect(errorPlaceholder).toBeInTheDocument(); expect(errorPlaceholder).toHaveAttribute('data-type', 'PERMISSION'); - - expect(errorPlaceholder).toHaveTextContent('message.no-access-placeholder'); + + expect(errorPlaceholder).toHaveTextContent( + 'message.no-access-placeholder' + ); expect(screen.queryByTestId('search-bar')).not.toBeInTheDocument(); expect(screen.queryByTestId('property-name')).not.toBeInTheDocument(); @@ -281,7 +283,7 @@ describe('CustomPropertiesSection', () => { expect(errorPlaceholder).toBeInTheDocument(); expect(errorPlaceholder).toHaveAttribute('data-type', 'CUSTOM'); - + expect(errorPlaceholder).toHaveTextContent( 'message.no-custom-properties-entity' ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx index 495c931aa41c..20a3dfdeee8e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/ChangeParentHierarchy/ChangeParentHierarchy.test.tsx @@ -11,17 +11,12 @@ * limitations under the License. */ -import { - findByRole, - fireEvent, - render, - screen, -} from '@testing-library/react'; +import { findByRole, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { act } from 'react'; import { PageType } from '../../../generated/system/ui/page'; import { mockedGlossaryTerms } from '../../../mocks/Glossary.mock'; import ChangeParent from './ChangeParentHierarchy.component'; -import { act } from 'react'; const mockOnCancel = jest.fn(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx index dc114512ef00..e79e9e8298c0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityDeleteModal/EntityDeleteModal.test.tsx @@ -11,11 +11,11 @@ * limitations under the License. */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { act } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import EntityDeleteModal from './EntityDeleteModal'; import { Transi18next } from '../../../utils/i18next/LocalUtil'; -import { act } from 'react'; +import EntityDeleteModal from './EntityDeleteModal'; const onCancel = jest.fn(); const onConfirm = jest.fn(); @@ -135,8 +135,6 @@ describe('Test EntityDelete Modal Component', () => { }); it('should render with correct brandName (OpenMetadata or Collate)', async () => { - - await act(async () => { render(, { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx index 89285f6dc116..0333de2c7150 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ import { render, screen } from '@testing-library/react'; +import { act } from 'react'; import { LAST_VERSION_FETCH_TIME_KEY, ONE_HOUR_MS, @@ -18,7 +19,6 @@ import { import { getVersion } from '../../rest/miscAPI'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import NavBarComponent from './NavBar'; -import { act } from 'react'; // Place these at the very top of your test file, before any imports! const mockGetItem = jest.fn(); @@ -216,9 +216,9 @@ jest.mock('./PopupAlertClassBase', () => ({ jest.mock('../../utils/i18next/i18nextUtil', () => ({ languageSelectOptions: [], - loadLocale: jest.fn() -})) - + loadLocale: jest.fn(), +})); + describe('Test NavBar Component', () => { it('Should render NavBar component', async () => { render(); @@ -293,7 +293,7 @@ describe('handleDocumentVisibilityChange one hour threshold', () => { jest.resetModules(); jest.clearAllMocks(); global.Date.now = jest.fn(); - mockUseCustomLocation.pathname = '/' + mockUseCustomLocation.pathname = '/'; }); afterEach(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx index cf8f0986dba9..360e28d182c9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/Ingestion/IngestionListTable/IngestionListTable.test.tsx @@ -13,6 +13,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { act } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { usePermissionProvider } from '../../../../../context/PermissionProvider/PermissionProvider'; import { mockIngestionData } from '../../../../../mocks/Ingestion.mock'; @@ -26,7 +27,6 @@ import { getRunHistoryForPipeline, } from '../../../../../rest/ingestionPipelineAPI'; import IngestionListTable from './IngestionListTable'; -import { act } from 'react'; const mockGetEntityPermissionByFqn = jest.fn(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx index 76ed3603e402..20e864d9ed8a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/ServiceConfig/ConnectionConfigForm.test.tsx @@ -216,7 +216,7 @@ describe('ServiceConfig', () => { jest .spyOn(LocalUtils, 'Transi18next') .mockImplementation(() => <>message.airflow-host-ip-address); - }) + }); it('should render Service Config', async () => { render(); @@ -307,9 +307,7 @@ describe('ServiceConfig', () => {
)); - jest - .spyOn(LocalUtils, 'Transi18next') - .mockImplementation(mockTransi18next); + jest.spyOn(LocalUtils, 'Transi18next').mockImplementation(mockTransi18next); await act(async () => { render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx index d07518f1a2ed..ce489ed09450 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx @@ -16,11 +16,11 @@ import { screen, waitForElementToBeRemoved, } from '@testing-library/react'; +import { act } from 'react'; import { EntityType } from '../../../enums/entity.enum'; import { Table } from '../../../generated/entity/data/table'; import { getTypeByFQN } from '../../../rest/metadataTypeAPI'; import { CustomPropertyTable } from './CustomPropertyTable'; -import { act } from 'react'; const mockCustomProperties = [ { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx index 66875e341a52..e1ed48bf2705 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx @@ -13,11 +13,11 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ReactNode } from 'react'; import { EntityType } from '../../../enums/entity.enum'; import { mockUserData } from '../../../mocks/MyDataPage.mock'; import { DeleteWidgetModalProps } from './DeleteWidget.interface'; import DeleteWidgetModal from './DeleteWidgetModal'; -import { ReactNode } from 'react'; const mockProps: DeleteWidgetModalProps = { visible: true, @@ -37,7 +37,6 @@ const mockPropsUser: DeleteWidgetModalProps = { const mockOnLogoutHandler = jest.fn(); - jest.mock('../../../rest/miscAPI', () => ({ deleteEntity: jest.fn().mockImplementation(() => Promise.resolve({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx index 86b210571a14..5c3088f42542 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.test.tsx @@ -16,6 +16,7 @@ import { screen, waitForElementToBeRemoved, } from '@testing-library/react'; +import { act } from 'react'; import { useAirflowStatus } from '../../../context/AirflowStatusProvider/AirflowStatusProvider'; import { ServiceCategory } from '../../../enums/service.enum'; import { ConfigData } from '../../../interface/service.interface'; @@ -35,7 +36,6 @@ import { TEST_CONNECTION_DEFINITION, WORKFLOW_DETAILS, } from './TestConnection.mock'; -import { act } from 'react'; const mockonValidateFormRequiredFields = jest.fn(); @@ -96,7 +96,6 @@ jest.mock( }) ); - jest.useFakeTimers(); describe('Test Connection Component', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx index 2bb9186b9a4e..042a819f2aa7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.test.tsx @@ -11,7 +11,8 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import { act } from 'react'; import { useParams } from 'react-router-dom'; import { Page, PageType } from '../../generated/system/ui/page'; import { @@ -25,7 +26,6 @@ import { getDocumentByFQN } from '../../rest/DocStoreAPI'; import { getPersonaByName } from '../../rest/PersonaAPI'; import { CustomizablePage } from './CustomizablePage'; import { WidgetConfig } from './CustomizablePage.interface'; -import { act } from 'react'; jest.mock( '../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder', diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx index 66c217dfbad3..1b84c6e9b18a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LoginPage/LoginCarousel.test.tsx @@ -12,10 +12,10 @@ */ import { render, screen } from '@testing-library/react'; +import { act } from 'react'; import { MemoryRouter } from 'react-router-dom'; import loginClassBase from '../../constants/LoginClassBase'; import LoginCarousel from './LoginCarousel'; -import { act } from 'react'; const LOGIN_SLIDE = loginClassBase.getLoginCarouselContent(); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx index ffeb009d58b2..6189d8352393 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.test.tsx @@ -12,11 +12,11 @@ */ import { fireEvent, render, screen } from '@testing-library/react'; +import { act } from 'react'; import { createUser } from '../../rest/userAPI'; +import { getImages } from '../../utils/UserDataUtils'; import { mockChangedFormData, mockCreateUser } from './mocks/SignupData.mock'; import SignUp from './SignUpPage'; -import { getImages } from '../../utils/UserDataUtils'; -import { act } from 'react'; let letExpectedUserName = { name: 'sample123', @@ -109,12 +109,8 @@ describe('SignUp page', () => { render(); const form = screen.getByTestId('create-user-form'); - const fullNameInput = screen.getByTestId( - 'full-name-input' - ); - const userNameInput = screen.getByTestId( - 'username-input' - ) ; + const fullNameInput = screen.getByTestId('full-name-input'); + const userNameInput = screen.getByTestId('username-input'); const emailInput = screen.getByTestId('email-input'); const submitButton = screen.getByTestId('create-button'); diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 936624b11508..90151e5ad10b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -229,24 +229,29 @@ jest.mock('@mui/material', () => { }; }); - jest.mock('./utils/i18next/LocalUtil', () => { const React = require('react'); - return ({ - Transi18next: jest.fn().mockImplementation(({ i18nKey, renderElement, values }) => { - const valueArr = Object.values(values ?? {}) - - return React.createElement('div', i18nKey, - [i18nKey, renderElement, valueArr]); - }), - __esModule: true, - default: { + return { + Transi18next: jest + .fn() + .mockImplementation(({ i18nKey, renderElement, values }) => { + const valueArr = Object.values(values ?? {}); + + return React.createElement('div', i18nKey, [ + i18nKey, + renderElement, + valueArr, + ]); + }), + __esModule: true, + default: { + t: jest.fn().mockImplementation((key) => key), + }, t: jest.fn().mockImplementation((key) => key), - }, - t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), - translateWithNestedKeys: jest.fn().mockImplementation((key, params) => { - return params ? `${key}_${JSON.stringify(params)}` : key; - }), -})}); + detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), + translateWithNestedKeys: jest.fn().mockImplementation((key, params) => { + return params ? `${key}_${JSON.stringify(params)}` : key; + }), + }; +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts index 99f58dbf27cd..9a0c1581c729 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIServiceUtils.ts @@ -11,15 +11,15 @@ * limitations under the License. */ import { cloneDeep } from 'lodash'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { APIServiceType } from '../generated/entity/services/apiService'; import restConnection from '../jsons/connectionSchemas/connections/api/restConnection.json'; -import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; export const getAPIConfig = (type: APIServiceType) => { let schema = {}; const uiSchema = { ...COMMON_UI_SCHEMA }; if (type === APIServiceType.REST) { - schema = restConnection; + schema = restConnection; } return cloneDeep({ schema, uiSchema }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx index b88b170e9556..cf4c15964c84 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.test.tsx @@ -21,7 +21,6 @@ jest.mock('./TableUtils', () => ({ getUsagePercentile: jest.fn().mockImplementation((value) => value + 'th'), })); - jest.mock('./CommonUtils', () => ({ getPartialNameFromTableFQN: jest.fn().mockImplementation((value) => value), getTableFQNFromColumnFQN: jest.fn().mockImplementation((value) => value), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts index 1187695bc719..eac49336ce74 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/LocationUtils.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { getBasePath } from "./HistoryUtils"; +import { getBasePath } from './HistoryUtils'; export const getPathNameFromWindowLocation = () => { return globalThis.location.pathname.replace(getBasePath() ?? '', ''); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index bef009c30e69..ff7703150a52 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -11,249 +11,246 @@ * limitations under the License. */ -import mysql from '../assets/img/service-icon-sql.png'; -import sqlite from '../assets/img/service-icon-sqlite.png'; -import mssql from '../assets/img/service-icon-mssql.png'; -import redshift from '../assets/img/service-icon-redshift.png'; -import bigquery from '../assets/img/service-icon-query.png'; -import bigtable from '../assets/img/service-icon-bigtable.png'; -import hive from '../assets/img/service-icon-hive.png'; -import impala from '../assets/img/service-icon-impala.png'; -import postgres from '../assets/img/service-icon-post.png'; -import oracle from '../assets/img/service-icon-oracle.png'; -import snowflake from '../assets/img/service-icon-snowflakes.png'; import athena from '../assets/img/service-icon-athena.png'; -import presto from '../assets/img/service-icon-presto.png'; -import trino from '../assets/img/service-icon-trino.png'; -import glue from '../assets/img/service-icon-glue.png'; -import mariadb from '../assets/img/service-icon-mariadb.png'; -import vertica from '../assets/img/service-icon-vertica.png'; import azuresql from '../assets/img/service-icon-azuresql.png'; +import bigtable from '../assets/img/service-icon-bigtable.png'; +import burstiq from '../assets/img/service-icon-burstiq.png'; +import cassandra from '../assets/img/service-icon-cassandra.png'; import clickhouse from '../assets/img/service-icon-clickhouse.png'; +import cockroach from '../assets/img/service-icon-cockroach.png'; +import couchbase from '../assets/img/service-icon-couchbase.svg'; import databrick from '../assets/img/service-icon-databrick.png'; -import unitycatalog from '../assets/img/service-icon-unitycatalog.svg'; -import ibmdb2 from '../assets/img/service-icon-ibmdb2.png'; +import datalake from '../assets/img/service-icon-datalake.png'; +import deltalake from '../assets/img/service-icon-delta-lake.png'; import doris from '../assets/img/service-icon-doris.png'; -import starrocks from '../assets/img/service-icon-starrocks.png'; import druid from '../assets/img/service-icon-druid.png'; import dynamodb from '../assets/img/service-icon-dynamodb.png'; -import singlestore from '../assets/img/service-icon-singlestore.png'; -import salesforce from '../assets/img/service-icon-salesforce.png'; -import saphana from '../assets/img/service-icon-sap-hana.png'; -import saperp from '../assets/img/service-icon-sap-erp.png'; -import deltalake from '../assets/img/service-icon-delta-lake.png'; -import pinot from '../assets/img/service-icon-pinot.png'; -import datalake from '../assets/img/service-icon-datalake.png'; import exasol from '../assets/img/service-icon-exasol.png'; -import mongodb from '../assets/img/service-icon-mongodb.png'; -import cassandra from '../assets/img/service-icon-cassandra.png'; -import couchbase from '../assets/img/service-icon-couchbase.svg'; +import glue from '../assets/img/service-icon-glue.png'; import greenplum from '../assets/img/service-icon-greenplum.png'; -import teradata from '../assets/svg/teradata.svg'; -import cockroach from '../assets/img/service-icon-cockroach.png'; -import timescale from '../assets/img/service-icon-timescale.png'; -import burstiq from '../assets/img/service-icon-burstiq.png'; +import hive from '../assets/img/service-icon-hive.png'; +import ibmdb2 from '../assets/img/service-icon-ibmdb2.png'; +import impala from '../assets/img/service-icon-impala.png'; +import mariadb from '../assets/img/service-icon-mariadb.png'; +import mongodb from '../assets/img/service-icon-mongodb.png'; +import mssql from '../assets/img/service-icon-mssql.png'; +import oracle from '../assets/img/service-icon-oracle.png'; +import pinot from '../assets/img/service-icon-pinot.png'; +import postgres from '../assets/img/service-icon-post.png'; +import presto from '../assets/img/service-icon-presto.png'; +import bigquery from '../assets/img/service-icon-query.png'; +import redshift from '../assets/img/service-icon-redshift.png'; +import salesforce from '../assets/img/service-icon-salesforce.png'; +import saperp from '../assets/img/service-icon-sap-erp.png'; +import saphana from '../assets/img/service-icon-sap-hana.png'; import sas from '../assets/img/service-icon-sas.svg'; +import singlestore from '../assets/img/service-icon-singlestore.png'; +import snowflake from '../assets/img/service-icon-snowflakes.png'; +import mysql from '../assets/img/service-icon-sql.png'; +import sqlite from '../assets/img/service-icon-sqlite.png'; +import starrocks from '../assets/img/service-icon-starrocks.png'; +import timescale from '../assets/img/service-icon-timescale.png'; +import trino from '../assets/img/service-icon-trino.png'; +import unitycatalog from '../assets/img/service-icon-unitycatalog.svg'; +import vertica from '../assets/img/service-icon-vertica.png'; +import teradata from '../assets/svg/teradata.svg'; - // Messaging services +// Messaging services import kafka from '../assets/img/service-icon-kafka.png'; -import pubsub from '../assets/svg/service-icon-pubsub.svg'; -import redpanda from '../assets/img/service-icon-redpanda.png'; import kinesis from '../assets/img/service-icon-kinesis.png'; +import redpanda from '../assets/img/service-icon-redpanda.png'; +import pubsub from '../assets/svg/service-icon-pubsub.svg'; - // Dashboard services -import superset from '../assets/img/service-icon-superset.png'; +// Dashboard services +import domo from '../assets/img/service-icon-domo.png'; +import grafana from '../assets/img/service-icon-grafana.png'; +import lightdash from '../assets/img/service-icon-lightdash.png'; import looker from '../assets/img/service-icon-looker.png'; -import tableau from '../assets/img/service-icon-tableau.png'; -import redash from '../assets/img/service-icon-redash.png'; import metabase from '../assets/img/service-icon-metabase.png'; -import powerbi from '../assets/img/service-icon-power-bi.png'; -import sigma from '../assets/img/service-icon-sigma.png'; +import microstrategy from '../assets/img/service-icon-microstrategy.svg'; import mode from '../assets/img/service-icon-mode.png'; -import domo from '../assets/img/service-icon-domo.png'; -import quicksight from '../assets/img/service-icon-quicksight.png'; +import powerbi from '../assets/img/service-icon-power-bi.png'; import qliksense from '../assets/img/service-icon-qlik-sense.png'; -import lightdash from '../assets/img/service-icon-lightdash.png'; -import microstrategy from '../assets/img/service-icon-microstrategy.svg'; -import grafana from '../assets/img/service-icon-grafana.png'; +import quicksight from '../assets/img/service-icon-quicksight.png'; +import redash from '../assets/img/service-icon-redash.png'; +import sigma from '../assets/img/service-icon-sigma.png'; +import superset from '../assets/img/service-icon-superset.png'; +import tableau from '../assets/img/service-icon-tableau.png'; import hex from '../assets/svg/service-icon-hex.svg'; - // Pipeline services -import airflow from '../assets/img/service-icon-airflow.png'; +// Pipeline services import airbyte from '../assets/img/Airbyte.png'; +import airflow from '../assets/img/service-icon-airflow.png'; import dagster from '../assets/img/service-icon-dagster.png'; import dbt from '../assets/img/service-icon-dbt.png'; import fivetran from '../assets/img/service-icon-fivetran.png'; +import flink from '../assets/img/service-icon-flink.png'; import nifi from '../assets/img/service-icon-nifi.png'; +import openlineage from '../assets/img/service-icon-openlineage.svg'; import spark from '../assets/img/service-icon-spark.png'; import spline from '../assets/img/service-icon-spline.png'; -import flink from '../assets/img/service-icon-flink.png'; -import openlineage from '../assets/img/service-icon-openlineage.svg'; - // ML Model services -import mlflow from '../assets/svg/service-icon-mlflow.svg'; -import scikit from '../assets/img/service-icon-scikit.png'; +// ML Model services import sagemaker from '../assets/img/service-icon-sagemaker.png'; +import scikit from '../assets/img/service-icon-scikit.png'; +import mlflow from '../assets/svg/service-icon-mlflow.svg'; - // Storage services +// Storage services import amazons3 from '../assets/img/service-icon-amazon-s3.svg'; import gcs from '../assets/img/service-icon-gcs.png'; - // Search services +// Search services import elasticsearch from '../assets/svg/elasticsearch.svg'; import opensearch from '../assets/svg/open-search.svg'; - // Metadata services +// Metadata services +import alationsink from '../assets/img/service-icon-alation-sink.png'; import amundsen from '../assets/img/service-icon-amundsen.png'; import atlas from '../assets/img/service-icon-atlas.svg'; -import alationsink from '../assets/img/service-icon-alation-sink.png'; - // Drive services +// Drive services import googledrive from '../assets/svg/service-icon-google-drive.svg'; import sftp from '../assets/svg/service-icon-sftp.svg'; - // Default icons +// Default icons +import synapse from '../assets/img/service-icon-synapse.png'; +import dashboarddefault from '../assets/svg/dashboard.svg'; import defaultservice from '../assets/svg/default-service-icon.svg'; import databasedefault from '../assets/svg/ic-custom-database.svg'; -import topicdefault from '../assets/svg/topic.svg'; -import dashboarddefault from '../assets/svg/dashboard.svg'; -import pipelinedefault from '../assets/svg/pipeline.svg'; +import customdrivedefault from '../assets/svg/ic-custom-drive.svg'; import mlmodeldefault from '../assets/svg/ic-custom-model.svg'; +import searchdefault from '../assets/svg/ic-custom-search.svg'; import storagedefault from '../assets/svg/ic-custom-storage.svg'; import drivedefault from '../assets/svg/ic-drive-service.svg'; -import customdrivedefault from '../assets/svg/ic-custom-drive.svg'; -import searchdefault from '../assets/svg/ic-custom-search.svg'; -import securitydefault from '../assets/svg/security-safe.svg'; import restservice from '../assets/svg/ic-service-rest-api.svg'; import logo from '../assets/svg/logo-monogram.svg'; -import synapse from '../assets/img/service-icon-synapse.png'; - - -const SERVICE_ICON_LOADERS: Record = - { - // Database services - mysql: mysql, - sqlite: sqlite, - mssql: mssql, - redshift: redshift, - bigquery: bigquery, - bigtable: bigtable, - hive: hive, - impala: impala, - postgres: postgres, - oracle: oracle, - snowflake: snowflake, - athena: athena, - presto: presto, - trino: trino, - glue: glue, - mariadb: mariadb, - vertica: vertica, - azuresql: azuresql, - clickhouse: clickhouse, - databrick: databrick, - unitycatalog: unitycatalog, - ibmdb2: ibmdb2, - doris: doris, - starrocks: starrocks, - druid: druid, - dynamodb: dynamodb, - singlestore: singlestore, - salesforce: salesforce, - saphana: saphana, - saperp: saperp, - deltalake: deltalake, - pinot: pinot, - datalake: datalake, - exasol: exasol, - mongodb: mongodb, - cassandra: cassandra, - couchbase: couchbase, - greenplum: greenplum, - teradata: teradata, - cockroach: cockroach, - timescale: timescale, - burstiq: burstiq, - sas: sas, +import pipelinedefault from '../assets/svg/pipeline.svg'; +import securitydefault from '../assets/svg/security-safe.svg'; +import topicdefault from '../assets/svg/topic.svg'; - // Messaging services - kafka: kafka, - pubsub: pubsub, - redpanda: redpanda, - kinesis: kinesis, +const SERVICE_ICON_LOADERS: Record = { + // Database services + mysql: mysql, + sqlite: sqlite, + mssql: mssql, + redshift: redshift, + bigquery: bigquery, + bigtable: bigtable, + hive: hive, + impala: impala, + postgres: postgres, + oracle: oracle, + snowflake: snowflake, + athena: athena, + presto: presto, + trino: trino, + glue: glue, + mariadb: mariadb, + vertica: vertica, + azuresql: azuresql, + clickhouse: clickhouse, + databrick: databrick, + unitycatalog: unitycatalog, + ibmdb2: ibmdb2, + doris: doris, + starrocks: starrocks, + druid: druid, + dynamodb: dynamodb, + singlestore: singlestore, + salesforce: salesforce, + saphana: saphana, + saperp: saperp, + deltalake: deltalake, + pinot: pinot, + datalake: datalake, + exasol: exasol, + mongodb: mongodb, + cassandra: cassandra, + couchbase: couchbase, + greenplum: greenplum, + teradata: teradata, + cockroach: cockroach, + timescale: timescale, + burstiq: burstiq, + sas: sas, - // Dashboard services - superset: superset, - looker: looker, - tableau: tableau, - redash: redash, - metabase: metabase, - powerbi: powerbi, - sigma: sigma, - mode: mode, - domo: domo, - quicksight: quicksight, - qliksense: qliksense, - lightdash: lightdash, - microstrategy: microstrategy, - grafana: grafana, - hex: hex, + // Messaging services + kafka: kafka, + pubsub: pubsub, + redpanda: redpanda, + kinesis: kinesis, - // Pipeline services - airflow: airflow, - airbyte: airbyte, - dagster: dagster, - dbt: dbt, - fivetran: fivetran, - nifi: nifi, - spark: spark, - spline: spline, - flink: flink, - openlineage: openlineage, + // Dashboard services + superset: superset, + looker: looker, + tableau: tableau, + redash: redash, + metabase: metabase, + powerbi: powerbi, + sigma: sigma, + mode: mode, + domo: domo, + quicksight: quicksight, + qliksense: qliksense, + lightdash: lightdash, + microstrategy: microstrategy, + grafana: grafana, + hex: hex, - // ML Model services - mlflow: mlflow, - scikit: scikit, - sagemaker: sagemaker, + // Pipeline services + airflow: airflow, + airbyte: airbyte, + dagster: dagster, + dbt: dbt, + fivetran: fivetran, + nifi: nifi, + spark: spark, + spline: spline, + flink: flink, + openlineage: openlineage, - // Storage services - amazons3: amazons3, - gcs: gcs, + // ML Model services + mlflow: mlflow, + scikit: scikit, + sagemaker: sagemaker, - // Search services - elasticsearch: elasticsearch, - opensearch: opensearch, + // Storage services + amazons3: amazons3, + gcs: gcs, - // Metadata services - amundsen: amundsen, - atlas: atlas, - alationsink: alationsink, + // Search services + elasticsearch: elasticsearch, + opensearch: opensearch, - // Drive services - googledrive: googledrive, - sftp: sftp, + // Metadata services + amundsen: amundsen, + atlas: atlas, + alationsink: alationsink, - // Default icons - defaultservice: defaultservice, - databasedefault: databasedefault, - topicdefault: topicdefault, - dashboarddefault: dashboarddefault, - pipelinedefault: pipelinedefault, - mlmodeldefault: mlmodeldefault, - storagedefault: storagedefault, - drivedefault: drivedefault, - customdrivedefault: customdrivedefault, - searchdefault: searchdefault, - securitydefault: securitydefault, - restservice: restservice, - logo: logo, - synapse: synapse, - }; + // Drive services + googledrive: googledrive, + sftp: sftp, + // Default icons + defaultservice: defaultservice, + databasedefault: databasedefault, + topicdefault: topicdefault, + dashboarddefault: dashboarddefault, + pipelinedefault: pipelinedefault, + mlmodeldefault: mlmodeldefault, + storagedefault: storagedefault, + drivedefault: drivedefault, + customdrivedefault: customdrivedefault, + searchdefault: searchdefault, + securitydefault: securitydefault, + restservice: restservice, + logo: logo, + synapse: synapse, +}; export const getServiceIcon = (iconKey: string): string => { const normalizedKey = iconKey.toLowerCase().replaceAll(/[_-]/g, ''); const icon = SERVICE_ICON_LOADERS[normalizedKey] ?? defaultservice; return icon; -}; \ No newline at end of file +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 4a795bfe3075..76075542a83d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -126,6 +126,7 @@ import { ReactComponent as TaskIcon } from '../assets/svg/task-ic.svg'; import { ReactComponent as UserIcon } from '../assets/svg/user.svg'; import { ActivityFeedTab } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component'; import { ActivityFeedLayoutType } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; +import withSuspenseFallback from '../components/AppRouter/withSuspenseFallback'; import { CustomPropertyTable } from '../components/common/CustomPropertyTable/CustomPropertyTable'; import ErrorPlaceHolder from '../components/common/ErrorWithPlaceholder/ErrorPlaceHolder'; import Loader from '../components/common/Loader/Loader'; @@ -198,32 +199,53 @@ import { ordinalize } from './StringsUtils'; import { TableDetailPageTabProps } from './TableClassBase'; import { TableFieldsInfoCommonEntities } from './TableUtils.interface'; import { extractTopicFields } from './TopicDetailsUtils'; -import withSuspenseFallback from '../components/AppRouter/withSuspenseFallback'; - -const SampleDataTableComponent = withSuspenseFallback(lazy(() => import('../components/Database/SampleDataTable/SampleDataTable.component'))); -const TableQueries = withSuspenseFallback(lazy(() => import('../components/Database/TableQueries/TableQueries'))); +const SampleDataTableComponent = withSuspenseFallback( + lazy( + () => + import('../components/Database/SampleDataTable/SampleDataTable.component') + ) +); -const ContractTab = withSuspenseFallback(lazy(() => import('../components/DataContract/ContractTab/ContractTab').then( - (module) => ({ default: module.ContractTab }) -))); +const TableQueries = withSuspenseFallback( + lazy(() => import('../components/Database/TableQueries/TableQueries')) +); -const DataObservabilityTab = withSuspenseFallback(lazy( - () => - import( - '../components/Database/Profiler/DataObservability/DataObservabilityTab' +const ContractTab = withSuspenseFallback( + lazy(() => + import('../components/DataContract/ContractTab/ContractTab').then( + (module) => ({ default: module.ContractTab }) ) -)); + ) +); -const EntityLineageTab = withSuspenseFallback(lazy(() => - import('../components/Lineage/EntityLineageTab/EntityLineageTab').then( - (module) => ({ default: module.EntityLineageTab }) +const DataObservabilityTab = withSuspenseFallback( + lazy( + () => + import( + '../components/Database/Profiler/DataObservability/DataObservabilityTab' + ) ) -)); +); -const TableConstraints = withSuspenseFallback(lazy(() => import('../pages/TableDetailsPageV1/TableConstraints/TableConstraints'))); +const EntityLineageTab = withSuspenseFallback( + lazy(() => + import('../components/Lineage/EntityLineageTab/EntityLineageTab').then( + (module) => ({ default: module.EntityLineageTab }) + ) + ) +); + +const TableConstraints = withSuspenseFallback( + lazy( + () => + import('../pages/TableDetailsPageV1/TableConstraints/TableConstraints') + ) +); -const KnowledgeGraph = withSuspenseFallback(lazy(() => import('../components/KnowledgeGraph/KnowledgeGraph'))); +const KnowledgeGraph = withSuspenseFallback( + lazy(() => import('../components/KnowledgeGraph/KnowledgeGraph')) +); export const getUsagePercentile = (pctRank: number, isLiteral = false) => { const percentile = Math.round(pctRank * 10) / 10; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts index 61bd4488d49e..4e4d6005b0fb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils.ts @@ -33,7 +33,7 @@ export const imageTypes = { image48: 's48-c', image512: 's512-c', image72: 's72-c', -} +}; export const getImages = (imageUri: string) => { const imagesObj: typeof imageTypes = imageTypes; @@ -55,10 +55,7 @@ export const getUserDataFromOidc = ( ? getImages(oidcUser.profile.picture) : undefined; const profileEmail = oidcUser.profile.email; - const email = - profileEmail?.includes('@') - ? profileEmail - : userData.email; + const email = profileEmail?.includes('@') ? profileEmail : userData.email; return { ...userData, From f35d8e662567f5757810dd981b500f0b8d84303a Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:45:54 +0530 Subject: [PATCH 22/47] fix build issue --- .../ExploreV1/IndexNotFoundBanner.test.tsx | 26 +++---------------- .../ExploreV1/IndexNotFoundBanner.tsx | 2 +- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx index 51bd41bc57b9..36c482f9ad89 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx @@ -16,11 +16,6 @@ import { SEARCH_INDEXING_APPLICATION } from '../../constants/explore.constants'; import { getApplicationDetailsPath } from '../../utils/RouterUtils'; import { IndexNotFoundBanner } from './IndexNotFoundBanner'; -jest.mock('react-i18next', () => ({ - useTranslation: jest.fn().mockReturnValue({ - t: (key: string) => key, - }), -})); jest.mock('../../hooks/useApplicationStore', () => ({ useApplicationStore: jest.fn().mockReturnValue({ @@ -34,17 +29,6 @@ jest.mock('../../utils/RouterUtils', () => ({ getApplicationDetailsPath: jest.fn().mockReturnValue('/settings/search'), })); -jest.mock('../../utils/CommonUtils', () => ({ - Transi18next: jest - .fn() - .mockImplementation(({ i18nKey, renderElement, values }) => ( -
- {i18nKey} - {values?.settings} - {renderElement} -
- )), -})); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -86,17 +70,13 @@ jest.mock('antd', () => ({ })); describe('IndexNotFoundBanner', () => { - it('renders indexing error details and re-index help text', () => { + it('renders indexing error details and re-index help text', async () => { render(); expect(screen.getByTestId('index-not-found-alert')).toBeInTheDocument(); expect(screen.getByText('server.indexing-error')).toBeInTheDocument(); - expect(screen.getByTestId('trans-i18next')).toHaveTextContent( - 'message.configure-search-re-index' - ); - expect(screen.getByTestId('trans-settings-value')).toHaveTextContent( - 'label.search-index-setting-plural' - ); + expect(await screen.findByText(/message.configure-search-re-index/)).toBeInTheDocument(); + expect(await screen.findByText(/label.search-index-setting-plural/)).toBeInTheDocument(); }); it('builds the settings link using search indexing application path', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.tsx index fe54bddd81bd..2af63492c9d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.tsx @@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { SEARCH_INDEXING_APPLICATION } from '../../constants/explore.constants'; import { useApplicationStore } from '../../hooks/useApplicationStore'; -import { Transi18next } from '../../utils/CommonUtils'; +import { Transi18next } from '../../utils/i18next/LocalUtil'; import { getApplicationDetailsPath } from '../../utils/RouterUtils'; export const IndexNotFoundBanner = () => { From 269a59d8495efc8f17d0db401cec650307749504 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:09:29 +0530 Subject: [PATCH 23/47] address comments --- .../ui/src/components/AppRouter/AppRouter.tsx | 18 ++++++++++++++++++ .../resources/ui/src/constants/constants.ts | 1 - .../ui/src/constants/router.constants.ts | 6 +++--- .../src/main/resources/ui/vite.config.ts | 4 ++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 425604af136b..2695b4430b30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -20,6 +20,8 @@ import { useApplicationStore } from '../../hooks/useApplicationStore'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; import Loader from '../common/Loader/Loader'; import withSuspenseFallback from './withSuspenseFallback'; +import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; +import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; const AuthenticatedApp = withSuspenseFallback( lazy(() => import('./AuthenticatedApp')) @@ -72,6 +74,8 @@ const AppRouter = () => { })) ); + const { plugins = [] } = useApplicationsProvider(); + /** * isApplicationLoading is true when the application is loading in AuthProvider * and is false when the application is loaded. @@ -112,6 +116,20 @@ const AppRouter = () => { path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> + {/* Render APP position plugin routes (they handle their own layouts) */} + { + plugins?.flatMap((plugin) => { + const routes = plugin.getRoutes?.() || []; + // Filter routes with APP position + const appRoutes = routes.filter( + (route) => route.position === RoutePosition.APP + ); + + return appRoutes.map((route, idx) => ( + + )); + })} + } path="*" /> diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 1b1fc28cf9ae..f813a321a94d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -82,7 +82,6 @@ export const LAST_VERSION_FETCH_TIME_KEY = 'versionFetchTime'; export const LOCALSTORAGE_RECENTLY_VIEWED = `recentlyViewedData`; export const LOCALSTORAGE_RECENTLY_SEARCHED = `recentlySearchedData`; export const VERSION = 'VERSION'; -export const REDIRECT_PATHNAME = 'redirectUrlPath'; export const TERM_ADMIN = 'Admin'; export const TERM_USER = 'User'; export const DISABLED = 'disabled'; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts index 67c816bd0a64..75a501588f70 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/router.constants.ts @@ -18,14 +18,14 @@ export const APP_ROUTER_ROUTES = { LOGOUT: '/logout', UNAUTHORISED: '/unauthorised', SIGNUP: '/signup', - AUTH_CALLBACK: '/auth-callback', + AUTH_CALLBACK: '/auth/callback', SIGNIN: '/signin', FORGOT_PASSWORD: '/forgot-password', CALLBACK: '/callback', SILENT_CALLBACK: '/silent-callback', REGISTER: '/register', - RESET_PASSWORD: '/reset-password', - ACCOUNT_ACTIVATION: '/account-activation', + RESET_PASSWORD: '/users/password/reset', + ACCOUNT_ACTIVATION: '/users/registrationConfirmation', } as const; export const UNPROTECTED_ROUTES: Set = new Set([ diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index a2a1d95ed24f..b5e72d369646 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -63,8 +63,8 @@ export default defineConfig(({ mode }) => { const appVersion = process.env.APP_VERSION || 'unknown'; return html.replaceAll( /(href|src)="([^"]+\.(?:js|css))(\?[^"]*)?"/g, - (_, attr, path, qs) => - `${attr}="${path}${qs ? qs + '&' : '?'}v=${appVersion}"` + (_, attr, pathInternal, qs) => + `${attr}="${pathInternal}${qs ? qs + '&' : '?'}v=${appVersion}"` ); }, }, From af65a6a72dbb0afe63cc5867d81eb0a3f954b299 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:47:27 +0530 Subject: [PATCH 24/47] fix import error --- .../src/main/resources/ui/src/utils/AuthProvider.util.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts index 83d9edcec97f..513f43c7dfda 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider.util.ts @@ -21,8 +21,9 @@ import { OidcUser, UserProfile, } from '../components/Auth/AuthProviders/AuthProvider.interface'; -import { REDIRECT_PATHNAME, ROUTES } from '../constants/constants'; +import { ROUTES } from '../constants/constants'; import { EMAIL_REG_EX } from '../constants/regex.constants'; +import { REDIRECT_PATHNAME } from '../constants/router.constants'; import { AuthenticationConfiguration, ClientType, From 84daf1e1a232569e8b6d6d4c6f65750afcaaccd4 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:53:42 +0530 Subject: [PATCH 25/47] fix import error --- .../src/context/PermissionProvider/PermissionProvider.tsx | 7 +++---- .../src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx | 7 ++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/context/PermissionProvider/PermissionProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/context/PermissionProvider/PermissionProvider.tsx index e72e99960193..52aeb541ab87 100644 --- a/openmetadata-ui/src/main/resources/ui/src/context/PermissionProvider/PermissionProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/context/PermissionProvider/PermissionProvider.tsx @@ -24,20 +24,19 @@ import { } from 'react'; import { useNavigate } from 'react-router-dom'; import Loader from '../../components/common/Loader/Loader'; -import { REDIRECT_PATHNAME } from '../../constants/constants'; +import { REDIRECT_PATHNAME } from '../../constants/router.constants'; +import { useApplicationStore } from '../../hooks/useApplicationStore'; import { getEntityPermissionByFqn, getEntityPermissionById, getLoggedInUserPermissions, getResourcePermission, } from '../../rest/permissionAPI'; +import { setUrlPathnameExpiryAfterRoute } from '../../utils/AuthProvider.util'; import { getOperationPermissions, getUIPermission, } from '../../utils/PermissionsUtils'; - -import { useApplicationStore } from '../../hooks/useApplicationStore'; -import { setUrlPathnameExpiryAfterRoute } from '../../utils/AuthProvider.util'; import { EntityPermissionMap, PermissionContextType, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx index 96313b112fc8..da76a19f29a6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SignUp/SignUpPage.tsx @@ -19,11 +19,8 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { UserProfile } from '../../components/Auth/AuthProviders/AuthProvider.interface'; import TeamsSelectable from '../../components/Settings/Team/TeamsSelectable/TeamsSelectable'; -import { - REDIRECT_PATHNAME, - ROUTES, - VALIDATION_MESSAGES, -} from '../../constants/constants'; +import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants'; +import { REDIRECT_PATHNAME } from '../../constants/router.constants'; import { ClientType } from '../../generated/configuration/authenticationConfiguration'; import { EntityReference } from '../../generated/entity/type'; import { useApplicationStore } from '../../hooks/useApplicationStore'; From 7fce04e718c308b12df24fa5167c1c3d827d7c9e Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Mon, 13 Apr 2026 18:34:54 +0530 Subject: [PATCH 26/47] fix tests --- .../src/main/resources/ui/src/App.test.tsx | 72 ++++++++++++------ .../components/AppBar/Suggestions.test.tsx | 4 +- .../ui/src/components/AppRouter/AppRouter.tsx | 29 ++++---- .../components/DataInsight/KPIChart.test.tsx | 8 -- .../ExploreV1/IndexNotFoundBanner.test.tsx | 10 ++- .../AppDetails/ApplicationsClassBase.ts | 12 ++- .../main/resources/ui/src/utils/APIUtils.ts | 2 +- .../utils/ApplicationRoutesClassBase.test.ts | 74 +++++++++++++++++-- .../src/utils/ApplicationRoutesClassBase.ts | 5 +- .../ui/src/utils/DriveServiceUtils.test.ts | 4 +- .../ui/src/utils/ServiceIconUtils.ts | 2 +- 11 files changed, 154 insertions(+), 68 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/App.test.tsx b/openmetadata-ui/src/main/resources/ui/src/App.test.tsx index 5bf0fbfab636..afeca7c9a0ab 100644 --- a/openmetadata-ui/src/main/resources/ui/src/App.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/App.test.tsx @@ -12,30 +12,58 @@ */ import { render } from '@testing-library/react'; +import React from 'react'; import App from './App'; -import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider'; +import AppRouter from './components/AppRouter/AppRouter'; -jest.mock('./components/AppRouter/AppRouter', () => { - return jest.fn().mockReturnValue(

AppRouter

); -}); +const mockAuthProvider = jest.fn(); -jest.mock('./components/Auth/AuthProviders/AuthProvider', () => { - return { - AuthProvider: jest - .fn() - .mockImplementation(({ children }) => <>{children}), - AuthContext: { - Provider: jest.fn().mockImplementation(({ children }) => <>{children}), - }, - }; -}); +jest.mock('./components/AppRouter/AppRouter', () => ({ + __esModule: true, + default: function AppRouter() { + return React.createElement( + 'div', + { 'data-testid': 'app-router' }, + 'AppRouter' + ); + }, +})); + +jest.mock('./components/Auth/AuthProviders/AuthProvider', () => ({ + AuthProvider: function AuthProvider({ + children, + childComponentType, + }: { + children: React.ReactNode; + childComponentType: React.ComponentType; + }) { + mockAuthProvider({ childComponentType }); + + return React.createElement( + 'div', + { 'data-testid': 'auth-provider' }, + children + ); + }, +})); + +describe('App', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render AuthProvider wrapping AppRouter', () => { + const { getByTestId } = render(React.createElement(App)); + + expect(getByTestId('auth-provider')).toBeInTheDocument(); + expect(getByTestId('app-router')).toBeInTheDocument(); + }); + + it('should pass AppRouter as childComponentType to AuthProvider', () => { + render(React.createElement(App)); -it('renders learn react link', () => { - const { getAllByTestId } = render( - - - - ); - const linkElement = getAllByTestId(/content-wrapper/i); - linkElement.map((elm) => expect(elm).toBeInTheDocument()); + expect(mockAuthProvider).toHaveBeenCalledWith({ + childComponentType: AppRouter, + }); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx index dec2ecf91242..0287ee2392b2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx @@ -134,7 +134,9 @@ describe('Suggestions Component', () => { render(); // The component should show the no results message - expect(screen.getByTestId('transi18next')).toBeInTheDocument(); + expect( + screen.getByText('message.please-enter-to-find-data-assets') + ).toBeInTheDocument(); }); it('should not call searchQuery when tour is open', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 2695b4430b30..b87925eeedee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -19,9 +19,9 @@ import { APP_ROUTER_ROUTES } from '../../constants/router.constants'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; import Loader from '../common/Loader/Loader'; -import withSuspenseFallback from './withSuspenseFallback'; import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; +import withSuspenseFallback from './withSuspenseFallback'; const AuthenticatedApp = withSuspenseFallback( lazy(() => import('./AuthenticatedApp')) @@ -74,7 +74,7 @@ const AppRouter = () => { })) ); - const { plugins = [] } = useApplicationsProvider(); + const { plugins = [] } = useApplicationsProvider(); /** * isApplicationLoading is true when the application is loading in AuthProvider @@ -116,19 +116,18 @@ const AppRouter = () => { path={APP_ROUTER_ROUTES.AUTH_CALLBACK} /> - {/* Render APP position plugin routes (they handle their own layouts) */} - { - plugins?.flatMap((plugin) => { - const routes = plugin.getRoutes?.() || []; - // Filter routes with APP position - const appRoutes = routes.filter( - (route) => route.position === RoutePosition.APP - ); - - return appRoutes.map((route, idx) => ( - - )); - })} + {/* Render APP position plugin routes (they handle their own layouts) */} + {plugins?.flatMap((plugin) => { + const routes = plugin.getRoutes?.() || []; + // Filter routes with APP position + const appRoutes = routes.filter( + (route) => route.position === RoutePosition.APP + ); + + return appRoutes.map((route, idx) => ( + + )); + })} } path="*" /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/KPIChart.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/KPIChart.test.tsx index f8fa7e2c1f0c..9e24397d17e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/KPIChart.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsight/KPIChart.test.tsx @@ -22,14 +22,6 @@ jest.mock('../../rest/KpiAPI', () => ({ .mockImplementation(() => Promise.resolve({ data: KPI_LIST })), })); -jest.mock('../../utils/i18next/LocalUtil', () => ({ - t: jest.fn((key: string) => key), - translateWithNestedKeys: jest.fn((key: string, nestedKey?: string) => { - return nestedKey ? `${key}.${nestedKey}` : key; - }), - detectBrowserLanguage: jest.fn(() => 'en-US'), -})); - describe('Test KPIChart Component', () => { const mockProps = { chartFilter: { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx index 36c482f9ad89..6b7c1a44d0eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/IndexNotFoundBanner.test.tsx @@ -16,7 +16,6 @@ import { SEARCH_INDEXING_APPLICATION } from '../../constants/explore.constants'; import { getApplicationDetailsPath } from '../../utils/RouterUtils'; import { IndexNotFoundBanner } from './IndexNotFoundBanner'; - jest.mock('../../hooks/useApplicationStore', () => ({ useApplicationStore: jest.fn().mockReturnValue({ theme: { @@ -29,7 +28,6 @@ jest.mock('../../utils/RouterUtils', () => ({ getApplicationDetailsPath: jest.fn().mockReturnValue('/settings/search'), })); - jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), Link: ({ @@ -75,8 +73,12 @@ describe('IndexNotFoundBanner', () => { expect(screen.getByTestId('index-not-found-alert')).toBeInTheDocument(); expect(screen.getByText('server.indexing-error')).toBeInTheDocument(); - expect(await screen.findByText(/message.configure-search-re-index/)).toBeInTheDocument(); - expect(await screen.findByText(/label.search-index-setting-plural/)).toBeInTheDocument(); + expect( + await screen.findByText(/message.configure-search-re-index/) + ).toBeInTheDocument(); + expect( + await screen.findByText(/label.search-index-setting-plural/) + ).toBeInTheDocument(); }); it('builds the settings link using search indexing application path', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.ts index c31f76df4079..843783db5d87 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.ts @@ -11,14 +11,18 @@ * limitations under the License. */ -import { ComponentType, FC } from 'react'; +import { ComponentType, FC, lazy } from 'react'; import { AppType } from '../../../../generated/entity/applications/app'; import { getScheduleOptionsFromSchedules } from '../../../../utils/SchedularUtils'; -import ApplicationConfiguration, { - ApplicationConfigurationProps, -} from '../ApplicationConfiguration/ApplicationConfiguration'; +import withSuspenseFallback from '../../../AppRouter/withSuspenseFallback'; +import type { ApplicationConfigurationProps } from '../ApplicationConfiguration/ApplicationConfiguration'; import { AppPlugin } from '../plugins/AppPlugin'; +const ApplicationConfiguration = + withSuspenseFallback( + lazy(() => import('../ApplicationConfiguration/ApplicationConfiguration')) + ); + class ApplicationsClassBase { public async importSchema(fqn: string) { const module = await import( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts index f2d1f9354f43..edd35871042c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/APIUtils.ts @@ -12,7 +12,7 @@ */ import { AxiosError } from 'axios'; -import { get, isArray, isObject, transform, isString } from 'lodash'; +import { get, isArray, isObject, isString, transform } from 'lodash'; import { SearchIndex } from '../enums/search.enum'; import { DataProduct } from '../generated/entity/domains/dataProduct'; import { Domain } from '../generated/entity/domains/domain'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.test.ts index 927930dc8654..39db2517477e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.test.ts @@ -10,20 +10,78 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { FC } from 'react'; -import AuthenticatedAppRouter from '../components/AppRouter/AuthenticatedAppRouter'; -import { ApplicationRoutesClassBase } from './ApplicationRoutesClassBase'; +import { render, waitFor } from '@testing-library/react'; +import React, { FC } from 'react'; +import { UnAuthenticatedAppRouter } from '../components/AppRouter/UnAuthenticatedAppRouter'; +import { APP_ROUTER_ROUTES } from '../constants/router.constants'; +import applicationRoutesClassBase, { + ApplicationRoutesClassBase, +} from './ApplicationRoutesClassBase'; + +jest.mock('../components/AppRouter/AuthenticatedAppRouter', () => ({ + __esModule: true, + default: function AuthenticatedAppRouter() { + return React.createElement( + 'div', + { + 'data-testid': 'authenticated-app-router', + }, + 'Authenticated' + ); + }, +})); describe('ApplicationRoutesClassBase', () => { - let applicationRoutesClassBase: ApplicationRoutesClassBase; + let instance: ApplicationRoutesClassBase; beforeEach(() => { - applicationRoutesClassBase = new ApplicationRoutesClassBase(); + instance = new ApplicationRoutesClassBase(); + }); + + describe('getRouteElements', () => { + it('should return a lazy-loaded AuthenticatedAppRouter wrapped with Suspense', async () => { + const RouterComponent = instance.getRouteElements(); + + const { getByTestId } = render( + React.createElement(RouterComponent, null) + ); + + await waitFor(() => { + expect(getByTestId('authenticated-app-router')).toBeInTheDocument(); + }); + }); + + it('should return the same component reference as the default export', () => { + const result = instance.getRouteElements(); + const defaultResult = applicationRoutesClassBase.getRouteElements(); + + expect(result).toBe(defaultResult); + }); + }); + + describe('getUnAuthenticatedRouteElements', () => { + it('should return UnAuthenticatedAppRouter', () => { + const result: FC = instance.getUnAuthenticatedRouteElements(); + + expect(result).toBe(UnAuthenticatedAppRouter); + }); }); - it('should return AuthenticatedAppRouter from getRouteElements', () => { - const result: FC = applicationRoutesClassBase.getRouteElements(); + describe('isProtectedRoute', () => { + it('should identify protected routes correctly', () => { + expect(instance.isProtectedRoute('/dashboard')).toBe(true); + expect(instance.isProtectedRoute('/settings')).toBe(true); + expect(instance.isProtectedRoute('/explore')).toBe(true); + }); - expect(result).toBe(AuthenticatedAppRouter); + it('should identify unprotected routes correctly', () => { + expect(instance.isProtectedRoute(APP_ROUTER_ROUTES.SIGNIN)).toBe(false); + expect(instance.isProtectedRoute(APP_ROUTER_ROUTES.SIGNUP)).toBe(false); + expect(instance.isProtectedRoute(APP_ROUTER_ROUTES.HOME)).toBe(false); + expect(instance.isProtectedRoute(APP_ROUTER_ROUTES.FORGOT_PASSWORD)).toBe( + false + ); + expect(instance.isProtectedRoute(APP_ROUTER_ROUTES.CALLBACK)).toBe(false); + }); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts index 2ac2f289217d..cf8250ab69d6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ApplicationRoutesClassBase.ts @@ -13,10 +13,11 @@ import { FC, lazy } from 'react'; import { UnAuthenticatedAppRouter } from '../components/AppRouter/UnAuthenticatedAppRouter'; +import withSuspenseFallback from '../components/AppRouter/withSuspenseFallback'; import { UNPROTECTED_ROUTES } from '../constants/router.constants'; -const AuthenticatedAppRouter = lazy( - () => import('../components/AppRouter/AuthenticatedAppRouter') +const AuthenticatedAppRouter = withSuspenseFallback( + lazy(() => import('../components/AppRouter/AuthenticatedAppRouter')) ); class ApplicationRoutesClassBase { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.test.ts index 7fd96a15bc82..c22ecf825695 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DriveServiceUtils.test.ts @@ -12,7 +12,7 @@ */ import { cloneDeep } from 'lodash'; -import { COMMON_UI_SCHEMA } from '../constants/Services.constant'; +import { COMMON_UI_SCHEMA } from '../constants/ServiceUISchema.constant'; import { DriveServiceType } from '../generated/entity/services/driveService'; import customDriveConnection from '../jsons/connectionSchemas/connections/drive/customDriveConnection.json'; import googleDriveConnection from '../jsons/connectionSchemas/connections/drive/googleDriveConnection.json'; @@ -22,7 +22,7 @@ jest.mock('lodash', () => ({ cloneDeep: jest.fn(), })); -jest.mock('../constants/Services.constant', () => ({ +jest.mock('../constants/ServiceUISchema.constant', () => ({ COMMON_UI_SCHEMA: { connection: { 'ui:field': 'collapsible', diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index 644b513d74d7..a12d9158a858 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -31,6 +31,7 @@ import greenplum from '../assets/img/service-icon-greenplum.png'; import hive from '../assets/img/service-icon-hive.png'; import ibmdb2 from '../assets/img/service-icon-ibmdb2.png'; import impala from '../assets/img/service-icon-impala.png'; +import iomete from '../assets/img/service-icon-iomete.png'; import mariadb from '../assets/img/service-icon-mariadb.png'; import mongodb from '../assets/img/service-icon-mongodb.png'; import mssql from '../assets/img/service-icon-mssql.png'; @@ -54,7 +55,6 @@ import trino from '../assets/img/service-icon-trino.png'; import unitycatalog from '../assets/img/service-icon-unitycatalog.svg'; import vertica from '../assets/img/service-icon-vertica.png'; import teradata from '../assets/svg/teradata.svg'; -import iomete from '../assets/img/service-icon-iomete.png'; // Messaging services import kafka from '../assets/img/service-icon-kafka.png'; From b089ef64e48748583ecea7113a3f978936cce53c Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:14:25 +0530 Subject: [PATCH 27/47] fix tests --- .../ui/src/components/AppRouter/AppRouter.tsx | 2 +- .../AppAuthenticators/LazyAuthenticators.tsx | 70 +++++++++++-------- .../EntitySummaryPanel.test.tsx | 4 ++ .../MyData/LeftSidebar/left-sidebar.less | 6 +- .../src/main/resources/ui/src/setupTests.js | 13 +--- 5 files changed, 51 insertions(+), 44 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index b87925eeedee..1cf0f8114eec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -74,7 +74,7 @@ const AppRouter = () => { })) ); - const { plugins = [] } = useApplicationsProvider(); + const { plugins = [] } = useApplicationsProvider() ?? {}; /** * isApplicationLoading is true when the application is loading in AuthProvider diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx index ac7630e1c281..0fa6a7a5d1d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/LazyAuthenticators.tsx @@ -12,51 +12,51 @@ */ import type { WebStorageStateStore } from 'oidc-client'; -import { forwardRef, lazy, ReactNode, type ComponentType } from 'react'; -import withSuspenseFallback from '../../AppRouter/withSuspenseFallback'; +import { ComponentType, forwardRef, lazy, ReactNode, Suspense } from 'react'; +import Loader from '../../common/Loader/Loader'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; -const Auth0Authenticator = withSuspenseFallback( - lazy(() => import('./Auth0Authenticator')) -); -const BasicAuthAuthenticator = withSuspenseFallback( - lazy(() => import('./BasicAuthAuthenticator')) -); -const MsalAuthenticator = withSuspenseFallback( - lazy(() => import('./MsalAuthenticator')) -); -const OidcAuthenticator = withSuspenseFallback( - lazy(() => import('./OidcAuthenticator')) -); -const OktaAuthenticator = withSuspenseFallback( - lazy(() => import('./OktaAuthenticator')) -); -const GenericAuthenticator = withSuspenseFallback( - lazy(() => - import('./GenericAuthenticator').then((m) => ({ - default: m.GenericAuthenticator, - })) - ) +const Auth0Authenticator = lazy(() => import('./Auth0Authenticator')); +const BasicAuthAuthenticator = lazy(() => import('./BasicAuthAuthenticator')); +const MsalAuthenticator = lazy(() => import('./MsalAuthenticator')); +const OidcAuthenticator = lazy(() => import('./OidcAuthenticator')); +const OktaAuthenticator = lazy(() => import('./OktaAuthenticator')); +const GenericAuthenticator = lazy(() => + import('./GenericAuthenticator').then((m) => ({ + default: m.GenericAuthenticator, + })) ); export const LazyAuth0Authenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyAuth0Authenticator.displayName = 'LazyAuth0Authenticator'; export const LazyBasicAuthAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyBasicAuthAuthenticator.displayName = 'LazyBasicAuthAuthenticator'; export const LazyMsalAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyMsalAuthenticator.displayName = 'LazyMsalAuthenticator'; @@ -67,20 +67,32 @@ export const LazyOidcAuthenticator = forwardRef< childComponentType: ComponentType; userConfig: Record; } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyOidcAuthenticator.displayName = 'LazyOidcAuthenticator'; export const LazyOktaAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyOktaAuthenticator.displayName = 'LazyOktaAuthenticator'; export const LazyGenericAuthenticator = forwardRef< AuthenticatorRef, { children: ReactNode } ->((props, ref) => ); +>((props, ref) => ( + }> + + +)); LazyGenericAuthenticator.displayName = 'LazyGenericAuthenticator'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx index 2191e89c0fe8..9bc36b61d308 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx @@ -91,14 +91,18 @@ jest.mock('../../../utils/EntityUtils', () => { getEntityName: jest.fn().mockImplementation(() => 'displayName'), hasLineageTab: jest.fn((entityType) => LINEAGE_TABS_SET.has(entityType)), hasSchemaTab: jest.fn((entityType) => SCHEMA_TABS_SET.has(entityType)), + getEntityOverview: jest.fn().mockImplementation(() => []), hasCustomPropertiesTab: jest.fn((entityType) => CUSTOM_PROPERTIES_TABS_SET.has(entityType) ), + DRAWER_NAVIGATION_OPTIONS: [], }; }); jest.mock('../../../utils/StringsUtils', () => ({ getEncodedFqn: jest.fn().mockImplementation((fqn) => fqn), stringToHTML: jest.fn(), + bytesToSize: jest.fn(), + ordinalize: jest.fn(), })); jest.mock('react-router-dom', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/LeftSidebar/left-sidebar.less b/openmetadata-ui/src/main/resources/ui/src/components/MyData/LeftSidebar/left-sidebar.less index e46656063771..6434a41d33c2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/LeftSidebar/left-sidebar.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/LeftSidebar/left-sidebar.less @@ -36,8 +36,10 @@ } .left-sidebar-menu { - display: flex; - flex-direction: column; + &:not(.ant-menu-submenu-hidden) { + display: flex; + flex-direction: column; + } .ant-menu-item, .ant-menu-submenu { diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 90151e5ad10b..4bc1e0c7c433 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -100,17 +100,6 @@ jest.mock('i18next', () => ({ t: jest.fn().mockImplementation((key) => key), })); -jest.mock('utils/i18next/LocalUtil', () => ({ - useTranslation: jest.fn().mockReturnValue({ - t: (key) => key, - }), - detectBrowserLanguage: jest.fn().mockReturnValue('en-US'), - t: (key) => key, - translateWithNestedKeys: jest.fn((key, params) => { - return params ? `${key}_${JSON.stringify(params)}` : key; - }), - dir: jest.fn().mockReturnValue('ltr'), -})); /** * mock react-i18next */ @@ -238,7 +227,7 @@ jest.mock('./utils/i18next/LocalUtil', () => { .mockImplementation(({ i18nKey, renderElement, values }) => { const valueArr = Object.values(values ?? {}); - return React.createElement('div', i18nKey, [ + return React.createElement('div', { dataTestid: i18nKey }, [ i18nKey, renderElement, valueArr, From 9ee338eccb3e8dc03bd80f1424f16b6913068f90 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:58:18 +0530 Subject: [PATCH 28/47] move local to classBase approach --- .../components/AppContainer/AppContainer.tsx | 10 ++- .../MarketplaceNavBar.component.tsx | 8 +- .../ui/src/components/NavBar/NavBar.tsx | 8 +- .../src/utils/i18next/LocalUtilClassBase.ts | 79 +++++++++++++++++++ .../ui/src/utils/i18next/i18nextUtil.ts | 42 ---------- 5 files changed, 91 insertions(+), 56 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 50f4251be5b8..978462d41118 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -26,8 +26,8 @@ import { useLineageStore } from '../../hooks/useLineageStore'; import { getLimitConfig } from '../../rest/limitsAPI'; import { getSettingsByType } from '../../rest/settingConfigAPI'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; -import { loadLocale } from '../../utils/i18next/i18nextUtil'; import i18n from '../../utils/i18next/LocalUtil'; +import LocalUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; import { isNewLayoutRoute } from '../../utils/LayoutUtils'; import AppSidebar from '../AppSidebar/AppSidebar.component'; import { LimitBanner } from '../common/LimitBanner/LimitBanner'; @@ -95,9 +95,11 @@ const AppContainer = () => { useEffect(() => { if (language) { - loadLocale(language).then(() => { - i18n.changeLanguage(language); - }); + LocalUtilClassBase.getInstance() + .loadLocales(language) + .then(() => { + i18n.changeLanguage(language); + }); } }, [language]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 03d6134c7e07..4abfa3f6b29f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -57,11 +57,9 @@ import { getEntityType, prepareFeedLink, } from '../../../utils/FeedUtils'; -import { - languageSelectOptions, - loadLocale, -} from '../../../utils/i18next/i18nextUtil'; +import { languageSelectOptions } from '../../../utils/i18next/i18nextUtil'; import { SupportedLocales } from '../../../utils/i18next/LocalUtil.interface'; +import LocalUtilClassBase from '../../../utils/i18next/LocalUtilClassBase'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; @@ -367,7 +365,7 @@ const MarketplaceNavBar = () => { }, []); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await loadLocale(key); + await LocalUtilClassBase.getInstance().loadLocales(key); i18next.changeLanguage(key); setPreference({ language: key as SupportedLocales }); navigate(0); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index f3b5aab06d53..266be7431f06 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -72,11 +72,9 @@ import { getEntityType, prepareFeedLink, } from '../../utils/FeedUtils'; -import { - languageSelectOptions, - loadLocale, -} from '../../utils/i18next/i18nextUtil'; +import { languageSelectOptions } from '../../utils/i18next/i18nextUtil'; import { SupportedLocales } from '../../utils/i18next/LocalUtil.interface'; +import LocalUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import { getSettingPath } from '../../utils/RouterUtils'; @@ -442,7 +440,7 @@ const NavBar = () => { ); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await loadLocale(key); + await LocalUtilClassBase.getInstance().loadLocales(key); i18next.changeLanguage(key); setPreference({ language: key as SupportedLocales }); navigate(0); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts new file mode 100644 index 000000000000..0f1b4746de5f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2024 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 i18next from 'i18next'; + +class LocalUtilClassBase { + private static _instance: LocalUtilClassBase; + loadedLocales: Set = new Set(['en-US']); + + LOCALE_LOADERS: Record< + string, + () => Promise<{ default: Record }> + >; + + constructor() { + this.loadedLocales = new Set(['en-US']); + this.LOCALE_LOADERS = { + 'en-US': () => import('../../locale/languages/en-us.json'), + 'ko-KR': () => import('../../locale/languages/ko-kr.json'), + 'fr-FR': () => import('../../locale/languages/fr-fr.json'), + 'zh-CN': () => import('../../locale/languages/zh-cn.json'), + 'zh-TW': () => import('../../locale/languages/zh-tw.json'), + 'ja-JP': () => import('../../locale/languages/ja-jp.json'), + 'pt-BR': () => import('../../locale/languages/pt-br.json'), + 'pt-PT': () => import('../../locale/languages/pt-pt.json'), + 'es-ES': () => import('../../locale/languages/es-es.json'), + 'gl-ES': () => import('../../locale/languages/gl-es.json'), + 'ru-RU': () => import('../../locale/languages/ru-ru.json'), + 'de-DE': () => import('../../locale/languages/de-de.json'), + 'he-HE': () => import('../../locale/languages/he-he.json'), + 'nl-NL': () => import('../../locale/languages/nl-nl.json'), + 'pr-PR': () => import('../../locale/languages/pr-pr.json'), + 'th-TH': () => import('../../locale/languages/th-th.json'), + 'mr-IN': () => import('../../locale/languages/mr-in.json'), + 'tr-TR': () => import('../../locale/languages/tr-tr.json'), + 'ar-SA': () => import('../../locale/languages/ar-sa.json'), + }; + } + + async loadLocales(locale: string): Promise { + if (this.loadedLocales.has(locale)) { + return; + } + + const loader = this.LOCALE_LOADERS[locale]; + if (!loader) { + return; + } + + const translations = await loader(); + i18next.addResourceBundle( + locale, + 'translation', + translations.default, + true + ); + this.loadedLocales.add(locale); + } + + static getInstance(): LocalUtilClassBase { + if (!LocalUtilClassBase._instance) { + LocalUtilClassBase._instance = new LocalUtilClassBase(); + } + + return LocalUtilClassBase._instance; + } +} + +export default LocalUtilClassBase; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts index 2680b554663b..a174b7760ab2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts @@ -16,48 +16,6 @@ import { map, upperCase } from 'lodash'; import enUS from '../../locale/languages/en-us.json'; import { SupportedLocales } from './LocalUtil.interface'; -const LOCALE_LOADERS: Record< - string, - () => Promise<{ default: Record }> -> = { - 'en-US': () => import('../../locale/languages/en-us.json'), - 'ko-KR': () => import('../../locale/languages/ko-kr.json'), - 'fr-FR': () => import('../../locale/languages/fr-fr.json'), - 'zh-CN': () => import('../../locale/languages/zh-cn.json'), - 'zh-TW': () => import('../../locale/languages/zh-tw.json'), - 'ja-JP': () => import('../../locale/languages/ja-jp.json'), - 'pt-BR': () => import('../../locale/languages/pt-br.json'), - 'pt-PT': () => import('../../locale/languages/pt-pt.json'), - 'es-ES': () => import('../../locale/languages/es-es.json'), - 'gl-ES': () => import('../../locale/languages/gl-es.json'), - 'ru-RU': () => import('../../locale/languages/ru-ru.json'), - 'de-DE': () => import('../../locale/languages/de-de.json'), - 'he-HE': () => import('../../locale/languages/he-he.json'), - 'nl-NL': () => import('../../locale/languages/nl-nl.json'), - 'pr-PR': () => import('../../locale/languages/pr-pr.json'), - 'th-TH': () => import('../../locale/languages/th-th.json'), - 'mr-IN': () => import('../../locale/languages/mr-in.json'), - 'tr-TR': () => import('../../locale/languages/tr-tr.json'), - 'ar-SA': () => import('../../locale/languages/ar-sa.json'), -}; - -const loadedLocales = new Set(['en-US']); - -export const loadLocale = async (locale: string): Promise => { - if (loadedLocales.has(locale)) { - return; - } - - const loader = LOCALE_LOADERS[locale]; - if (!loader) { - return; - } - - const translations = await loader(); - i18next.addResourceBundle(locale, 'translation', translations.default, true); - loadedLocales.add(locale); -}; - export const languageSelectOptions = map(SupportedLocales, (value, key) => ({ label: `${key} - ${upperCase(value.split('-')[0])}`, key: value, From 8d15f9e21b9fd3febe0fed179d1d8c1367e92804 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:12:49 +0530 Subject: [PATCH 29/47] fix tests and update local usage --- .../DestinationSelectItem.test.tsx | 1 - .../components/AppBar/Suggestions.test.tsx | 1 - .../components/AppContainer/AppContainer.tsx | 16 --------- .../ContractSLACard/ContractSLA.test.tsx | 1 - .../DomainDetailPage.test.tsx | 1 - .../ui/src/components/NavBar/NavBar.tsx | 14 ++++---- .../DeleteWidget/DeleteWidgetModal.test.tsx | 1 - .../ErrorPlaceHolderES.test.tsx | 1 - .../useCurrentUserStore.test.ts | 16 ++------- .../currentUserStore/useCurrentUserStore.ts | 4 --- .../src/main/resources/ui/src/setupTests.js | 13 +------- .../DataContract/DataContractUtils.test.ts | 9 ----- .../ui/src/utils/FileDetailsUtils.test.tsx | 5 --- .../ui/src/utils/i18next/LocalUtil.tsx | 33 +++---------------- .../src/utils/i18next/LocalUtilClassBase.ts | 29 +++++++++++++++- .../ui/src/utils/i18next/i18nextUtil.ts | 4 +-- 16 files changed, 45 insertions(+), 104 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx index b292ded8900a..4d6127c024b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx @@ -68,7 +68,6 @@ jest.mock('../../../../utils/i18next/LocalUtil', () => ({ t: jest.fn().mockImplementation((key) => key), }, t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); describe('DestinationSelectItem component', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx index 0287ee2392b2..c1215220b4d2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx @@ -44,7 +44,6 @@ jest.mock('../../utils/i18next/LocalUtil', () => ({ t: jest.fn().mockImplementation((key) => key), }, t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); // Mock location.search for the component diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 978462d41118..3b77aefeed40 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -19,15 +19,12 @@ import { useLimitStore } from '../../context/LimitsProvider/useLimitsStore'; import { CustomEventTypes } from '../../generated/analytics/webAnalyticEventData'; import { LineageSettings } from '../../generated/configuration/lineageSettings'; import { SettingType } from '../../generated/settings/settings'; -import { useCurrentUserPreferences } from '../../hooks/currentUserStore/useCurrentUserStore'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; import { useLineageStore } from '../../hooks/useLineageStore'; import { getLimitConfig } from '../../rest/limitsAPI'; import { getSettingsByType } from '../../rest/settingConfigAPI'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; -import i18n from '../../utils/i18next/LocalUtil'; -import LocalUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; import { isNewLayoutRoute } from '../../utils/LayoutUtils'; import AppSidebar from '../AppSidebar/AppSidebar.component'; import { LimitBanner } from '../common/LimitBanner/LimitBanner'; @@ -42,9 +39,6 @@ const AppContainer = () => { const analytics = useAnalytics(); const { currentUser, setAppPreferences, appPreferences } = useApplicationStore(); - const { - preferences: { language }, - } = useCurrentUserPreferences(); const AuthenticatedRouter = applicationRoutesClass.getRouteElements(); const ApplicationExtras = applicationsClassBase.getApplicationExtension(); const { isAuthenticated } = useApplicationStore(); @@ -93,16 +87,6 @@ const AppContainer = () => { } }, [currentUser?.id]); - useEffect(() => { - if (language) { - LocalUtilClassBase.getInstance() - .loadLocales(language) - .then(() => { - i18n.changeLanguage(language); - }); - } - }, [language]); - useEffect(() => { const { pathname } = location; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx index 19a064379e5e..1f3ec3324e7b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractSLACard/ContractSLA.test.tsx @@ -39,7 +39,6 @@ jest.mock('../../../utils/i18next/LocalUtil', () => ({ t: jest.fn().mockImplementation((key) => key), }, t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); jest.mock('../../../assets/svg/ic-check-circle-2.svg', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailPage/DomainDetailPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailPage/DomainDetailPage.test.tsx index 6f342eca829b..8a97f23e0c4d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailPage/DomainDetailPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailPage/DomainDetailPage.test.tsx @@ -26,7 +26,6 @@ jest.mock('../../../utils/i18next/LocalUtil', () => ({ t: (key: string) => key, }, t: (key: string) => key, - detectBrowserLanguage: () => 'en-US', })); // Mock react-helmet-async diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 266be7431f06..d9608403dbd7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -73,8 +73,7 @@ import { prepareFeedLink, } from '../../utils/FeedUtils'; import { languageSelectOptions } from '../../utils/i18next/i18nextUtil'; -import { SupportedLocales } from '../../utils/i18next/LocalUtil.interface'; -import LocalUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; +import localUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import { getSettingPath } from '../../utils/RouterUtils'; @@ -112,7 +111,7 @@ const NavBar = () => { const { appVersion: version, setAppVersion } = useApplicationStore(); const [isDomainDropdownOpen, setIsDomainDropdownOpen] = useState(false); const { - preferences: { isSidebarCollapsed, language }, + preferences: { isSidebarCollapsed }, setPreference, } = useCurrentUserPreferences(); @@ -440,12 +439,15 @@ const NavBar = () => { ); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await LocalUtilClassBase.getInstance().loadLocales(key); + await localUtilClassBase.loadLocales(key); i18next.changeLanguage(key); - setPreference({ language: key as SupportedLocales }); navigate(0); }, []); + const currentLanguage = i18next.language + ? upperCase(i18next.language.split('-')[0]) + : ''; + return ( <>
{ className="flex-center gap-2 p-x-xs font-medium" data-testid="language-selector-button" type="text"> - {language ? upperCase(language.split('-')[0]) : ''}{' '} + {currentLanguage} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx index e1ed48bf2705..07b8eb24d1e0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DeleteWidget/DeleteWidgetModal.test.tsx @@ -63,7 +63,6 @@ jest.mock('../../../utils/ToastUtils', () => ({ jest.mock('../../../utils/i18next/LocalUtil', () => ({ Transi18next: ({ children }: { children: ReactNode }) => children, - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), t: jest.fn().mockImplementation((key: string) => key), })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx index eea00ff62300..e443809b4592 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ErrorWithPlaceholder/ErrorPlaceHolderES.test.tsx @@ -50,7 +50,6 @@ jest.mock('../../../utils/i18next/LocalUtil', () => ({ t: jest.fn().mockImplementation((key) => key), }, t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), })); const mockErrorMessage = diff --git a/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.test.ts b/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.test.ts index 55a5604fe9fa..6fbad8742ca1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.test.ts @@ -11,7 +11,6 @@ * limitations under the License. */ import { renderHook, waitFor } from '@testing-library/react'; -import { SupportedLocales } from '../../utils/i18next/LocalUtil.interface'; import { useApplicationStore } from '../useApplicationStore'; import { useCurrentUserPreferences, @@ -23,11 +22,6 @@ jest.mock('../useApplicationStore', () => ({ useApplicationStore: jest.fn(), })); -// Mock the detectBrowserLanguage function -jest.mock('../../utils/i18next/LocalUtil', () => ({ - detectBrowserLanguage: jest.fn(() => 'en-US'), -})); - jest.mock('../../constants/constants', () => ({ PAGE_SIZE_BASE: 15, })); @@ -57,7 +51,6 @@ describe('useCurrentUserStore', () => { const defaultPreferences = { isSidebarCollapsed: false, - language: SupportedLocales.English, selectedEntityTableColumns: {}, globalPageSize: 15, recentlySearched: [], @@ -78,14 +71,12 @@ describe('useCurrentUserStore', () => { result.current.setPreference({ isSidebarCollapsed: true, - language: SupportedLocales.简体中文, }); // Preferences should remain default since no user expect(result.current.preferences).toEqual({ ...defaultPreferences, isSidebarCollapsed: false, - language: SupportedLocales.English, }); }); @@ -104,14 +95,14 @@ describe('useCurrentUserStore', () => { // Set preferences directly through the setPreference method await waitFor(async () => { result.current.setPreference({ - language: SupportedLocales.简体中文, + isSidebarCollapsed: true, }); }); // Direct check without waitFor expect(result.current.preferences).toEqual({ ...defaultPreferences, - language: SupportedLocales.简体中文, + isSidebarCollapsed: true, }); }); @@ -142,7 +133,6 @@ describe('useCurrentUserStore', () => { // Should spread language from defaultPreferences since it's missing expect(result.current.preferences).toEqual({ isSidebarCollapsed: true, - language: SupportedLocales.English, // From defaultPreferences selectedEntityTableColumns: { table1: ['col1', 'col2'] }, globalPageSize: 15, recentlySearched: [], @@ -167,7 +157,6 @@ describe('useCurrentUserStore', () => { preferences: { userWithLanguage: { isSidebarCollapsed: false, - language: SupportedLocales.简体中文, selectedEntityTableColumns: {}, globalPageSize: 15, recentlySearched: [], @@ -183,7 +172,6 @@ describe('useCurrentUserStore', () => { // Should preserve the existing language preference expect(result.current.preferences).toEqual({ isSidebarCollapsed: false, - language: SupportedLocales.简体中文, // User's existing preference preserved selectedEntityTableColumns: {}, globalPageSize: 15, recentlySearched: [], diff --git a/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.ts b/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.ts index 5a788490c3d1..c0b326109614 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.ts +++ b/openmetadata-ui/src/main/resources/ui/src/hooks/currentUserStore/useCurrentUserStore.ts @@ -15,8 +15,6 @@ import { RecentlySearchedData, RecentlyViewedData } from 'Models'; import { create } from 'zustand'; import { createJSONStorage, persist } from 'zustand/middleware'; import { PAGE_SIZE_BASE } from '../../constants/constants'; -import { detectBrowserLanguage } from '../../utils/i18next/LocalUtil'; -import { SupportedLocales } from '../../utils/i18next/LocalUtil.interface'; import { useApplicationStore } from '../useApplicationStore'; export interface MarketplaceRecentSearchEntry { @@ -26,7 +24,6 @@ export interface MarketplaceRecentSearchEntry { export interface UserPreferences { isSidebarCollapsed: boolean; - language: SupportedLocales; selectedEntityTableColumns: Record; globalPageSize: number; recentlyViewed: RecentlyViewedData[]; @@ -47,7 +44,6 @@ interface Store { const defaultPreferences: UserPreferences = { isSidebarCollapsed: false, - language: detectBrowserLanguage(), selectedEntityTableColumns: {}, globalPageSize: PAGE_SIZE_BASE, recentlyViewed: [], diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 4bc1e0c7c433..787f5fbaccef 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -89,17 +89,6 @@ window.IntersectionObserver = jest.fn().mockImplementation(() => ({ disconnect: jest.fn(), })); -/** - * mock i18next - */ - -jest.mock('i18next', () => ({ - ...jest.requireActual('i18next'), - use: jest.fn(), - init: jest.fn(), - t: jest.fn().mockImplementation((key) => key), -})); - /** * mock react-i18next */ @@ -236,9 +225,9 @@ jest.mock('./utils/i18next/LocalUtil', () => { __esModule: true, default: { t: jest.fn().mockImplementation((key) => key), + on: jest.fn(), }, t: jest.fn().mockImplementation((key) => key), - detectBrowserLanguage: jest.fn().mockImplementation(() => 'en'), translateWithNestedKeys: jest.fn().mockImplementation((key, params) => { return params ? `${key}_${JSON.stringify(params)}` : key; }), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.test.ts index 99ccde2b7bb2..79009802716d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.test.ts @@ -43,15 +43,6 @@ jest.mock('js-yaml', () => ({ dump: jest.fn((data) => JSON.stringify(data)), })); -jest.mock('../i18next/LocalUtil', () => ({ - __esModule: true, - default: { - t: jest.fn((key: string) => key), - }, - t: jest.fn((key: string) => key), - detectBrowserLanguage: jest.fn(() => 'en-US'), -})); - // Import after mocks are set up import { DataContractProcessedResultCharts } from '../../components/DataContract/ContractExecutionChart/ContractExecutionChart.interface'; import { DataContract } from '../../generated/entity/data/dataContract'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FileDetailsUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FileDetailsUtils.test.tsx index 8a202c0a9dab..5d6ddb29fa86 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FileDetailsUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FileDetailsUtils.test.tsx @@ -48,11 +48,6 @@ jest.mock('../components/DataAssets/CommonWidgets/CommonWidgets', () => ({ )), })); -jest.mock('../utils/i18next/LocalUtil', () => ({ - t: (key: string) => key, - detectBrowserLanguage: jest.fn().mockReturnValue('en-US'), -})); - jest.mock('../components/DataContract/ContractTab/ContractTab.tsx', () => { return jest.fn().mockImplementation(() =>

DataContractComponent

); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index 19044e9bc062..9e1559f46223 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -11,37 +11,12 @@ * limitations under the License. */ -import i18n, { t as i18nextT } from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; +import { t as i18nextT } from 'i18next'; import { ReactNode } from 'react'; -import { initReactI18next, Trans } from 'react-i18next'; -import { getInitOptions, languageMap } from './i18nextUtil'; -import { SupportedLocales } from './LocalUtil.interface'; +import { Trans } from 'react-i18next'; +import localUtilClassBase from './LocalUtilClassBase'; -// Function to detect browser language -export const detectBrowserLanguage = (): SupportedLocales => { - const browserLang = navigator.language; - const browserLangs = navigator.languages || [browserLang]; - let browserLanguage = undefined; - for (const lang of browserLangs) { - const langCode = lang.split('-')[0]; - - if (languageMap[langCode]) { - browserLanguage = languageMap[langCode]; - - return browserLanguage; - } - } - - // English is the default language when we don't support browser language - return browserLanguage ?? SupportedLocales.English; -}; - -// Initialize i18next (language) -i18n - .use(LanguageDetector) // Detects system language - .use(initReactI18next) - .init(getInitOptions()); +const i18n = localUtilClassBase.getI18nInstance(); export const t = (key: string, options?: Record): string => { const translation = i18nextT(key, options); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts index 0f1b4746de5f..ba30b5fa6048 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -12,6 +12,9 @@ */ import i18next from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; +import { getInitOptions } from './i18nextUtil'; class LocalUtilClassBase { private static _instance: LocalUtilClassBase; @@ -45,6 +48,22 @@ class LocalUtilClassBase { 'tr-TR': () => import('../../locale/languages/tr-tr.json'), 'ar-SA': () => import('../../locale/languages/ar-sa.json'), }; + + i18next.on('languageChanged', async (lng) => { + await this.loadLocales(lng); + }); + + setTimeout(() => { + i18next + .use(LanguageDetector) + .use(initReactI18next) + .init(getInitOptions()) + .then(async () => { + if (i18next.language !== i18next.resolvedLanguage) { + await i18next.changeLanguage(i18next.language); + } + }); + }, 0); } async loadLocales(locale: string): Promise { @@ -67,6 +86,10 @@ class LocalUtilClassBase { this.loadedLocales.add(locale); } + getI18nInstance() { + return i18next; + } + static getInstance(): LocalUtilClassBase { if (!LocalUtilClassBase._instance) { LocalUtilClassBase._instance = new LocalUtilClassBase(); @@ -76,4 +99,8 @@ class LocalUtilClassBase { } } -export default LocalUtilClassBase; +const localUtilClassBase = LocalUtilClassBase.getInstance(); + +export { LocalUtilClassBase }; + +export default localUtilClassBase; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts index a174b7760ab2..b866263496d4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts @@ -34,12 +34,12 @@ export const getInitOptions = (): InitOptions => { caches: ['cookie'], // cache user language on }, interpolation: { - escapeValue: false, // XSS safety provided by React + escapeValue: false, }, missingKeyHandler: (_lngs, _ns, key) => // eslint-disable-next-line no-console console.error(`i18next: key not found "${key}"`), - saveMissing: true, // Required for missing key handler + saveMissing: true, }; }; From 61adb1ae860845518b5f66bb33033a4170029b2b Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:45:53 +0530 Subject: [PATCH 30/47] fix import error --- openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index ac533e475b93..867798f402bb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -199,9 +199,6 @@ import { ordinalize } from './StringsUtils'; import { TableDetailPageTabProps } from './TableClassBase'; import { TableFieldsInfoCommonEntities } from './TableUtils.interface'; import { extractTopicFields } from './TopicDetailsUtils'; -const KnowledgeGraph = lazy( - () => import('../components/KnowledgeGraph/KnowledgeGraph') -); const SampleDataTableComponent = withSuspenseFallback( lazy( From 1ee853a562e87bb78f18d8970e6e0edfd67886a9 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:14:47 +0530 Subject: [PATCH 31/47] fix unit tests --- .../MarketplaceNavBar.component.tsx | 19 +- .../ui/src/components/NavBar/NavBar.tsx | 8 +- .../NotificationBox/NotificationBox.utils.tsx | 6 +- .../ui/src/constants/TestSuite.constant.ts | 13 +- .../utils/CustomizableLandingPageUtils.tsx | 8 +- .../src/utils/DataAssetSummaryPanelUtils.tsx | 180 +++++++++--------- .../ui/src/utils/DataAssetsHeader.utils.tsx | 4 +- .../src/main/resources/ui/src/utils/Fqn.ts | 8 +- .../ui/src/utils/GlobalSettingsUtils.tsx | 4 +- .../resources/ui/src/utils/SchedularUtils.tsx | 26 ++- .../resources/ui/src/utils/SearchUtils.tsx | 46 ++--- .../main/resources/ui/src/utils/TagsUtils.tsx | 12 +- .../main/resources/ui/src/utils/TourUtils.tsx | 33 ++-- 13 files changed, 184 insertions(+), 183 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 4abfa3f6b29f..4d03e544ffc6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -15,7 +15,6 @@ import { Alert, Badge, Button, Dropdown, Tooltip } from 'antd'; import { Header } from 'antd/lib/layout/layout'; import { AxiosError } from 'axios'; import { CookieStorage } from 'cookie-storage'; -import i18next from 'i18next'; import { startCase, upperCase } from 'lodash'; import { MenuInfo } from 'rc-menu/lib/interface'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -58,8 +57,8 @@ import { prepareFeedLink, } from '../../../utils/FeedUtils'; import { languageSelectOptions } from '../../../utils/i18next/i18nextUtil'; -import { SupportedLocales } from '../../../utils/i18next/LocalUtil.interface'; -import LocalUtilClassBase from '../../../utils/i18next/LocalUtilClassBase'; +import i18n from '../../../utils/i18next/LocalUtil'; +import localUtilClassBase from '../../../utils/i18next/LocalUtilClassBase'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; @@ -91,7 +90,7 @@ const MarketplaceNavBar = () => { const [activeTab, setActiveTab] = useState('Task'); const { appVersion: version, setAppVersion } = useApplicationStore(); const { - preferences: { isSidebarCollapsed, language }, + preferences: { isSidebarCollapsed }, setPreference, } = useCurrentUserPreferences(); @@ -365,12 +364,15 @@ const MarketplaceNavBar = () => { }, []); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await LocalUtilClassBase.getInstance().loadLocales(key); - i18next.changeLanguage(key); - setPreference({ language: key as SupportedLocales }); + await localUtilClassBase.loadLocales(key); + i18n.changeLanguage(key); navigate(0); }, []); + const currentLanguage = i18n.language + ? upperCase(i18n.language.split('-')[0]) + : ''; + return ( <>
{ className="flex-center gap-2 p-x-xs font-medium" data-testid="language-selector-button" type="text"> - {language ? upperCase(language.split('-')[0]) : ''}{' '} - + {currentLanguage} { const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { await localUtilClassBase.loadLocales(key); - i18next.changeLanguage(key); + i18n.changeLanguage(key); navigate(0); }, []); - const currentLanguage = i18next.language - ? upperCase(i18next.language.split('-')[0]) + const currentLanguage = i18n.language + ? upperCase(i18n.language.split('-')[0]) : ''; return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NotificationBox/NotificationBox.utils.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NotificationBox/NotificationBox.utils.tsx index 5ddb863e62e0..7680c59b148b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NotificationBox/NotificationBox.utils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NotificationBox/NotificationBox.utils.tsx @@ -12,16 +12,16 @@ */ import Icon from '@ant-design/icons'; -import i18next from 'i18next'; import { ReactComponent as IconMentions } from '../../assets/svg/ic-mentions.svg'; import { ReactComponent as IconTask } from '../../assets/svg/ic-task.svg'; import { FeedFilter } from '../../enums/mydata.enum'; import { NotificationTabsKey } from '../../enums/notification.enum'; import { ThreadType } from '../../generated/api/feed/createThread'; +import i18n from '../../utils/i18next/LocalUtil'; export const tabsInfo = [ { - name: i18next.t('label.task-plural'), + name: i18n.t('label.task-plural'), key: NotificationTabsKey.TASK, icon: ( = [ @@ -28,13 +27,13 @@ export const STEPS_FOR_ADD_TEST_SUITE: Array = [ step: 1, }, { - name: i18next.t('label.add-entity', { - entity: i18next.t('label.test-case'), + name: i18n.t('label.add-entity', { + entity: i18n.t('label.test-case'), }), step: 2, }, { - name: i18next.t('label.test-suite-status'), + name: i18n.t('label.test-suite-status'), step: 3, }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CustomizableLandingPageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CustomizableLandingPageUtils.tsx index eba886ea6c43..713ebe74530f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CustomizableLandingPageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CustomizableLandingPageUtils.tsx @@ -12,7 +12,6 @@ */ import Icon from '@ant-design/icons'; -import i18next from 'i18next'; import { capitalize, isUndefined, uniqBy, uniqueId } from 'lodash'; import { DOMAttributes } from 'react'; import { Layout } from 'react-grid-layout'; @@ -22,6 +21,7 @@ import { LandingPageWidgetKeys } from '../enums/CustomizablePage.enum'; import { Document } from '../generated/entity/docStore/document'; import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface'; import customizeMyDataPageClassBase from './CustomizeMyDataPageClassBase'; +import i18n from './i18next/LocalUtil'; /** * Ensures widget width doesn't exceed the maximum allowed width of 2 @@ -371,11 +371,11 @@ export const getRemoveWidgetHandler = export const getWidgetWidthLabelFromKey = (widgetKey: string): string => { switch (widgetKey) { case 'large': - return i18next.t('label.large'); + return i18n.t('label.large'); case 'medium': - return i18next.t('label.medium'); + return i18n.t('label.medium'); case 'small': - return i18next.t('label.small'); + return i18n.t('label.small'); default: return capitalize(widgetKey); } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx index d0be44d62eae..f1771765fb2d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetSummaryPanelUtils.tsx @@ -10,7 +10,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import i18next from 'i18next'; import { isEmpty, isNil, isObject, isUndefined } from 'lodash'; import { DomainLabel } from '../components/common/DomainLabel/DomainLabel.component'; import { OwnerLabel } from '../components/common/OwnerLabel/OwnerLabel.component'; @@ -56,6 +55,7 @@ import { } from './CommonUtils'; import { DRAWER_NAVIGATION_OPTIONS, getEntityName } from './EntityUtils'; import { BasicEntityOverviewInfo } from './EntityUtils.interface'; +import i18n from './i18next/LocalUtil'; import { getEntityDetailsPath, getServiceDetailsPath } from './RouterUtils'; import { bytesToSize, stringToHTML } from './StringsUtils'; import { getTierTags, getUsagePercentile } from './TableUtils'; @@ -157,7 +157,7 @@ const getCommonOverview = ( ...(showOwner ? [ { - name: i18next.t('label.owner-plural'), + name: i18n.t('label.owner-plural'), value: ( , isLink: false, visible: [ @@ -272,7 +272,7 @@ const getTableOverview = ( ], }, { - name: i18next.t('label.column-plural'), + name: i18n.t('label.column-plural'), value: columns ? columns.length : NO_DATA, isLink: false, visible: [ @@ -281,7 +281,7 @@ const getTableOverview = ( ], }, { - name: i18next.t('label.row-plural'), + name: i18n.t('label.row-plural'), value: !isUndefined(profile) && profile?.rowCount ? formatNumberWithComma(profile.rowCount) @@ -290,7 +290,7 @@ const getTableOverview = ( visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.incident-plural'), + name: i18n.t('label.incident-plural'), value: additionalInfo?.incidentCount ?? 0, isLink: true, linkProps: { @@ -325,7 +325,7 @@ const getTopicOverview = (topicDetails: Topic) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ domains, owners: topicDetails.owners }), { - name: i18next.t('label.partition-plural'), + name: i18n.t('label.partition-plural'), value: partitions ?? NO_DATA, isLink: false, visible: [ @@ -334,7 +334,7 @@ const getTopicOverview = (topicDetails: Topic) => { ], }, { - name: i18next.t('label.replication-factor'), + name: i18n.t('label.replication-factor'), value: replicationFactor, isLink: false, visible: [ @@ -343,7 +343,7 @@ const getTopicOverview = (topicDetails: Topic) => { ], }, { - name: i18next.t('label.retention-size'), + name: i18n.t('label.retention-size'), value: bytesToSize(retentionSize ?? 0), isLink: false, visible: [ @@ -352,7 +352,7 @@ const getTopicOverview = (topicDetails: Topic) => { ], }, { - name: i18next.t('label.clean-up-policy-plural'), + name: i18n.t('label.clean-up-policy-plural'), value: cleanupPolicies ? cleanupPolicies.join(', ') : NO_DATA, isLink: false, visible: [ @@ -361,7 +361,7 @@ const getTopicOverview = (topicDetails: Topic) => { ], }, { - name: i18next.t('label.max-message-size'), + name: i18n.t('label.max-message-size'), value: bytesToSize(maximumMessageSize ?? 0), isLink: false, visible: [ @@ -370,7 +370,7 @@ const getTopicOverview = (topicDetails: Topic) => { ], }, { - name: i18next.t('label.schema-type'), + name: i18n.t('label.schema-type'), value: messageSchema?.schemaType ?? NO_DATA, isLink: false, visible: [ @@ -392,9 +392,7 @@ const getPipelineOverview = (pipelineDetails: Pipeline) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: `${i18next.t('label.pipeline')} ${i18next.t( - 'label.url-uppercase' - )}`, + name: `${i18n.t('label.pipeline')} ${i18n.t('label.url-uppercase')}`, dataTestId: 'pipeline-url-label', value: stringToHTML(displayName ?? '') || NO_DATA, url: sourceUrl, @@ -406,7 +404,7 @@ const getPipelineOverview = (pipelineDetails: Pipeline) => { ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: serviceDisplayName || NO_DATA, url: getServiceDetailsPath( service?.name ?? '', @@ -417,7 +415,7 @@ const getPipelineOverview = (pipelineDetails: Pipeline) => { visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.lineage], @@ -436,9 +434,7 @@ const getDashboardOverview = (dashboardDetails: Dashboard) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: `${i18next.t('label.dashboard')} ${i18next.t( - 'label.url-uppercase' - )}`, + name: `${i18n.t('label.dashboard')} ${i18n.t('label.url-uppercase')}`, value: stringToHTML(displayName ?? '') || NO_DATA, url: sourceUrl, isLink: true, @@ -449,7 +445,7 @@ const getDashboardOverview = (dashboardDetails: Dashboard) => { ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: serviceDisplayName || NO_DATA, url: getServiceDetailsPath( service?.name ?? '', @@ -460,14 +456,14 @@ const getDashboardOverview = (dashboardDetails: Dashboard) => { visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, isExternal: false, visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.project'), + name: i18n.t('label.project'), value: project ?? NO_DATA, isLink: false, visible: [ @@ -487,14 +483,14 @@ export const getSearchIndexOverview = (searchIndexDetails: SearchIndex) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, isExternal: false, visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName ?? NO_DATA, url: getServiceDetailsPath( service?.name ?? '', @@ -516,7 +512,7 @@ const getMlModelOverview = (mlModelDetails: Mlmodel) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.algorithm'), + name: i18n.t('label.algorithm'), value: algorithm || NO_DATA, url: '', isLink: false, @@ -526,7 +522,7 @@ const getMlModelOverview = (mlModelDetails: Mlmodel) => { ], }, { - name: i18next.t('label.target'), + name: i18n.t('label.target'), value: target ?? NO_DATA, url: '', isLink: false, @@ -536,7 +532,7 @@ const getMlModelOverview = (mlModelDetails: Mlmodel) => { ], }, { - name: i18next.t('label.server'), + name: i18n.t('label.server'), value: server ?? NO_DATA, url: server, isLink: Boolean(server), @@ -547,7 +543,7 @@ const getMlModelOverview = (mlModelDetails: Mlmodel) => { ], }, { - name: i18next.t('label.dashboard'), + name: i18n.t('label.dashboard'), value: getEntityName(dashboard) || NO_DATA, url: getEntityDetailsPath( EntityType.DASHBOARD, @@ -577,19 +573,19 @@ const getContainerOverview = (containerDetails: Container) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.object-plural'), + name: i18n.t('label.object-plural'), value: numberOfObjects, isLink: false, visible, }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible, }, { - name: i18next.t('label.column-plural'), + name: i18n.t('label.column-plural'), value: !isUndefined(dataModel) && dataModel.columns ? dataModel.columns.length @@ -617,7 +613,7 @@ const getChartOverview = (chartDetails: Chart) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: `${i18next.t('label.chart')} ${i18next.t('label.url-uppercase')}`, + name: `${i18n.t('label.chart')} ${i18n.t('label.url-uppercase')}`, value: stringToHTML(displayName ?? '') || NO_DATA, url: sourceUrl, isLink: true, @@ -628,7 +624,7 @@ const getChartOverview = (chartDetails: Chart) => { ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: serviceDisplayName || NO_DATA, url: getServiceDetailsPath( service?.name ?? '', @@ -642,7 +638,7 @@ const getChartOverview = (chartDetails: Chart) => { ], }, { - name: i18next.t('label.chart-type'), + name: i18n.t('label.chart-type'), value: chartType ?? NO_DATA, isLink: false, visible: [ @@ -651,7 +647,7 @@ const getChartOverview = (chartDetails: Chart) => { ], }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType ?? NO_DATA, isLink: false, visible: [ @@ -679,9 +675,7 @@ const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: `${i18next.t('label.data-model')} ${i18next.t( - 'label.url-uppercase' - )}`, + name: `${i18n.t('label.data-model')} ${i18n.t('label.url-uppercase')}`, value: stringToHTML(displayName ?? '') || NO_DATA, url: getEntityDetailsPath( EntityType.DASHBOARD_DATA_MODEL, @@ -694,7 +688,7 @@ const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName ?? NO_DATA, url: getServiceDetailsPath( service?.name ?? '', @@ -709,7 +703,7 @@ const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { }, { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, isExternal: false, @@ -719,7 +713,7 @@ const getDataModelOverview = (dataModelDetails: DashboardDataModel) => { ], }, { - name: i18next.t('label.data-model-type'), + name: i18n.t('label.data-model-type'), value: dataModelType, isLink: false, isExternal: false, @@ -749,14 +743,14 @@ const getStoredProcedureOverview = ( const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service || NO_DATA, url: getServiceDetailsPath(service, ServiceCategory.DATABASE_SERVICES), isLink: true, visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.database'), + name: i18n.t('label.database'), value: database || NO_DATA, url: getEntityDetailsPath( EntityType.DATABASE, @@ -770,7 +764,7 @@ const getStoredProcedureOverview = ( visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { - name: i18next.t('label.schema'), + name: i18n.t('label.schema'), value: schema || NO_DATA, url: getEntityDetailsPath( EntityType.DATABASE_SCHEMA, @@ -787,7 +781,7 @@ const getStoredProcedureOverview = ( ], }, { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, visible: [ @@ -798,7 +792,7 @@ const getStoredProcedureOverview = ( ...(isObject(storedProcedureCode) ? [ { - name: i18next.t('label.language'), + name: i18n.t('label.language'), value: (storedProcedureCode as StoredProcedureCodeObject).language ?? NO_DATA, @@ -822,19 +816,19 @@ const getDatabaseOverview = (databaseDetails: Database) => { const overview: BasicEntityOverviewInfo[] = [ { - name: i18next.t('label.owner-plural'), + name: i18n.t('label.owner-plural'), value: , visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, ...getCommonOverview({ domains }, false), { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName || NO_DATA, url: getServiceDetailsPath( service?.fullyQualifiedName ?? '', @@ -845,7 +839,7 @@ const getDatabaseOverview = (databaseDetails: Database) => { }, { - name: i18next.t('label.usage'), + name: i18n.t('label.usage'), value: getUsageData(usageSummary), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], @@ -863,19 +857,19 @@ const getDatabaseSchemaOverview = (databaseSchemaDetails: DatabaseSchema) => { const overview: BasicEntityOverviewInfo[] = [ { - name: i18next.t('label.owner-plural'), + name: i18n.t('label.owner-plural'), value: , visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, ...getCommonOverview({ domains }, false), { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName ?? NO_DATA, url: getServiceDetailsPath( service?.fullyQualifiedName ?? '', @@ -885,7 +879,7 @@ const getDatabaseSchemaOverview = (databaseSchemaDetails: DatabaseSchema) => { visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.database'), + name: i18n.t('label.database'), value: database?.fullyQualifiedName ?? NO_DATA, url: getEntityDetailsPath( EntityType.DATABASE, @@ -895,7 +889,7 @@ const getDatabaseSchemaOverview = (databaseSchemaDetails: DatabaseSchema) => { visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.usage'), + name: i18n.t('label.usage'), value: getUsageData(usageSummary), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], @@ -912,19 +906,19 @@ const getEntityServiceOverview = (serviceDetails: EntityServiceUnion) => { const overview: BasicEntityOverviewInfo[] = [ { - name: i18next.t('label.owner-plural'), + name: i18n.t('label.owner-plural'), value: , visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, ...getCommonOverview({ domains }, false), { - name: i18next.t('label.tier'), + name: i18n.t('label.tier'), value: entityTierRenderer(tier), isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible: [DRAWER_NAVIGATION_OPTIONS.explore], @@ -944,7 +938,7 @@ const getApiCollectionOverview = (apiCollection: APICollection) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ domains }, false), { - name: i18next.t('label.endpoint-url'), + name: i18n.t('label.endpoint-url'), value: apiCollection.endpointURL || NO_DATA, url: apiCollection.endpointURL, isLink: true, @@ -952,7 +946,7 @@ const getApiCollectionOverview = (apiCollection: APICollection) => { visible: [DRAWER_NAVIGATION_OPTIONS.explore], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName ?? NO_DATA, url: getServiceDetailsPath( service?.fullyQualifiedName ?? '', @@ -974,7 +968,7 @@ const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ domains }, false), { - name: i18next.t('label.endpoint-url'), + name: i18n.t('label.endpoint-url'), value: apiEndpoint.endpointURL || NO_DATA, url: apiEndpoint.endpointURL, isLink: true, @@ -985,7 +979,7 @@ const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { ], }, { - name: i18next.t('label.api-collection'), + name: i18n.t('label.api-collection'), value: apiEndpoint.apiCollection?.fullyQualifiedName ?? '', url: getEntityDetailsPath( EntityType.API_COLLECTION, @@ -998,7 +992,7 @@ const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.fullyQualifiedName ?? '', url: getServiceDetailsPath( service?.fullyQualifiedName ?? '', @@ -1011,7 +1005,7 @@ const getApiEndpointOverview = (apiEndpoint: APIEndpoint) => { ], }, { - name: i18next.t('label.request-method'), + name: i18n.t('label.request-method'), value: apiEndpoint.requestMethod || NO_DATA, isLink: false, visible: [ @@ -1031,7 +1025,7 @@ const getMetricOverview = (metric: Metric) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ domains: metric.domains }, false), { - name: i18next.t('label.metric-type'), + name: i18n.t('label.metric-type'), value: metric.metricType || NO_DATA, isLink: false, visible: [ @@ -1040,7 +1034,7 @@ const getMetricOverview = (metric: Metric) => { ], }, { - name: i18next.t('label.unit-of-measurement'), + name: i18n.t('label.unit-of-measurement'), value: metric.unitOfMeasurement || NO_DATA, isLink: false, visible: [ @@ -1049,7 +1043,7 @@ const getMetricOverview = (metric: Metric) => { ], }, { - name: i18next.t('label.granularity'), + name: i18n.t('label.granularity'), value: metric.granularity || NO_DATA, isLink: false, visible: [ @@ -1079,19 +1073,19 @@ const getDirectoryOverview = (directoryDetails: Directory) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.directory-plural'), + name: i18n.t('label.directory-plural'), value: numberOfSubDirectories ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.file-plural'), + name: i18n.t('label.file-plural'), value: numberOfFiles ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible, @@ -1113,25 +1107,25 @@ const getFileOverview = (fileDetails: File) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.file-extension'), + name: i18n.t('label.file-extension'), value: fileExtension ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.file-type'), + name: i18n.t('label.file-type'), value: fileType ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.file-version'), + name: i18n.t('label.file-version'), value: fileVersion ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible, @@ -1152,13 +1146,13 @@ const getSpreadsheetOverview = (spreadsheetDetails: Spreadsheet) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.file-version'), + name: i18n.t('label.file-version'), value: fileVersion ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible, @@ -1180,19 +1174,19 @@ const getWorksheetOverview = (worksheetDetails: Worksheet) => { const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.column-plural'), + name: i18n.t('label.column-plural'), value: columnCount ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.row-plural'), + name: i18n.t('label.row-plural'), value: rowCount ?? NO_DATA, isLink: false, visible, }, { - name: i18next.t('label.service-type'), + name: i18n.t('label.service-type'), value: serviceType, isLink: false, visible, @@ -1220,7 +1214,7 @@ const getColumnOverview = ( const overview: BasicEntityOverviewInfo[] = [ ...getCommonOverview({ owners, domains }), { - name: i18next.t('label.data-type'), + name: i18n.t('label.data-type'), value: dataTypeDisplay || dataType || '--', isLink: false, visible: [ @@ -1229,7 +1223,7 @@ const getColumnOverview = ( ], }, { - name: i18next.t('label.table'), + name: i18n.t('label.table'), value: table?.displayName || table?.name || '--', url: table?.fullyQualifiedName ? getEntityDetailsPath(EntityType.TABLE, table.fullyQualifiedName) @@ -1241,7 +1235,7 @@ const getColumnOverview = ( ], }, { - name: i18next.t('label.service'), + name: i18n.t('label.service'), value: service?.displayName || service?.name || '--', url: service?.fullyQualifiedName ? getServiceDetailsPath(service.fullyQualifiedName, service.type || '') @@ -1253,7 +1247,7 @@ const getColumnOverview = ( ], }, { - name: i18next.t('label.database'), + name: i18n.t('label.database'), value: database?.displayName || database?.name || '--', url: database?.fullyQualifiedName ? getEntityDetailsPath(EntityType.DATABASE, database.fullyQualifiedName) @@ -1265,7 +1259,7 @@ const getColumnOverview = ( ], }, { - name: i18next.t('label.schema'), + name: i18n.t('label.schema'), value: databaseSchema?.displayName || databaseSchema?.name || '--', url: databaseSchema?.fullyQualifiedName ? getEntityDetailsPath( @@ -1283,7 +1277,7 @@ const getColumnOverview = ( if (constraint) { overview.push({ - name: i18next.t('label.constraint'), + name: i18n.t('label.constraint'), value: constraint, isLink: false, visible: [ diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx index ecdb3ad4c2cc..61df51113828 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataAssetsHeader.utils.tsx @@ -15,7 +15,6 @@ import Icon from '@ant-design/icons'; import { Divider, Tooltip, Typography } from 'antd'; import classNames from 'classnames'; -import { t } from 'i18next'; import { isArray, isEmpty, isObject, isUndefined } from 'lodash'; import React, { ReactNode } from 'react'; import { ReactComponent as IconExternalLink } from '../assets/svg/external-links.svg'; @@ -70,10 +69,13 @@ import { getBreadcrumbForTable, getEntityBreadcrumbs, } from './EntityUtils'; +import i18n from './i18next/LocalUtil'; import { getEntityDetailsPath } from './RouterUtils'; import { bytesToSize } from './StringsUtils'; import { getUsagePercentile } from './TableUtils'; +const { t } = i18n; + export const ExtraInfoLabel = ({ label, value, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.ts b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.ts index 2b9924e7a073..2fe0430f7676 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.ts @@ -13,10 +13,10 @@ import antlr4 from 'antlr4'; import { ParseTreeWalker } from 'antlr4/src/antlr4/tree'; -import i18next from 'i18next'; import SplitListener from '../antlr/SplitListener'; import FqnLexer from '../generated/antlr/FqnLexer'; import FqnParser from '../generated/antlr/FqnParser'; +import i18n from './i18next/LocalUtil'; export default class Fqn { // Equivalent of Java's FullyQualifiedName#split @@ -45,8 +45,8 @@ export default class Fqn { // Equivalent of Java's FullyQualifiedName#quoteName static quoteName(name: string) { const matcher = /^(")([^"]+)(")$|^(.*)$/.exec(name); - if (!matcher || matcher[0].length !== name.length) { - throw new Error(`${i18next.t('label.invalid-name')} ${name}`); + if (matcher?.[0].length !== name.length) { + throw new Error(`${i18n.t('label.invalid-name')} ${name}`); } // Name matches quoted string "sss". @@ -64,6 +64,6 @@ export default class Fqn { return unquotedName.includes('.') ? '"' + name + '"' : unquotedName; } - throw new Error(`${i18next.t('label.invalid-name')} ${name}`); + throw new Error(`${i18n.t('label.invalid-name')} ${name}`); } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx index 90e3d8bf8c7f..07ea3e8934be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx @@ -11,7 +11,6 @@ * limitations under the License. */ -import i18next from 'i18next'; import { PLACEHOLDER_ROUTE_FQN, ROUTES } from '../constants/constants'; import { GlobalSettingOptions, @@ -19,6 +18,7 @@ import { } from '../constants/GlobalSettings.constants'; import { EntityType } from '../enums/entity.enum'; import globalSettingsClassBase from './GlobalSettingsClassBase'; +import i18n from './i18next/LocalUtil'; import { getSettingPath } from './RouterUtils'; import { getEncodedFqn } from './StringsUtils'; @@ -113,7 +113,7 @@ export const getSettingPageEntityBreadCrumb = ( return [ { - name: i18next.t('label.setting-plural'), + name: i18n.t('label.setting-plural'), url: ROUTES.SETTINGS, }, { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx index 59da299ce37c..61508f3e9d6d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx @@ -13,7 +13,6 @@ import { Select } from 'antd'; import cronstrue from 'cronstrue/i18n'; -import { t } from 'i18next'; import { isUndefined, toNumber, toString } from 'lodash'; import { RuleObject } from 'rc-field-form/es/interface'; import { @@ -36,6 +35,7 @@ import { } from '../constants/Schedular.constants'; import { CronTypes } from '../enums/Schedular.enum'; import { FieldTypes, FormItemLayout } from '../interface/FormUtils.interface'; +import i18n from './i18next/LocalUtil'; export const getScheduleOptionsFromSchedules = ( scheduleOptions: string[] @@ -303,29 +303,35 @@ export const cronValidator = async (_: RuleObject, value: string) => { // Check if the cron expression has exactly 5 fields (standard Unix cron) if (cronParts.length !== 5) { - return Promise.reject(new Error(t('message.cron-invalid-field-count'))); + return Promise.reject( + new Error(i18n.t('message.cron-invalid-field-count')) + ); } // Validate that each field follows standard Unix cron format const [minute, hour, dayOfMonth, month, dayOfWeek] = cronParts; if (!MINUTE_PATTERN.test(minute)) { - return Promise.reject(new Error(t('message.cron-invalid-minute-field'))); + return Promise.reject( + new Error(i18n.t('message.cron-invalid-minute-field')) + ); } if (!HOUR_PATTERN.test(hour)) { - return Promise.reject(new Error(t('message.cron-invalid-hour-field'))); + return Promise.reject(new Error(i18n.t('message.cron-invalid-hour-field'))); } if (!DAY_OF_MONTH_PATTERN.test(dayOfMonth)) { return Promise.reject( - new Error(t('message.cron-invalid-day-of-month-field')) + new Error(i18n.t('message.cron-invalid-day-of-month-field')) ); } if (!MONTH_PATTERN.test(month)) { - return Promise.reject(new Error(t('message.cron-invalid-month-field'))); + return Promise.reject( + new Error(i18n.t('message.cron-invalid-month-field')) + ); } if (!DAY_OF_WEEK_PATTERN.test(dayOfWeek)) { return Promise.reject( - new Error(t('message.cron-invalid-day-of-week-field')) + new Error(i18n.t('message.cron-invalid-day-of-week-field')) ); } @@ -339,14 +345,14 @@ export const cronValidator = async (_: RuleObject, value: string) => { if (isFrequencyInMinutes || isFrequencyInSeconds) { return Promise.reject( - new Error(t('message.cron-less-than-hour-message')) + new Error(i18n.t('message.cron-less-than-hour-message')) ); } return Promise.resolve(); } catch { // If cronstrue fails to parse, it's an invalid cron expression - return Promise.reject(new Error(t('message.cron-invalid-expression'))); + return Promise.reject(new Error(i18n.t('message.cron-invalid-expression'))); } }; @@ -355,7 +361,7 @@ export const getRaiseOnErrorFormField = ( ) => { return { name: 'raiseOnError', - label: t('label.raise-on-error'), + label: i18n.t('label.raise-on-error'), type: FieldTypes.SWITCH, required: false, formItemProps: { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx index a0d3f6f46f17..21161bec89bd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchUtils.tsx @@ -13,7 +13,6 @@ import { SearchOutlined } from '@ant-design/icons'; import { Button, Typography } from 'antd'; -import i18next from 'i18next'; import { Bucket } from 'Models'; import { Link } from 'react-router-dom'; import { ReactComponent as GlossaryTermIcon } from '../assets/svg/book.svg'; @@ -40,6 +39,7 @@ import { EntityType, FqnPart } from '../enums/entity.enum'; import { SearchIndex } from '../enums/search.enum'; import { SearchSourceAlias } from '../interface/search.interface'; import { getPartialNameFromTableFQN } from './CommonUtils'; +import i18n from './i18next/LocalUtil'; import { ElasticsearchQuery } from './QueryBuilderUtils'; import searchClassBase from './SearchClassBase'; import serviceUtilClassBase from './ServiceUtilClassBase'; @@ -49,119 +49,119 @@ export const getGroupLabel = (index: string) => { let GroupIcon; switch (index) { case SearchIndex.TOPIC: - label = i18next.t('label.topic-plural'); + label = i18n.t('label.topic-plural'); GroupIcon = IconTopic; break; case SearchIndex.DATABASE: - label = i18next.t('label.database-plural'); + label = i18n.t('label.database-plural'); GroupIcon = IconDatabase; break; case SearchIndex.DATABASE_SCHEMA: - label = i18next.t('label.database-schema-plural'); + label = i18n.t('label.database-schema-plural'); GroupIcon = IconDatabaseSchema; break; case SearchIndex.DASHBOARD: - label = i18next.t('label.dashboard-plural'); + label = i18n.t('label.dashboard-plural'); GroupIcon = IconDashboard; break; case SearchIndex.PIPELINE: - label = i18next.t('label.pipeline-plural'); + label = i18n.t('label.pipeline-plural'); GroupIcon = IconPipeline; break; case SearchIndex.MLMODEL: - label = i18next.t('label.ml-model-plural'); + label = i18n.t('label.ml-model-plural'); GroupIcon = IconMlModal; break; case SearchIndex.GLOSSARY_TERM: - label = i18next.t('label.glossary-term-plural'); + label = i18n.t('label.glossary-term-plural'); GroupIcon = GlossaryTermIcon; break; case SearchIndex.TAG: - label = i18next.t('label.tag-plural'); + label = i18n.t('label.tag-plural'); GroupIcon = IconTag; break; case SearchIndex.CONTAINER: - label = i18next.t('label.container-plural'); + label = i18n.t('label.container-plural'); GroupIcon = IconContainer; break; case SearchIndex.STORED_PROCEDURE: - label = i18next.t('label.stored-procedure-plural'); + label = i18n.t('label.stored-procedure-plural'); GroupIcon = IconStoredProcedure; break; case SearchIndex.DASHBOARD_DATA_MODEL: - label = i18next.t('label.data-model-plural'); + label = i18n.t('label.data-model-plural'); GroupIcon = IconDashboard; break; case SearchIndex.SEARCH_INDEX: - label = i18next.t('label.search-index-plural'); + label = i18n.t('label.search-index-plural'); GroupIcon = SearchOutlined; break; case SearchIndex.DATA_PRODUCT: - label = i18next.t('label.data-product-plural'); + label = i18n.t('label.data-product-plural'); GroupIcon = DataProductIcon; break; case SearchIndex.CHART: - label = i18next.t('label.chart-plural'); + label = i18n.t('label.chart-plural'); GroupIcon = IconChart; break; case SearchIndex.API_COLLECTION: - label = i18next.t('label.api-collection-plural'); + label = i18n.t('label.api-collection-plural'); GroupIcon = IconApiCollection; break; case SearchIndex.API_ENDPOINT: - label = i18next.t('label.api-endpoint-plural'); + label = i18n.t('label.api-endpoint-plural'); GroupIcon = IconApiEndpoint; break; case SearchIndex.METRIC: - label = i18next.t('label.metric-plural'); + label = i18n.t('label.metric-plural'); GroupIcon = MetricIcon; break; case SearchIndex.DIRECTORY: - label = i18next.t('label.directory-plural'); + label = i18n.t('label.directory-plural'); GroupIcon = MetricIcon; break; case SearchIndex.FILE: - label = i18next.t('label.file-plural'); + label = i18n.t('label.file-plural'); GroupIcon = MetricIcon; break; case SearchIndex.SPREADSHEET: - label = i18next.t('label.spreadsheet-plural'); + label = i18n.t('label.spreadsheet-plural'); GroupIcon = MetricIcon; break; case SearchIndex.WORKSHEET: - label = i18next.t('label.worksheet-plural'); + label = i18n.t('label.worksheet-plural'); GroupIcon = MetricIcon; break; case SearchIndex.COLUMN: - label = i18next.t('label.column-plural'); + label = i18n.t('label.column-plural'); GroupIcon = ColumnIcon; break; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx index c30251434b49..1682ef29218b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx @@ -12,9 +12,8 @@ */ import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; -import { Space, Tag as AntdTag, Tooltip, Typography } from 'antd'; +import { Tag as AntdTag, Space, Tooltip, Typography } from 'antd'; import { AxiosError } from 'axios'; -import i18next from 'i18next'; import { isString, omit } from 'lodash'; import { EntityTags } from 'Models'; import type { CustomTagProps } from 'rc-select/lib/BaseSelect'; @@ -52,6 +51,7 @@ import { } from '../rest/tagAPI'; import { getEntityName } from './EntityUtils'; import { getQueryFilterToIncludeApprovedTerm } from './GlossaryUtils'; +import i18n from './i18next/LocalUtil'; import { checkPermissionEntityResource } from './PermissionsUtils'; import { getClassificationTagPath, @@ -228,11 +228,11 @@ export const getUsageCountLink = (tagFQN: string) => { export const getTagPlaceholder = (isGlossaryType: boolean): string => isGlossaryType - ? i18next.t('label.search-entity', { - entity: i18next.t('label.glossary-term-plural'), + ? i18n.t('label.search-entity', { + entity: i18n.t('label.glossary-term-plural'), }) - : i18next.t('label.search-entity', { - entity: i18next.t('label.tag-plural'), + : i18n.t('label.search-entity', { + entity: i18n.t('label.tag-plural'), }); export const tagRender = (customTagProps: CustomTagProps) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx index 246e025052f1..60827895769c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx @@ -11,10 +11,9 @@ * limitations under the License. */ -import i18next from 'i18next'; import { EntityTabs } from '../enums/entity.enum'; import { CurrentTourPageType } from '../enums/tour.enum'; -import { Transi18next } from './i18next/LocalUtil'; +import i18n, { Transi18next } from './i18next/LocalUtil'; interface ArgObject { searchTerm: string; @@ -36,7 +35,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-activity-feed" renderElement={} values={{ - text: i18next.t('label.activity-feed-plural'), + text: i18n.t('label.activity-feed-plural'), }} />

@@ -51,7 +50,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-search-for-matching-dataset" renderElement={} values={{ - text: i18next.t('label.search'), + text: i18n.t('label.search'), }} />

@@ -69,7 +68,7 @@ export const getTourSteps = ({ renderElement={} values={{ text: searchTerm, - enterText: i18next.t('label.enter'), + enterText: i18n.t('label.enter'), }} />

@@ -92,7 +91,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-explore-summary-asset" renderElement={} values={{ - text: i18next.t('label.explore'), + text: i18n.t('label.explore'), }} />

@@ -125,7 +124,7 @@ export const getTourSteps = ({ i18nKey="message.tour-high-level-assets-information-step" renderElement={} values={{ - text: i18next.t('label.schema'), + text: i18n.t('label.schema'), }} />

@@ -140,7 +139,7 @@ export const getTourSteps = ({ i18nKey="message.tour-owner-step" renderElement={} values={{ - text: i18next.t('label.schema'), + text: i18n.t('label.schema'), }} />

@@ -155,7 +154,7 @@ export const getTourSteps = ({ i18nKey="message.tour-follow-step" renderElement={} values={{ - text: i18next.t('label.schema'), + text: i18n.t('label.schema'), }} />

@@ -170,7 +169,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-get-to-know-table-schema" renderElement={} values={{ - text: i18next.t('label.schema'), + text: i18n.t('label.schema'), }} />

@@ -189,7 +188,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-click-on-entity-tab" renderElement={} values={{ - text: i18next.t('label.sample-data'), + text: i18n.t('label.sample-data'), }} />

@@ -206,7 +205,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-look-at-sample-data" renderElement={} values={{ - text: i18next.t('label.sample-data'), + text: i18n.t('label.sample-data'), }} />

@@ -227,7 +226,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-click-on-entity-tab" renderElement={} values={{ - text: i18next.t('label.data-observability'), + text: i18n.t('label.data-observability'), }} />

@@ -241,8 +240,8 @@ export const getTourSteps = ({ i18nKey="message.tour-step-discover-data-assets-with-data-profile" renderElement={} values={{ - text: i18next.t('label.data-entity', { - entity: i18next.t('label.profiler'), + text: i18n.t('label.data-entity', { + entity: i18n.t('label.profiler'), }), }} /> @@ -265,7 +264,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-click-on-entity-tab" renderElement={} values={{ - text: i18next.t('label.lineage'), + text: i18n.t('label.lineage'), }} />

@@ -279,7 +278,7 @@ export const getTourSteps = ({ i18nKey="message.tour-step-trace-path-across-tables" renderElement={} values={{ - text: i18next.t('label.lineage'), + text: i18n.t('label.lineage'), }} />

From 368ab82ec4b4cad5cd56267c7f62f392fcf1d0a4 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:28:54 +0530 Subject: [PATCH 32/47] fix entityUtils imports --- .../resources/ui/src/utils/EntityUtils.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx index bf2cedadd1c0..069ed1b32baf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -12,7 +12,6 @@ */ import { Popover, Space, Typography } from 'antd'; -import i18next, { t } from 'i18next'; import { isEmpty, isUndefined, lowerCase, startCase } from 'lodash'; import { EntityDetailUnion } from 'Models'; import { Fragment } from 'react'; @@ -99,6 +98,7 @@ import { import { getDataInsightPathWithFqn } from './DataInsightUtils'; import EntityLink from './EntityLink'; import Fqn from './Fqn'; +import i18n from './i18next/LocalUtil'; import { getApplicationDetailsPath, getBotsPagePath, @@ -128,6 +128,8 @@ import { getEncodedFqn } from './StringsUtils'; import { getDataTypeString, getTagsWithoutTier } from './TableUtils'; import { getTableTags } from './TagsUtils'; +const { t } = i18n; + export enum DRAWER_NAVIGATION_OPTIONS { explore = 'Explore', lineage = 'Lineage', @@ -745,7 +747,7 @@ export function getBreadcrumbForEntityWithParent< export const getBreadcrumbForTestCase = (entity: TestCase): TitleLink[] => [ { - name: i18next.t('label.data-quality'), + name: i18n.t('label.data-quality'), url: `${ROUTES.DATA_QUALITY}/${DataQualityPageTabs.TEST_CASES}`, }, { @@ -758,7 +760,7 @@ export const getBreadcrumbForTestCase = (entity: TestCase): TitleLink[] => [ state: { breadcrumbData: [ { - name: i18next.t('label.data-quality'), + name: i18n.t('label.data-quality'), url: `${ROUTES.DATA_QUALITY}/${DataQualityPageTabs.TEST_CASES}`, }, ], @@ -797,7 +799,7 @@ export const getBreadcrumbForTestSuite = (entity: TestSuite) => { export const getBreadCrumbForKpi = (entity: Kpi) => { return [ { - name: i18next.t('label.kpi-uppercase'), + name: i18n.t('label.kpi-uppercase'), url: getDataInsightPathWithFqn(DataInsightTabs.KPIS), }, { @@ -1125,7 +1127,7 @@ export const getEntityBreadcrumbs = ( case EntityType.DOMAIN: return [ { - name: i18next.t('label.domain-plural'), + name: i18n.t('label.domain-plural'), url: getDomainPath(), }, ]; @@ -1202,7 +1204,7 @@ export const getEntityBreadcrumbs = ( case EntityType.APPLICATION: { return [ { - name: i18next.t('label.application-plural'), + name: i18n.t('label.application-plural'), url: getSettingPath(GlobalSettingsMenuCategory.APPLICATIONS), }, { @@ -1215,7 +1217,7 @@ export const getEntityBreadcrumbs = ( case EntityType.PERSONA: { return [ { - name: i18next.t('label.persona-plural'), + name: i18n.t('label.persona-plural'), url: getSettingPath( GlobalSettingsMenuCategory.MEMBERS, GlobalSettingOptions.PERSONA @@ -1231,7 +1233,7 @@ export const getEntityBreadcrumbs = ( case EntityType.ROLE: { return [ { - name: i18next.t('label.role-plural'), + name: i18n.t('label.role-plural'), url: getSettingPath( GlobalSettingsMenuCategory.ACCESS, GlobalSettingOptions.ROLES @@ -1247,7 +1249,7 @@ export const getEntityBreadcrumbs = ( case EntityType.POLICY: { return [ { - name: i18next.t('label.policy-plural'), + name: i18n.t('label.policy-plural'), url: getSettingPath( GlobalSettingsMenuCategory.ACCESS, GlobalSettingOptions.POLICIES From 0b8c9576360e5dbf91fbaf5c00e06a6ecf1c8db2 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:38:36 +0530 Subject: [PATCH 33/47] fix test and checkstyle --- .../src/main/resources/ui/src/utils/EntityUtils.test.tsx | 4 ++-- openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx index 4304ee07d104..99f91c8f4432 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.test.tsx @@ -577,10 +577,10 @@ describe('EntityUtils unit tests', () => { expect(result).toBe('Engineering'); }); - it('should return translated "All Domains" when activeDomain is DEFAULT_DOMAIN_VALUE', () => { + it('should return translated "label.all-domain-plural" when activeDomain is DEFAULT_DOMAIN_VALUE', () => { const result = getDomainDisplayName(undefined, DEFAULT_DOMAIN_VALUE); - expect(result).toBe('All Domains'); + expect(result).toBe('label.all-domain-plural'); }); it('should return custom domain name when activeDomain is a custom value', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx index 1682ef29218b..4d98d9e34320 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.tsx @@ -12,7 +12,7 @@ */ import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; -import { Tag as AntdTag, Space, Tooltip, Typography } from 'antd'; +import { Space, Tag as AntdTag, Tooltip, Typography } from 'antd'; import { AxiosError } from 'axios'; import { isString, omit } from 'lodash'; import { EntityTags } from 'Models'; From 9a181312e6e4c733e3afd05ad602846c22dde746 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 15 Apr 2026 23:43:16 +0530 Subject: [PATCH 34/47] fix failing playwright --- .../ui/playwright/e2e/Pages/Login.spec.ts | 2 ++ .../src/utils/i18next/LocalUtilClassBase.ts | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts index ab15a0d61478..71e63f7e56d2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts @@ -150,6 +150,8 @@ test.describe('Login flow should work properly', () => { }); test('Refresh should work', async ({ browser }) => { + test.slow(); + const browserContext = await browser.newContext(); const { apiContext, afterAction } = await performAdminLogin(browser); const page1 = await browserContext.newPage(), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts index ba30b5fa6048..b52c096e6897 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -16,6 +16,16 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; import { getInitOptions } from './i18nextUtil'; +i18next + .use(LanguageDetector) + .use(initReactI18next) + .init(getInitOptions()) + .then(async () => { + if (i18next.language !== i18next.resolvedLanguage) { + await i18next.changeLanguage(i18next.language); + } + }); + class LocalUtilClassBase { private static _instance: LocalUtilClassBase; loadedLocales: Set = new Set(['en-US']); @@ -52,18 +62,6 @@ class LocalUtilClassBase { i18next.on('languageChanged', async (lng) => { await this.loadLocales(lng); }); - - setTimeout(() => { - i18next - .use(LanguageDetector) - .use(initReactI18next) - .init(getInitOptions()) - .then(async () => { - if (i18next.language !== i18next.resolvedLanguage) { - await i18next.changeLanguage(i18next.language); - } - }); - }, 0); } async loadLocales(locale: string): Promise { From 11b2b242311a4ed330bb05c43b07efd62b13cbff Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:00:53 +0530 Subject: [PATCH 35/47] fix plugin issue --- .../ui/src/components/AppRouter/AppRouter.tsx | 53 +++------------- .../AppRouter/AuthenticatedRoutes.tsx | 63 +++++++++++++++++++ .../resources/ui/src/utils/StringsUtils.ts | 10 +-- 3 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx index 1cf0f8114eec..8fc8921459a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.tsx @@ -19,16 +19,18 @@ import { APP_ROUTER_ROUTES } from '../../constants/router.constants'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase'; import Loader from '../common/Loader/Loader'; -import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; -import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; import withSuspenseFallback from './withSuspenseFallback'; const AuthenticatedApp = withSuspenseFallback( lazy(() => import('./AuthenticatedApp')) ); -const AppContainer = withSuspenseFallback( - lazy(() => import('../AppContainer/AppContainer')) +const AuthenticatedRoutes = withSuspenseFallback( + lazy(() => + import('./AuthenticatedRoutes').then((m) => ({ + default: m.AuthenticatedRoutes, + })) + ) ); // Lazy-load infrequently-visited unauthenticated pages @@ -74,8 +76,6 @@ const AppRouter = () => { })) ); - const { plugins = [] } = useApplicationsProvider() ?? {}; - /** * isApplicationLoading is true when the application is loading in AuthProvider * and is false when the application is loaded. @@ -91,46 +91,7 @@ const AppRouter = () => { if (isAuthenticated) { return ( - - } - path={APP_ROUTER_ROUTES.NOT_FOUND} - /> - } path={APP_ROUTER_ROUTES.LOGOUT} /> - } - path={APP_ROUTER_ROUTES.UNAUTHORISED} - /> - - ) : ( - - ) - } - path={APP_ROUTER_ROUTES.SIGNUP} - /> - } - path={APP_ROUTER_ROUTES.AUTH_CALLBACK} - /> - - {/* Render APP position plugin routes (they handle their own layouts) */} - {plugins?.flatMap((plugin) => { - const routes = plugin.getRoutes?.() || []; - // Filter routes with APP position - const appRoutes = routes.filter( - (route) => route.position === RoutePosition.APP - ); - - return appRoutes.map((route, idx) => ( - - )); - })} - - } path="*" /> - + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx new file mode 100644 index 000000000000..1196732c8785 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx @@ -0,0 +1,63 @@ +import { isEmpty } from 'lodash'; +import { Navigate, Route, Routes } from 'react-router-dom'; +import { useShallow } from 'zustand/react/shallow'; +import { APP_ROUTER_ROUTES } from '../../constants/router.constants'; +import { useApplicationStore } from '../../hooks/useApplicationStore'; +import AccessNotAllowedPage from '../../pages/AccessNotAllowedPage/AccessNotAllowedPage'; +import { LogoutPage } from '../../pages/LogoutPage/LogoutPage'; +import PageNotFound from '../../pages/PageNotFound/PageNotFound'; +import SamlCallback from '../../pages/SamlCallback'; +import SignUpPage from '../../pages/SignUp/SignUpPage'; +import AppContainer from '../AppContainer/AppContainer'; +import { useApplicationsProvider } from '../Settings/Applications/ApplicationsProvider/ApplicationsProvider'; +import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; + +export const AuthenticatedRoutes = () => { + const { currentUser } = useApplicationStore( + useShallow((state) => ({ + currentUser: state.currentUser, + })) + ); + + const { plugins = [] } = useApplicationsProvider() ?? {}; + + return ( + + } path={APP_ROUTER_ROUTES.NOT_FOUND} /> + } path={APP_ROUTER_ROUTES.LOGOUT} /> + } + path={APP_ROUTER_ROUTES.UNAUTHORISED} + /> + + ) : ( + + ) + } + path={APP_ROUTER_ROUTES.SIGNUP} + /> + } + path={APP_ROUTER_ROUTES.AUTH_CALLBACK} + /> + + {/* Render APP position plugin routes (they handle their own layouts) */} + {plugins?.flatMap((plugin) => { + const routes = plugin.getRoutes?.() || []; + // Filter routes with APP position + const appRoutes = routes.filter( + (route) => route.position === RoutePosition.APP + ); + + return appRoutes.map((route, idx) => ( + + )); + })} + + } path="*" /> + + ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts index 3771251103b9..3002ea1ab0b6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StringsUtils.ts @@ -70,10 +70,7 @@ export const getJSONFromString = (data: string): string | null => { try { // Format string if possible and return valid JSON return JSON.parse(data); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Invalid JSON string:', error); - + } catch (_error) { return null; } }; @@ -248,10 +245,7 @@ export const formatJsonString = (jsonString: string, indent = '') => { } return formattedJson; - } catch (error) { - // eslint-disable-next-line no-console - console.error('Invalid JSON string:', error); - + } catch (_error) { // Return the original JSON string if parsing fails return jsonString; } From 1518df397c9bcef7ce32b1b49cf5f103dbc27946 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:10:50 +0530 Subject: [PATCH 36/47] address comments --- .../components/AppRouter/AuthenticatedRoutes.tsx | 13 +++++++++++++ .../src/main/resources/ui/src/utils/TableUtils.tsx | 10 ++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx index 1196732c8785..748c4ca2e108 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx @@ -1,3 +1,16 @@ +/* + * Copyright 2022 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 { isEmpty } from 'lodash'; import { Navigate, Route, Routes } from 'react-router-dom'; import { useShallow } from 'zustand/react/shallow'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 867798f402bb..09021e6a38a6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -977,12 +977,10 @@ export const getTableDetailPageBaseTabs = ({ ), key: EntityTabs.PROFILER, children: ( - }> - - + ), }, { From c685ef9e2179502792ecc2a3b5bdaa8462e40026 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:11:02 +0530 Subject: [PATCH 37/47] address comments --- openmetadata-ui/src/main/resources/ui/src/setupTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 787f5fbaccef..b7ff1e84e5e4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -216,7 +216,7 @@ jest.mock('./utils/i18next/LocalUtil', () => { .mockImplementation(({ i18nKey, renderElement, values }) => { const valueArr = Object.values(values ?? {}); - return React.createElement('div', { dataTestid: i18nKey }, [ + return React.createElement('div', { 'data-testid': i18nKey }, [ i18nKey, renderElement, valueArr, From 2747ce8be801036fc373491ade9a08b007a3cdda Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:43:32 +0530 Subject: [PATCH 38/47] fix unit tests --- .../src/main/resources/ui/src/components/NavBar/NavBar.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx index 0333de2c7150..8066e4d6cacb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx @@ -216,7 +216,7 @@ jest.mock('./PopupAlertClassBase', () => ({ jest.mock('../../utils/i18next/i18nextUtil', () => ({ languageSelectOptions: [], - loadLocale: jest.fn(), + getInitOptions: jest.fn().mockImplementation(() => ({})), })); describe('Test NavBar Component', () => { From b11ef67c1b42e319e16f1528510f416a1b6595b6 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:05:05 +0530 Subject: [PATCH 39/47] address comment --- .../MarketplaceNavBar.component.tsx | 2 +- .../ui/src/components/NavBar/NavBar.tsx | 2 +- .../ui/src/utils/i18next/LocalUtil.tsx | 19 ++++++++++++++----- .../src/utils/i18next/LocalUtilClassBase.ts | 15 +-------------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 4d03e544ffc6..8d6c103601ca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -365,7 +365,7 @@ const MarketplaceNavBar = () => { const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { await localUtilClassBase.loadLocales(key); - i18n.changeLanguage(key); + await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index e790dc91b144..0bed65667459 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -440,7 +440,7 @@ const NavBar = () => { const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { await localUtilClassBase.loadLocales(key); - i18n.changeLanguage(key); + await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index 9e1559f46223..0e64f27bbde9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -11,12 +11,21 @@ * limitations under the License. */ -import { t as i18nextT } from 'i18next'; +import i18next, { t as i18nextT } from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; import { ReactNode } from 'react'; -import { Trans } from 'react-i18next'; -import localUtilClassBase from './LocalUtilClassBase'; +import { initReactI18next, Trans } from 'react-i18next'; +import { getInitOptions } from './i18nextUtil'; -const i18n = localUtilClassBase.getI18nInstance(); +i18next + .use(LanguageDetector) + .use(initReactI18next) + .init(getInitOptions()) + .then(async () => { + if (i18next.language !== i18next.resolvedLanguage) { + await i18next.changeLanguage(i18next.language); + } + }); export const t = (key: string, options?: Record): string => { const translation = i18nextT(key, options); @@ -70,4 +79,4 @@ export const Transi18next = ({ ); -export default i18n; +export default i18next; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts index b52c096e6897..010ceb8c97cf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -11,20 +11,7 @@ * limitations under the License. */ -import i18next from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import { initReactI18next } from 'react-i18next'; -import { getInitOptions } from './i18nextUtil'; - -i18next - .use(LanguageDetector) - .use(initReactI18next) - .init(getInitOptions()) - .then(async () => { - if (i18next.language !== i18next.resolvedLanguage) { - await i18next.changeLanguage(i18next.language); - } - }); +import i18next from './LocalUtil'; class LocalUtilClassBase { private static _instance: LocalUtilClassBase; From f05a5609ea7399e70d10b368fffe495c8fa385e3 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 22:39:20 +0530 Subject: [PATCH 40/47] fix localization issue with playwright --- .../ui/playwright/e2e/Features/RTL.spec.ts | 4 +- .../MarketplaceNavBar.component.tsx | 4 +- .../ui/src/components/NavBar/NavBar.tsx | 4 +- .../ui/src/utils/i18next/LocalUtil.tsx | 5 ++ .../src/utils/i18next/LocalUtilClassBase.ts | 71 ++++++++----------- 5 files changed, 40 insertions(+), 48 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts index 19aff83d36d3..a30820566432 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { expect, Page, test as base } from '@playwright/test'; +import { test as base, expect, Page } from '@playwright/test'; import { toLower } from 'lodash'; import { EntityDataClass } from '../../support/entity/EntityDataClass'; import { UserClass } from '../../support/user/UserClass'; @@ -20,6 +20,7 @@ import { clickOutside, redirectToHomePage } from '../../utils/common'; import { followEntity, validateFollowedEntityToWidget, + waitForAllLoadersToDisappear, } from '../../utils/entity'; const user = new UserClass(); @@ -52,6 +53,7 @@ test.describe('Verify RTL Layout for landing page', () => { .locator('.ant-dropdown:visible [data-menu-id*="-he-HE"]') .click(); await page.waitForLoadState('domcontentloaded'); + await waitForAllLoadersToDisappear(page); }); test('Verify DataAssets widget functionality', async ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 8d6c103601ca..7ffdd98d69a8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -58,7 +58,7 @@ import { } from '../../../utils/FeedUtils'; import { languageSelectOptions } from '../../../utils/i18next/i18nextUtil'; import i18n from '../../../utils/i18next/LocalUtil'; -import localUtilClassBase from '../../../utils/i18next/LocalUtilClassBase'; +import { LocalUtilClassBase } from '../../../utils/i18next/LocalUtilClassBase'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; @@ -364,7 +364,7 @@ const MarketplaceNavBar = () => { }, []); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await localUtilClassBase.loadLocales(key); + await LocalUtilClassBase.loadLocales(key); await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 0bed65667459..45ef64da65ca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -73,7 +73,7 @@ import { } from '../../utils/FeedUtils'; import { languageSelectOptions } from '../../utils/i18next/i18nextUtil'; import i18n from '../../utils/i18next/LocalUtil'; -import localUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; +import { LocalUtilClassBase } from '../../utils/i18next/LocalUtilClassBase'; import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import { getSettingPath } from '../../utils/RouterUtils'; @@ -439,7 +439,7 @@ const NavBar = () => { ); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await localUtilClassBase.loadLocales(key); + await LocalUtilClassBase.loadLocales(key); await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index 0e64f27bbde9..9dbb463dbbf4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -16,6 +16,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import { ReactNode } from 'react'; import { initReactI18next, Trans } from 'react-i18next'; import { getInitOptions } from './i18nextUtil'; +import { LocalUtilClassBase } from './LocalUtilClassBase'; i18next .use(LanguageDetector) @@ -27,6 +28,10 @@ i18next } }); +i18next.on('languageChanged', async (lng) => { + await LocalUtilClassBase.loadLocales(lng); +}); + export const t = (key: string, options?: Record): string => { const translation = i18nextT(key, options); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts index 010ceb8c97cf..1242a163fac9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -13,50 +13,40 @@ import i18next from './LocalUtil'; +const LOCALE_LOADERS: Record< + string, + () => Promise<{ default: Record }> +> = { + 'en-US': () => import('../../locale/languages/en-us.json'), + 'ko-KR': () => import('../../locale/languages/ko-kr.json'), + 'fr-FR': () => import('../../locale/languages/fr-fr.json'), + 'zh-CN': () => import('../../locale/languages/zh-cn.json'), + 'zh-TW': () => import('../../locale/languages/zh-tw.json'), + 'ja-JP': () => import('../../locale/languages/ja-jp.json'), + 'pt-BR': () => import('../../locale/languages/pt-br.json'), + 'pt-PT': () => import('../../locale/languages/pt-pt.json'), + 'es-ES': () => import('../../locale/languages/es-es.json'), + 'gl-ES': () => import('../../locale/languages/gl-es.json'), + 'ru-RU': () => import('../../locale/languages/ru-ru.json'), + 'de-DE': () => import('../../locale/languages/de-de.json'), + 'he-HE': () => import('../../locale/languages/he-he.json'), + 'nl-NL': () => import('../../locale/languages/nl-nl.json'), + 'pr-PR': () => import('../../locale/languages/pr-pr.json'), + 'th-TH': () => import('../../locale/languages/th-th.json'), + 'mr-IN': () => import('../../locale/languages/mr-in.json'), + 'tr-TR': () => import('../../locale/languages/tr-tr.json'), + 'ar-SA': () => import('../../locale/languages/ar-sa.json'), +}; + class LocalUtilClassBase { private static _instance: LocalUtilClassBase; - loadedLocales: Set = new Set(['en-US']); - - LOCALE_LOADERS: Record< - string, - () => Promise<{ default: Record }> - >; - constructor() { - this.loadedLocales = new Set(['en-US']); - this.LOCALE_LOADERS = { - 'en-US': () => import('../../locale/languages/en-us.json'), - 'ko-KR': () => import('../../locale/languages/ko-kr.json'), - 'fr-FR': () => import('../../locale/languages/fr-fr.json'), - 'zh-CN': () => import('../../locale/languages/zh-cn.json'), - 'zh-TW': () => import('../../locale/languages/zh-tw.json'), - 'ja-JP': () => import('../../locale/languages/ja-jp.json'), - 'pt-BR': () => import('../../locale/languages/pt-br.json'), - 'pt-PT': () => import('../../locale/languages/pt-pt.json'), - 'es-ES': () => import('../../locale/languages/es-es.json'), - 'gl-ES': () => import('../../locale/languages/gl-es.json'), - 'ru-RU': () => import('../../locale/languages/ru-ru.json'), - 'de-DE': () => import('../../locale/languages/de-de.json'), - 'he-HE': () => import('../../locale/languages/he-he.json'), - 'nl-NL': () => import('../../locale/languages/nl-nl.json'), - 'pr-PR': () => import('../../locale/languages/pr-pr.json'), - 'th-TH': () => import('../../locale/languages/th-th.json'), - 'mr-IN': () => import('../../locale/languages/mr-in.json'), - 'tr-TR': () => import('../../locale/languages/tr-tr.json'), - 'ar-SA': () => import('../../locale/languages/ar-sa.json'), - }; - - i18next.on('languageChanged', async (lng) => { - await this.loadLocales(lng); - }); - } - - async loadLocales(locale: string): Promise { - if (this.loadedLocales.has(locale)) { + static async loadLocales(locale: string): Promise { + if (i18next.hasResourceBundle(locale, 'translation')) { return; } - const loader = this.LOCALE_LOADERS[locale]; + const loader = LOCALE_LOADERS[locale]; if (!loader) { return; } @@ -68,11 +58,6 @@ class LocalUtilClassBase { translations.default, true ); - this.loadedLocales.add(locale); - } - - getI18nInstance() { - return i18next; } static getInstance(): LocalUtilClassBase { From 6a551ff98f0bb368070602fe880bcec87689f462 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:22:49 +0530 Subject: [PATCH 41/47] fix login.spec fix icon issue --- .../ui/playwright/e2e/Pages/Login.spec.ts | 22 +++++++---- .../ui/src/utils/ServiceIconUtils.ts | 2 +- .../ui/src/utils/ServiceUtilClassBase.ts | 39 ++++++++++++++++++- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts index 71e63f7e56d2..6213769f8fce 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts @@ -15,7 +15,12 @@ import { JWT_EXPIRY_TIME_MAP, LOGIN_ERROR_MESSAGE } from '../../constant/login'; import { AdminClass } from '../../support/user/AdminClass'; import { UserClass } from '../../support/user/UserClass'; import { performAdminLogin } from '../../utils/admin'; -import { clickOutside, redirectToHomePage } from '../../utils/common'; +import { + clickOutside, + getDefaultAdminAPIContext, + redirectToHomePage, +} from '../../utils/common'; +import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { updateJWTTokenExpiryTime } from '../../utils/login'; import { visitUserProfilePage } from '../../utils/user'; @@ -149,13 +154,14 @@ test.describe('Login flow should work properly', () => { await page.locator('[data-testid="go-back-button"]').click(); }); - test('Refresh should work', async ({ browser }) => { + test('Refresh should work', async ({ page: page1, browser }) => { test.slow(); - const browserContext = await browser.newContext(); - const { apiContext, afterAction } = await performAdminLogin(browser); - const page1 = await browserContext.newPage(), - page2 = await browserContext.newPage(); + const { apiContext, afterAction } = await getDefaultAdminAPIContext( + browser + ); + const context = page1.context(); + const page2 = await context.newPage(); const testUser = new UserClass(); await testUser.create(apiContext); @@ -165,7 +171,10 @@ test.describe('Login flow should work properly', () => { await testUser.login(page1); await redirectToHomePage(page1); + await waitForAllLoadersToDisappear(page1); await redirectToHomePage(page2); + await waitForAllLoadersToDisappear(page2); + await page2.reload(); // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for token refresh timer to fire await page1.waitForTimeout(3 * 60 * 1000); @@ -180,7 +189,6 @@ test.describe('Login flow should work properly', () => { await page2.close(); }); - await browserContext.close(); await afterAction(); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index a12d9158a858..af46af71f0e0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -252,7 +252,7 @@ const SERVICE_ICON_LOADERS: Record = { export const getServiceIcon = (iconKey: string): string => { const normalizedKey = iconKey.toLowerCase().replaceAll(/[_-]/g, ''); - const icon = SERVICE_ICON_LOADERS[normalizedKey] ?? defaultservice; + const icon = SERVICE_ICON_LOADERS[normalizedKey]; return icon; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index 6b35aaa61760..22228d14234f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -317,8 +317,45 @@ class ServiceUtilClassBase { return EntityType.TABLE; } + private getDefaultLogoForServiceType(type: string): string { + const serviceTypes = this.getSupportedServiceFromList(); + + if (serviceTypes.messagingServices.includes(type)) { + return getServiceIcon('topicdefault'); + } + if (serviceTypes.dashboardServices.includes(type)) { + return getServiceIcon('dashboarddefault'); + } + if (serviceTypes.pipelineServices.includes(type)) { + return getServiceIcon('pipelinedefault'); + } + if (serviceTypes.databaseServices.includes(type)) { + return getServiceIcon('databasedefault'); + } + if (serviceTypes.mlmodelServices.includes(type)) { + return getServiceIcon('mlmodeldefault'); + } + if (serviceTypes.storageServices.includes(type)) { + return getServiceIcon('storagedefault'); + } + if (serviceTypes.searchServices.includes(type)) { + return getServiceIcon('searchdefault'); + } + if (serviceTypes.securityServices.includes(type)) { + return getServiceIcon('securitydefault'); + } + if (serviceTypes.driveServices.includes(type)) { + return getServiceIcon('drivedefault'); + } + if (serviceTypes.apiServices.includes(type)) { + return getServiceIcon('restservice'); + } + + return getServiceIcon('defaultservice'); + } + public getServiceLogo(type: string) { - return getServiceIcon(type); + return getServiceIcon(type) ?? this.getDefaultLogoForServiceType(type); } public getServiceTypeLogo(searchSource: { From d66719b0879b718fcd7a922d11b30fbae9655db4 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:58:24 +0530 Subject: [PATCH 42/47] fix login spec failure --- .../src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts index 6213769f8fce..9351122c9e44 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts @@ -165,6 +165,7 @@ test.describe('Login flow should work properly', () => { const testUser = new UserClass(); await testUser.create(apiContext); + await testUser.setAdminRole(apiContext); await test.step('Login and wait for refresh call is made', async () => { // User login From 92cfc32181f8d933ddb336653fe84dc543bef973 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:12:06 +0530 Subject: [PATCH 43/47] fix login spec --- .../ui/playwright/e2e/Features/RTL.spec.ts | 2 +- .../ui/playwright/e2e/Pages/Login.spec.ts | 24 +++++-- .../ui/src/utils/ServiceUtilClassBase.ts | 66 ++++++++++++------- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts index a30820566432..46c36f33e345 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RTL.spec.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { test as base, expect, Page } from '@playwright/test'; +import { expect, Page, test as base } from '@playwright/test'; import { toLower } from 'lodash'; import { EntityDataClass } from '../../support/entity/EntityDataClass'; import { UserClass } from '../../support/user/UserClass'; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts index 9351122c9e44..36c2cdd8e7dc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Login.spec.ts @@ -19,10 +19,10 @@ import { clickOutside, getDefaultAdminAPIContext, redirectToHomePage, + visitOwnProfilePage, } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { updateJWTTokenExpiryTime } from '../../utils/login'; -import { visitUserProfilePage } from '../../utils/user'; const user = new UserClass(); const CREDENTIALS = user.data; @@ -180,11 +180,25 @@ test.describe('Login flow should work properly', () => { // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for token refresh timer to fire await page1.waitForTimeout(3 * 60 * 1000); - await redirectToHomePage(page1); + await page1.bringToFront(); + await visitOwnProfilePage(page1); + await waitForAllLoadersToDisappear(page1); + await expect(page1.getByTestId('user-display-name')).toHaveText( + testUser.responseData.displayName ?? testUser.responseData.name + ); - await visitUserProfilePage(page1, testUser.responseData.name); - await redirectToHomePage(page2); - await visitUserProfilePage(page2, testUser.responseData.name); + await page2.bringToFront(); + await page2.evaluate(() => { + document.dispatchEvent( + new Event('visibilitychange', { bubbles: true }) + ); + }); + + await visitOwnProfilePage(page2); + await waitForAllLoadersToDisappear(page2); + await expect(page2.getByTestId('user-display-name')).toHaveText( + testUser.responseData.displayName ?? testUser.responseData.name + ); await page1.close(); await page2.close(); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index 22228d14234f..0ba38c95fbbb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -235,37 +235,59 @@ class ServiceUtilClassBase { return { databaseServices: this.filterUnsupportedServiceType( Object.values(DatabaseServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), messagingServices: this.filterUnsupportedServiceType( Object.values(MessagingServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), dashboardServices: this.filterUnsupportedServiceType( Object.values(DashboardServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), pipelineServices: this.filterUnsupportedServiceType( Object.values(PipelineServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), mlmodelServices: this.filterUnsupportedServiceType( Object.values(MlModelServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), metadataServices: this.filterUnsupportedServiceType( Object.values(MetadataServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), storageServices: this.filterUnsupportedServiceType( Object.values(StorageServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), searchServices: this.filterUnsupportedServiceType( Object.values(SearchServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), apiServices: this.filterUnsupportedServiceType( Object.values(APIServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), driveServices: this.filterUnsupportedServiceType( Object.values(DriveServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), securityServices: this.filterUnsupportedServiceType( Object.values(SecurityServiceType) as string[] - ).sort(customServiceComparator), + ) + .sort(customServiceComparator) + .map((s) => s.toLowerCase()), }; } @@ -319,35 +341,35 @@ class ServiceUtilClassBase { private getDefaultLogoForServiceType(type: string): string { const serviceTypes = this.getSupportedServiceFromList(); - - if (serviceTypes.messagingServices.includes(type)) { + const typeLowerCase = type.toLowerCase(); + if (serviceTypes.messagingServices.includes(typeLowerCase)) { return getServiceIcon('topicdefault'); } - if (serviceTypes.dashboardServices.includes(type)) { + if (serviceTypes.dashboardServices.includes(typeLowerCase)) { return getServiceIcon('dashboarddefault'); } - if (serviceTypes.pipelineServices.includes(type)) { + if (serviceTypes.pipelineServices.includes(typeLowerCase)) { return getServiceIcon('pipelinedefault'); } - if (serviceTypes.databaseServices.includes(type)) { + if (serviceTypes.databaseServices.includes(typeLowerCase)) { return getServiceIcon('databasedefault'); } - if (serviceTypes.mlmodelServices.includes(type)) { + if (serviceTypes.mlmodelServices.includes(typeLowerCase)) { return getServiceIcon('mlmodeldefault'); } - if (serviceTypes.storageServices.includes(type)) { + if (serviceTypes.storageServices.includes(typeLowerCase)) { return getServiceIcon('storagedefault'); } - if (serviceTypes.searchServices.includes(type)) { + if (serviceTypes.searchServices.includes(typeLowerCase)) { return getServiceIcon('searchdefault'); } - if (serviceTypes.securityServices.includes(type)) { + if (serviceTypes.securityServices.includes(typeLowerCase)) { return getServiceIcon('securitydefault'); } - if (serviceTypes.driveServices.includes(type)) { + if (serviceTypes.driveServices.includes(typeLowerCase)) { return getServiceIcon('drivedefault'); } - if (serviceTypes.apiServices.includes(type)) { + if (serviceTypes.apiServices.includes(typeLowerCase)) { return getServiceIcon('restservice'); } From eff7401967466854132903b965899be13618f326 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:48:40 +0530 Subject: [PATCH 44/47] address comments --- .../MarketplaceNavBar.component.tsx | 4 ++-- .../resources/ui/src/components/NavBar/NavBar.tsx | 4 ++-- .../resources/ui/src/utils/ServiceIconUtils.ts | 5 +++-- .../resources/ui/src/utils/i18next/LocalUtil.tsx | 5 ----- .../ui/src/utils/i18next/LocalUtilClassBase.ts | 2 +- .../src/main/resources/ui/vite.config.ts | 14 +------------- 6 files changed, 9 insertions(+), 25 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx index 7ffdd98d69a8..8d6c103601ca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceNavBar/MarketplaceNavBar.component.tsx @@ -58,7 +58,7 @@ import { } from '../../../utils/FeedUtils'; import { languageSelectOptions } from '../../../utils/i18next/i18nextUtil'; import i18n from '../../../utils/i18next/LocalUtil'; -import { LocalUtilClassBase } from '../../../utils/i18next/LocalUtilClassBase'; +import localUtilClassBase from '../../../utils/i18next/LocalUtilClassBase'; import { getHelpDropdownItems } from '../../../utils/NavbarUtils'; import { getSettingPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; @@ -364,7 +364,7 @@ const MarketplaceNavBar = () => { }, []); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await LocalUtilClassBase.loadLocales(key); + await localUtilClassBase.loadLocales(key); await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 45ef64da65ca..0bed65667459 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -73,7 +73,7 @@ import { } from '../../utils/FeedUtils'; import { languageSelectOptions } from '../../utils/i18next/i18nextUtil'; import i18n from '../../utils/i18next/LocalUtil'; -import { LocalUtilClassBase } from '../../utils/i18next/LocalUtilClassBase'; +import localUtilClassBase from '../../utils/i18next/LocalUtilClassBase'; import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; import { getSettingPath } from '../../utils/RouterUtils'; @@ -439,7 +439,7 @@ const NavBar = () => { ); const handleLanguageChange = useCallback(async ({ key }: MenuInfo) => { - await LocalUtilClassBase.loadLocales(key); + await localUtilClassBase.loadLocales(key); await i18n.changeLanguage(key); navigate(0); }, []); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index af46af71f0e0..c621c3c85bb1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -150,7 +150,7 @@ const SERVICE_ICON_LOADERS: Record = { vertica: vertica, azuresql: azuresql, clickhouse: clickhouse, - databrick: databrick, + databricks: databrick, unitycatalog: unitycatalog, ibmdb2: ibmdb2, doris: doris, @@ -217,7 +217,7 @@ const SERVICE_ICON_LOADERS: Record = { sagemaker: sagemaker, // Storage services - amazons3: amazons3, + s3: amazons3, gcs: gcs, // Search services @@ -228,6 +228,7 @@ const SERVICE_ICON_LOADERS: Record = { amundsen: amundsen, atlas: atlas, alationsink: alationsink, + openmetadata: logo, // Drive services googledrive: googledrive, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index 9dbb463dbbf4..0e64f27bbde9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -16,7 +16,6 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import { ReactNode } from 'react'; import { initReactI18next, Trans } from 'react-i18next'; import { getInitOptions } from './i18nextUtil'; -import { LocalUtilClassBase } from './LocalUtilClassBase'; i18next .use(LanguageDetector) @@ -28,10 +27,6 @@ i18next } }); -i18next.on('languageChanged', async (lng) => { - await LocalUtilClassBase.loadLocales(lng); -}); - export const t = (key: string, options?: Record): string => { const translation = i18nextT(key, options); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts index 1242a163fac9..a7840219811a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtilClassBase.ts @@ -41,7 +41,7 @@ const LOCALE_LOADERS: Record< class LocalUtilClassBase { private static _instance: LocalUtilClassBase; - static async loadLocales(locale: string): Promise { + async loadLocales(locale: string): Promise { if (i18next.hasResourceBundle(locale, 'translation')) { return; } diff --git a/openmetadata-ui/src/main/resources/ui/vite.config.ts b/openmetadata-ui/src/main/resources/ui/vite.config.ts index b5e72d369646..3052f0d6b65c 100644 --- a/openmetadata-ui/src/main/resources/ui/vite.config.ts +++ b/openmetadata-ui/src/main/resources/ui/vite.config.ts @@ -56,18 +56,6 @@ export default defineConfig(({ mode }) => { ); }, }, - { - name: 'html-cache-buster', - transformIndexHtml(html: string) { - // APP_VERSION is injected by the Maven build - const appVersion = process.env.APP_VERSION || 'unknown'; - return html.replaceAll( - /(href|src)="([^"]+\.(?:js|css))(\?[^"]*)?"/g, - (_, attr, pathInternal, qs) => - `${attr}="${pathInternal}${qs ? qs + '&' : '?'}v=${appVersion}"` - ); - }, - }, tailwindcss(), react(), svgr(), @@ -185,7 +173,7 @@ export default defineConfig(({ mode }) => { minify: mode === 'production' ? 'esbuild' : false, cssMinify: 'esbuild', cssCodeSplit: true, - reportCompressedSize: true, + reportCompressedSize: false, chunkSizeWarningLimit: 1500, rollupOptions: { output: { From 9707eba84f250f84d09ec127aaeb8c362cae93e0 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:26:51 +0530 Subject: [PATCH 45/47] final fix to icons --- .../ui/src/utils/ServiceIconUtils.ts | 22 +++++-- .../ui/src/utils/ServiceUtilClassBase.ts | 66 +++++++------------ .../ui/src/utils/i18next/LocalUtil.tsx | 5 ++ 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts index c621c3c85bb1..2b28006e2168 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceIconUtils.ts @@ -152,7 +152,7 @@ const SERVICE_ICON_LOADERS: Record = { clickhouse: clickhouse, databricks: databrick, unitycatalog: unitycatalog, - ibmdb2: ibmdb2, + db2: ibmdb2, doris: doris, starrocks: starrocks, druid: druid, @@ -162,7 +162,7 @@ const SERVICE_ICON_LOADERS: Record = { saphana: saphana, saperp: saperp, deltalake: deltalake, - pinot: pinot, + pinotdb: pinot, datalake: datalake, exasol: exasol, mongodb: mongodb, @@ -175,12 +175,15 @@ const SERVICE_ICON_LOADERS: Record = { burstiq: burstiq, sas: sas, iomete: iomete, + domodatabase: domo, + customdatabase: databasedefault, // Messaging services kafka: kafka, pubsub: pubsub, redpanda: redpanda, kinesis: kinesis, + custommessaging: topicdefault, // Dashboard services superset: superset, @@ -191,30 +194,37 @@ const SERVICE_ICON_LOADERS: Record = { powerbi: powerbi, sigma: sigma, mode: mode, - domo: domo, + domodashboard: domo, quicksight: quicksight, qliksense: qliksense, lightdash: lightdash, microstrategy: microstrategy, grafana: grafana, hex: hex, + customdashboard: dashboarddefault, // Pipeline services airflow: airflow, airbyte: airbyte, dagster: dagster, - dbt: dbt, + dbtcloud: dbt, fivetran: fivetran, nifi: nifi, spark: spark, spline: spline, flink: flink, openlineage: openlineage, + domopipeline: domo, + kafkaconnect: kafka, + databrickspipeline: databrick, + gluepipeline: glue, + custompipeline: pipelinedefault, // ML Model services mlflow: mlflow, scikit: scikit, sagemaker: sagemaker, + custommlmodel: mlmodeldefault, // Storage services s3: amazons3, @@ -233,6 +243,10 @@ const SERVICE_ICON_LOADERS: Record = { // Drive services googledrive: googledrive, sftp: sftp, + customdrive: customdrivedefault, + + // API services + rest: restservice, // Default icons defaultservice: defaultservice, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts index 0ba38c95fbbb..22228d14234f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtilClassBase.ts @@ -235,59 +235,37 @@ class ServiceUtilClassBase { return { databaseServices: this.filterUnsupportedServiceType( Object.values(DatabaseServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), messagingServices: this.filterUnsupportedServiceType( Object.values(MessagingServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), dashboardServices: this.filterUnsupportedServiceType( Object.values(DashboardServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), pipelineServices: this.filterUnsupportedServiceType( Object.values(PipelineServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), mlmodelServices: this.filterUnsupportedServiceType( Object.values(MlModelServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), metadataServices: this.filterUnsupportedServiceType( Object.values(MetadataServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), storageServices: this.filterUnsupportedServiceType( Object.values(StorageServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), searchServices: this.filterUnsupportedServiceType( Object.values(SearchServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), apiServices: this.filterUnsupportedServiceType( Object.values(APIServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), driveServices: this.filterUnsupportedServiceType( Object.values(DriveServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), securityServices: this.filterUnsupportedServiceType( Object.values(SecurityServiceType) as string[] - ) - .sort(customServiceComparator) - .map((s) => s.toLowerCase()), + ).sort(customServiceComparator), }; } @@ -341,35 +319,35 @@ class ServiceUtilClassBase { private getDefaultLogoForServiceType(type: string): string { const serviceTypes = this.getSupportedServiceFromList(); - const typeLowerCase = type.toLowerCase(); - if (serviceTypes.messagingServices.includes(typeLowerCase)) { + + if (serviceTypes.messagingServices.includes(type)) { return getServiceIcon('topicdefault'); } - if (serviceTypes.dashboardServices.includes(typeLowerCase)) { + if (serviceTypes.dashboardServices.includes(type)) { return getServiceIcon('dashboarddefault'); } - if (serviceTypes.pipelineServices.includes(typeLowerCase)) { + if (serviceTypes.pipelineServices.includes(type)) { return getServiceIcon('pipelinedefault'); } - if (serviceTypes.databaseServices.includes(typeLowerCase)) { + if (serviceTypes.databaseServices.includes(type)) { return getServiceIcon('databasedefault'); } - if (serviceTypes.mlmodelServices.includes(typeLowerCase)) { + if (serviceTypes.mlmodelServices.includes(type)) { return getServiceIcon('mlmodeldefault'); } - if (serviceTypes.storageServices.includes(typeLowerCase)) { + if (serviceTypes.storageServices.includes(type)) { return getServiceIcon('storagedefault'); } - if (serviceTypes.searchServices.includes(typeLowerCase)) { + if (serviceTypes.searchServices.includes(type)) { return getServiceIcon('searchdefault'); } - if (serviceTypes.securityServices.includes(typeLowerCase)) { + if (serviceTypes.securityServices.includes(type)) { return getServiceIcon('securitydefault'); } - if (serviceTypes.driveServices.includes(typeLowerCase)) { + if (serviceTypes.driveServices.includes(type)) { return getServiceIcon('drivedefault'); } - if (serviceTypes.apiServices.includes(typeLowerCase)) { + if (serviceTypes.apiServices.includes(type)) { return getServiceIcon('restservice'); } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx index 0e64f27bbde9..840c6b8f54c0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.tsx @@ -16,6 +16,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import { ReactNode } from 'react'; import { initReactI18next, Trans } from 'react-i18next'; import { getInitOptions } from './i18nextUtil'; +import localUtilClassBase from './LocalUtilClassBase'; i18next .use(LanguageDetector) @@ -27,6 +28,10 @@ i18next } }); +i18next.on('languageChanged', async (lng) => { + await localUtilClassBase.loadLocales(lng); +}); + export const t = (key: string, options?: Record): string => { const translation = i18nextT(key, options); From bc47919bd9c369d27996105a15d56170bbc75bdf Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sat, 18 Apr 2026 17:02:50 +0530 Subject: [PATCH 46/47] fix failing playwrights --- .../resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts | 3 +-- .../ui/playwright/e2e/Pages/Customproperties-part1.spec.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts index 6f35a103ef5d..0b1b2ce4f3e4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect, Page, test as base } from '@playwright/test'; +import { test as base, expect, Page } from '@playwright/test'; import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass'; import { DatabaseClass } from '../../support/entity/DatabaseClass'; import { TableClass } from '../../support/entity/TableClass'; @@ -411,7 +411,6 @@ test.describe('Mention notifications in Notification Box', () => { await apiContext.post('/api/v1/feed', { data: { - from: adminUser.responseData.name, message: 'Initial conversation thread for mention test', about: `<#E::table::${entity.entityResponseData.fullyQualifiedName}>`, type: 'Conversation', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Customproperties-part1.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Customproperties-part1.spec.ts index 6e048e5f4296..b953ece8346a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Customproperties-part1.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Customproperties-part1.spec.ts @@ -142,7 +142,7 @@ test.describe( await page.locator("pre[role='presentation']").last().click(); await page.keyboard.type( - "SELECT id, name, email\nFROM users\nWHERE active = true\nAND department = 'engineering'\nORDER BY created_at DESC\nLIMIT 100" + "SELECT id, name, email\n\nFROM users\n\nWHERE active = true\n\nAND department = 'engineering'\n\nORDER BY created_at DESC\n\nLIMIT 100" ); const patchResponse = page.waitForResponse( From 179c8bd969859c5e2242b85823e85dd52327b090 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Sat, 18 Apr 2026 20:06:40 +0530 Subject: [PATCH 47/47] fix checkstyle --- .../resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts index 0b1b2ce4f3e4..8bf8897ff0ad 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { test as base, expect, Page } from '@playwright/test'; +import { expect, Page, test as base } from '@playwright/test'; import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass'; import { DatabaseClass } from '../../support/entity/DatabaseClass'; import { TableClass } from '../../support/entity/TableClass';