From b8e15bd96944c34e6c6cffd8f62ea614259ba410 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Tue, 7 Feb 2023 19:39:56 +0530 Subject: [PATCH] fixed: tag api being called on every click in create glossary and create glossary term page (#10126) * fixed: tag api being called on every click in create glossary and create glossary term page * added unit test and optimise the code --- .../src/components/AddTags/AddTags.test.tsx | 73 +++++++++++++++++++ .../components/AddTags/add-tags.component.tsx | 32 +++++--- .../main/resources/ui/src/mocks/Tags.mock.ts | 56 ++++++++++++++ 3 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/AddTags/AddTags.test.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AddTags/AddTags.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AddTags/AddTags.test.tsx new file mode 100644 index 00000000000..c9efe03d354 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/AddTags/AddTags.test.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2023 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, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { mockTagsApiResponse } from 'mocks/Tags.mock'; +import React from 'react'; +import { getAllTagsForOptions } from 'utils/TagsUtils'; +import { AddTags } from './add-tags.component'; + +const mockSetTags = jest.fn(); + +jest.mock('../../utils/TagsUtils', () => ({ + ...jest.requireActual('../../utils/TagsUtils'), + getAllTagsForOptions: jest + .fn() + .mockImplementation(() => Promise.resolve(mockTagsApiResponse.data)), +})); + +describe('AddTags Component', () => { + it('component should render', async () => { + const mockGetAllTagsForOptions = getAllTagsForOptions as jest.Mock; + await act(async () => { + render(); + }); + + expect(await screen.findByRole('combobox')).toBeInTheDocument(); + expect(mockGetAllTagsForOptions).not.toHaveBeenCalled(); + }); + + it('Tags api should call once', async () => { + const mockGetAllTagsForOptions = getAllTagsForOptions as jest.Mock; + const flushPromises = () => new Promise(setImmediate); + await act(async () => { + render(); + }); + const selectBox = await screen.findByRole('combobox'); + + // There should not be any call at 1st render + expect(mockGetAllTagsForOptions.mock.calls).toHaveLength(0); + expect(selectBox).toBeInTheDocument(); + + await act(async () => { + // Click on select element + userEvent.click(selectBox); + await flushPromises(); + + expect( + await mockGetAllTagsForOptions.mock.results[0].value + ).toStrictEqual(mockTagsApiResponse.data); + }); + + const options = await screen.findAllByRole('option'); + + expect(options).toHaveLength(2); + + await act(async () => { + userEvent.click(options[0]); + }); + + expect(mockGetAllTagsForOptions.mock.calls).toHaveLength(1); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AddTags/add-tags.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AddTags/add-tags.component.tsx index 6907d56cf94..68efe16c4aa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AddTags/add-tags.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AddTags/add-tags.component.tsx @@ -12,16 +12,17 @@ */ import { Select } from 'antd'; +import { AxiosError } from 'axios'; import { EntityTags, TagOption } from 'Models'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { showErrorToast } from 'utils/ToastUtils'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { getAllTagsForOptions, getTagOptions } from '../../utils/TagsUtils'; export const AddTags = ({ setTags, }: { - selectedTags?: Array; setTags?: (tags: EntityTags[]) => void; }) => { const [isTagLoading, setIsTagLoading] = useState(false); @@ -33,10 +34,14 @@ export const AddTags = ({ const fetchTags = async () => { setIsTagLoading(true); - const tags = await getAllTagsForOptions(); - setTagList(tags.map((t) => t.fullyQualifiedName || t.name)); - - setIsTagLoading(false); + try { + const tags = await getAllTagsForOptions(); + setTagList(tags.map((t) => t.fullyQualifiedName || t.name)); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsTagLoading(false); + } }; const tagsList = useMemo(() => { @@ -53,12 +58,17 @@ export const AddTags = ({ }, [tagList]); const onClickSelect = useCallback(() => { - fetchTags(); - }, []); + if (tagList.length === 0) { + fetchTags(); + } + }, [tagList]); - const handleChange = useCallback((value: string[]) => { - setSelectedTags && setSelectedTags(value); - }, []); + const handleChange = useCallback( + (value: string[]) => { + setSelectedTags(value); + }, + [setSelectedTags] + ); tagsList.forEach((tag) => options.push({tag.value}) @@ -72,6 +82,8 @@ export const AddTags = ({ return (