diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.test.tsx
new file mode 100644
index 00000000000..e8d4495cf6e
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.test.tsx
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2024 Collate.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render, waitFor } from '@testing-library/react';
+import React from 'react';
+import { useParams } from 'react-router-dom';
+import { EntityType, TabSpecificField } from '../../enums/entity.enum';
+import { Include } from '../../generated/type/include';
+import { useFqn } from '../../hooks/useFqn';
+import { getApiCollectionByFQN } from '../../rest/apiCollectionsAPI';
+import { getApiEndPoints } from '../../rest/apiEndpointsAPI';
+import { getFeedCounts } from '../../utils/CommonUtils';
+import APICollectionPage from './APICollectionPage';
+
+jest.mock('../../rest/apiCollectionsAPI', () => ({
+ getApiCollectionByFQN: jest.fn().mockResolvedValue({}),
+ restoreApiCollection: jest.fn().mockResolvedValue({ version: 1 }),
+ patchApiCollection: jest.fn().mockResolvedValue({}),
+ updateApiCollectionVote: jest.fn().mockResolvedValue({}),
+}));
+
+jest.mock('../../rest/apiEndpointsAPI', () => ({
+ getApiEndPoints: jest.fn().mockResolvedValue({ paging: { total: 0 } }),
+}));
+
+jest.mock('../../utils/CommonUtils', () => ({
+ getFeedCounts: jest.fn(),
+ getEntityMissingError: jest.fn(),
+ showErrorToast: jest.fn(),
+ showSuccessToast: jest.fn(),
+ getCountBadge: jest.fn().mockImplementation((count) => {count}),
+}));
+
+jest.mock('../../hooks/useFqn', () => ({
+ useFqn: jest.fn().mockReturnValue({ fqn: 'api.collection.v1' }),
+}));
+
+jest.mock('../../hooks/useCustomPages', () => ({
+ useCustomPages: jest.fn().mockReturnValue({
+ customizedPage: null,
+ isLoading: false,
+ }),
+}));
+
+jest.mock('../../hooks/useTableFilters', () => ({
+ useTableFilters: jest.fn().mockReturnValue({
+ filters: { showDeletedEndpoints: false },
+ setFilters: jest.fn(),
+ }),
+}));
+
+jest.mock('../../context/PermissionProvider/PermissionProvider', () => ({
+ usePermissionProvider: jest.fn().mockReturnValue({
+ getEntityPermissionByFqn: jest.fn().mockResolvedValue({
+ ViewAll: true,
+ EditAll: true,
+ }),
+ }),
+}));
+
+jest.mock('react-router-dom', () => ({
+ useHistory: jest.fn().mockReturnValue({ push: jest.fn() }),
+ useParams: jest
+ .fn()
+ .mockReturnValue({ fqn: 'api.collection.v1', tab: 'api_endpoint' }),
+ useLocation: jest.fn().mockReturnValue({ pathname: '/test' }),
+}));
+
+jest.mock('../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder', () =>
+ jest.fn().mockImplementation(() =>
ErrorPlaceHolder
)
+);
+
+jest.mock('../../components/common/Loader/Loader', () =>
+ jest.fn().mockImplementation(() => Loader
)
+);
+
+jest.mock('../../components/AppRouter/withActivityFeed', () => ({
+ withActivityFeed: jest.fn().mockImplementation((Component) => Component),
+}));
+
+jest.mock('../../components/common/DocumentTitle/DocumentTitle', () =>
+ jest.fn().mockImplementation(() => DocumentTitle
)
+);
+
+jest.mock(
+ '../../components/DataAssets/DataAssetsHeader/DataAssetsHeader.component',
+ () => ({
+ DataAssetsHeader: jest
+ .fn()
+ .mockImplementation(() => DataAssetsHeader
),
+ })
+);
+
+jest.mock(
+ '../../components/Customization/GenericProvider/GenericProvider',
+ () => ({
+ GenericProvider: jest
+ .fn()
+ .mockImplementation(({ children }) => {children}
),
+ })
+);
+
+jest.mock('../../utils/AdvancedSearchClassBase', () => {
+ const mockAutocomplete = () => async () => ({
+ data: [],
+ paging: { total: 0 },
+ });
+
+ const AdvancedSearchClassBase = Object.assign(
+ jest.fn().mockImplementation(() => ({
+ baseConfig: {
+ types: {
+ multiselect: {
+ widgets: {},
+ },
+ select: {
+ widgets: {
+ text: {
+ operators: ['like', 'not_like', 'regexp'],
+ },
+ },
+ },
+ },
+ },
+ })),
+ {
+ autocomplete: mockAutocomplete,
+ }
+ );
+
+ return {
+ AdvancedSearchClassBase,
+ __esModule: true,
+ default: AdvancedSearchClassBase,
+ };
+});
+
+describe('APICollectionPage', () => {
+ const renderComponent = () => {
+ return render();
+ };
+
+ it('should call APIs with updated FQN when FQN changes', async () => {
+ // Set initial FQN
+ (useParams as jest.Mock).mockReturnValue({
+ fqn: 'api.collection.v1',
+ tab: 'api_endpoint',
+ });
+
+ const { rerender } = renderComponent();
+
+ // Verify initial API calls
+ await waitFor(() => {
+ expect(getApiCollectionByFQN).toHaveBeenCalledWith('api.collection.v1', {
+ fields: `${TabSpecificField.OWNERS},${TabSpecificField.TAGS},${TabSpecificField.DOMAIN},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION},${TabSpecificField.DATA_PRODUCTS}`,
+ include: Include.All,
+ });
+ expect(getApiEndPoints).toHaveBeenCalledWith({
+ apiCollection: 'api.collection.v1',
+ service: '',
+ paging: { limit: 0 },
+ include: Include.NonDeleted,
+ });
+ expect(getFeedCounts).toHaveBeenCalledWith(
+ EntityType.API_COLLECTION,
+ 'api.collection.v1',
+ expect.any(Function)
+ );
+ });
+
+ // Clear mocks to track new calls
+ jest.clearAllMocks();
+
+ // Change FQN
+ (useParams as jest.Mock).mockReturnValue({
+ fqn: 'api.collection.v2',
+ tab: 'api_endpoint',
+ });
+ (useFqn as jest.Mock).mockReturnValue({ fqn: 'api.collection.v2' });
+
+ // Rerender with new FQN
+ rerender();
+
+ // Verify APIs are called with new FQN
+ await waitFor(() => {
+ expect(getApiCollectionByFQN).toHaveBeenCalledWith('api.collection.v2', {
+ fields: `${TabSpecificField.OWNERS},${TabSpecificField.TAGS},${TabSpecificField.DOMAIN},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION},${TabSpecificField.DATA_PRODUCTS}`,
+ include: Include.All,
+ });
+ expect(getApiEndPoints).toHaveBeenCalledWith({
+ apiCollection: 'api.collection.v2',
+ service: '',
+ paging: { limit: 0 },
+ include: Include.NonDeleted,
+ });
+ expect(getFeedCounts).toHaveBeenCalledWith(
+ EntityType.API_COLLECTION,
+ 'api.collection.v2',
+ expect.any(Function)
+ );
+ });
+
+ // Verify each API was called exactly once with new FQN
+ expect(getApiCollectionByFQN).toHaveBeenCalledTimes(1);
+ expect(getApiEndPoints).toHaveBeenCalledTimes(1);
+ expect(getFeedCounts).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.tsx
index d0fd21704a2..6698f74b89b 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/APICollectionPage/APICollectionPage.tsx
@@ -144,13 +144,13 @@ const APICollectionPage: FunctionComponent = () => {
setFeedCount(data);
}, []);
- const getEntityFeedCount = () => {
+ const getEntityFeedCount = useCallback(() => {
getFeedCounts(
EntityType.API_COLLECTION,
decodedAPICollectionFQN,
handleFeedCount
);
- };
+ }, [handleFeedCount, decodedAPICollectionFQN]);
const fetchAPICollectionDetails = useCallback(async () => {
try {
@@ -350,7 +350,11 @@ const APICollectionPage: FunctionComponent = () => {
fetchAPICollectionDetails();
getEntityFeedCount();
}
- }, [viewAPICollectionPermission]);
+ }, [
+ viewAPICollectionPermission,
+ fetchAPICollectionDetails,
+ getEntityFeedCount,
+ ]);
useEffect(() => {
if (viewAPICollectionPermission && decodedAPICollectionFQN) {
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx
index 2c4e1bf6649..a7d31e29b9c 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx
@@ -171,13 +171,13 @@ const DatabaseSchemaPage: FunctionComponent = () => {
setFeedCount(data);
}, []);
- const getEntityFeedCount = () => {
+ const getEntityFeedCount = useCallback(() => {
getFeedCounts(
EntityType.DATABASE_SCHEMA,
decodedDatabaseSchemaFQN,
handleFeedCount
);
- };
+ }, [decodedDatabaseSchemaFQN, handleFeedCount]);
const fetchDatabaseSchemaDetails = useCallback(async () => {
try {
@@ -403,10 +403,14 @@ const DatabaseSchemaPage: FunctionComponent = () => {
if (viewDatabaseSchemaPermission) {
fetchDatabaseSchemaDetails();
fetchStoreProcedureCount();
-
getEntityFeedCount();
}
- }, [viewDatabaseSchemaPermission]);
+ }, [
+ viewDatabaseSchemaPermission,
+ fetchDatabaseSchemaDetails,
+ fetchStoreProcedureCount,
+ getEntityFeedCount,
+ ]);
useEffect(() => {
fetchTableCount();
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx
index 9dda0b9265d..543f78db4cd 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx
@@ -11,12 +11,13 @@
* limitations under the License.
*/
-import { act, render, screen } from '@testing-library/react';
+import { act, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { FEED_COUNT_INITIAL_DATA } from '../../constants/entity.constants';
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
import { getDatabaseSchemaDetailsByFQN } from '../../rest/databaseAPI';
import { getStoredProceduresList } from '../../rest/storedProceduresAPI';
+import { getFeedCounts } from '../../utils/CommonUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import DatabaseSchemaPageComponent from './DatabaseSchemaPage.component';
import {
@@ -322,4 +323,64 @@ describe('Tests for DatabaseSchemaPage', () => {
expect(await screen.findByText('testSchemaTablesTab')).toBeInTheDocument();
});
+
+ it('should refetch data when decodedDatabaseSchemaFQN changes', async () => {
+ const mockUseParams = jest.requireMock('react-router-dom').useParams;
+ mockUseParams.mockReturnValue({
+ fqn: 'sample_data.ecommerce_db.shopify',
+ tab: 'table',
+ });
+
+ (usePermissionProvider as jest.Mock).mockImplementation(() => ({
+ getEntityPermissionByFqn: jest.fn().mockResolvedValue({
+ ViewBasic: true,
+ }),
+ }));
+
+ const { rerender } = render();
+
+ // Wait for initial API calls
+ await waitFor(() => {
+ expect(getDatabaseSchemaDetailsByFQN).toHaveBeenCalledWith(
+ 'sample_data.ecommerce_db.shopify',
+ expect.any(Object)
+ );
+ expect(getStoredProceduresList).toHaveBeenCalledWith({
+ databaseSchema: 'sample_data.ecommerce_db.shopify',
+ limit: 0,
+ });
+ expect(getFeedCounts).toHaveBeenCalledWith(
+ 'databaseSchema',
+ 'sample_data.ecommerce_db.shopify',
+ expect.any(Function)
+ );
+ });
+
+ jest.clearAllMocks();
+
+ mockUseParams.mockReturnValue({
+ fqn: 'Glue.default.information_schema',
+ tab: 'table',
+ });
+
+ // Rerender with new FQN
+ rerender();
+
+ // API calls should be made again with new FQN
+ await waitFor(() => {
+ expect(getDatabaseSchemaDetailsByFQN).toHaveBeenCalledWith(
+ 'Glue.default.information_schema',
+ expect.any(Object)
+ );
+ expect(getStoredProceduresList).toHaveBeenCalledWith({
+ databaseSchema: 'Glue.default.information_schema',
+ limit: 0,
+ });
+ expect(getFeedCounts).toHaveBeenCalledWith(
+ 'databaseSchema',
+ 'Glue.default.information_schema',
+ expect.any(Function)
+ );
+ });
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.test.tsx
new file mode 100644
index 00000000000..b4b923c6c3f
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.test.tsx
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2024 Collate.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render, waitFor } from '@testing-library/react';
+import React from 'react';
+import { useFqn } from '../../hooks/useFqn';
+import { searchData } from '../../rest/miscAPI';
+import { getTagByFqn } from '../../rest/tagAPI';
+import TagPage from './TagPage';
+
+jest.mock('../../rest/tagAPI', () => ({
+ getTagByFqn: jest.fn().mockResolvedValue({
+ name: 'NonSensitive',
+ fullyQualifiedName: 'PII.NonSensitive',
+ }),
+}));
+
+jest.mock('../../rest/miscAPI', () => ({
+ searchData: jest.fn().mockResolvedValue({
+ data: {
+ hits: {
+ total: { value: 0 },
+ },
+ },
+ }),
+}));
+
+jest.mock('../../hooks/useFqn', () => ({
+ useFqn: jest.fn(),
+}));
+
+jest.mock('react-router-dom', () => ({
+ useHistory: jest.fn().mockReturnValue({ push: jest.fn() }),
+ useParams: jest.fn().mockReturnValue({ fqn: 'PII.NonSensitive' }),
+ useLocation: jest
+ .fn()
+ .mockReturnValue({ pathname: '/tags/PII.NonSensitive' }),
+}));
+
+jest.mock('../../context/PermissionProvider/PermissionProvider', () => ({
+ usePermissionProvider: jest.fn().mockReturnValue({
+ getEntityPermission: jest.fn().mockResolvedValue({
+ Create: true,
+ Delete: true,
+ ViewAll: true,
+ EditAll: true,
+ EditDescription: true,
+ EditDisplayName: true,
+ EditCustomFields: true,
+ }),
+ }),
+}));
+
+jest.mock(
+ '../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
+ () => ({
+ useActivityFeedProvider: jest.fn().mockReturnValue({
+ postFeed: jest.fn(),
+ deleteFeed: jest.fn(),
+ updateFeed: jest.fn(),
+ }),
+ __esModule: true,
+ default: 'ActivityFeedProvider',
+ })
+);
+
+jest.mock(
+ '../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component',
+ () => ({
+ ActivityFeedTab: jest.fn().mockImplementation(() => <>ActivityFeedTab>),
+ })
+);
+
+jest.mock('../../components/PageLayoutV1/PageLayoutV1', () => {
+ return jest.fn().mockImplementation(({ children }) => {children}
);
+});
+
+jest.mock(
+ '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component',
+ () => {
+ return jest.fn().mockImplementation(() => TitleBreadcrumb
);
+ }
+);
+
+jest.mock('../../components/common/EntityDescription/DescriptionV1', () => {
+ return jest.fn().mockImplementation(() => DescriptionV1
);
+});
+
+jest.mock('../../components/common/DomainLabel/DomainLabel.component', () => ({
+ DomainLabel: jest.fn().mockImplementation(() => DomainLabel
),
+}));
+
+jest.mock('../../components/common/ResizablePanels/ResizablePanels', () => {
+ return jest.fn().mockImplementation(({ children }) => {children}
);
+});
+
+jest.mock(
+ '../../components/Entity/EntityHeader/EntityHeader.component',
+ () => ({
+ EntityHeader: jest.fn().mockImplementation(() => EntityHeader
),
+ })
+);
+
+jest.mock(
+ '../../components/Explore/EntitySummaryPanel/EntitySummaryPanel.component',
+ () => {
+ return jest.fn().mockImplementation(() => EntitySummaryPanel
);
+ }
+);
+
+jest.mock(
+ '../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.component',
+ () => {
+ return jest.fn().mockImplementation(() => AssetsTabs
);
+ }
+);
+
+jest.mock('../../components/Modals/EntityDeleteModal/EntityDeleteModal', () => {
+ return jest.fn().mockImplementation(() => EntityDeleteModal
);
+});
+
+jest.mock(
+ '../../components/Modals/EntityNameModal/EntityNameModal.component',
+ () => {
+ return jest.fn().mockImplementation(() => EntityNameModal
);
+ }
+);
+
+jest.mock('../../components/Modals/StyleModal/StyleModal.component', () => {
+ return jest.fn().mockImplementation(() => StyleModal
);
+});
+
+jest.mock(
+ '../../components/DataAssets/AssetsSelectionModal/AssetSelectionModal',
+ () => ({
+ AssetSelectionModal: jest
+ .fn()
+ .mockImplementation(() => AssetSelectionModal
),
+ })
+);
+
+describe('TagPage', () => {
+ it('should call getTagData and fetchClassificationTagAssets when tagFqn changes', async () => {
+ (useFqn as jest.Mock).mockReturnValue({ fqn: 'PII.NonSensitive' });
+
+ const { rerender } = render();
+
+ // Verify initial API calls
+ await waitFor(() => {
+ expect(getTagByFqn).toHaveBeenCalledWith('PII.NonSensitive', {
+ fields: 'domain',
+ });
+ expect(searchData).toHaveBeenCalled();
+ });
+
+ jest.clearAllMocks();
+
+ // Change FQN
+ (useFqn as jest.Mock).mockReturnValue({ fqn: 'Certification.Gold' });
+
+ (getTagByFqn as jest.Mock).mockResolvedValueOnce({
+ name: 'Gold',
+ fullyQualifiedName: 'Certification.Gold',
+ });
+
+ rerender();
+
+ await waitFor(() => {
+ expect(getTagByFqn).toHaveBeenCalledWith('Certification.Gold', {
+ fields: 'domain',
+ });
+ expect(searchData).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.tsx
index 280e642cb94..3958b678358 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TagPage/TagPage.tsx
@@ -566,7 +566,7 @@ const TagPage = () => {
useEffect(() => {
getTagData();
fetchClassificationTagAssets();
- }, []);
+ }, [tagFqn]);
useEffect(() => {
if (tagItem) {