mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-08 00:58:06 +00:00
Fix landing page feedbacks (#22648)
* Fix landing page feedbacks * fix failing tests
This commit is contained in:
parent
dd879e2976
commit
f098e30e80
@ -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}>
|
||||
|
@ -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')}
|
||||
|
@ -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')}
|
||||
|
@ -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 () => {
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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')}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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={
|
||||
|
@ -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')}
|
||||
|
@ -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')}
|
||||
|
@ -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 {
|
||||
|
@ -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}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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')}
|
||||
|
@ -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')}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user