Fix(UI): Fixed tables from all the services showing on database Schema page (#9202)

* Fixed tables from all the services showing on database Schema page

* Removed RequestDescription component from DatabaseSchemaPage as it is not required

* Added unit tests for DatabaseSchemaPage

* Removed unnecessary mocks in unit test
This commit is contained in:
Aniket Katkar 2022-12-08 19:03:47 +05:30 committed by GitHub
parent 601b9cd38a
commit 6166d193a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 596 additions and 30 deletions

View File

@ -54,7 +54,6 @@ import TitleBreadcrumb from '../../components/common/title-breadcrumb/title-brea
import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
import PageContainerV1 from '../../components/containers/PageContainerV1';
import Loader from '../../components/Loader/Loader';
import RequestDescriptionModal from '../../components/Modals/RequestDescriptionModal/RequestDescriptionModal';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import {
OperationPermission,
@ -80,7 +79,6 @@ import { CreateThread } from '../../generated/api/feed/createThread';
import { DatabaseSchema } from '../../generated/entity/data/databaseSchema';
import { Table } from '../../generated/entity/data/table';
import { Post, Thread } from '../../generated/entity/feed/thread';
import { EntityReference } from '../../generated/entity/teams/user';
import { Paging } from '../../generated/type/paging';
import { useInfiniteScroll } from '../../hooks/useInfiniteScroll';
import jsonData from '../../jsons/en';
@ -91,10 +89,10 @@ import {
import {
databaseSchemaDetailsTabs,
getCurrentDatabaseSchemaDetailsTab,
getQueryStringForSchemaTables,
getTablesFromSearchResponse,
} from '../../utils/DatabaseSchemaDetailsUtils';
import { getEntityFeedLink } from '../../utils/EntityUtils';
import { getDefaultValue } from '../../utils/FeedElementUtils';
import {
deletePost,
getEntityFieldThreadCounts,
@ -144,7 +142,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
>([]);
const [threadLink, setThreadLink] = useState<string>('');
const [selectedField, setSelectedField] = useState<string>('');
const [paging, setPaging] = useState<Paging>({} as Paging);
const [currentTablesPage, setCurrentTablesPage] =
useState<number>(INITIAL_PAGING_VALUE);
@ -230,13 +227,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
setThreadLink('');
};
const onEntityFieldSelect = (value: string) => {
setSelectedField(value);
};
const closeRequestModal = () => {
setSelectedField('');
};
const getEntityFeedCount = () => {
getFeedCount(
getEntityFeedLink(EntityType.DATABASE_SCHEMA, databaseSchemaFQN)
@ -330,9 +320,13 @@ const DatabaseSchemaPage: FunctionComponent = () => {
try {
setCurrentTablesPage(pageNumber);
const res = await searchQuery({
query: getQueryStringForSchemaTables(
databaseSchema.service,
databaseSchema.database,
databaseSchema
),
pageNumber,
pageSize: PAGE_SIZE,
queryFilter: { 'databaseSchema.name': databaseSchema.name },
searchIndex: SearchIndex.TABLE,
includeDeleted: false,
});
@ -741,7 +735,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onEntityFieldSelect={onEntityFieldSelect}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
@ -802,23 +795,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
/>
) : null}
</Col>
<Col span={24}>
{selectedField ? (
<RequestDescriptionModal
createThread={createThread}
defaultValue={getDefaultValue(
databaseSchema?.owner as EntityReference
)}
header="Request description"
threadLink={getEntityFeedLink(
EntityType.DATABASE_SCHEMA,
databaseSchemaFQN,
selectedField
)}
onCancel={closeRequestModal}
/>
) : null}
</Col>
</Row>
</PageContainerV1>
) : (

View File

@ -0,0 +1,325 @@
/*
* Copyright 2022 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter, useParams } from 'react-router-dom';
import { getDatabaseSchemaDetailsByFQN } from '../../axiosAPIs/databaseAPI';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import DatabaseSchemaPageComponent from './DatabaseSchemaPage.component';
import {
mockEntityPermissions,
mockGetAllFeedsData,
mockGetDatabaseSchemaDetailsByFQNData,
mockGetFeedCountData,
mockPatchDatabaseSchemaDetailsData,
mockPostFeedByIdData,
mockPostThreadData,
mockSearchQueryData,
} from './mocks/DatabaseSchemaPage.mock';
jest.mock('../../utils/ToastUtils', () => ({
showErrorToast: jest
.fn()
.mockImplementation(({ children }) => <div>{children}</div>),
}));
jest.mock('../../components/Loader/Loader', () =>
jest.fn().mockImplementation(() => <div data-testid="Loader">Loader</div>)
);
jest.mock('../../components/containers/PageContainerV1', () =>
jest
.fn()
.mockImplementation(({ children }) => (
<div data-testid="PageContainer">{children}</div>
))
);
jest.mock(
'../../components/common/title-breadcrumb/title-breadcrumb.component',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="TitleBreadcrumb">titleBreadcrumb</div>
))
);
jest.mock('../../components/common/TabsPane/TabsPane', () =>
jest.fn().mockImplementation(() => <div data-testid="TabsPane">TabsPane</div>)
);
jest.mock(
'../../components/common/rich-text-editor/RichTextEditorPreviewer',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="RichTextEditorPreviewer">RichTextEditorPreviewer</div>
))
);
jest.mock('../../components/common/next-previous/NextPrevious', () =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="NextPrevious">NextPrevious</div>
))
);
jest.mock(
'../../components/common/error-with-placeholder/ErrorPlaceHolder',
() =>
jest
.fn()
.mockImplementation(({ children }) => (
<div data-testid="ErrorPlaceHolder">{children}</div>
))
);
jest.mock(
'../../components/common/EntitySummaryDetails/EntitySummaryDetails',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="EntitySummaryDetails">EntitySummaryDetails</div>
))
);
jest.mock(
'../../components/common/entityPageInfo/ManageButton/ManageButton',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="ManageButton">ManageButton</div>
))
);
jest.mock('../../components/common/description/Description', () =>
jest
.fn()
.mockImplementation(
({ onThreadLinkSelect, onDescriptionEdit, onDescriptionUpdate }) => (
<div
data-testid="Description"
onClick={() => {
onThreadLinkSelect('threadLink');
onDescriptionEdit();
onDescriptionUpdate('Updated Description');
}}>
Description
</div>
)
)
);
jest.mock(
'../../components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="ActivityThreadPanel">ActivityThreadPanel</div>
))
);
jest.mock(
'../../components/ActivityFeed/ActivityFeedList/ActivityFeedList',
() =>
jest
.fn()
.mockImplementation(() => (
<div data-testid="ActivityFeedList">ActivityFeedList</div>
))
);
jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockImplementation(() => ({
getEntityPermissionByFqn: jest
.fn()
.mockImplementation(() => Promise.resolve(mockEntityPermissions)),
})),
}));
jest.mock('../../axiosAPIs/searchAPI', () => ({
searchQuery: jest
.fn()
.mockImplementation(() => Promise.resolve(mockSearchQueryData)),
}));
jest.mock('../../axiosAPIs/feedsAPI', () => ({
getAllFeeds: jest
.fn()
.mockImplementation(() => Promise.resolve(mockGetAllFeedsData)),
getFeedCount: jest
.fn()
.mockImplementation(() => Promise.resolve(mockGetFeedCountData)),
postFeedById: jest
.fn()
.mockImplementation(() => Promise.resolve(mockPostFeedByIdData)),
postThread: jest
.fn()
.mockImplementation(() => Promise.resolve(mockPostThreadData)),
}));
jest.mock('../../axiosAPIs/databaseAPI', () => ({
getDatabaseSchemaDetailsByFQN: jest
.fn()
.mockImplementation(() =>
Promise.resolve(mockGetDatabaseSchemaDetailsByFQNData)
),
patchDatabaseSchemaDetails: jest
.fn()
.mockImplementation(() =>
Promise.resolve(mockPatchDatabaseSchemaDetailsData)
),
}));
jest.mock('../../AppState', () => ({
inPageSearchText: '',
}));
jest.mock('react-router-dom', () => ({
Link: jest
.fn()
.mockImplementation(({ children }) => (
<div data-testid="link">{children}</div>
)),
useHistory: jest.fn().mockImplementation(() => ({
history: {
push: jest.fn(),
},
})),
useParams: jest.fn().mockImplementation(() => ({
databaseSchemaFQN: 'sample_data.ecommerce_db.shopify',
tab: 'tables',
})),
}));
describe('Tests for DatabaseSchemaPage', () => {
it('Page should render properly for "Tables" tab', async () => {
act(() => {
render(<DatabaseSchemaPageComponent />, {
wrapper: MemoryRouter,
});
});
const pageContainer = await screen.findByTestId('PageContainer');
const titleBreadcrumb = await screen.findByTestId('TitleBreadcrumb');
const tabsPane = await screen.findByTestId('TabsPane');
const richTextEditorPreviewer = await screen.findAllByTestId(
'RichTextEditorPreviewer'
);
const entitySummaryDetails = await screen.findByTestId(
'EntitySummaryDetails'
);
const manageButton = await screen.findByTestId('ManageButton');
const description = await screen.findByTestId('Description');
const nextPrevious = await screen.findByTestId('NextPrevious');
const databaseSchemaTable = await screen.findByTestId(
'databaseSchema-tables'
);
expect(pageContainer).toBeInTheDocument();
expect(titleBreadcrumb).toBeInTheDocument();
expect(tabsPane).toBeInTheDocument();
expect(richTextEditorPreviewer).toHaveLength(10);
expect(entitySummaryDetails).toBeInTheDocument();
expect(manageButton).toBeInTheDocument();
expect(description).toBeInTheDocument();
expect(nextPrevious).toBeInTheDocument();
expect(databaseSchemaTable).toBeInTheDocument();
});
it('Activity Feed List should render properly for "Activity Feeds" tab', async () => {
(useParams as jest.Mock).mockImplementationOnce(() => ({
databaseSchemaFQN: 'sample_data.ecommerce_db.shopify',
tab: 'activity_feed',
}));
await act(async () => {
render(<DatabaseSchemaPageComponent />, {
wrapper: MemoryRouter,
});
});
const activityFeedList = await screen.findByTestId('ActivityFeedList');
expect(activityFeedList).toBeInTheDocument();
});
it('AcivityThreadPanel should render properly after clicked on thread panel button', async () => {
await act(async () => {
render(<DatabaseSchemaPageComponent />, {
wrapper: MemoryRouter,
});
});
const description = await screen.findByTestId('Description');
expect(description).toBeInTheDocument();
act(() => {
fireEvent.click(description);
});
const activityThreadPanel = await screen.findByTestId(
'ActivityThreadPanel'
);
expect(activityThreadPanel).toBeInTheDocument();
});
it('ErrorPlaceholder should be displayed in case error occurs while fetching database schema details', async () => {
(getDatabaseSchemaDetailsByFQN as jest.Mock).mockImplementationOnce(() =>
Promise.reject('An error occurred')
);
await act(async () => {
render(<DatabaseSchemaPageComponent />, {
wrapper: MemoryRouter,
});
});
const errorPlaceHolder = await screen.findByTestId('ErrorPlaceHolder');
const errorMessage = await screen.findByTestId('error-message');
expect(errorPlaceHolder).toBeInTheDocument();
expect(errorMessage).toHaveTextContent('An error occurred');
});
it('ErrorPlaceholder should be shown in case of not viewing permissions', async () => {
(usePermissionProvider as jest.Mock).mockImplementationOnce(() => ({
getEntityPermissionByFqn: jest.fn().mockImplementation(() =>
Promise.resolve({
...mockEntityPermissions,
ViewAll: false,
ViewBasic: false,
})
),
}));
await act(async () => {
render(<DatabaseSchemaPageComponent />, {
wrapper: MemoryRouter,
});
});
const errorPlaceHolder = await screen.findByTestId('ErrorPlaceHolder');
expect(errorPlaceHolder).toBeInTheDocument();
expect(errorPlaceHolder).toHaveTextContent('message.no-permission-to-view');
});
});

View File

@ -0,0 +1,256 @@
/*
* Copyright 2022 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const mockSearchQueryData = {
hits: {
total: {
value: 18,
relation: 'eq',
},
hits: [
{
_source: {
id: '0e8ec01e-a57f-4173-8d30-deda453174d0',
name: 'dim.api/client',
fullyQualifiedName:
'sample_data.ecommerce_db.shopify."dim.api/client"',
description: 'Description',
displayName: 'dim.api/client',
},
},
{
_source: {
id: '2bcadace-b42b-4f86-b865-72fd1bc0338f',
name: 'dim.product',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify."dim.product"',
description: 'Description',
displayName: 'dim.product',
},
},
{
_source: {
id: '3a39a1ef-0ef4-44ca-ad01-abe389b69a7c',
name: 'dim.shop',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify."dim.shop"',
description:
'This dimension table contains online shop information. This table contains one shop per row.',
displayName: 'dim.shop',
},
},
{
_source: {
id: '06d629d7-6d38-4e57-83d2-c9c4235a7f66',
name: 'dim_address',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_address',
description: 'Description',
displayName: 'dim_address',
},
},
{
_source: {
id: 'a3bbcfef-9704-4cc5-b4ab-f5356afb9c79',
name: 'dim_address_clean',
fullyQualifiedName:
'sample_data.ecommerce_db.shopify.dim_address_clean',
description: 'Created from dim_address after a small cleanup.',
displayName: 'dim_address_clean',
},
},
{
_source: {
id: 'a6d06d44-32f0-491a-99e9-5c319dd8ff23',
name: 'dim_customer',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_customer',
description: 'Description',
displayName: 'dim_customer',
},
},
{
_source: {
id: '86076923-2365-454c-897f-36be6ca02ef2',
name: 'dim_location',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_location',
description: 'Description',
displayName: 'dim_location',
},
},
{
_source: {
id: 'eba81b7a-d086-4daf-ab88-041d4703a2e7',
name: 'dim_staff',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_staff',
description: 'Description',
displayName: 'dim_staff',
},
},
{
_source: {
id: 'bc4c7a5f-2964-4582-adb1-2b523d6035b8',
name: 'fact_line_item',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_line_item',
description: 'Description',
displayName: 'fact_line_item',
},
},
{
_source: {
id: '833438ab-a267-4331-9f9e-be1d9399bda6',
name: 'fact_order',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_order',
description: 'Description',
displayName: 'fact_order',
},
},
],
},
aggregations: {},
};
export const mockGetFeedCountData = {
totalCount: 0,
counts: [],
};
export const mockGetDatabaseSchemaDetailsByFQNData = {
id: '06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
name: 'shopify',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify',
description:
'This **mock** database contains schema related to shopify sales and orders with related dimension tables.',
href: 'http://localhost:8585/api/v1/databaseSchemas/06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
service: {
id: '0e736c54-0c2b-41bc-a877-6df3cdb5a2dd',
type: 'databaseService',
name: 'sample_data',
fullyQualifiedName: 'sample_data',
deleted: false,
href: 'http://localhost:8585/api/v1/services/databaseServices/0e736c54-0c2b-41bc-a877-6df3cdb5a2dd',
},
serviceType: 'BigQuery',
database: {
id: '2e45b3a0-6cfa-470a-862e-4b89f28965c3',
type: 'database',
name: 'ecommerce_db',
fullyQualifiedName: 'sample_data.ecommerce_db',
description:
'This **mock** database contains schemas related to shopify sales and orders with related dimension tables.',
deleted: false,
href: 'http://localhost:8585/api/v1/databases/2e45b3a0-6cfa-470a-862e-4b89f28965c3',
},
deleted: false,
};
export const mockGetAllFeedsData = {
data: [],
paging: {
total: 0,
},
};
export const mockPostThreadData = {
id: 'f5d62891-3381-4adc-91dc-0a886b9cd751',
type: 'Conversation',
href: 'http://localhost:8585/api/v1/feed/f5d62891-3381-4adc-91dc-0a886b9cd751',
threadTs: 1670412439695,
about: '<#E::databaseSchema::sample_data.ecommerce_db.shopify::description>',
entityId: '06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
createdBy: 'admin',
updatedAt: 1670412439696,
updatedBy: 'admin',
resolved: false,
message: 'Testing Conversation',
postsCount: 0,
posts: [],
reactions: [],
};
export const mockPostFeedByIdData = {
id: 'f5d62891-3381-4adc-91dc-0a886b9cd751',
type: 'Conversation',
href: 'http://localhost:8585/api/v1/feed/f5d62891-3381-4adc-91dc-0a886b9cd751',
threadTs: 1670412439695,
about: '<#E::databaseSchema::sample_data.ecommerce_db.shopify::description>',
entityId: '06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
createdBy: 'admin',
updatedAt: 1670414002766,
updatedBy: 'admin',
resolved: false,
message: 'Testing Conversation',
postsCount: 2,
posts: [
{
id: '943f793a-35be-4942-adfe-6b5457f95978',
message: 'Testing',
postTs: 1670414002762,
from: 'admin',
reactions: [],
},
],
reactions: [],
};
export const mockPatchDatabaseSchemaDetailsData = {
id: '06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
name: 'shopify',
fullyQualifiedName: 'sample_data.ecommerce_db.shopify',
description:
'This **mock** database contains schema related to shopify sales and orders with related dimension tables. updated',
version: 0.3,
updatedAt: 1670414195234,
updatedBy: 'admin',
href: 'http://localhost:8585/api/v1/databaseSchemas/06f0c9ef-708a-47e1-a36e-0a2b864c9e5d',
service: {
id: '0e736c54-0c2b-41bc-a877-6df3cdb5a2dd',
type: 'databaseService',
name: 'sample_data',
fullyQualifiedName: 'sample_data',
deleted: false,
href: 'http://localhost:8585/api/v1/services/databaseServices/0e736c54-0c2b-41bc-a877-6df3cdb5a2dd',
},
serviceType: 'BigQuery',
database: {
id: '2e45b3a0-6cfa-470a-862e-4b89f28965c3',
type: 'database',
name: 'ecommerce_db',
fullyQualifiedName: 'sample_data.ecommerce_db',
description:
'This **mock** database contains schemas related to shopify sales and orders with related dimension tables.',
deleted: false,
href: 'http://localhost:8585/api/v1/databases/2e45b3a0-6cfa-470a-862e-4b89f28965c3',
},
changeDescription: {
fieldsAdded: [],
fieldsUpdated: [
{
name: 'description',
oldValue:
'This **mock** database contains schema related to shopify sales and orders with related dimension tables. ',
newValue:
'This **mock** database contains schema related to shopify sales and orders with related dimension tables. updated',
},
],
fieldsDeleted: [],
previousVersion: 0.2,
},
deleted: false,
};
export const mockEntityPermissions = {
Create: true,
Delete: true,
ViewAll: true,
ViewBasic: true,
EditAll: true,
EditDescription: true,
EditDisplayName: true,
EditCustomFields: true,
};

View File

@ -1,5 +1,7 @@
import { TabSpecificField } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum';
import { DatabaseSchema } from '../generated/entity/data/databaseSchema';
import { EntityReference } from '../generated/type/entityReference';
import {
SearchResponse,
TableSearchSource,
@ -38,3 +40,10 @@ export const getCurrentDatabaseSchemaDetailsTab = (tab: string) => {
export const getTablesFromSearchResponse = (
res: SearchResponse<SearchIndex.TABLE, keyof TableSearchSource>
) => res.hits.hits.map((hit) => hit._source);
export const getQueryStringForSchemaTables = (
serviceName: EntityReference,
databaseName: EntityReference,
schemaName: DatabaseSchema
) =>
`(service.name:${serviceName.name}) AND (database.name:${databaseName.name}) AND (databaseSchema.name:${schemaName.name})`;