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"