Support default persona selection (#22592)

* Support default persona selection

* address comments and add styling changes

* fix failing tests

* fix failing tests

* update api calls

* fix failing tests
This commit is contained in:
Harshit Shah 2025-07-29 14:20:36 +05:30 committed by GitHub
parent 9529af123e
commit fc50cce12a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 259 additions and 159 deletions

View File

@ -30,7 +30,6 @@ import {
} from '../../utils/customizeDetails';
import {
checkDefaultStateForNavigationTree,
selectPersona,
validateLeftSidebarWithHiddenItems,
} from '../../utils/customizeNavigation';
import { settingClick } from '../../utils/sidebar';
@ -157,7 +156,7 @@ test.describe('Persona customize UI tab', async () => {
test('should show all the customize options', async ({ adminPage }) => {
await expect(adminPage.getByText('Navigation')).toBeVisible();
await expect(adminPage.getByText('Homepage')).toBeVisible();
await expect(adminPage.getByText('Home Page')).toBeVisible();
await expect(adminPage.getByText('Governance')).toBeVisible();
await expect(adminPage.getByText('Data Assets')).toBeVisible();
});
@ -232,7 +231,6 @@ test.describe('Persona customize UI tab', async () => {
// Select navigation persona
await redirectToHomePage(userPage);
await selectPersona(userPage, navigationPersona);
await userPage.reload();
await userPage.waitForLoadState('networkidle');

View File

@ -83,7 +83,6 @@
}
&.selected {
background: @primary-50;
color: @primary-7;
}
}

View File

@ -14,15 +14,16 @@ import { Document } from '../../../../generated/entity/docStore/document';
export interface CustomiseLandingPageHeaderProps {
addedWidgetsList?: string[];
backgroundColor?: string;
handleAddWidget?: (
newWidgetData: Document,
placeholderWidgetKey: string,
widgetSize: number
) => void;
hideCustomiseButton?: boolean;
overlappedContainer?: boolean;
backgroundColor?: string;
isPreviewHeader?: boolean;
onBackgroundColorUpdate?: (color: string) => Promise<void>;
placeholderWidgetKey?: string;
onHomePage?: boolean;
overlappedContainer?: boolean;
placeholderWidgetKey?: string;
}

View File

@ -50,12 +50,13 @@ import CustomiseSearchBar from './CustomiseSearchBar';
const CustomiseLandingPageHeader = ({
addedWidgetsList,
backgroundColor,
handleAddWidget,
hideCustomiseButton = false,
overlappedContainer = false,
onHomePage = false,
backgroundColor,
isPreviewHeader = false,
onBackgroundColorUpdate,
onHomePage = false,
overlappedContainer = false,
placeholderWidgetKey,
}: CustomiseLandingPageHeaderProps) => {
const { t } = useTranslation();
@ -67,7 +68,7 @@ const CustomiseLandingPageHeader = ({
const [isDomainDropdownOpen, setIsDomainDropdownOpen] = useState(false);
const [announcements, setAnnouncements] = useState<Thread[]>([]);
const [isAnnouncementLoading, setIsAnnouncementLoading] = useState(true);
const [showAnnouncements, setShowAnnouncements] = useState(true);
const [showAnnouncements, setShowAnnouncements] = useState(false);
const bgColor = backgroundColor ?? DEFAULT_HEADER_BG_COLOR;
const landingPageStyle = useMemo(() => {
@ -148,7 +149,7 @@ const CustomiseLandingPageHeader = ({
})}>
<Typography.Text className="welcome-user">
{t('label.welcome', {
name: currentUser?.displayName ?? currentUser?.name,
name: currentUser?.displayName || currentUser?.name,
})}
</Typography.Text>
{!hideCustomiseButton && (
@ -216,7 +217,7 @@ const CustomiseLandingPageHeader = ({
</div>
</DomainSelectableList>
</div>
{recentlyViewData.length > 0 && (
{!isPreviewHeader && recentlyViewData.length > 0 && (
<Carousel
arrows
className={classNames('recently-viewed-data-carousel', {
@ -257,7 +258,8 @@ const CustomiseLandingPageHeader = ({
</div>
</div>
{showAnnouncements &&
{!isPreviewHeader &&
showAnnouncements &&
!isAnnouncementLoading &&
announcements.length > 0 && (
<div className="announcements-container">

View File

@ -210,7 +210,9 @@ export const CustomiseSearchBar = ({ disabled }: { disabled?: boolean }) => {
placement="bottom"
showArrow={false}
trigger={['click']}
onOpenChange={setIsSearchBoxOpen}>
onOpenChange={(open) => {
setIsSearchBoxOpen(isNLPEnabled ? open : !!searchValue && open);
}}>
<Input
autoComplete="off"
bordered={false}

View File

@ -76,6 +76,19 @@
max-height: 125px;
}
}
.search-input {
min-width: 0;
flex: 1 1 auto;
border: 1px solid @grey-25;
border-radius: @border-rad-sm;
background: @white;
padding: @padding-xs @padding-sm;
.ant-popover-inner {
border-radius: @border-rad-sm;
}
}
}
.landing-page-header-bg {
@ -140,15 +153,6 @@
width: 100%;
}
.search-input {
min-width: 0;
flex: 1 1 auto;
border: 1px solid @grey-25;
border-radius: @border-rad-sm;
background: @white;
padding: @padding-xs @padding-sm;
}
.domain-selector {
flex-shrink: 0;
white-space: nowrap;

View File

@ -179,7 +179,7 @@ describe('CustomizablePageHeader', () => {
</MemoryRouter>
);
expect(translation).toHaveBeenCalledWith('label.homepage');
expect(translation).toHaveBeenCalledWith('label.home-page');
});
it('should handle navigation link to persona details', () => {

View File

@ -98,7 +98,7 @@ export const CustomizablePageHeader = ({
() => ({
persona: personaName,
entity: isLandingPage
? t('label.homepage')
? t('label.home-page')
: t(`label.${kebabCase(currentPageType as string)}`),
}),
[personaName, isLandingPage]
@ -121,7 +121,7 @@ export const CustomizablePageHeader = ({
level={5}>
{t('label.customize-entity', {
entity: isLandingPage
? t('label.homepage')
? t('label.home-page')
: t(`label.${kebabCase(currentPageType as string)}`),
})}
</Typography.Title>
@ -129,7 +129,7 @@ export const CustomizablePageHeader = ({
<Transi18next
i18nKey={
isLandingPage
? 'message.customize-homepage-page-header-for-persona'
? 'message.customize-home-page-page-header-for-persona'
: 'message.customize-entity-landing-page-header-for-persona'
}
renderElement={<Link to={getPersonaDetailsPath(personaFqn)} />}

View File

@ -36,6 +36,7 @@ const HeaderTheme = ({ selectedColor, setSelectedColor }: HeaderThemeProps) => {
<div className="header-theme-container p-box bg-white">
<CustomiseLandingPageHeader
hideCustomiseButton
isPreviewHeader
backgroundColor={selectedColor}
/>
</div>

View File

@ -21,13 +21,17 @@ import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { VALIDATION_MESSAGES } from '../../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../../constants/Form.constants';
import { TabSpecificField } from '../../../../enums/entity.enum';
import { Persona } from '../../../../generated/entity/teams/persona';
import { EntityReference } from '../../../../generated/entity/type';
import { Include } from '../../../../generated/type/include';
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
import {
FieldTypes,
FormItemLayout,
} from '../../../../interface/FormUtils.interface';
import { createPersona, updatePersona } from '../../../../rest/PersonaAPI';
import { getUserById } from '../../../../rest/userAPI';
import { getEntityName } from '../../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../../utils/formUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
@ -42,12 +46,28 @@ export const AddEditPersonaForm = ({
}: AddPersonaFormProps) => {
const [form] = useForm();
const [isSaving, setIsSaving] = useState(false);
const { currentUser, setCurrentUser } = useApplicationStore();
const { t } = useTranslation();
const usersList =
Form.useWatch<EntityReference[]>('users', form) ?? persona?.users ?? [];
const isEditMode = !isEmpty(persona);
const fetchCurrentUser = useCallback(async () => {
try {
if (currentUser) {
const user = await getUserById(currentUser.id, {
fields: [TabSpecificField.PERSONAS],
include: Include.All,
});
setCurrentUser({ ...currentUser, ...user });
}
} catch {
return;
}
}, [currentUser, setCurrentUser]);
const handleSubmit = useCallback(
async (data: Persona) => {
try {
@ -65,6 +85,7 @@ export const AddEditPersonaForm = ({
} else {
await createPersona({ ...data, users: usersList, domains });
}
await fetchCurrentUser();
onSave();
} catch (error) {
showErrorToast(error as AxiosError);

View File

@ -216,16 +216,16 @@ function FollowingWidget({
</div>
}
type="text">
<div className="d-flex w-max-full w-min-0 flex-column gap-1">
<div className="d-flex w-max-full w-min-0 flex-column">
{'serviceType' in item && item.serviceType && (
<Typography.Text
className="text-left text-sm font-regular"
className="text-left text-sm font-regular text-grey-600"
ellipsis={{ tooltip: true }}>
{item.serviceType}
</Typography.Text>
)}
<Typography.Text
className="text-left text-sm font-semibold"
className="text-left text-sm font-regular text-grey-800"
ellipsis={{ tooltip: true }}>
{getEntityName(item)}
</Typography.Text>

View File

@ -253,10 +253,8 @@ describe('Test NavBar Component', () => {
render(<NavBarComponent />);
expect(screen.queryByTestId('global-search-bar')).not.toBeInTheDocument();
expect(
screen.queryByTestId('domain-selectable-list')
).not.toBeInTheDocument();
expect(screen.getByTestId('global-search-bar')).toBeInTheDocument();
expect(screen.getByTestId('domain-selectable-list')).toBeInTheDocument();
});
it('should show global search bar and domain dropdown on other routes', () => {

View File

@ -119,14 +119,11 @@ const NavBar = () => {
setPreference,
} = useCurrentUserPreferences();
// Check if current route should hide global search
const shouldHideGlobalSearchAndDomainDropdown = useMemo(() => {
// Check if current route is home page
const isHomePage = useMemo(() => {
const pathname = location.pathname;
return (
pathname === ROUTES.MY_DATA ||
pathname.startsWith(ROUTES.CUSTOMIZE_PAGE.replace('/:fqn/:pageFqn', ''))
);
return pathname === ROUTES.MY_DATA;
}, [location.pathname]);
const fetchOMVersion = async () => {
@ -463,46 +460,52 @@ const NavBar = () => {
}
/>
</Tooltip>
{!shouldHideGlobalSearchAndDomainDropdown && <GlobalSearchBar />}
{!shouldHideGlobalSearchAndDomainDropdown && (
<DomainSelectableList
hasPermission
showAllDomains
popoverProps={{
open: isDomainDropdownOpen,
onOpenChange: (open) => {
setIsDomainDropdownOpen(open);
},
}}
selectedDomain={activeDomainEntityRef}
wrapInButton={false}
onCancel={() => setIsDomainDropdownOpen(false)}
onUpdate={handleDomainChange}>
<Button
className={classNames(
'domain-nav-btn flex-center gap-2 p-x-sm p-y-xs font-medium m-l-md',
{
'domain-active': activeDomain !== DEFAULT_DOMAIN_VALUE,
}
)}
data-testid="domain-dropdown"
onClick={() =>
setIsDomainDropdownOpen(!isDomainDropdownOpen)
}>
<DomainIcon
className="d-flex"
height={20}
name="domain"
width={20}
/>
<Typography.Text ellipsis className="domain-text">
{activeDomainEntityRef
? getEntityName(activeDomainEntityRef)
: activeDomain}
</Typography.Text>
<DropDownIcon width={12} />
</Button>
</DomainSelectableList>
{isHomePage ? (
<Typography.Text className="font-semibold navbar-title">
{t('label.home')}
</Typography.Text>
) : (
<>
<GlobalSearchBar />
<DomainSelectableList
hasPermission
showAllDomains
popoverProps={{
open: isDomainDropdownOpen,
onOpenChange: (open) => {
setIsDomainDropdownOpen(open);
},
}}
selectedDomain={activeDomainEntityRef}
wrapInButton={false}
onCancel={() => setIsDomainDropdownOpen(false)}
onUpdate={handleDomainChange}>
<Button
className={classNames(
'domain-nav-btn flex-center gap-2 p-x-sm p-y-xs font-medium m-l-md',
{
'domain-active': activeDomain !== DEFAULT_DOMAIN_VALUE,
}
)}
data-testid="domain-dropdown"
onClick={() =>
setIsDomainDropdownOpen(!isDomainDropdownOpen)
}>
<DomainIcon
className="d-flex"
height={20}
name="domain"
width={20}
/>
<Typography.Text ellipsis className="domain-text">
{activeDomainEntityRef
? getEntityName(activeDomainEntityRef)
: activeDomain}
</Typography.Text>
<DropDownIcon width={12} />
</Button>
</DomainSelectableList>
</>
)}
</div>

View File

@ -41,6 +41,13 @@
line-height: 21px;
}
}
.navbar-title {
font-size: 22px;
line-height: 32px;
font-weight: 600;
color: @grey-700;
}
}
.domain-dropdown-menu {

View File

@ -13,6 +13,7 @@
import { CheckOutlined } from '@ant-design/icons';
import { Button, Dropdown, Space, Tooltip, Typography } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { compare } from 'fast-json-patch';
import { isEmpty } from 'lodash';
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -30,6 +31,7 @@ import {
} from '../../../../constants/constants';
import { EntityReference } from '../../../../generated/entity/type';
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
import { updateUserDetail } from '../../../../rest/userAPI';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
getImageWithResolutionAndFallback,
@ -94,11 +96,8 @@ const renderLimitedListMenuItem = ({
};
export const UserProfileIcon = () => {
const {
currentUser,
selectedPersona,
setSelectedPersona: updateSelectedPersona,
} = useApplicationStore();
const { currentUser, selectedPersona, setCurrentUser } =
useApplicationStore();
const { onLogoutHandler } = useAuthProvider();
const [isImgUrlValid, setIsImgUrlValid] = useState<boolean>(true);
@ -120,7 +119,18 @@ export const UserProfileIcon = () => {
if (!currentUser) {
return;
}
updateSelectedPersona(persona);
const isAlreadySelected = selectedPersona?.id === persona.id;
const updatedDetails = {
...currentUser,
defaultPersona: isAlreadySelected ? undefined : persona,
};
const jsonPatch = compare(currentUser, updatedDetails);
const response = await updateUserDetail(currentUser?.id, jsonPatch);
setCurrentUser(response);
};
useEffect(() => {

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fireEvent, render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
import { getImageWithResolutionAndFallback } from '../../../../utils/ProfilerUtils';
@ -18,11 +18,12 @@ import { mockPersonaData, mockUserData } from '../mocks/User.mocks';
import { UserProfileIcon } from './UserProfileIcon.component';
const mockLogout = jest.fn();
const mockSetCurrentUser = jest.fn();
jest.mock('../../../../hooks/useApplicationStore', () => ({
useApplicationStore: jest.fn().mockImplementation(() => ({
selectedPersona: {},
setSelectedPersona: jest.fn(),
setCurrentUser: mockSetCurrentUser,
onLogoutHandler: mockLogout,
currentUser: mockUserData,
})),
@ -43,6 +44,14 @@ jest.mock('../../../common/ProfilePicture/ProfilePicture', () =>
jest.fn().mockReturnValue(<div>ProfilePicture</div>)
);
jest.mock('../../../../rest/userAPI', () => ({
updateUserDetail: jest.fn().mockImplementation(() => Promise.resolve({})),
}));
jest.mock('fast-json-patch', () => ({
compare: jest.fn().mockImplementation(() => []),
}));
jest.mock('react-router-dom', () => ({
Link: jest
.fn()
@ -52,6 +61,10 @@ jest.mock('react-router-dom', () => ({
}));
describe('UserProfileIcon', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render User Profile Icon', () => {
const { getByTestId } = render(<UserProfileIcon />);
@ -92,7 +105,8 @@ describe('UserProfileIcon', () => {
id: '3362fe18-05ad-4457-9632-84f22887dda6',
type: 'team',
},
setSelectedPersona: jest.fn(),
setCurrentUser: mockSetCurrentUser,
currentUser: mockUserData,
}));
const { getByTestId } = render(<UserProfileIcon />);
@ -103,7 +117,14 @@ describe('UserProfileIcon', () => {
(useApplicationStore as unknown as jest.Mock).mockImplementation(() => ({
currentUser: { ...mockUserData, teams: [] },
onLogoutHandler: mockLogout,
setCurrentUser: mockSetCurrentUser,
selectedPersona: {},
}));
await act(async () => {
render(<UserProfileIcon />);
});
const teamLabels = screen.queryAllByText('label.team-plural');
teamLabels.forEach((label) => {
@ -122,17 +143,23 @@ describe('UserProfileIcon', () => {
id: '0430976d-092a-46c9-90a8-61c6091a6f38',
type: 'persona',
},
setSelectedPersona: jest.fn(),
setCurrentUser: mockSetCurrentUser,
}));
const { getByTestId } = render(<UserProfileIcon />);
fireEvent.click(getByTestId('dropdown-profile'));
fireEvent.click(getByTestId('persona-label'));
await act(async () => {
fireEvent.click(getByTestId('dropdown-profile'));
});
await act(async () => {
fireEvent.click(getByTestId('persona-label'));
});
expect(getByTestId('check-outlined')).toBeInTheDocument();
});
it('should not show checked if selected persona is true', async () => {
it('should not show checked if selected persona is false', async () => {
(useApplicationStore as unknown as jest.Mock).mockImplementation(() => ({
currentUser: {
...mockUserData,
@ -143,12 +170,18 @@ describe('UserProfileIcon', () => {
id: 'test',
type: 'persona',
},
setSelectedPersona: jest.fn(),
setCurrentUser: mockSetCurrentUser,
}));
const { getByTestId, queryByTestId } = render(<UserProfileIcon />);
fireEvent.click(getByTestId('dropdown-profile'));
fireEvent.click(getByTestId('persona-label'));
await act(async () => {
fireEvent.click(getByTestId('dropdown-profile'));
});
await act(async () => {
fireEvent.click(getByTestId('persona-label'));
});
expect(queryByTestId('check-outlined')).not.toBeInTheDocument();
});

View File

@ -51,7 +51,7 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
set({ inlineAlertDetails });
},
setSelectedPersona: (persona: EntityReference) => {
setSelectedPersona: (persona: EntityReference | undefined) => {
set({ selectedPersona: persona });
},
@ -60,13 +60,16 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
},
setCurrentUser: (user) => {
const { personas, defaultPersona } = user;
// Update selected Persona to fetch the customized pages
if (defaultPersona && personas?.find((p) => p.id === defaultPersona.id)) {
set({ selectedPersona: defaultPersona });
}
const doesDefaultPersonaExist = personas?.find(
(p) => p.id === defaultPersona?.id
);
// Update the current user
set({ currentUser: user });
set({
currentUser: user,
selectedPersona: doesDefaultPersonaExist ? defaultPersona : undefined,
});
},
setAuthConfig: (authConfig: AuthenticationConfigurationWithScope) => {
set({ authConfig });

View File

@ -54,7 +54,7 @@ export interface ApplicationStore
applications: string[];
appPreferences: AppPreferences;
setInlineAlertDetails: (alertDetails?: InlineAlertProps) => void;
setSelectedPersona: (persona: EntityReference) => void;
setSelectedPersona: (persona?: EntityReference) => void;
setApplicationConfig: (config: UIThemePreference) => void;
setAppPreferences: (preferences: AppPreferences) => void;
setCurrentUser: (user: User) => void;

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Hervorgehobene Felder",
"history": "History",
"home": "Startseite",
"home-page": "Startseite",
"homepage": "Startseite",
"hour": "Stunde",
"http-config-source": "HTTP-Konfigurationsquelle",
@ -1853,7 +1854,7 @@
"custom-property-update": "Aktualisierung der benutzerdefinierten Eigenschaft '{{propertyName}}' in {{entityName}} ist {{status}}",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "Personalisieren Sie die {{entity}}-Entitätsseite für die <0>{{persona}}</0>-Persona",
"customize-homepage-page-header-for-persona": "Personalisieren Sie die Homepage-Erfahrung für die <0>{{persona}}</0> Persona",
"customize-home-page-page-header-for-persona": "Personalisieren Sie die Homepage-Erfahrung für die <0>{{persona}}</0> Persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "Der Datenvermögenswert wurde {{actionType}}",
"data-insight-alert-destination-description": "Senden Sie E-Mail-Benachrichtigungen an Administratoren oder Teams.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Highlight Fields",
"history": "History",
"home": "Home",
"home-page": "Home Page",
"homepage": "Homepage",
"hour": "Hour",
"http-config-source": "HTTP Config Source",
@ -1853,7 +1854,7 @@
"custom-property-update": "Custom property '{{propertyName}}' update in {{entityName}} is {{status}}",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "Personalize the {{entity}} Entity Page experience for the <0>{{persona}}</0> persona",
"customize-homepage-page-header-for-persona": "Personalize the Homepage experience for the <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personalize the Home page experience for the <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "Data Asset has been {{actionType}}",
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Campos destacados",
"history": "Historia",
"home": "Inicio",
"home-page": "Página Principal",
"homepage": "Homepage",
"hour": "Hora",
"http-config-source": "Fuente de configuración HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "La actualización de la propiedad personalizada '{{propertyName}}' en {{entityName}} está {{status}}",
"customize-brand-description": "Adapte la experiencia de usuario de {{brandName}} para satisfacer las necesidades de su organización y equipo.",
"customize-entity-landing-page-header-for-persona": "Personaliza la experiencia de la Página de Entidad {{entity}} para la persona <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Personaliza la experiencia de la página de inicio para la <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personaliza la experiencia de la página de inicio para la <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "El activo de datos ha sido {{actionType}}",
"data-insight-alert-destination-description": "Enviar notificaciones por correo electrónico a administradores o equipos.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Champs en surbrillance",
"history": "History",
"home": "Accueil",
"home-page": "Page d'Accueil",
"homepage": "Homepage",
"hour": "Heure",
"http-config-source": "Source de Configuration HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "La mise à jour de la propriété personnalisée '{{propertyName}}' dans {{entityName}} est {{status}}",
"customize-brand-description": "Ajustez l'expérience utilisateur d'{{brandName}} pour répondre à vos besoins d'équipe et d'organisation.",
"customize-entity-landing-page-header-for-persona": "Personnalisez l'expérience de la page d'entité {{entity}} pour la persona <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Personnalisez l'expérience de la page d'accueil pour la <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personnalisez l'expérience de la page d'accueil pour la <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "l'actif de données a été {{actionType}}",
"data-insight-alert-destination-description": "Envoyez des notifications par email aux administrateurs ou aux équipes.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Campos destacados",
"history": "Historial",
"home": "Inicio",
"home-page": "Páxina de Inicio",
"homepage": "Páxina de inicio",
"hour": "Hora",
"http-config-source": "Fonte de configuración HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "A actualización da propiedade personalizada '{{propertyName}}' en {{entityName}} está {{status}}",
"customize-brand-description": "Adapta a experiencia de usuario de {{brandName}} ás necesidades da túa organización e equipo.",
"customize-entity-landing-page-header-for-persona": "Personaliza a experiencia da páxina de entidade {{entity}} para a persoa <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Personaliza a experiencia da páxina de inicio para a <0>{{persona}}</0> persoa",
"customize-home-page-page-header-for-persona": "Personaliza a experiencia da páxina de inicio para a <0>{{persona}}</0> persoa",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "O activo de datos foi {{actionType}}",
"data-insight-alert-destination-description": "Envía notificacións por correo electrónico aos administradores ou equipos.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "שדות מודגשים",
"history": "היסטוריה",
"home": "דף הבית",
"home-page": "דף הבית",
"homepage": "Homepage",
"hour": "שעה",
"http-config-source": "מקור תצורת HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "עדכון המאפיין המותאם אישית '{{propertyName}}' ב{{entityName}} הוא {{status}}",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "התאם אישית את חוויית דף הישות של {{entity}} עבור הפרסונה <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "התאמה אישית של חוויית דף הבית עבור <0>{{persona}}</0> פרסונה",
"customize-home-page-page-header-for-persona": "התאמה אישית של חוויית דף הבית עבור <0>{{persona}}</0> פרסונה",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "נכנס של {{actionType}} נתונים",
"data-insight-alert-destination-description": "שלח התראות בדואר אלקטרוני למנהלים או לצוותים.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "ハイライトフィールド",
"history": "History",
"home": "ホーム",
"home-page": "ホームページ",
"homepage": "Homepage",
"hour": "時",
"http-config-source": "HTTP Config Source",
@ -1853,7 +1854,7 @@
"custom-property-update": "カスタムプロパティ'{{propertyName}}'の{{entityName}}での更新は{{status}}です",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "{{entity}}エンティティページの体験を<0>{{persona}}</0>ペルソナ向けにパーソナライズする",
"customize-homepage-page-header-for-persona": "<0>{{persona}}</0> ペルソナのホームページ体験をパーソナライズする",
"customize-home-page-page-header-for-persona": "<0>{{persona}}</0> ペルソナのホームページ体験をパーソナライズする",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "データアセットが{{actionType}}されました",
"data-insight-alert-destination-description": "Send email notifications to admins or teams.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "하이라이트 필드",
"history": "기록",
"home": "홈",
"home-page": "홈페이지",
"homepage": "홈페이지",
"hour": "시간",
"http-config-source": "HTTP 설정 소스",
@ -1853,7 +1854,7 @@
"custom-property-update": "{{entityName}}의 사용자 정의 속성 '{{propertyName}}' 업데이트가 {{status}}입니다",
"customize-brand-description": "조직 및 팀의 요구에 맞게 {{brandName}} UX를 맞춤 설정하세요.",
"customize-entity-landing-page-header-for-persona": "<0>{{persona}}</0> 페르소나를 위한 {{entity}} 엔티티 페이지 경험을 개인화하세요.",
"customize-homepage-page-header-for-persona": "<0>{{persona}}</0> 페르소나를 위한 홈페이지 경험을 개인화하세요",
"customize-home-page-page-header-for-persona": "<0>{{persona}}</0> 페르소나를 위한 홈페이지 경험을 개인화하세요",
"customize-your-navigation-subheader": "더 나은 접근성을 위해 사이드 내비게이션 메뉴를 관리하고 구성하세요.",
"data-asset-has-been-action-type": "데이터 자산이 {{actionType}}되었습니다",
"data-insight-alert-destination-description": "관리자 또는 팀에게 이메일 알림을 보냅니다.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "हायलाइट फील्ड्स",
"history": "इतिहास",
"home": "मुख्यपृष्ठ",
"home-page": "मुख्यपृष्ठ",
"homepage": "मुख्यपृष्ठ",
"hour": "तास",
"http-config-source": "HTTP कॉन्फिग स्रोत",
@ -1853,7 +1854,7 @@
"custom-property-update": "सानुकूल गुणधर्म '{{propertyName}}' चे {{entityName}} मधील अद्यतन {{status}} आहे",
"customize-brand-description": "तुमच्या संस्थात्मक आणि टीमच्या गरजांसाठी {{brandName}} UX सानुकूलित करा.",
"customize-entity-landing-page-header-for-persona": "{{persona}} व्यक्तिमत्वासाठी {{entity}} घटक पृष्ठ अनुभव वैयक्तिकृत करा",
"customize-homepage-page-header-for-persona": "<0>{{persona}}</0> पर्सोनासाठी होमपेज अनुभव वैयक्तिकृत करा",
"customize-home-page-page-header-for-persona": "<0>{{persona}}</0> पर्सोनासाठी होमपेज अनुभव वैयक्तिकृत करा",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "डेटा ॲसेट {{actionType}} केले आहे",
"data-insight-alert-destination-description": "प्रशासक किंवा टीमला ईमेल सूचना पाठवा.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Gemarkeerde velden",
"history": "Geschiedenis",
"home": "Home",
"home-page": "Startpagina",
"homepage": "Homepage",
"hour": "Uur",
"http-config-source": "HTTP Configuratiebron",
@ -1853,7 +1854,7 @@
"custom-property-update": "Aangepaste eigenschap '{{propertyName}}' update in {{entityName}} is {{status}}",
"customize-brand-description": "De {{brandName}} UX aanpassen aan de behoeften van uw organisatie en team.",
"customize-entity-landing-page-header-for-persona": "Personaliseer de {{entity}} entiteitspagina-ervaring voor de <0>{{persona}}</0> persona",
"customize-homepage-page-header-for-persona": "Personaliseer de homepage-ervaring voor de <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personaliseer de Home page-ervaring voor de <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "Data-asset is {{actionType}}",
"data-insight-alert-destination-description": "Stuur e-mailmeldingen naar beheerders of teams.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "فیلدهای برجسته",
"history": "تاریخچه",
"home": "خانه",
"home-page": "صفحه اصلی",
"homepage": "Homepage",
"hour": "ساعت",
"http-config-source": "منبع پیکربندی HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "بروزرسانی ویژگی سفارشی '{{propertyName}}' در {{entityName}} {{status}} است",
"customize-brand-description": "تجربه کاربری {{brandName}} را برای نیازهای سازمانی و تیمی خود تنظیم کنید.",
"customize-entity-landing-page-header-for-persona": "تجربه صفحه موجودیت {{entity}} را برای شخصیت <0>{{persona}}</0> شخصی‌سازی کنید",
"customize-homepage-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "دارایی داده‌ای {{actionType}} شده است",
"data-insight-alert-destination-description": "اعلان‌های ایمیلی را به مدیران یا تیم‌ها ارسال کنید.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Campos destacados",
"history": "Histórico",
"home": "Início",
"home-page": "Página Inicial",
"homepage": "Página inicial",
"hour": "Hora",
"http-config-source": "Fonte de Configuração HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "Atualização da propriedade personalizada '{{propertyName}}' em {{entityName}} está {{status}}",
"customize-brand-description": "Personalize o layout de {{brandName}} para atender às suas necessidades organizacionais e de equipe.",
"customize-entity-landing-page-header-for-persona": "Personalize a experiência da Página de Entidade {{entity}} para a persona <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Gerencie e organize o menu de navegação lateral para melhorar a acessibilidade.",
"data-asset-has-been-action-type": "O Ativo de Dados foi {{actionType}}",
"data-insight-alert-destination-description": "Envie notificações por e-mail para administradores ou equipes.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Campos destacados",
"history": "Histórico",
"home": "Início",
"home-page": "Página Inicial",
"homepage": "Homepage",
"hour": "Hora",
"http-config-source": "Fonte de Configuração HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "A atualização da propriedade personalizada '{{propertyName}}' em {{entityName}} está {{status}}",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "Personalize a experiência da Página da Entidade {{entity}} para a persona <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-home-page-page-header-for-persona": "Personalize a experiência da página inicial para a <0>{{persona}}</0> persona",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "O Ativo de Dados foi {{actionType}}",
"data-insight-alert-destination-description": "Envie notificações por e-mail para administradores ou equipas.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Выделенные поля",
"history": "History",
"home": "Домой",
"home-page": "Главная Страница",
"homepage": "Homepage",
"hour": "Час",
"http-config-source": "Источник конфигурации HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "Обновление пользовательского свойства '{{propertyName}}' в {{entityName}} имеет статус {{status}}",
"customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.",
"customize-entity-landing-page-header-for-persona": "Персонализируйте страницу сущности {{entity}} для персоны <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "Персонализируйте опыт домашней страницы для <0>{{persona}}</0> персоны",
"customize-home-page-page-header-for-persona": "Персонализируйте опыт домашней страницы для <0>{{persona}}</0> персоны",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "Объект данных был {{actionType}}",
"data-insight-alert-destination-description": "Отправляйте уведомления по электронной почте администраторам или командам.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "ฟิลด์ที่เน้น",
"history": "ประวัติ",
"home": "หน้าแรก",
"home-page": "หน้าแรก",
"homepage": "หน้าแรก",
"hour": "ชั่วโมง",
"http-config-source": "แหล่งที่มาของการตั้งค่า HTTP",
@ -1853,7 +1854,7 @@
"custom-property-update": "การอัปเดตคุณสมบัติที่กำหนดเอง '{{propertyName}}' ใน {{entityName}} คือ {{status}}",
"customize-brand-description": "ปรับแต่ง UX ของ {{brandName}} ให้เหมาะสมกับความต้องการขององค์กรและทีมของคุณ",
"customize-entity-landing-page-header-for-persona": "ปรับแต่งประสบการณ์หน้าเอนทิตี {{entity}} สำหรับบุคคล <0>{{persona}}</0>",
"customize-homepage-page-header-for-persona": "ปรับแต่งประสบการณ์หน้าแรกสำหรับ <0>{{persona}}</0> เพอร์โซนา",
"customize-home-page-page-header-for-persona": "ปรับแต่งประสบการณ์หน้าแรกสำหรับ <0>{{persona}}</0> เพอร์โซนา",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "สินทรัพย์ข้อมูลได้ทำการ {{actionType}}",
"data-insight-alert-destination-description": "ส่งการแจ้งเตือนทางอีเมลไปยังผู้ดูแลระบบหรือทีม",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "Alanları Vurgula",
"history": "Geçmiş",
"home": "Ana Sayfa",
"home-page": "Ana Sayfa",
"homepage": "Ana Sayfa",
"hour": "Saat",
"http-config-source": "HTTP Yapılandırma Kaynağı",
@ -1853,7 +1854,7 @@
"custom-property-update": "{{entityName}} içindeki '{{propertyName}}' özel özelliği güncellemesi {{status}}",
"customize-brand-description": "{{brandName}} kullanıcı deneyimini kurumsal ve ekip ihtiyaçlarınıza göre uyarlayın.",
"customize-entity-landing-page-header-for-persona": "<0>{{persona}}</0> personası için {{entity}} Varlık Sayfası deneyimini kişiselleştirin",
"customize-homepage-page-header-for-persona": "<0>{{persona}}</0> kişiliği için Ana Sayfa deneyimini kişiselleştirin",
"customize-home-page-page-header-for-persona": "<0>{{persona}}</0> kişiliği için Ana Sayfa deneyimini kişiselleştirin",
"customize-your-navigation-subheader": "Daha iyi erişilebilirlik için yan gezinme menüsünü yönetin ve düzenleyin.",
"data-asset-has-been-action-type": "Veri Varlığı {{actionType}} oldu",
"data-insight-alert-destination-description": "Yöneticilere veya takımlara e-posta bildirimleri gönderin.",

View File

@ -740,6 +740,7 @@
"highlight-field-plural": "高亮字段",
"history": "历史",
"home": "主页",
"home-page": "主页",
"homepage": "Homepage",
"hour": "小时",
"http-config-source": "HTTP 配置源",
@ -1853,7 +1854,7 @@
"custom-property-update": "自定义属性'{{propertyName}}'在{{entityName}}中的更新{{status}}",
"customize-brand-description": "自定义 {{brandName}}, 以满足您的组织和团队需求",
"customize-entity-landing-page-header-for-persona": "为<0>{{persona}}</0>角色个性化{{entity}}实体页面体验",
"customize-homepage-page-header-for-persona": "为 <0>{{persona}}</0> 角色个性化首页体验",
"customize-home-page-page-header-for-persona": "为 <0>{{persona}}</0> 角色个性化首页体验",
"customize-your-navigation-subheader": "Manage and organize the side navigation menu for better accessibility.",
"data-asset-has-been-action-type": "数据资产已{{actionType}}",
"data-insight-alert-destination-description": "发送通知邮件给管理员或团队",

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import {
act,
fireEvent,
render,
screen,
@ -155,6 +156,10 @@ jest.mock('../../../hooks/useCustomLocation/useCustomLocation', () => {
}));
});
jest.mock('../../../rest/userAPI', () => {
return jest.fn().mockImplementation(() => Promise.resolve({}));
});
describe('PersonaDetailsPage', () => {
it('Component should render', async () => {
render(<PersonaDetailsPage />), { wrapper: MemoryRouter };
@ -188,7 +193,9 @@ describe('PersonaDetailsPage', () => {
const deleteBtn = await screen.findByTestId('delete-btn');
fireEvent.click(deleteBtn);
await act(async () => {
fireEvent.click(deleteBtn);
});
expect(mockNavigate).toHaveBeenCalledWith('/settings/persona');
});

View File

@ -26,7 +26,6 @@ import Loader from '../../../components/common/Loader/Loader';
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { UserSelectableList } from '../../../components/common/UserSelectableList/UserSelectableList.component';
import EntityHeaderTitle from '../../../components/Entity/EntityHeaderTitle/EntityHeaderTitle.component';
import { EntityName } from '../../../components/Modals/EntityNameModal/EntityNameModal.interface';
import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1';
import { CustomizeUI } from '../../../components/Settings/Persona/CustomizeUI/CustomizeUI';
import { UsersTab } from '../../../components/Settings/Users/UsersTab/UsersTabs.component';
@ -34,11 +33,14 @@ import { GlobalSettingsMenuCategory } from '../../../constants/GlobalSettings.co
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
import { SIZE } from '../../../enums/common.enum';
import { EntityType } from '../../../enums/entity.enum';
import { EntityType, TabSpecificField } from '../../../enums/entity.enum';
import { Persona } from '../../../generated/entity/teams/persona';
import { Include } from '../../../generated/type/include';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation';
import { useFqn } from '../../../hooks/useFqn';
import { getPersonaByName, updatePersona } from '../../../rest/PersonaAPI';
import { getUserById } from '../../../rest/userAPI';
import { getEntityName } from '../../../utils/EntityUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
import { getSettingPath } from '../../../utils/RouterUtils';
@ -47,6 +49,7 @@ import { showErrorToast } from '../../../utils/ToastUtils';
export const PersonaDetailsPage = () => {
const { fqn } = useFqn();
const navigate = useNavigate();
const { currentUser, setCurrentUser } = useApplicationStore();
const [personaDetails, setPersonaDetails] = useState<Persona>();
const [isLoading, setIsLoading] = useState(true);
const { t } = useTranslation();
@ -105,38 +108,23 @@ export const PersonaDetailsPage = () => {
}
}, [fqn]);
const handleDescriptionUpdate = async (description: string) => {
if (!personaDetails) {
const fetchCurrentUser = useCallback(async () => {
try {
if (currentUser) {
const user = await getUserById(currentUser.id, {
fields: [TabSpecificField.PERSONAS],
include: Include.All,
});
setCurrentUser({ ...currentUser, ...user });
}
} catch {
return;
}
const updatedData = { ...personaDetails, description };
const diff = compare(personaDetails, updatedData);
try {
const response = await updatePersona(personaDetails?.id, diff);
setPersonaDetails(response);
} catch (error) {
showErrorToast(error as AxiosError);
}
};
const handleDisplayNameUpdate = async (data: EntityName) => {
if (!personaDetails) {
return;
}
const updatedData = { ...personaDetails, ...data };
const diff = compare(personaDetails, updatedData);
try {
const response = await updatePersona(personaDetails?.id, diff);
setPersonaDetails(response);
} catch (error) {
showErrorToast(error as AxiosError);
}
};
}, [currentUser, setCurrentUser]);
const handlePersonaUpdate = useCallback(
async (data: Partial<Persona>) => {
async (data: Partial<Persona>, shouldRefetch = false) => {
if (!personaDetails) {
return;
}
@ -145,6 +133,9 @@ export const PersonaDetailsPage = () => {
try {
const response = await updatePersona(personaDetails?.id, diff);
setPersonaDetails(response);
if (shouldRefetch) {
await fetchCurrentUser();
}
} catch (error) {
showErrorToast(error as AxiosError);
}
@ -158,12 +149,13 @@ export const PersonaDetailsPage = () => {
(user) => user.id !== userId
);
handlePersonaUpdate({ users: updatedUsers });
handlePersonaUpdate({ users: updatedUsers }, true);
},
[personaDetails]
);
const handleAfterDeleteAction = () => {
const handleAfterDeleteAction = async () => {
await fetchCurrentUser();
navigate(getSettingPath(GlobalSettingsMenuCategory.PERSONA));
};
@ -239,7 +231,7 @@ export const PersonaDetailsPage = () => {
entityId={personaDetails.id}
entityName={personaDetails.name}
entityType={EntityType.PERSONA}
onEditDisplayName={handleDisplayNameUpdate}
onEditDisplayName={(data) => handlePersonaUpdate(data, true)}
/>
</div>
</Col>
@ -252,7 +244,9 @@ export const PersonaDetailsPage = () => {
entityPermission.EditAll || entityPermission.EditDescription
}
showCommentsIcon={false}
onDescriptionUpdate={handleDescriptionUpdate}
onDescriptionUpdate={(description) =>
handlePersonaUpdate({ description })
}
/>
</Col>
<Col span={24}>
@ -266,7 +260,7 @@ export const PersonaDetailsPage = () => {
hasPermission
multiSelect
selectedUsers={personaDetails.users ?? []}
onUpdate={(users) => handlePersonaUpdate({ users })}>
onUpdate={(users) => handlePersonaUpdate({ users }, true)}>
<Button
data-testid="add-persona-button"
size="small"

View File

@ -29,7 +29,7 @@ describe('PersonaUtils', () => {
}),
expect.objectContaining({
key: PageType.LandingPage,
label: 'label.homepage',
label: 'label.home-page',
icon: 'svg-mock',
}),
expect.objectContaining({

View File

@ -70,7 +70,7 @@ export const getCustomizePageCategories = (): SettingMenuItem[] => {
},
{
key: PageType.LandingPage,
label: i18n.t('label.homepage'),
label: i18n.t('label.home-page'),
description: 'Customize the My data page with widget of your preference',
icon: ENTITY_ICONS[camelCase('Homepage')],
},