feat(ui): supported pagination of glossary terms in glossary overview section (#12177)

* supported pagination of glossary terms in glossay overview section

* remove unwanted try catch

* change as per comments
This commit is contained in:
Ashish Gupta 2023-06-28 03:39:06 +05:30 committed by GitHub
parent 206b155a8b
commit d9cd484657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 133 deletions

View File

@ -288,7 +288,7 @@ const updateTerms = (newTerm) => {
.contains(newTerm)
.should('be.visible')
.click();
cy.get('[data-testid="save-related-term-btn"]').should('be.visible').click();
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
verifyResponseStatusCode('@saveGlossaryTermData', 200);
cy.get('[data-testid="related-term-container"]')

View File

@ -18,7 +18,7 @@ export type SelectOption = {
value: string;
};
export interface InfiniteSelectScrollProps {
export interface AsyncSelectListProps {
mode?: 'multiple';
placeholder?: string;
debounceTimeout?: number;

View File

@ -21,11 +21,11 @@ import { tagRender } from 'utils/TagsUtils';
import { showErrorToast } from 'utils/ToastUtils';
import Fqn from '../../utils/Fqn';
import {
InfiniteSelectScrollProps,
AsyncSelectListProps,
SelectOption,
} from './InfiniteSelectScroll.interface';
} from './AsyncSelectList.interface';
const InfiniteSelectScroll: FC<InfiniteSelectScrollProps> = ({
const AsyncSelectList: FC<AsyncSelectListProps> = ({
mode,
onChange,
fetchOptions,
@ -44,10 +44,11 @@ const InfiniteSelectScroll: FC<InfiniteSelectScrollProps> = ({
setOptions([]);
setIsLoading(true);
try {
const res = await fetchOptions(value, currentPage);
const res = await fetchOptions(value, 1);
setOptions(res.data);
setPaging(res.paging);
setSearchValue(value);
setCurrentPage(1);
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
@ -156,4 +157,4 @@ const InfiniteSelectScroll: FC<InfiniteSelectScrollProps> = ({
);
};
export default InfiniteSelectScroll;
export default AsyncSelectList;

View File

@ -32,7 +32,7 @@ import RelatedTerms from './RelatedTerms';
type Props = {
selectedData: Glossary | GlossaryTerm;
permissions: OperationPermission;
onUpdate: (data: GlossaryTerm | Glossary) => void;
onUpdate: (data: GlossaryTerm | Glossary) => Promise<void>;
isGlossary: boolean;
isVersionView?: boolean;
};

View File

@ -11,19 +11,21 @@
* limitations under the License.
*/
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Select, Space, Spin, Tooltip, Typography } from 'antd';
import { Button, Tooltip, Typography } from 'antd';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import { ReactComponent as IconFlatDoc } from 'assets/svg/ic-flat-doc.svg';
import TagSelectForm from 'components/Tag/TagsSelectForm/TagsSelectForm.component';
import TagButton from 'components/TagButton/TagButton.component';
import { EntityField } from 'constants/Feeds.constants';
import { NO_PERMISSION_FOR_ACTION } from 'constants/HelperTextUtil';
import { ChangeDescription } from 'generated/entity/type';
import { Paging } from 'generated/type/paging';
import { t } from 'i18next';
import { cloneDeep, debounce, includes, isEmpty } from 'lodash';
import { cloneDeep, includes, isEmpty, uniqWith } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { searchData } from 'rest/miscAPI';
import { formatSearchGlossaryTermResponse } from 'utils/APIUtils';
import { getEntityName } from 'utils/EntityUtils';
import {
getChangedEntityNewValue,
@ -41,7 +43,6 @@ import {
import { SearchIndex } from '../../../enums/search.enum';
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
import { EntityReference } from '../../../generated/type/entityReference';
import { formatSearchGlossaryTermResponse } from '../../../utils/APIUtils';
import { getEntityReferenceFromGlossary } from '../../../utils/GlossaryUtils';
import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface';
@ -49,7 +50,7 @@ interface RelatedTermsProps {
isVersionView?: boolean;
permissions: OperationPermission;
glossaryTerm: GlossaryTerm;
onGlossaryTermUpdate: (data: GlossaryTerm) => void;
onGlossaryTermUpdate: (data: GlossaryTerm) => Promise<void>;
}
const RelatedTerms = ({
@ -60,26 +61,21 @@ const RelatedTerms = ({
}: RelatedTermsProps) => {
const history = useHistory();
const [isIconVisible, setIsIconVisible] = useState<boolean>(true);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [options, setOptions] = useState<EntityReference[]>([]);
const [selectedOption, setSelectedOption] = useState<EntityReference[]>([]);
const getSearchedTerms = (searchedData: EntityReference[]) => {
const currOptions = selectedOption.map(
(item) => item.fullyQualifiedName || item.name
);
const data = searchedData.filter((item: EntityReference) => {
return !currOptions.includes(item.fullyQualifiedName);
});
return [...selectedOption, ...data];
};
const handleRelatedTermClick = (fqn: string) => {
history.push(getGlossaryPath(fqn));
};
const handleRelatedTermsSave = (newOptions: EntityReference[]) => {
const handleRelatedTermsSave = async (
selectedData: string[]
): Promise<void> => {
const newOptions = uniqWith(
options,
(arrVal, othVal) => arrVal.id === othVal.id
).filter((item) => includes(selectedData, item.fullyQualifiedName));
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
const oldTerms = newOptions.filter((d) =>
includes(glossaryTerm.relatedTerms, d)
@ -97,33 +93,50 @@ const RelatedTerms = ({
relatedTerms: [...oldTerms, ...newTerms],
};
onGlossaryTermUpdate(updatedGlossaryTerm);
await onGlossaryTermUpdate(updatedGlossaryTerm);
setIsIconVisible(true);
};
const suggestionSearch = (searchText = '') => {
setIsLoading(true);
searchData(searchText, 1, PAGE_SIZE, '', '', '', SearchIndex.GLOSSARY)
.then((res) => {
const termResult = formatSearchGlossaryTermResponse(
res.data.hits.hits
).filter((item) => {
return item.fullyQualifiedName !== glossaryTerm.fullyQualifiedName;
});
const fetchGlossaryTerms = async (
searchText = '',
page: number
): Promise<{
data: {
label: string;
value: string;
}[];
paging: Paging;
}> => {
const res = await searchData(
searchText,
page,
PAGE_SIZE,
'',
'',
'',
SearchIndex.GLOSSARY
);
const results = termResult.map(getEntityReferenceFromGlossary);
const termResult = formatSearchGlossaryTermResponse(
res.data.hits.hits
).filter(
(item) => item.fullyQualifiedName !== glossaryTerm.fullyQualifiedName
);
const data = searchText ? getSearchedTerms(results) : results;
setOptions(data);
})
.catch(() => {
setOptions(selectedOption);
})
.finally(() => setIsLoading(false));
const results = termResult.map(getEntityReferenceFromGlossary);
setOptions((prev) => [...prev, ...results]);
return {
data: results.map((item) => ({
label: item.fullyQualifiedName ?? '',
value: item.fullyQualifiedName ?? '',
})),
paging: {
total: res.data.hits.total.value,
},
};
};
const debounceOnSearch = useCallback(debounce(suggestionSearch, 250), []);
const formatOptions = (data: EntityReference[]) => {
return data.map((value) => ({
...value,
@ -134,14 +147,13 @@ const RelatedTerms = ({
};
const handleCancel = () => {
setSelectedOption(formatOptions(glossaryTerm.relatedTerms || []));
setIsIconVisible(true);
};
useEffect(() => {
if (glossaryTerm.relatedTerms?.length) {
setOptions(glossaryTerm.relatedTerms);
setSelectedOption(formatOptions(glossaryTerm.relatedTerms));
if (glossaryTerm) {
setOptions(glossaryTerm.relatedTerms ?? []);
setSelectedOption(formatOptions(glossaryTerm.relatedTerms ?? []));
}
}, [glossaryTerm]);
@ -279,41 +291,17 @@ const RelatedTerms = ({
{isIconVisible ? (
relatedTermsContainer
) : (
<>
<Space className="justify-end w-full m-b-xs" size={8}>
<Button
className="w-6 p-x-05"
data-testid="cancel-related-term-btn"
icon={<CloseOutlined size={12} />}
size="small"
onClick={() => handleCancel()}
/>
<Button
className="w-6 p-x-05"
data-testid="save-related-term-btn"
icon={<CheckOutlined size={12} />}
size="small"
type="primary"
onClick={() => handleRelatedTermsSave(selectedOption)}
/>
</Space>
<Select
className="glossary-select w-full"
filterOption={false}
mode="multiple"
notFoundContent={isLoading ? <Spin size="small" /> : null}
options={formatOptions(options)}
placeholder={t('label.add-entity', {
entity: t('label.related-term-plural'),
})}
value={selectedOption}
onChange={(_, data) => {
setSelectedOption(data as EntityReference[]);
}}
onFocus={() => suggestionSearch()}
onSearch={debounceOnSearch}
/>
</>
<TagSelectForm
defaultValue={selectedOption.map(
(item) => item.fullyQualifiedName ?? ''
)}
fetchApi={fetchGlossaryTerms}
placeholder={t('label.add-entity', {
entity: t('label.related-term-plural'),
})}
onCancel={handleCancel}
onSubmit={handleRelatedTermsSave}
/>
)}
</div>
);

View File

@ -13,7 +13,6 @@
import { Button, Col, Form, Row, Space, Tooltip, Typography } from 'antd';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import { AxiosError } from 'axios';
import { TableTagsProps } from 'components/TableTags/TableTags.interface';
import { DE_ACTIVE_COLOR, PAGE_SIZE } from 'constants/constants';
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
@ -94,29 +93,25 @@ const TagsContainerV2 = ({
}[];
paging: Paging;
}> => {
try {
const tagResponse = await searchQuery({
query: searchQueryParam ? searchQueryParam : '*',
pageNumber: page,
pageSize: PAGE_SIZE,
queryFilter: {},
searchIndex: SearchIndex.TAG,
});
const tagResponse = await searchQuery({
query: searchQueryParam ? searchQueryParam : '*',
pageNumber: page,
pageSize: PAGE_SIZE,
queryFilter: {},
searchIndex: SearchIndex.TAG,
});
return Promise.resolve({
data: formatSearchTagsResponse(tagResponse.hits.hits ?? []).map(
(item) => ({
label: item.fullyQualifiedName ?? '',
value: item.fullyQualifiedName ?? '',
})
),
paging: {
total: tagResponse.hits.total.value,
},
});
} catch (error) {
return Promise.reject({ data: (error as AxiosError).response });
}
return {
data: formatSearchTagsResponse(tagResponse.hits.hits ?? []).map(
(item) => ({
label: item.fullyQualifiedName ?? '',
value: item.fullyQualifiedName ?? '',
})
),
paging: {
total: tagResponse.hits.total.value,
},
};
},
[getTags]
);
@ -132,29 +127,25 @@ const TagsContainerV2 = ({
}[];
paging: Paging;
}> => {
try {
const glossaryResponse = await searchQuery({
query: searchQueryParam ? searchQueryParam : '*',
pageNumber: page,
pageSize: 10,
queryFilter: {},
searchIndex: SearchIndex.GLOSSARY,
});
const glossaryResponse = await searchQuery({
query: searchQueryParam ? searchQueryParam : '*',
pageNumber: page,
pageSize: 10,
queryFilter: {},
searchIndex: SearchIndex.GLOSSARY,
});
return Promise.resolve({
data: formatSearchGlossaryTermResponse(
glossaryResponse.hits.hits ?? []
).map((item) => ({
label: item.fullyQualifiedName ?? '',
value: item.fullyQualifiedName ?? '',
})),
paging: {
total: glossaryResponse.hits.total.value,
},
});
} catch (error) {
return Promise.reject({ data: (error as AxiosError).response });
}
return {
data: formatSearchGlossaryTermResponse(
glossaryResponse.hits.hits ?? []
).map((item) => ({
label: item.fullyQualifiedName ?? '',
value: item.fullyQualifiedName ?? '',
})),
paging: {
total: glossaryResponse.hits.total.value,
},
};
},
[searchQuery, getGlossaryTerms, formatSearchGlossaryTermResponse]
);

View File

@ -13,7 +13,7 @@
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Col, Form, Row, Space } from 'antd';
import { useForm } from 'antd/lib/form/Form';
import InfiniteSelectScroll from 'components/InfiniteSelectScroll/InfiniteSelectScroll';
import AsyncSelectList from 'components/AsyncSelectList/AsyncSelectList';
import React, { useState } from 'react';
import { TagsSelectFormProps } from './TagsSelectForm.interface';
@ -61,7 +61,7 @@ const TagSelectForm = ({
<Col className="gutter-row" span={24}>
<Form.Item noStyle name="tags">
<InfiniteSelectScroll
<AsyncSelectList
fetchOptions={fetchApi}
mode="multiple"
placeholder={placeholder}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { SelectOption } from 'components/InfiniteSelectScroll/InfiniteSelectScroll.interface';
import { SelectOption } from 'components/AsyncSelectList/AsyncSelectList.interface';
import { Paging } from 'generated/type/paging';
export type TagsSelectFormProps = {