Fix landing page feedbacks (#22648)

* Fix landing page feedbacks

* fix failing tests
This commit is contained in:
Harshit Shah 2025-07-30 12:39:08 +05:30 committed by GitHub
parent dd879e2976
commit f098e30e80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 116 additions and 53 deletions

View File

@ -132,11 +132,6 @@ function CustomizeMyData({
[layout]
);
const emptyWidgetPlaceholder = useMemo(
() => layout.find((widget) => widget.i.endsWith('.EmptyWidgetPlaceholder')),
[layout]
);
const disableSave = useMemo(() => {
const filteredLayout = layout.filter((widget) =>
widget.i.startsWith('KnowledgePanel')
@ -242,7 +237,6 @@ function CustomizeMyData({
customizeMyDataPageClassBase.landingPageWidgetMargin,
customizeMyDataPageClassBase.landingPageWidgetMargin,
]}
maxRows={emptyWidgetPlaceholder?.y}
preventCollision={false}
rowHeight={customizeMyDataPageClassBase.landingPageRowHeight}
onLayoutChange={handleLayoutUpdate}>

View File

@ -152,6 +152,7 @@ const MyFeedWidgetInternal = ({
handleRemoveWidget={handleRemoveWidget}
icon={<ActivityFeedIcon height={22} width={22} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.EXPLORE}
selectedSortBy={selectedFilter}
sortOptions={FEED_WIDGET_FILTER_OPTIONS}
title={t('label.activity-feed')}

View File

@ -281,6 +281,7 @@ const MyDataWidgetInternal = ({
handleRemoveWidget={handleRemoveWidget}
icon={<MyDataIcon height={24} width={24} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.EXPLORE}
selectedSortBy={selectedFilter}
sortOptions={MY_DATA_WIDGET_FILTER_OPTIONS}
title={t('label.my-data')}

View File

@ -82,7 +82,10 @@ describe('PersonaDetailsCard Component', () => {
const personaCardTitle = await screen.findByText('John Doe');
fireEvent.click(personaCardTitle);
expect(mockNavigate).toHaveBeenCalledWith('/settings/persona/john-doe');
expect(mockNavigate).toHaveBeenCalledWith({
hash: '#customize-ui',
pathname: '/settings/persona/john-doe',
});
});
it('should not navigate when persona.fullyQualifiedName is missing', async () => {

View File

@ -29,7 +29,10 @@ export const PersonaDetailsCard = ({ persona }: PersonaDetailsCardProps) => {
const handleCardClick = useCallback(() => {
if (persona.fullyQualifiedName) {
navigate(getPersonaDetailsPath(persona.fullyQualifiedName));
navigate({
pathname: getPersonaDetailsPath(persona.fullyQualifiedName),
hash: '#customize-ui',
});
}
}, [persona]);

View File

@ -266,6 +266,7 @@ function FollowingWidget({
handleRemoveWidget={handleRemoveWidget}
icon={<FollowingAssetsIcon height={22} width={22} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.EXPLORE}
selectedSortBy={selectedEntityFilter}
sortOptions={FOLLOWING_WIDGET_FILTER_OPTIONS}
title={t('label.following-assets')}

View File

@ -16,6 +16,7 @@ import { Button, Col, Row, Typography } from 'antd';
import { MenuInfo } from 'rc-menu/lib/interface';
import { ReactNode } from 'react';
import { Layout } from 'react-grid-layout';
import { useNavigate } from 'react-router-dom';
import { ReactComponent as EditIcon } from '../../../../../assets/svg/edit-new.svg';
import { WidgetConfig } from '../../../../../pages/CustomizablePage/CustomizablePage.interface';
import WidgetMoreOptions from '../WidgetMoreOptions/WidgetMoreOptions';
@ -33,6 +34,7 @@ export interface WidgetHeaderProps {
isEditView?: boolean;
onEditClick?: () => void;
onSortChange?: (key: string) => void;
redirectUrlOnTitleClick?: string;
selectedSortBy?: string;
sortOptions?: Array<{
key: string;
@ -53,12 +55,14 @@ const WidgetHeader = ({
isEditView = false,
onEditClick,
onSortChange,
redirectUrlOnTitleClick,
selectedSortBy,
sortOptions,
title,
widgetKey,
widgetWidth = 2,
}: WidgetHeaderProps) => {
const navigate = useNavigate();
const handleSortByClick = (e: MenuInfo) => {
onSortChange?.(e.key);
};
@ -83,6 +87,12 @@ const WidgetHeader = ({
}
};
const handleTitleClick = () => {
if (redirectUrlOnTitleClick) {
navigate(redirectUrlOnTitleClick);
}
};
return (
<Row
className={`widget-header h-15 ${className}`}
@ -91,11 +101,12 @@ const WidgetHeader = ({
<Col className="d-flex items-center h-full min-h-8">
{icon && <div className="d-flex h-6 w-6 m-r-xs">{icon}</div>}
<Typography.Paragraph
className="widget-title"
className="widget-title cursor-pointer"
ellipsis={{ tooltip: true }}
style={{
maxWidth: widgetWidth === 1 ? '200px' : '525px',
}}>
}}
onClick={handleTitleClick}>
{title}
</Typography.Paragraph>
</Col>

View File

@ -26,6 +26,7 @@ jest.mock('../../../../rest/searchAPI', () => ({
}));
jest.mock('react-router-dom', () => ({
useNavigate: jest.fn(),
Link: jest.fn().mockImplementation(({ children, to }) => (
<a data-testid="entity-link" href={to}>
{children}

View File

@ -22,6 +22,7 @@ import { Link } from 'react-router-dom';
import { ReactComponent as CuratedAssetsEmptyIcon } from '../../../../assets/svg/curated-assets-no-data-placeholder.svg';
import { ReactComponent as CuratedAssetsNoDataIcon } from '../../../../assets/svg/curated-assets-not-found-placeholder.svg';
import { ReactComponent as StarOutlinedIcon } from '../../../../assets/svg/star-outlined.svg';
import { ROUTES } from '../../../../constants/constants';
import {
getSortField,
getSortOrder,
@ -374,6 +375,7 @@ const CuratedAssetsWidget = ({
)
}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.EXPLORE}
selectedSortBy={selectedSortBy}
sortOptions={CURATED_ASSETS_SORT_BY_OPTIONS}
title={

View File

@ -159,6 +159,7 @@ const DataAssetsWidget = ({
handleRemoveWidget={handleRemoveWidget}
icon={<DataAssetIcon height={24} width={24} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.EXPLORE}
selectedSortBy={selectedSortBy}
sortOptions={DATA_ASSETS_SORT_BY_OPTIONS}
title={t('label.data-asset-plural')}

View File

@ -10,10 +10,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Typography } from 'antd';
import { Button, Typography } from 'antd';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ReactComponent as DomainNoDataPlaceholder } from '../../../../assets/svg/domain-no-data-placeholder.svg';
import { ReactComponent as DomainIcon } from '../../../../assets/svg/ic-domains-widget.svg';
import {
@ -35,6 +37,7 @@ import {
} from '../../../../pages/CustomizablePage/CustomizablePage.interface';
import { searchData } from '../../../../rest/miscAPI';
import { getDomainIcon } from '../../../../utils/DomainUtils';
import { getDomainDetailsPath } from '../../../../utils/RouterUtils';
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import WidgetEmptyState from '../Common/WidgetEmptyState/WidgetEmptyState';
import WidgetFooter from '../Common/WidgetFooter/WidgetFooter';
@ -55,6 +58,7 @@ const DomainsWidget = ({
}: WidgetCommonProps) => {
const { t } = useTranslation();
const [domains, setDomains] = useState<Domain[]>([]);
const navigate = useNavigate();
const [selectedSortBy, setSelectedSortBy] = useState<string>(
DOMAIN_SORT_BY_KEYS.LATEST
);
@ -89,6 +93,13 @@ const DomainsWidget = ({
}
}, [selectedSortBy, getSortField, getSortOrder, applySortToData]);
const handleDomainClick = useCallback(
(domain: Domain) => {
navigate(getDomainDetailsPath(domain.fullyQualifiedName ?? ''));
},
[navigate]
);
const domainsWidget = useMemo(() => {
const widget = currentLayout?.find(
(widget: WidgetConfig) => widget.i === widgetKey
@ -127,11 +138,15 @@ const DomainsWidget = ({
<div className="entity-list-body">
<div className="domains-widget-grid">
{domains.map((domain) => (
<div
className={`domain-card${isFullSize ? ' domain-card-full' : ''}`}
key={domain.id}>
<Button
className={classNames('domain-card', {
'domain-card-full': isFullSize,
'p-0': !isFullSize,
})}
key={domain.id}
onClick={() => handleDomainClick(domain)}>
{isFullSize ? (
<>
<div className="d-flex gap-2">
<div
className="domain-card-full-icon"
style={{ background: domain.style?.color }}>
@ -140,11 +155,10 @@ const DomainsWidget = ({
<div className="domain-card-full-content">
<div className="domain-card-full-title-row">
<Typography.Text
className="text-md"
className="font-semibold"
ellipsis={{
tooltip: true,
}}
style={{ fontWeight: 600 }}>
}}>
{domain.displayName || domain.name}
</Typography.Text>
<span className="domain-card-full-count">
@ -152,13 +166,11 @@ const DomainsWidget = ({
</span>
</div>
</div>
</>
</div>
) : (
<>
<div
className="domain-card-bar"
style={{ background: domain.style?.color }}
/>
<div
className="d-flex domain-card-bar"
style={{ borderLeftColor: domain.style?.color }}>
<div className="domain-card-content">
<span className="domain-card-title">
<div className="domain-card-icon">
@ -166,8 +178,7 @@ const DomainsWidget = ({
</div>
<Typography.Text
className="domain-card-name"
ellipsis={{ tooltip: true }}
style={{ marginBottom: 0 }}>
ellipsis={{ tooltip: true }}>
{domain.displayName || domain.name}
</Typography.Text>
</span>
@ -175,9 +186,9 @@ const DomainsWidget = ({
{domain.assets?.length || 0}
</span>
</div>
</>
</div>
)}
</div>
</Button>
))}
</div>
</div>
@ -218,6 +229,7 @@ const DomainsWidget = ({
/>
}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.DOMAIN}
selectedSortBy={selectedSortBy}
sortOptions={DOMAIN_SORT_BY_OPTIONS}
title={t('label.domain-plural')}

View File

@ -72,24 +72,21 @@
box-shadow: @button-box-shadow-default;
position: relative;
overflow: hidden;
height: @size-2xl;
height: 32px;
border: 1px solid #dbe0e7;
border-radius: @size-xs;
}
.domain-card-bar {
width: 6px;
height: 100%;
border-radius: 6px 0 0 6px;
flex-shrink: 0;
background: @primary-color;
border-left: 6px solid @primary-color;
}
.domain-card-content {
display: flex;
align-items: center;
width: 100%;
padding: 0 @size-md;
padding: 0 @size-xs;
min-width: 0;
}
@ -97,33 +94,33 @@
display: flex;
align-items: center;
gap: @size-xs;
font-size: @size-md;
font-size: @font-size-base;
font-weight: @font-medium;
color: @text-color;
color: @grey-700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.domain-card-name {
max-width: 100%;
color: @grey-700 !important;
display: inline-block;
margin-bottom: 0 !important;
max-width: 150px !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
color: @grey-700 !important;
white-space: nowrap;
}
.domain-card-count {
background: @grey-31;
color: @grey-32;
font-size: @font-size-base;
background: @grey-15;
color: @grey-700;
font-size: @size-sm;
font-weight: @font-semibold;
border-radius: @size-md;
padding: 2px @size-sm;
padding: 2px @size-xs;
margin-left: @size-xs;
min-width: 40px;
text-align: center;
display: inline-block;
}
@ -134,11 +131,10 @@
background: @white;
border: 1px solid #dbe0e7;
border-radius: @size-sm;
width: 340px;
height: 80px;
box-shadow: @button-box-shadow-default;
height: 70px !important;
padding: 0 @size-sm;
gap: @size-mlg;
gap: @size-sm;
}
.domain-card-full-icon {
@ -151,6 +147,11 @@
font-size: 32px;
color: @white;
padding: @size-xs;
background: @primary-color;
svg {
color: @white;
}
}
.domain-card-full-content {
@ -166,18 +167,23 @@
align-items: center;
gap: @size-xs;
margin-bottom: 2px;
color: @grey-700;
.ant-typography {
color: @grey-700;
max-width: 240px;
}
}
.domain-card-full-count {
background: @grey-30;
color: @blue-35;
background: @grey-15;
color: @grey-700;
font-size: @size-sm;
font-weight: @font-regular;
border-radius: @size-xs;
font-weight: @font-semibold;
border-radius: @size-md;
padding: 2px @size-xs;
text-align: center;
display: inline-block;
margin-left: 6px;
}
.domain-card-icon {

View File

@ -337,6 +337,7 @@ const KPIWidget = ({
handleRemoveWidget={handleRemoveWidget}
icon={<KPIIcon className="kpi-widget-icon" height={22} width={22} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.KPI_LIST}
title={widgetData?.w === 2 ? t('label.kpi-title') : t('label.kpi')}
widgetKey={widgetKey}
widgetWidth={widgetData?.w}

View File

@ -90,6 +90,10 @@ jest.mock('./KPILegend/KPILegend', () =>
jest.fn().mockReturnValue(<p>KPILegend.Component</p>)
);
jest.mock('react-router-dom', () => ({
useNavigate: jest.fn(),
}));
const mockHandleRemoveWidget = jest.fn();
const mockHandleLayoutUpdate = jest.fn();

View File

@ -97,6 +97,9 @@ const MyTaskWidget = ({
handleRemoveWidget={handleRemoveWidget}
icon={<MyTaskIcon data-testid="task-icon" height={22} width={22} />}
isEditView={isEditView}
redirectUrlOnTitleClick={
currentUser?.name && `users/${currentUser?.name}/task`
}
selectedSortBy={selectedFilter}
sortOptions={MY_TASK_WIDGET_FILTER_OPTIONS}
title={t('label.my-task-plural')}

View File

@ -322,6 +322,7 @@ const TotalDataAssetsWidget = ({
handleRemoveWidget={handleRemoveWidget}
icon={<TotalAssetsWidgetIcon height={24} width={24} />}
isEditView={isEditView}
redirectUrlOnTitleClick={ROUTES.DATA_INSIGHT}
selectedSortBy={selectedSortBy}
sortOptions={DATA_ASSETS_SORT_BY_OPTIONS}
title={t('label.data-insight-total-entity-summary')}

View File

@ -108,6 +108,24 @@ export const PersonaDetailsPage = () => {
}
}, [fqn]);
// Add #customize-ui to URL if # doesn't exist
useEffect(() => {
if (location.hash) {
return;
}
if (!location.hash.includes('customize-ui')) {
navigate(
{
pathname: location.pathname,
search: location.search,
hash: '#customize-ui',
},
{ replace: true }
);
}
}, []);
const fetchCurrentUser = useCallback(async () => {
try {
if (currentUser) {