mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-29 10:55:09 +00:00
supported scroll in certification popover list (#23534)
This commit is contained in:
parent
bcae472cc0
commit
f934b79d65
@ -532,3 +532,44 @@ export const executeWithRetry = async <T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const readElementInListWithScroll = async (
|
||||||
|
page: Page,
|
||||||
|
locator: Locator,
|
||||||
|
hierarchyElementLocator: Locator
|
||||||
|
) => {
|
||||||
|
const element = locator;
|
||||||
|
|
||||||
|
// Reset scroll position to top before starting pagination
|
||||||
|
await hierarchyElementLocator.hover();
|
||||||
|
await page.mouse.wheel(0, -99999);
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Retry mechanism for pagination
|
||||||
|
let elementCount = await element.count();
|
||||||
|
let retryCount = 0;
|
||||||
|
const maxRetries = 10;
|
||||||
|
|
||||||
|
while (elementCount === 0 && retryCount < maxRetries) {
|
||||||
|
await hierarchyElementLocator.hover();
|
||||||
|
await page.mouse.wheel(0, 1000);
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
// Create fresh locator and check if the article is now visible after this retry
|
||||||
|
const freshArticle = locator;
|
||||||
|
const count = await freshArticle.count();
|
||||||
|
|
||||||
|
// Check if the article is now visible after this retry
|
||||||
|
elementCount = count;
|
||||||
|
|
||||||
|
// If we found the element, validate it and break out of the loop
|
||||||
|
if (count > 0) {
|
||||||
|
await expect(freshArticle).toBeVisible();
|
||||||
|
|
||||||
|
return; // Exit the function early since we found and validated the article
|
||||||
|
}
|
||||||
|
|
||||||
|
retryCount++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -26,6 +26,7 @@ import { TagClass } from '../support/tag/TagClass';
|
|||||||
import {
|
import {
|
||||||
clickOutside,
|
clickOutside,
|
||||||
descriptionBox,
|
descriptionBox,
|
||||||
|
readElementInListWithScroll,
|
||||||
redirectToHomePage,
|
redirectToHomePage,
|
||||||
toastNotification,
|
toastNotification,
|
||||||
uuid,
|
uuid,
|
||||||
@ -486,8 +487,24 @@ export const assignCertification = async (
|
|||||||
certification: TagClass,
|
certification: TagClass,
|
||||||
endpoint: string
|
endpoint: string
|
||||||
) => {
|
) => {
|
||||||
|
const certificationResponse = page.waitForResponse(
|
||||||
|
'/api/v1/tags?parent=Certification&limit=50'
|
||||||
|
);
|
||||||
await page.getByTestId('edit-certification').click();
|
await page.getByTestId('edit-certification').click();
|
||||||
|
await certificationResponse;
|
||||||
|
await page.waitForSelector('.certification-card-popover', {
|
||||||
|
state: 'visible',
|
||||||
|
});
|
||||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||||
|
|
||||||
|
await readElementInListWithScroll(
|
||||||
|
page,
|
||||||
|
page.getByTestId(
|
||||||
|
`radio-btn-${certification.responseData.fullyQualifiedName}`
|
||||||
|
),
|
||||||
|
page.locator('[data-testid="certification-cards"] .ant-radio-group')
|
||||||
|
);
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByTestId(`radio-btn-${certification.responseData.fullyQualifiedName}`)
|
.getByTestId(`radio-btn-${certification.responseData.fullyQualifiedName}`)
|
||||||
.click();
|
.click();
|
||||||
@ -503,6 +520,9 @@ export const assignCertification = async (
|
|||||||
|
|
||||||
export const removeCertification = async (page: Page, endpoint: string) => {
|
export const removeCertification = async (page: Page, endpoint: string) => {
|
||||||
await page.getByTestId('edit-certification').click();
|
await page.getByTestId('edit-certification').click();
|
||||||
|
await page.waitForSelector('.certification-card-popover', {
|
||||||
|
state: 'visible',
|
||||||
|
});
|
||||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||||
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
|
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
|
||||||
await page.getByTestId('clear-certification').click();
|
await page.getByTestId('clear-certification').click();
|
||||||
|
@ -17,6 +17,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ReactComponent as CertificationIcon } from '../../assets/svg/ic-certification.svg';
|
import { ReactComponent as CertificationIcon } from '../../assets/svg/ic-certification.svg';
|
||||||
import { Tag } from '../../generated/entity/classification/tag';
|
import { Tag } from '../../generated/entity/classification/tag';
|
||||||
|
import { Paging } from '../../generated/type/paging';
|
||||||
import { getTags } from '../../rest/tagAPI';
|
import { getTags } from '../../rest/tagAPI';
|
||||||
import { getEntityName } from '../../utils/EntityUtils';
|
import { getEntityName } from '../../utils/EntityUtils';
|
||||||
import { stringToHTML } from '../../utils/StringsUtils';
|
import { stringToHTML } from '../../utils/StringsUtils';
|
||||||
@ -35,18 +36,101 @@ const Certification = ({
|
|||||||
onClose,
|
onClose,
|
||||||
}: CertificationProps) => {
|
}: CertificationProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const popoverRef = useRef<any>(null);
|
const popoverRef = useRef<{ close: () => void } | null>(null);
|
||||||
const [isLoadingCertificationData, setIsLoadingCertificationData] =
|
const [isLoadingCertificationData, setIsLoadingCertificationData] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
const [hasContentLoading, setHasContentLoading] = useState<boolean>(false);
|
||||||
const [certifications, setCertifications] = useState<Array<Tag>>([]);
|
const [certifications, setCertifications] = useState<Array<Tag>>([]);
|
||||||
const [selectedCertification, setSelectedCertification] = useState<string>(
|
const [selectedCertification, setSelectedCertification] = useState<string>(
|
||||||
currentCertificate ?? ''
|
currentCertificate ?? ''
|
||||||
);
|
);
|
||||||
|
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const getCertificationData = async (page = 1, append = false) => {
|
||||||
|
if (page === 1) {
|
||||||
|
setIsLoadingCertificationData(true);
|
||||||
|
} else {
|
||||||
|
setHasContentLoading(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getTags({
|
||||||
|
parent: 'Certification',
|
||||||
|
limit: 50,
|
||||||
|
after: page > 1 ? paging.after : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data, paging: newPaging } = response;
|
||||||
|
|
||||||
|
// Sort certifications with Gold, Silver, Bronze first (only for initial load)
|
||||||
|
const sortedData =
|
||||||
|
page === 1
|
||||||
|
? [...data].sort((a, b) => {
|
||||||
|
const order: Record<string, number> = {
|
||||||
|
Gold: 0,
|
||||||
|
Silver: 1,
|
||||||
|
Bronze: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const aName = getEntityName(a);
|
||||||
|
const bName = getEntityName(b);
|
||||||
|
|
||||||
|
const aOrder = order[aName] ?? 3;
|
||||||
|
const bOrder = order[bName] ?? 3;
|
||||||
|
|
||||||
|
return aOrder - bOrder;
|
||||||
|
})
|
||||||
|
: data;
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
setCertifications((prev) => [...prev, ...sortedData]);
|
||||||
|
} else {
|
||||||
|
setCertifications(sortedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaging(newPaging);
|
||||||
|
setCurrentPage(page);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorToast(
|
||||||
|
err as AxiosError,
|
||||||
|
t('server.entity-fetch-error', {
|
||||||
|
entity: t('label.certification-plural-lowercase'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingCertificationData(false);
|
||||||
|
setHasContentLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = async (e: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
const { currentTarget } = e;
|
||||||
|
const isAtBottom =
|
||||||
|
currentTarget.scrollTop + currentTarget.offsetHeight >=
|
||||||
|
currentTarget.scrollHeight - 1; // -1 for precision tolerance
|
||||||
|
|
||||||
|
if (isAtBottom && paging.after && !hasContentLoading) {
|
||||||
|
await getCertificationData(currentPage + 1, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCertificationData = async (value?: string) => {
|
||||||
|
setIsLoadingCertificationData(true);
|
||||||
|
const certification = certifications.find(
|
||||||
|
(cert) => cert.fullyQualifiedName === value
|
||||||
|
);
|
||||||
|
await onCertificationUpdate?.(certification);
|
||||||
|
setIsLoadingCertificationData(false);
|
||||||
|
popoverRef.current?.close();
|
||||||
|
};
|
||||||
|
|
||||||
const certificationCardData = useMemo(() => {
|
const certificationCardData = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Radio.Group
|
<div
|
||||||
className="h-max-100 overflow-y-auto overflow-x-hidden"
|
className="h-max-100 overflow-y-auto overflow-x-hidden"
|
||||||
value={selectedCertification}>
|
onScroll={handleScroll}>
|
||||||
|
<Radio.Group className="w-full" value={selectedCertification}>
|
||||||
{certifications.map((certificate) => {
|
{certifications.map((certificate) => {
|
||||||
const tagSrc = getTagImageSrc(certificate.style?.iconURL ?? '');
|
const tagSrc = getTagImageSrc(certificate.style?.iconURL ?? '');
|
||||||
const title = getEntityName(certificate);
|
const title = getEntityName(certificate);
|
||||||
@ -86,55 +170,14 @@ const Certification = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
{hasContentLoading && (
|
||||||
|
<div className="flex justify-center p-2">
|
||||||
|
<Loader size="small" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}, [certifications, selectedCertification]);
|
}, [certifications, selectedCertification, hasContentLoading, handleScroll]);
|
||||||
|
|
||||||
const updateCertificationData = async (value?: string) => {
|
|
||||||
setIsLoadingCertificationData(true);
|
|
||||||
const certification = certifications.find(
|
|
||||||
(cert) => cert.fullyQualifiedName === value
|
|
||||||
);
|
|
||||||
await onCertificationUpdate?.(certification);
|
|
||||||
setIsLoadingCertificationData(false);
|
|
||||||
popoverRef.current?.close();
|
|
||||||
};
|
|
||||||
const getCertificationData = async () => {
|
|
||||||
setIsLoadingCertificationData(true);
|
|
||||||
try {
|
|
||||||
const { data } = await getTags({
|
|
||||||
parent: 'Certification',
|
|
||||||
limit: 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort certifications with Gold, Silver, Bronze first
|
|
||||||
const sortedData = [...data].sort((a, b) => {
|
|
||||||
const order: Record<string, number> = {
|
|
||||||
Gold: 0,
|
|
||||||
Silver: 1,
|
|
||||||
Bronze: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const aName = getEntityName(a);
|
|
||||||
const bName = getEntityName(b);
|
|
||||||
|
|
||||||
const aOrder = order[aName] ?? 3;
|
|
||||||
const bOrder = order[bName] ?? 3;
|
|
||||||
|
|
||||||
return aOrder - bOrder;
|
|
||||||
});
|
|
||||||
|
|
||||||
setCertifications(sortedData);
|
|
||||||
} catch (err) {
|
|
||||||
showErrorToast(
|
|
||||||
err as AxiosError,
|
|
||||||
t('server.entity-fetch-error', {
|
|
||||||
entity: t('label.certification-plural-lowercase'),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingCertificationData(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseCertification = async () => {
|
const handleCloseCertification = async () => {
|
||||||
popoverRef.current?.close();
|
popoverRef.current?.close();
|
||||||
@ -143,16 +186,19 @@ const Certification = ({
|
|||||||
|
|
||||||
const onOpenChange = (visible: boolean) => {
|
const onOpenChange = (visible: boolean) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
getCertificationData();
|
getCertificationData(1);
|
||||||
setSelectedCertification(currentCertificate);
|
setSelectedCertification(currentCertificate);
|
||||||
|
setCurrentPage(1);
|
||||||
|
setPaging({} as Paging);
|
||||||
} else {
|
} else {
|
||||||
setSelectedCertification('');
|
setSelectedCertification('');
|
||||||
|
setCertifications([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (popoverProps?.open && certifications.length === 0) {
|
if (popoverProps?.open && certifications.length === 0) {
|
||||||
getCertificationData();
|
getCertificationData(1);
|
||||||
}
|
}
|
||||||
}, [popoverProps?.open]);
|
}, [popoverProps?.open]);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user