diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 494d4a905f67..5d77de2cecb6 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -85,6 +85,7 @@ "@rjsf/core": "5.24.13", "@rjsf/utils": "5.24.13", "@rjsf/validator-ajv8": "5.24.13", + "@tanstack/react-query": "^5.62.0", "@tiptap/core": "^2.3.0", "@tiptap/extension-link": "^2.10.4", "@tiptap/extension-placeholder": "^2.3.0", diff --git a/openmetadata-ui/src/main/resources/ui/src/App.tsx b/openmetadata-ui/src/main/resources/ui/src/App.tsx index 35f6d39c8ce1..66cc89e3b862 100644 --- a/openmetadata-ui/src/main/resources/ui/src/App.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/App.tsx @@ -11,15 +11,23 @@ * limitations under the License. */ +import { QueryClientProvider } from '@tanstack/react-query'; import { FC } from 'react'; import AppRouter from './components/AppRouter/AppRouter'; import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider'; +import { queryClient } from './queryClient'; const App: FC = () => { + // QueryClientProvider sits OUTSIDE AuthProvider so any query made during the auth flow + // (e.g. fetching feature flags before login) reuses the same cache. AuthProvider remounts + // on logout — wrapping QueryClient inside would discard the cache on every logout, + // which is the opposite of what we want here. return ( - - - + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/hooks/useLazyEntityExtension.ts b/openmetadata-ui/src/main/resources/ui/src/hooks/useLazyEntityExtension.ts new file mode 100644 index 000000000000..c479377a00df --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/hooks/useLazyEntityExtension.ts @@ -0,0 +1,92 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { EntityTabs, TabSpecificField } from '../enums/entity.enum'; + +interface EntityWithExtension { + extension?: unknown; +} + +/** + * Lazily fetch an entity's `extension` (custom-property values) only when the user activates + * the Custom Properties tab. Replaces what used to be eager inclusion of {@code EXTENSION} + * in {@code defaultFields} on every entity-detail page load. + * + * Why this exists: + * - Custom property payloads can run into hundreds of KB on entities with many user-defined + * properties. Most users never open the Custom Properties tab, so paying for it on first + * paint is wasted bytes. + * - The pattern (gated useQuery + merge into local state) was the same on every entity + * detail page; centralising it avoids 8 copies of the same closure-with-effect. + * + * Per-page wiring (call at the page top-level, alongside the main entity state): + *
+ *   useLazyEntityExtension<Dashboard>({
+ *     entityType: EntityType.DASHBOARD,
+ *     fqn: dashboardFQN,
+ *     activeTab,
+ *     fetcher: getDashboardByFqn,
+ *     onResolve: (extension) =>
+ *       setDashboardDetails((prev) => ({ ...prev, extension })),
+ *   });
+ * 
+ * + * The {@code onResolve} callback shape (rather than passing a setState directly) keeps each + * consumer in control of their own state-shape semantics — some pages init state as + * `{} as T` (non-undefined), others as `useState()` (T | undefined). Either works. + * + * Behaviour: + * - Query is gated by `enabled: activeTab === CUSTOM_PROPERTIES && Boolean(fqn)` — does + * nothing on other tabs. + * - Stable `queryKey` of `[`-extension`, fqn]` — cached across tab toggles, refetched + * on FQN change with automatic in-flight cancellation. + * - 60s {@code staleTime} — custom property values change rarely. + * - On resolve, fires {@code onResolve(extension)} exactly once per fresh fetch. + * + * Caveats: + * - The {@code onResolve} callback identity is not memoised at the call site. We + * deliberately depend only on `data?.extension` so we don't fire the merge effect on + * every parent re-render — the latest callback is captured at fire time. + */ +export function useLazyEntityExtension({ + entityType, + fqn, + activeTab, + fetcher, + onResolve, +}: { + entityType: string; + fqn: string | undefined; + activeTab: string | undefined; + fetcher: (fqn: string, params: { fields: string }) => Promise; + onResolve: (extension: T['extension']) => void; +}): void { + const enabled = activeTab === EntityTabs.CUSTOM_PROPERTIES && Boolean(fqn); + + const { data } = useQuery({ + queryKey: [`${entityType}-extension`, fqn], + queryFn: () => + fetcher(fqn as string, { fields: TabSpecificField.EXTENSION }), + enabled, + staleTime: 60_000, + }); + + useEffect(() => { + if (data?.extension === undefined) { + return; + } + onResolve(data.extension); + // onResolve is intentionally omitted from deps — see header comment. + }, [data?.extension]); +} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx index 9e991915ae19..42629e9dcc90 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx @@ -27,12 +27,18 @@ import { usePermissionProvider } from '../../context/PermissionProvider/Permissi import { ResourceEntity } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Chart } from '../../generated/entity/data/chart'; import { Dashboard } from '../../generated/entity/data/dashboard'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addFollower, getDashboardByFqn, @@ -64,10 +70,21 @@ const DashboardDetailsPage = () => { const navigate = useNavigate(); const { getEntityPermissionByFqn } = usePermissionProvider(); const { entityFqn: dashboardFQN } = useFqn({ type: EntityType.DASHBOARD }); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [dashboardDetails, setDashboardDetails] = useState( {} as Dashboard ); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.DASHBOARD, + fqn: dashboardFQN, + activeTab, + fetcher: getDashboardByFqn, + onResolve: (extension) => + setDashboardDetails((prev) => ({ ...prev, extension })), + }); const [isLoading, setLoading] = useState(false); const [isError, setIsError] = useState(false); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DirectoryDetailsPage/DirectoryDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DirectoryDetailsPage/DirectoryDetailsPage.tsx index 040fd8898052..71424d5b55be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DirectoryDetailsPage/DirectoryDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DirectoryDetailsPage/DirectoryDetailsPage.tsx @@ -31,11 +31,17 @@ import { } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Directory } from '../../generated/entity/data/directory'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addDriveAssetFollower, getDriveAssetByFqn, @@ -64,9 +70,22 @@ const DirectoryDetailsPage = () => { const { getEntityPermissionByFqn } = usePermissionProvider(); const { fqn: directoryFQN } = useFqn(); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [directoryDetails, setDirectoryDetails] = useState( {} as Directory ); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + // getDriveAssetByFqn has a different signature (entityType-keyed) so we adapt it. + useLazyEntityExtension({ + entityType: EntityType.DIRECTORY, + fqn: directoryFQN, + activeTab, + fetcher: (fqn, params) => + getDriveAssetByFqn(fqn, EntityType.DIRECTORY, params.fields), + onResolve: (extension) => + setDirectoryDetails((prev) => ({ ...prev, extension })), + }); const [isLoading, setLoading] = useState(true); const [isError, setIsError] = useState(false); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx index 1e1c57d35d0c..81b9a296beee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx @@ -27,11 +27,17 @@ import { usePermissionProvider } from '../../context/PermissionProvider/Permissi import { ResourceEntity } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Mlmodel } from '../../generated/entity/data/mlmodel'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addFollower, getMlModelByFQN, @@ -57,7 +63,18 @@ const MlModelPage = () => { const { currentUser } = useApplicationStore(); const navigate = useNavigate(); const { entityFqn: mlModelFqn } = useFqn({ type: EntityType.MLMODEL }); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [mlModelDetail, setMlModelDetail] = useState({} as Mlmodel); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.MLMODEL, + fqn: mlModelFqn, + activeTab, + fetcher: getMlModelByFQN, + onResolve: (extension) => + setMlModelDetail((prev) => ({ ...prev, extension })), + }); const [isDetailLoading, setIsDetailLoading] = useState(false); const USERId = currentUser?.id ?? ''; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx index 7cd312470555..4f1befd9c226 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx @@ -27,12 +27,18 @@ import { usePermissionProvider } from '../../context/PermissionProvider/Permissi import { ResourceEntity } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Pipeline } from '../../generated/entity/data/pipeline'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { Paging } from '../../generated/type/paging'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addFollower, getPipelineByFqn, @@ -62,10 +68,21 @@ const PipelineDetailsPage = () => { const { entityFqn: decodedPipelineFQN } = useFqn({ type: EntityType.PIPELINE, }); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [pipelineDetails, setPipelineDetails] = useState( {} as Pipeline ); + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.PIPELINE, + fqn: decodedPipelineFQN, + activeTab, + fetcher: getPipelineByFqn, + onResolve: (extension) => + setPipelineDetails((prev) => ({ ...prev, extension })), + }); + const [isLoading, setLoading] = useState(true); const [isError, setIsError] = useState(false); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx index d1da2ef2a6b8..e63c685dc1c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx @@ -45,6 +45,7 @@ import LimitWrapper from '../../hoc/LimitWrapper'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useCustomPages } from '../../hooks/useCustomPages'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; import { FeedCounts } from '../../interface/feed.interface'; import { addFollower, @@ -89,6 +90,16 @@ function SearchIndexDetailsPage() { const USERId = currentUser?.id ?? ''; const [loading, setLoading] = useState(true); const [searchIndexDetails, setSearchIndexDetails] = useState(); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.SEARCH_INDEX, + fqn: decodedSearchIndexFQN, + activeTab, + fetcher: getSearchIndexDetailsByFQN, + onResolve: (extension) => + setSearchIndexDetails((prev) => (prev ? { ...prev, extension } : prev)), + }); const [feedCount, setFeedCount] = useState( FEED_COUNT_INITIAL_DATA ); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SpreadsheetDetailsPage/SpreadsheetDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SpreadsheetDetailsPage/SpreadsheetDetailsPage.tsx index 1022ea8bbdda..5020aa235917 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SpreadsheetDetailsPage/SpreadsheetDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SpreadsheetDetailsPage/SpreadsheetDetailsPage.tsx @@ -31,11 +31,17 @@ import { } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Spreadsheet } from '../../generated/entity/data/spreadsheet'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addDriveAssetFollower, getDriveAssetByFqn, @@ -64,9 +70,25 @@ const SpreadsheetDetailsPage = () => { const { getEntityPermissionByFqn } = usePermissionProvider(); const { fqn: spreadsheetFQN } = useFqn(); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [spreadsheetDetails, setSpreadsheetDetails] = useState( {} as Spreadsheet ); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.SPREADSHEET, + fqn: spreadsheetFQN, + activeTab, + fetcher: (fqn, params) => + getDriveAssetByFqn( + fqn, + EntityType.SPREADSHEET, + params.fields + ), + onResolve: (extension) => + setSpreadsheetDetails((prev) => ({ ...prev, extension })), + }); const [isLoading, setLoading] = useState(true); const [isError, setIsError] = useState(false); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedurePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedurePage.tsx index 1d3bbaed362a..2cfde5f325ff 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedurePage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedurePage.tsx @@ -48,6 +48,7 @@ import LimitWrapper from '../../hoc/LimitWrapper'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useCustomPages } from '../../hooks/useCustomPages'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; import { FeedCounts } from '../../interface/feed.interface'; import { addStoredProceduresFollower, @@ -93,6 +94,16 @@ const StoredProcedurePage = () => { const { getEntityPermissionByFqn } = usePermissionProvider(); const [isLoading, setIsLoading] = useState(true); const [storedProcedure, setStoredProcedure] = useState(); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.STORED_PROCEDURE, + fqn: decodedStoredProcedureFQN, + activeTab, + fetcher: getStoredProceduresByFqn, + onResolve: (extension) => + setStoredProcedure((prev) => (prev ? { ...prev, extension } : prev)), + }); const [storedProcedurePermissions, setStoredProcedurePermissions] = useState(DEFAULT_ENTITY_PERMISSION); const [isTabExpanded, setIsTabExpanded] = useState(false); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index 966e5ead5f89..5ec0df07b318 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Col, Row, Tabs, Tooltip } from 'antd'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; @@ -57,6 +58,7 @@ import LimitWrapper from '../../hoc/LimitWrapper'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useCustomPages } from '../../hooks/useCustomPages'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; import { useSub } from '../../hooks/usePubSub'; import { FeedCounts } from '../../interface/feed.interface'; import { fetchTestCaseResultByTestSuiteId } from '../../rest/dataQualityDashboardAPI'; @@ -108,7 +110,7 @@ const TableDetailsPageV1: React.FC = () => { useTourProvider(); const { currentUser } = useApplicationStore(); const { setDqLineageData } = useTestCaseStore(); - const [tableDetails, setTableDetails] = useState(); + const queryClient = useQueryClient(); const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const { t } = useTranslation(); const navigate = useNavigate(); @@ -120,7 +122,6 @@ const TableDetailsPageV1: React.FC = () => { const [queryCount, setQueryCount] = useState(0); - const [loading, setLoading] = useState(!isTourOpen); const [tablePermissions, setTablePermissions] = useState( DEFAULT_ENTITY_PERMISSION ); @@ -156,20 +157,6 @@ const TableDetailsPageV1: React.FC = () => { ) : undefined; }, [dqFailureCount, tableFqn]); - const extraDropdownContent = useMemo( - () => - tableDetails - ? entityUtilClassBase.getManageExtraOptions( - EntityType.TABLE, - tableFqn, - tablePermissions, - tableDetails, - navigate - ) - : [], - [tablePermissions, tableFqn, tableDetails] - ); - const { viewUsagePermission, viewTestCasePermission } = useMemo( () => ({ viewUsagePermission: getPrioritizedViewPermission( @@ -188,51 +175,159 @@ const TableDetailsPageV1: React.FC = () => { ] ); - const isViewTableType = useMemo( - () => tableDetails?.tableType === TableType.View, - [tableDetails?.tableType] + // Composed `fields=` value, derived from permissions. USAGE_SUMMARY / TESTSUITE are only + // appended when the caller can read them — drives both the queryKey identity and the + // queryFn payload, so changing permissions automatically invalidates the cache for the + // wrong-shape entry and refetches. + const tableQueryFields = useMemo(() => { + let fields: string = defaultFieldsWithColumns; + if (viewUsagePermission) { + fields += `,${TabSpecificField.USAGE_SUMMARY}`; + } + if (viewTestCasePermission) { + fields += `,${TabSpecificField.TESTSUITE}`; + } + + return fields; + }, [viewUsagePermission, viewTestCasePermission]); + + // Stable React Query key. Includes the FQN and the fields string so that: + // * navigating from one table to another swaps queryKey, picking up the new cache slot + // (or refetching on miss); + // * a permission change that mutates the fields string invalidates the existing entry + // because the key shape is different. + const tableQueryKey = useMemo( + () => ['table-detail', tableFqn, tableQueryFields] as const, + [tableFqn, tableQueryFields] + ); + + // Permissions are loaded asynchronously by `fetchResourcePermission`. Until that resolves, + // `tablePermissions` is the sentinel `DEFAULT_ENTITY_PERMISSION` reference. We use that as + // a "permissions still loading" signal — gates both the query and the page-level loader so + // the page doesn't race the permission fetch. + const tablePermissionsLoaded = tablePermissions !== DEFAULT_ENTITY_PERMISSION; + + // Main entity fetch — migrated from a hand-rolled `useState + useCallback + useEffect` + // pattern to React Query (P3.1). Replaces `fetchTableDetails` and the `[tableDetails, + // setTableDetails]` useState below. Existing call sites that did `setTableDetails(...)` or + // `fetchTableDetails()` continue to work via the wrapper functions defined below — the + // page state-shape contract is preserved. + const { + data: tableDetails, + isLoading: isTableLoading, + error: tableQueryError, + refetch: refetchTable, + } = useQuery({ + queryKey: tableQueryKey, + queryFn: () => getTableDetailsByFQN(tableFqn, { fields: tableQueryFields }), + enabled: + !isTourOpen && !isTourPage && tablePermissionsLoaded && Boolean(tableFqn), + }); + + // Bridge: existing call sites do `setTableDetails((prev) => ({...prev, ...patch}))` or + // `setTableDetails(newTable)`. Both forms still work because we forward to + // `queryClient.setQueryData`, which accepts a plain value or an updater closure. Keeping + // this wrapper means the ~25 mutation call sites in this file (edit handlers, follow, + // vote, restore, certification, tier, …) need no changes. + const setTableDetails = useCallback( + ( + next: Table | undefined | ((prev: Table | undefined) => Table | undefined) + ) => { + queryClient.setQueryData
(tableQueryKey, (prev) => + typeof next === 'function' + ? (next as (prev: Table | undefined) => Table | undefined)(prev) + : next + ); + }, + [queryClient, tableQueryKey] ); + // Bridge: `fetchTableDetails(showLoading?)` used to be a force-refetch helper. With React + // Query, the equivalent is `refetch()` — `isFetching` flips during the refetch but + // `isLoading` stays `false` after first success, which matches the old `showLoading=false` + // semantics naturally. The argument is now ignored. const fetchTableDetails = useCallback( - async (showLoading = true) => { - if (showLoading) { - setLoading(true); - } - try { - let fields = defaultFieldsWithColumns; - if (viewUsagePermission) { - fields += `,${TabSpecificField.USAGE_SUMMARY}`; - } - if (viewTestCasePermission) { - fields += `,${TabSpecificField.TESTSUITE}`; - } + () => refetchTable().then(() => undefined), + [refetchTable] + ); - const tableDetails = await getTableDetailsByFQN(tableFqn, { fields }); + // Surface a loader while permissions are still loading too — otherwise the page briefly + // renders the "no data" placeholder before the query is even enabled. + const loading = + !isTourOpen && !isTourPage && (!tablePermissionsLoaded || isTableLoading); - setTableDetails(tableDetails); - addToRecentViewed({ - displayName: getEntityName(tableDetails), - entityType: EntityType.TABLE, - fqn: tableDetails.fullyQualifiedName ?? '', - serviceType: tableDetails.serviceType, - timestamp: 0, - id: tableDetails.id, - }); - } catch (error) { - if ( - (error as AxiosError)?.response?.status === ClientErrors.FORBIDDEN - ) { - navigate(ROUTES.FORBIDDEN, { replace: true }); - } - } finally { - if (showLoading) { - setLoading(false); - } - } - }, - [tableFqn, viewUsagePermission] + // Forbidden navigation — used to live in fetchTableDetails' catch. React Query surfaces + // the same error via `error`; redirect on FORBIDDEN once. + useEffect(() => { + if ( + tableQueryError && + (tableQueryError as AxiosError)?.response?.status === + ClientErrors.FORBIDDEN + ) { + navigate(ROUTES.FORBIDDEN, { replace: true }); + } + }, [tableQueryError, navigate]); + + // Recently-viewed bookkeeping — used to live in fetchTableDetails' success path. Now an + // effect on the freshly-resolved entity id. + useEffect(() => { + if (!tableDetails || isTourOpen || isTourPage) { + return; + } + addToRecentViewed({ + displayName: getEntityName(tableDetails), + entityType: EntityType.TABLE, + fqn: tableDetails.fullyQualifiedName ?? '', + serviceType: tableDetails.serviceType, + timestamp: 0, + id: tableDetails.id, + }); + }, [tableDetails?.id, isTourOpen, isTourPage]); + + // Tour mode — prime the cache with mock data so consumers (which read from the cache via + // `tableDetails`) see the mock entity. Replaces a `setTableDetails(mock)` call from the + // legacy useEffect. + useEffect(() => { + if (isTourOpen || isTourPage) { + queryClient.setQueryData( + tableQueryKey, + mockDatasetData.tableDetails as unknown as Table + ); + } + }, [isTourOpen, isTourPage, queryClient, tableQueryKey]); + + const isViewTableType = useMemo( + () => tableDetails?.tableType === TableType.View, + [tableDetails?.tableType] + ); + + const extraDropdownContent = useMemo( + () => + tableDetails + ? entityUtilClassBase.getManageExtraOptions( + EntityType.TABLE, + tableFqn, + tablePermissions, + tableDetails, + navigate + ) + : [], + [tablePermissions, tableFqn, tableDetails, navigate] ); + // Lazy custom-properties fetch — see {@link useLazyEntityExtension} for rationale and + // the P3.1 React Query pilot pattern. Eager `defaultFieldsWithColumns` deliberately omits + // `extension` because the blob can be hundreds of KB on tables with many user-defined + // properties and only the Custom Properties tab consumes it. + useLazyEntityExtension
({ + entityType: EntityType.TABLE, + fqn: tableFqn, + activeTab: isTourOpen ? undefined : activeTab, + fetcher: getTableDetailsByFQN, + onResolve: (extension) => + setTableDetails((prev) => (prev ? { ...prev, extension } : prev)), + }); + const fetchDQUpstreamFailureCount = async () => { if (!tableClassBase.getAlertEnableStatus()) { setDqFailureCount(0); @@ -352,8 +447,6 @@ const TableDetailsPageV1: React.FC = () => { entity: t('label.resource-permission-lowercase'), }) ); - } finally { - setLoading(false); } }, [getEntityPermissionByFqn, setTablePermissions] @@ -775,12 +868,12 @@ const TableDetailsPageV1: React.FC = () => { [] ); + // Tour-mode mock priming and the table fetch itself now both live with the React Query + // setup above. This effect remains to drive the *non-table* side-effect — `getEntityFeedCount` + // — that used to be co-located with the legacy fetch on FQN change. Kept gated on + // `viewBasicPermission` so we don't issue a feed-count fetch the user isn't allowed to see. useEffect(() => { - if (isTourOpen || isTourPage) { - setTableDetails(mockDatasetData.tableDetails as unknown as Table); - } else if (viewBasicPermission) { - setTableDetails(undefined); - fetchTableDetails(); + if (!isTourOpen && !isTourPage && viewBasicPermission) { getEntityFeedCount(); } }, [tableFqn, isTourOpen, isTourPage, viewBasicPermission]); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/WorksheetDetailsPage/WorksheetDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/WorksheetDetailsPage/WorksheetDetailsPage.tsx index cd9a0711a4d1..c460fe6f1d70 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/WorksheetDetailsPage/WorksheetDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/WorksheetDetailsPage/WorksheetDetailsPage.tsx @@ -32,11 +32,17 @@ import { } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { EntityType, TabSpecificField } from '../../enums/entity.enum'; +import { + EntityTabs, + EntityType, + TabSpecificField, +} from '../../enums/entity.enum'; import { Worksheet } from '../../generated/entity/data/worksheet'; import { Operation as PermissionOperation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import { useFqn } from '../../hooks/useFqn'; +import { useLazyEntityExtension } from '../../hooks/useLazyEntityExtension'; +import { useRequiredParams } from '../../utils/useRequiredParams'; import { addDriveAssetFollower, getDriveAssetByFqn, @@ -66,9 +72,21 @@ const WorksheetDetailsPage = () => { const { getEntityPermissionByFqn } = usePermissionProvider(); const { fqn: decodedWorksheetFQN } = useFqn(); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const [worksheetDetails, setWorksheetDetails] = useState( {} as Worksheet ); + + // Lazy custom-properties fetch — see {@link useLazyEntityExtension}. + useLazyEntityExtension({ + entityType: EntityType.WORKSHEET, + fqn: decodedWorksheetFQN, + activeTab, + fetcher: (fqn, params) => + getDriveAssetByFqn(fqn, EntityType.WORKSHEET, params.fields), + onResolve: (extension) => + setWorksheetDetails((prev) => ({ ...prev, extension })), + }); const [isLoading, setLoading] = useState(true); const [isError, setIsError] = useState(false); const [resolvedEntityFqn, setResolvedEntityFqn] = useState(''); diff --git a/openmetadata-ui/src/main/resources/ui/src/queryClient.ts b/openmetadata-ui/src/main/resources/ui/src/queryClient.ts new file mode 100644 index 000000000000..371ea4ff476b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/queryClient.ts @@ -0,0 +1,42 @@ +/* + * 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 { QueryClient } from '@tanstack/react-query'; + +/** + * App-wide React Query client. + * + * Defaults are tuned for OpenMetadata's data shape: + * - {@link staleTime} 30 s — most entity reads are stable for tens of seconds at a time + * and pages flip back-and-forth (Schema → Lineage → Schema). The same query reissued + * within the window serves cached data instead of refetching. + * - {@link gcTime} 5 min — keep results around long enough for a tab-switch round-trip + * without holding memory for users who navigate away. + * - {@link refetchOnWindowFocus} true — picks up backend changes when the user returns + * to the tab, but not so aggressively that idle tabs hammer the API. + * - {@link retry} 1 — one network blip retry, no exponential backoff cascade. + * + * Per-query overrides in `useQuery({ queryKey, queryFn, staleTime, ... })` always win. + */ +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + gcTime: 5 * 60_000, + refetchOnWindowFocus: true, + retry: 1, + }, + mutations: { + retry: 0, + }, + }, +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx index 4ce69e41a22d..391decc33af1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx @@ -41,8 +41,11 @@ const EntityLineageTab = lazy(() => ) ); +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab — it can run into hundreds of KB on +// dashboards with many user-defined properties and only that tab consumes it. // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`; +export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS}`; export const fetchCharts = async ( charts: Dashboard['charts'], diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts index c04f9f1152df..375d771e9656 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts @@ -13,12 +13,16 @@ import { TabSpecificField } from '../enums/entity.enum'; -// Fields for table details - excludes columns which will be fetched separately with pagination +// Fields for table details first paint. Excludes columns (paginated separately) and +// `extension` (custom-property values — only the Custom Properties tab consumes this; the +// {@link useLazyEntityExtension} hook fetches it lazily on tab activation). Custom +// extension payloads can run into hundreds of KB on tables with many user-defined +// properties; trimming it saves wire bytes on every initial table-page load. // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; +export const defaultFields = `${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; // Legacy fields that include columns - only use when pagination is not needed // eslint-disable-next-line max-len -export const defaultFieldsWithColumns = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; +export const defaultFieldsWithColumns = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; export const commonTableFields = `${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.CERTIFICATION}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DirectoryDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DirectoryDetailsUtils.tsx index 8c258a0f4a97..5fe0689a0d6d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DirectoryDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DirectoryDetailsUtils.tsx @@ -35,6 +35,8 @@ export interface DirectoryDetailPageTabProps { labelMap?: Record; } +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. export const defaultFields = [ TabSpecificField.OWNERS, TabSpecificField.CHILDREN, @@ -43,7 +45,6 @@ export const defaultFields = [ TabSpecificField.DOMAINS, TabSpecificField.DATA_PRODUCTS, TabSpecificField.VOTES, - TabSpecificField.EXTENSION, TabSpecificField.DIRECTORY_TYPE, TabSpecificField.NUMBER_OF_FILES, TabSpecificField.NUMBER_OF_SUB_DIRECTORIES, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.tsx index ad0b56190d2a..c9d81841b052 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.tsx @@ -37,8 +37,10 @@ const EntityLineageTab = lazy(() => ) ); +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.DASHBOARD},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; +export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.DASHBOARD},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; export const getMlModelDetailsPageTabs = ({ feedCount, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.tsx index add5289066fc..37bf41237b59 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.tsx @@ -46,8 +46,11 @@ const EntityLineageTab = lazy(() => ) ); +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for the +// rationale behind this trim — same logic, applied here. // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.OWNERS},${TabSpecificField.TASKS}, ${TabSpecificField.PIPELINE_STATUS}, ${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; +export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.OWNERS},${TabSpecificField.TASKS}, ${TabSpecificField.PIPELINE_STATUS}, ${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; export const getTaskExecStatus = (taskName: string, tasks: TaskStatus[]) => { return tasks.find((task) => task.name === taskName)?.executionStatus; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx index 65db1b4f129e..3ee46d3d1ab8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx @@ -44,8 +44,10 @@ const EntityLineageTab = lazy(() => ) ); +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FIELDS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DOMAINS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`; +export const defaultFields = `${TabSpecificField.FIELDS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DOMAINS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS}`; export const makeData = ( columns: SearchIndexField[] = [] diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SpreadsheetDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SpreadsheetDetailsUtils.tsx index 65125b8feedf..e49e18ae655b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SpreadsheetDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SpreadsheetDetailsUtils.tsx @@ -35,6 +35,8 @@ export interface SpreadsheetDetailPageTabProps { labelMap?: Record; } +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. export const defaultFields = [ TabSpecificField.OWNERS, TabSpecificField.WORKSHEETS, @@ -43,7 +45,6 @@ export const defaultFields = [ TabSpecificField.DOMAINS, TabSpecificField.DATA_PRODUCTS, TabSpecificField.VOTES, - TabSpecificField.EXTENSION, TabSpecificField.MIME_TYPE, TabSpecificField.CREATED_TIME, TabSpecificField.MODIFIED_TIME, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx index 4bdf01f49632..40512da6072c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx @@ -34,8 +34,10 @@ const EntityLineageTab = lazy(() => ) ); +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. // eslint-disable-next-line max-len -export const STORED_PROCEDURE_DEFAULT_FIELDS = `${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS}, ${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS}, ${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; +export const STORED_PROCEDURE_DEFAULT_FIELDS = `${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS}, ${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS}, ${TabSpecificField.VOTES}`; export const getStoredProcedureDetailsPageTabs = ({ activeTab, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/WorksheetDetailsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/WorksheetDetailsUtils.tsx index 8ab775988535..c07634a60767 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/WorksheetDetailsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/WorksheetDetailsUtils.tsx @@ -34,6 +34,8 @@ export interface WorksheetDetailPageTabProps { labelMap?: Record; } +// `EXTENSION` (custom-property values) is fetched lazily by {@link useLazyEntityExtension} +// when the user activates the Custom Properties tab. See DatasetDetailsUtils.ts for context. export const defaultFields = [ TabSpecificField.OWNERS, TabSpecificField.FOLLOWERS, @@ -41,7 +43,6 @@ export const defaultFields = [ TabSpecificField.DOMAINS, TabSpecificField.DATA_PRODUCTS, TabSpecificField.VOTES, - TabSpecificField.EXTENSION, TabSpecificField.ROW_COUNT, TabSpecificField.COLUMNS, TabSpecificField.ROW_COUNT, diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 60466dff516a..6533a6a7d67c 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -2375,9 +2375,8 @@ compare-versions "^4.1.2" "@openmetadata/ui-core-components@link:../../../../../openmetadata-ui-core-components/src/main/resources/ui": - version "1.0.0" - dependencies: - "@material/material-color-utilities" "^0.3.0" + version "0.0.0" + uid "" "@peculiar/asn1-schema@^2.3.13", "@peculiar/asn1-schema@^2.3.8": version "2.6.0" @@ -3542,6 +3541,18 @@ "@tailwindcss/oxide" "4.2.4" tailwindcss "4.2.4" +"@tanstack/query-core@5.100.9": + version "5.100.9" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.100.9.tgz#dcf44ef25cf42a4da229bcab1d8d33e80a740a99" + integrity sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ== + +"@tanstack/react-query@^5.62.0": + version "5.100.9" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.100.9.tgz#0c701bf56f38b484602255a92d4c9e452a04807d" + integrity sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A== + dependencies: + "@tanstack/query-core" "5.100.9" + "@testing-library/dom@^9.0.0": version "9.3.4" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce"