fix(ui): address persona related feedbacks (#20833)

* fix(ui): address persona related feedbacks

* fix test and address comments

* fix minor ui fixes

* fix tests
This commit is contained in:
Chirag Madlani 2025-04-15 18:41:31 +05:30 committed by GitHub
parent 8e8cc3c614
commit 6495c0f0f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 150 additions and 51 deletions

View File

@ -32,10 +32,6 @@
margin-bottom: 0;
}
.service-icon {
height: 16px;
}
.entity-summary-details {
font-size: 12px;

View File

@ -35,20 +35,42 @@ export const CustomizablePageHeader = ({
const { fqn: personaFqn } = useFqn();
const { currentPageType } = useCustomizeStore();
const history = useHistory();
const [isResetModalOpen, setIsResetModalOpen] = React.useState(false);
const [saving, setSaving] = React.useState(false);
const [confirmationModalOpen, setConfirmationModalOpen] =
React.useState(false);
const [confirmationModalType, setConfirmationModalType] = React.useState<
'reset' | 'close'
>('close');
const handleCancel = () => {
// Go back in history
history.goBack();
};
const { modalTitle, modalDescription } = useMemo(() => {
if (confirmationModalType === 'reset') {
return {
modalTitle: t('label.reset-default-layout'),
modalDescription: t('message.reset-layout-confirmation'),
};
}
return {
modalTitle: t('message.are-you-sure-want-to-text', {
text: t('label.close'),
}),
modalDescription: t('message.unsaved-changes-warning'),
};
}, [confirmationModalType]);
const handleOpenResetModal = useCallback(() => {
setIsResetModalOpen(true);
setConfirmationModalType('reset');
setConfirmationModalOpen(true);
}, []);
const handleCloseResetModal = useCallback(() => {
setIsResetModalOpen(false);
setConfirmationModalOpen(false);
}, []);
const handleSave = useCallback(async () => {
@ -58,9 +80,10 @@ export const CustomizablePageHeader = ({
}, [onSave]);
const handleReset = useCallback(async () => {
onReset();
setIsResetModalOpen(false);
}, [onReset]);
confirmationModalType === 'reset' ? onReset() : handleCancel();
setConfirmationModalOpen(false);
}, [onReset, confirmationModalType, handleCancel]);
const i18Values = useMemo(
() => ({
persona: personaName,
@ -72,6 +95,11 @@ export const CustomizablePageHeader = ({
[personaName]
);
const handleClose = useCallback(() => {
setConfirmationModalType('close');
setConfirmationModalOpen(true);
}, []);
return (
<Card
className="customize-page-header"
@ -99,8 +127,8 @@ export const CustomizablePageHeader = ({
data-testid="cancel-button"
disabled={saving}
icon={<CloseOutlined />}
onClick={handleCancel}>
{t('label.cancel')}
onClick={handleClose}>
{t('label.close')}
</Button>
<Button
data-testid="reset-button"
@ -119,17 +147,18 @@ export const CustomizablePageHeader = ({
</Button>
</Space>
</div>
{isResetModalOpen && (
{confirmationModalOpen && (
<Modal
centered
cancelText={t('label.no')}
data-testid="reset-layout-modal"
okText={t('label.yes')}
open={isResetModalOpen}
title={t('label.reset-default-layout')}
open={confirmationModalOpen}
title={modalTitle}
onCancel={handleCloseResetModal}
onOk={handleReset}>
{t('message.reset-layout-confirmation')}
{modalDescription}
</Modal>
)}
</Card>

View File

@ -12,7 +12,7 @@
*/
import { Col, Row } from 'antd';
import { isEmpty } from 'lodash';
import React, { useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useHistory } from 'react-router-dom';
import { FQN_SEPARATOR_CHAR } from '../../../../constants/char.constants';
import useCustomLocation from '../../../../hooks/useCustomLocation/useCustomLocation';
@ -30,24 +30,29 @@ export const CustomizeUI = () => {
const history = useHistory();
const location = useCustomLocation();
const { fqn: personaFQN } = useFqn();
const activeCat = useMemo(
() => (location.hash?.replace('#', '') || '').split('.')[1] ?? '',
[location]
);
const { activeCat, fullHash } = useMemo(() => {
const activeCat =
(location.hash?.replace('#', '') || '').split('.')[1] ?? '';
return { activeCat, fullHash: location.hash?.replace('#', '') };
}, [location.hash]);
const [items, setItems] = React.useState(categories);
const handleCustomizeItemClick = (category: string) => {
const nestedItems = getCustomizePageOptions(category);
const handleCustomizeItemClick = useCallback(
(category: string) => {
const nestedItems = getCustomizePageOptions(category);
if (isEmpty(nestedItems)) {
history.push(getCustomizePagePath(personaFQN, category));
} else {
history.push({
hash: location.hash + FQN_SEPARATOR_CHAR + category,
});
}
};
if (isEmpty(nestedItems)) {
history.push(getCustomizePagePath(personaFQN, category));
} else {
history.push({
hash: fullHash + FQN_SEPARATOR_CHAR + category,
});
}
},
[history, fullHash, personaFQN]
);
useEffect(() => {
if (!activeCat) {

View File

@ -30,7 +30,10 @@ const SettingItemCard = ({
className,
}: SettingMenuItemProps) => {
const { t } = useTranslation();
const handleOnClick = useCallback(() => onClick(data.key), []);
const handleOnClick = useCallback(
() => onClick(data.key),
[onClick, data.key]
);
return (
<Card

View File

@ -152,7 +152,7 @@ export const UserProfileIcon = () => {
inheritedRoles: currentUser?.inheritedRoles,
personas: currentUser?.personas,
};
}, [currentUser]);
}, [currentUser, currentUser?.personas]);
const personaLabelRenderer = useCallback(
(item: EntityReference) => (

View File

@ -22,6 +22,7 @@ import React, {
import { Link } from 'react-router-dom';
import { BREADCRUMB_SEPARATOR } from '../../../constants/constants';
import TitleBreadcrumbSkeleton from '../Skeleton/BreadCrumb/TitleBreadcrumbSkeleton.component';
import './title-breadcrumb.less';
import { TitleBreadcrumbProps, TitleLink } from './TitleBreadcrumb.interface';
const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
@ -95,7 +96,9 @@ const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
return (
<TitleBreadcrumbSkeleton loading={loading}>
<nav className={className} data-testid="breadcrumb">
<nav
className={classNames('breadcrumb-container', className)}
data-testid="breadcrumb">
<ol className="rounded-4 text-sm font-regular d-flex flex-wrap">
{titleLinks.map((link, index) => {
const classes =

View File

@ -0,0 +1,20 @@
/*
* Copyright 2025 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.
*/
.breadcrumb-container {
ol,
ul {
list-style: none;
padding: 0;
margin: 0;
}
}

View File

@ -43,7 +43,7 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
jwtPrincipalClaimsMapping: [],
userProfilePics: {},
cachedEntityData: {},
selectedPersona: {} as EntityReference,
selectedPersona: undefined,
searchCriteria: '',
inlineAlertDetails: undefined,
applications: [],
@ -121,6 +121,20 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
// This is a placeholder function that will be replaced by the actual function
},
updateCurrentUser: (user) => {
const { personas, defaultPersona } = user;
const { selectedPersona } = get();
// Update selected Persona to fetch the customized pages
if (defaultPersona && personas?.find((p) => p.id === defaultPersona.id)) {
set({ selectedPersona: defaultPersona });
}
// Update selected Persona if Persona is not in the list of personas
if (
selectedPersona &&
!personas?.find((p) => p.id === selectedPersona.id)
) {
set({ selectedPersona: undefined });
}
set({ currentUser: user });
},
updateUserProfilePics: ({ id, user }: { id: string; user: User }) => {

View File

@ -25,7 +25,7 @@ export const useCustomPages = (pageType: PageType | 'Navigation') => {
const [isLoading, setIsLoading] = useState(true);
const fetchDocument = useCallback(async () => {
const pageFQN = `${EntityType.PERSONA}${FQN_SEPARATOR_CHAR}${selectedPersona.fullyQualifiedName}`;
const pageFQN = `${EntityType.PERSONA}${FQN_SEPARATOR_CHAR}${selectedPersona?.fullyQualifiedName}`;
try {
const doc = await getDocumentByFQN(pageFQN);
setCustomizedPage(
@ -37,7 +37,7 @@ export const useCustomPages = (pageType: PageType | 'Navigation') => {
} finally {
setIsLoading(false);
}
}, [selectedPersona.fullyQualifiedName, pageType]);
}, [selectedPersona?.fullyQualifiedName, pageType]);
useEffect(() => {
if (selectedPersona?.fullyQualifiedName) {

View File

@ -53,7 +53,7 @@ export interface ApplicationStore
setApplicationLoading: (loading: boolean) => void;
userProfilePics: Record<string, User>;
cachedEntityData: Record<string, EntityUnion>;
selectedPersona: EntityReference;
selectedPersona?: EntityReference;
authConfig?: AuthenticationConfigurationWithScope;
applicationConfig?: UIThemePreference;
searchCriteria: ExploreSearchIndex | '';

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Wir können {{error}} Elasticsearch für Entität-Indizes nicht durchführen.",
"uninstall-app": "Durch die Deinstallation dieser {{app}}-Anwendung wird sie aus OpenMetadata entfernt.",
"unix-epoch-time-in-ms": "{{prefix}} Unix-Epochenzeit in Millisekunden.",
"unsaved-changes-warning": "Sie haben möglicherweise ungespeicherte Änderungen, die beim Schließen verworfen werden.",
"update-description-message": "Anfrage zur Aktualisierung der Beschreibung für",
"update-displayName-entity": "Aktualisieren Sie die Anzeigenamen für das {{entity}}.",
"update-profiler-settings": "Profiler Einstellungen updaten.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "We are unable to {{error}} Elasticsearch for entity indexes.",
"uninstall-app": "Uninstalling this {{app}} application will remove it from OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "You may have unsaved changes which will be discarded upon close.",
"update-description-message": "Request to update description for",
"update-displayName-entity": "Update Display Name for the {{entity}}.",
"update-profiler-settings": "Update profiler setting.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "No podemos {{error}} Elasticsearch para los índices de entidades.",
"uninstall-app": "Desinstalar esta aplicación {{app}} la eliminará de OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "Puede que tengas cambios no guardados que se descartarán al cerrar.",
"update-description-message": "Solicitud para actualizar la descripción de",
"update-displayName-entity": "Actualizar el nombre visualizado para el {{entity}}.",
"update-profiler-settings": "Actualizar la configuración del perfilador.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Nous ne sommes pas en mesure de {{error}} Elasticsearch pour les index d'entités.",
"uninstall-app": "Désinstaller l'application {{app}} la retirera d'OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} temps Unix (epoch) en millisecondes",
"unsaved-changes-warning": "Vous pourriez avoir des modifications non enregistrées qui seront perdues à la fermeture.",
"update-description-message": "demander la mise à jour de la description pour",
"update-displayName-entity": "Mettre à Jour le Nom d'Affichage de {{entity}}.",
"update-profiler-settings": "Mettre à jour les réglages du profiler.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Non podemos {{error}} Elasticsearch para os índices de entidade.",
"uninstall-app": "Desinstalar esta aplicación {{app}} eliminaráa de OpenMetadata",
"unix-epoch-time-in-ms": "{{prefix}} Tempo Unix epoch en milisegundos",
"unsaved-changes-warning": "Podes ter cambios non gardados que se descartarán ao pechar.",
"update-description-message": "Solicitude de actualización de descrición para",
"update-displayName-entity": "Actualizar o Nome de Visualización para {{entity}}.",
"update-profiler-settings": "Actualizar a configuración do perfilador.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "אנו לא יכולים ל{{error}} לאלסטיקסרץ' שלך עבור אינדקסים של ישויות.",
"uninstall-app": "הסרת התקנת {{app}} תסיר אותו מ-OpenMetadata",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "ייתכן שיש לך שינויים שלא נשמרו שיימחקו בעת הסגירה.",
"update-description-message": "Request to update description for",
"update-displayName-entity": "עדכן את השם המוצג עבור {{entity}}.",
"update-profiler-settings": "עדכן הגדרות הפרופיילר.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "We are unable to {{error}} Elasticsearch for entity indexes.",
"uninstall-app": "Uninstalling this {{app}} application will remove it from OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "保存されていない変更があるかもしれません。閉じると破<E381A8><E7A0B4>されます。",
"update-description-message": "Request to update description for",
"update-displayName-entity": "Update Display Name for the {{entity}}.",
"update-profiler-settings": "Update profiler setting.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "엔터티 인덱스를 위해 Elasticsearch에서 {{error}}할 수 없습니다.",
"uninstall-app": "이 {{app}} 애플리케이션을 제거하면 OpenMetadata에서 삭제됩니다.",
"unix-epoch-time-in-ms": "{{prefix}} 밀리초 단위의 Unix 에포크 시간",
"unsaved-changes-warning": "저장되지 않은 변경 사항이 있을 수 있으며, <20><>으면 폐기됩니다.",
"update-description-message": "설명을 업데이트하라는 요청:",
"update-displayName-entity": "{{entity}}의 표시 이름을 업데이트하세요.",
"update-profiler-settings": "프로파일러 설정을 업데이트하세요.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "आम्ही घटक अनुक्रमणिकांसाठी Elasticsearch {{error}} करण्यात अक्षम आहोत.",
"uninstall-app": "हा {{app}} अनुप्रयोग अनइंस्टॉल केल्याने तो OpenMetaData मधून काढला जाईल",
"unix-epoch-time-in-ms": "{{prefix}} मिलीसेकंदात युनिक्स युग वेळ",
"unsaved-changes-warning": "तुमच्याकडे जतन न केलेले बदल असू शकतात जे बंद केल्यावर रद्द केले जातील.",
"update-description-message": "साठी वर्णन अद्यतनित करण्याची विनंती",
"update-displayName-entity": "{{entity}} साठी प्रदर्शन नाव अद्यतनित करा.",
"update-profiler-settings": "प्रोफाइलर सेटिंग अद्यतनित करा.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "We kunnen geen connectie maken met Elasticsearch voor entiteitsindexen.",
"uninstall-app": "Het verwijderen van deze {{app}}-toepassing verwijdert deze uit OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "Je hebt mogelijk niet-opgeslagen wijzigingen die worden verwijderd bij het sluiten.",
"update-description-message": "Verzoek om de beschrijving aan te passen voor",
"update-displayName-entity": "Update de weergavenaam voor de {{entity}}.",
"update-profiler-settings": "Profielinstellingen updaten.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "ما نمی‌توانیم به Elasticsearch برای ایندکس‌های موجودیت {{error}} دسترسی پیدا کنیم.",
"uninstall-app": "حذف این برنامه {{app}} آن را از OpenMetaData حذف خواهد کرد.",
"unix-epoch-time-in-ms": "{{prefix}} زمان Unix epoch به میلی‌ثانیه",
"unsaved-changes-warning": "ممکن است تغییرات ذخیره نشده‌ای داشته باشید که با بستن از بین خواهند رفت.",
"update-description-message": "درخواست برای به‌روزرسانی توضیحات برای",
"update-displayName-entity": "به‌روزرسانی نام نمایشی برای {{entity}}.",
"update-profiler-settings": "به‌روزرسانی تنظیمات پروفایلر.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Não é possível {{error}} Elasticsearch para índices de entidade.",
"uninstall-app": "Desinstalar este aplicativo {{app}} removerá ele do OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "Você pode ter alterações não salvas que serão descartadas ao fechar.",
"update-description-message": "Solicitar atualização de descrição para",
"update-displayName-entity": "Atualizar o Nome de Exibição para {{entity}}.",
"update-profiler-settings": "Atualizar configurações do examinador.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Não é possível {{error}} Elasticsearch para índices de entidade.",
"uninstall-app": "Desinstalar este aplicativo {{app}} removerá ele do OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "Você pode ter alterações não salvas que serão descartadas ao fechar.",
"update-description-message": "Solicitar atualização de descrição para",
"update-displayName-entity": "Atualizar o Nome de Exibição para {{entity}}.",
"update-profiler-settings": "Atualizar configurações do examinador.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "Мы не можем выполнить {{error}} Elasticsearch для индексов сущностей.",
"uninstall-app": "Uninstalling this {{app}} application will remove it from OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "У вас могут быть несохраненные изменения, которые будут потеряны при закрытии.",
"update-description-message": "Request to update description for",
"update-displayName-entity": "Обновите отображаемое имя для {{entity}}.",
"update-profiler-settings": "Update profiler setting.",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "เราไม่สามารถ {{error}} Elasticsearch สำหรับดัชนีเอนทิตีได้",
"uninstall-app": "การถอนการติดตั้งแอปพลิเคชัน {{app}} นี้จะลบออกจาก OpenMetaData",
"unix-epoch-time-in-ms": "{{prefix}} เวลายุค Unix ในมิลลิวินาที",
"unsaved-changes-warning": "คุณอาจมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกซึ่งจะถูกยกเลิกเมื่อปิด",
"update-description-message": "คำขอเพื่ออัปเดตคำอธิบายสำหรับ",
"update-displayName-entity": "อัปเดตชื่อแสดงสำหรับ {{entity}}",
"update-profiler-settings": "อัปเดตการตั้งค่าโปรไฟล์เลอร์",

View File

@ -2129,6 +2129,7 @@
"unable-to-error-elasticsearch": "无法为 Elasticsearch 进行实体索引{{error}}",
"uninstall-app": "卸载此 {{app}} 应用会将其从 OpenMetaData 中移除",
"unix-epoch-time-in-ms": "{{prefix}} Unix epoch time in milliseconds",
"unsaved-changes-warning": "您可能有未保存的更改关闭时将被<E5B086><E8A2AB>弃。",
"update-description-message": "Request to update description for",
"update-displayName-entity": "更改{{entity}}的显示名",
"update-profiler-settings": "更新分析器设置",

View File

@ -73,7 +73,7 @@ const MyDataPage = () => {
const fetchDocument = async () => {
try {
setIsLoading(true);
if (!isEmpty(selectedPersona)) {
if (selectedPersona) {
const pageFQN = `${EntityType.PERSONA}.${selectedPersona.fullyQualifiedName}`;
const docData = await getDocumentByFQN(pageFQN);

View File

@ -68,7 +68,7 @@ jest.mock(
}
);
let mockSelectedPersona: Record<string, string> = {
let mockSelectedPersona: Record<string, string> | null = {
fullyQualifiedName: mockPersonaName,
};
@ -262,7 +262,7 @@ describe('MyDataPage component', () => {
});
it('MyDataPage should render default widgets when there is no selected persona', async () => {
mockSelectedPersona = {};
mockSelectedPersona = null;
await act(async () => {
render(<MyDataPage />);
});

View File

@ -54,10 +54,16 @@ export const PersonaDetailsPage = () => {
DEFAULT_ENTITY_PERMISSION
);
const location = useCustomLocation();
const activeKey = useMemo(
() => (location.hash?.replace('#', '') || 'users').split('.')[0],
[location]
);
const { activeKey, fullHash } = useMemo(() => {
const activeKey = (location.hash?.replace('#', '') || 'users').split(
'.'
)[0];
return {
activeKey,
fullHash: location.hash?.replace('#', ''),
};
}, [location.hash]);
const { getEntityPermissionByFqn } = usePermissionProvider();
@ -161,11 +167,18 @@ export const PersonaDetailsPage = () => {
history.push(getSettingPath(GlobalSettingsMenuCategory.PERSONA));
};
const handleTabChange = (activeKey: string) => {
history.push({
hash: activeKey,
});
};
const handleTabClick = useCallback(
(key: string) => {
if (fullHash === key) {
return;
}
history.push({
hash: key,
});
},
[history, fullHash]
);
const tabItems = useMemo(() => {
return [
@ -264,7 +277,7 @@ export const PersonaDetailsPage = () => {
</UserSelectableList>
)
}
onChange={handleTabChange}
onTabClick={handleTabClick}
/>
</Col>
</Row>