chore(ui): customize landing page improvements (#13557)

* CustomizePageClassBase class improvements

* updated the reset button
reordered customize page option on setting page

* localization change for other languages

* added functionality to choose the widget size and updated widget content according to the sizes

* styling fix

* fixed double API calls on my data page and flakiness in showing loader for feeds widget

* fixed code smell
This commit is contained in:
Aniket Katkar 2023-10-14 00:52:06 +05:30 committed by GitHub
parent 2ef49d52ca
commit 53629c8cc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 474 additions and 310 deletions

View File

@ -19,5 +19,21 @@ export interface AddWidgetModalProps {
placeholderWidgetKey: string; placeholderWidgetKey: string;
addedWidgetsList: Array<string>; addedWidgetsList: Array<string>;
handleCloseAddWidgetModal: () => void; handleCloseAddWidgetModal: () => void;
handleAddWidget: (widget: Document, widgetKey: string) => void; handleAddWidget: (
widget: Document,
widgetKey: string,
widgetSize: number
) => void;
}
export interface AddWidgetTabContentProps {
widget: Document;
maxGridSizeSupport: number;
getAddWidgetHandler: (widget: Document, widgetSize: number) => () => void;
widgetSizeOptions: Array<WidgetSizeInfo>;
}
export interface WidgetSizeInfo {
label: string;
value: number;
} }

View File

@ -11,19 +11,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'; import { CheckOutlined } from '@ant-design/icons';
import { import { Modal, Space, Tabs, TabsProps } from 'antd';
Button,
Col,
Image,
Modal,
Row,
Space,
Tabs,
TabsProps,
Tooltip,
Typography,
} from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
@ -32,11 +21,14 @@ import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { WidgetWidths } from '../../../enums/CustomizablePage.enum'; import { WidgetWidths } from '../../../enums/CustomizablePage.enum';
import { Document } from '../../../generated/entity/docStore/document'; import { Document } from '../../../generated/entity/docStore/document';
import { getAllKnowledgePanels } from '../../../rest/DocStoreAPI'; import { getAllKnowledgePanels } from '../../../rest/DocStoreAPI';
import { CustomizePageClassBase } from '../../../utils/CustomizePageClassBase';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
import { AddWidgetModalProps } from './AddWidgetModal.interface'; import {
AddWidgetModalProps,
WidgetSizeInfo,
} from './AddWidgetModal.interface';
import './AddWidgetModal.less'; import './AddWidgetModal.less';
import AddWidgetTabContent from './AddWidgetTabContent';
function AddWidgetModal({ function AddWidgetModal({
open, open,
@ -62,29 +54,19 @@ function AddWidgetModal({
}, []); }, []);
const getAddWidgetHandler = useCallback( const getAddWidgetHandler = useCallback(
(widget: Document) => () => handleAddWidget(widget, placeholderWidgetKey), (widget: Document, widgetSize: number) => () =>
handleAddWidget(widget, placeholderWidgetKey, widgetSize),
[handleAddWidget, placeholderWidgetKey] [handleAddWidget, placeholderWidgetKey]
); );
const checkAddWidgetValidity = useCallback(
(widget: Document) => {
const gridSizes = widget.data.gridSizes;
const gridSizesInNumbers: Array<number> = gridSizes.map(
(size: WidgetWidths) => WidgetWidths[size]
);
return gridSizesInNumbers.every((size) => size <= maxGridSizeSupport);
},
[widgetsList]
);
const tabItems: TabsProps['items'] = useMemo( const tabItems: TabsProps['items'] = useMemo(
() => () =>
widgetsList?.map((widget) => { widgetsList?.map((widget) => {
const widgetAddable = checkAddWidgetValidity(widget); const widgetSizeOptions: Array<WidgetSizeInfo> =
const widgetImage = CustomizePageClassBase.getWidgetImageFromKey( widget.data.gridSizes.map((size: WidgetWidths) => ({
widget.fullyQualifiedName label: size,
); value: WidgetWidths[size],
}));
return { return {
label: ( label: (
@ -102,36 +84,16 @@ function AddWidgetModal({
), ),
key: widget.fullyQualifiedName, key: widget.fullyQualifiedName,
children: ( children: (
<Row align="middle" className="h-min-480" justify="center"> <AddWidgetTabContent
<Col> getAddWidgetHandler={getAddWidgetHandler}
<Space align="center" direction="vertical"> maxGridSizeSupport={maxGridSizeSupport}
<Image className="p-y-md" preview={false} src={widgetImage} /> widget={widget}
<Typography.Paragraph className="d-block text-center"> widgetSizeOptions={widgetSizeOptions}
{widget.description} />
</Typography.Paragraph>
<Tooltip
placement="bottom"
title={
widgetAddable ? '' : t('message.can-not-add-widget')
}>
<Button
ghost
className="p-x-lg m-t-md"
data-testid="add-widget-placeholder-button"
disabled={!widgetAddable}
icon={<PlusOutlined />}
type="primary"
onClick={getAddWidgetHandler(widget)}>
{t('label.add')}
</Button>
</Tooltip>
</Space>
</Col>
</Row>
), ),
}; };
}), }),
[widgetsList, addedWidgetsList, checkAddWidgetValidity, getAddWidgetHandler] [widgetsList, addedWidgetsList, getAddWidgetHandler, maxGridSizeSupport]
); );
useEffect(() => { useEffect(() => {

View File

@ -0,0 +1,103 @@
/*
* Copyright 2023 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.
*/
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Col,
Image,
Radio,
RadioChangeEvent,
Row,
Space,
Tooltip,
Typography,
} from 'antd';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import customizePageClassBase from '../../../utils/CustomizePageClassBase';
import { AddWidgetTabContentProps } from './AddWidgetModal.interface';
function AddWidgetTabContent({
getAddWidgetHandler,
maxGridSizeSupport,
widget,
widgetSizeOptions,
}: Readonly<AddWidgetTabContentProps>) {
const { t } = useTranslation();
const [selectedWidgetSize, setSelectedWidgetSize] = useState<number>(
widgetSizeOptions[0].value
);
const widgetAddable = useMemo(
() => selectedWidgetSize <= maxGridSizeSupport,
[selectedWidgetSize, maxGridSizeSupport]
);
const widgetImage = useMemo(
() =>
customizePageClassBase.getWidgetImageFromKey(
widget.fullyQualifiedName,
selectedWidgetSize
),
[widget, selectedWidgetSize]
);
const handleSizeChange = useCallback((e: RadioChangeEvent) => {
setSelectedWidgetSize(e.target.value);
}, []);
return (
<Row>
<Col span={24}>
<Space>
<Typography.Text>{`${t('label.size')}:`}</Typography.Text>
<Radio.Group
defaultValue={selectedWidgetSize}
optionType="button"
options={widgetSizeOptions}
onChange={handleSizeChange}
/>
</Space>
</Col>
<Col span={24}>
<Row className="h-min-480" justify="center">
<Col>
<Space align="center" direction="vertical">
<Image className="p-y-md" preview={false} src={widgetImage} />
<Typography.Paragraph className="d-block text-center">
{widget.description}
</Typography.Paragraph>
<Tooltip
placement="bottom"
title={widgetAddable ? '' : t('message.can-not-add-widget')}>
<Button
ghost
className="p-x-lg m-t-md"
data-testid="add-widget-placeholder-button"
disabled={!widgetAddable}
icon={<PlusOutlined />}
type="primary"
onClick={getAddWidgetHandler(widget, selectedWidgetSize)}>
{t('label.add')}
</Button>
</Tooltip>
</Space>
</Col>
</Row>
</Col>
</Row>
);
}
export default AddWidgetTabContent;

View File

@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Space, Typography } from 'antd'; import { Button, Col, Modal, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isEmpty, isNil, uniqBy } from 'lodash'; import { isEmpty, isNil, uniqBy } from 'lodash';
@ -21,12 +21,6 @@ import { useTranslation } from 'react-i18next';
import { Link, useHistory, useLocation, useParams } from 'react-router-dom'; import { Link, useHistory, useLocation, useParams } from 'react-router-dom';
import AppState from '../../../AppState'; import AppState from '../../../AppState';
import gridBgImg from '../../../assets/img/grid-bg-img.png'; import gridBgImg from '../../../assets/img/grid-bg-img.png';
import {
LANDING_PAGE_LAYOUT,
LANDING_PAGE_MAX_GRID_SIZE,
LANDING_PAGE_ROW_HEIGHT,
LANDING_PAGE_WIDGET_MARGIN,
} from '../../../constants/CustomizePage.constants';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
@ -48,7 +42,7 @@ import {
getLayoutUpdateHandler, getLayoutUpdateHandler,
getRemoveWidgetHandler, getRemoveWidgetHandler,
} from '../../../utils/CustomizableLandingPageUtils'; } from '../../../utils/CustomizableLandingPageUtils';
import { CustomizePageClassBase } from '../../../utils/CustomizePageClassBase'; import customizePageClassBase from '../../../utils/CustomizePageClassBase';
import { import {
getPersonaDetailsPath, getPersonaDetailsPath,
getSettingPath, getSettingPath,
@ -75,7 +69,8 @@ function CustomizeMyData({
const [resetRightPanelLayout, setResetRightPanelLayout] = const [resetRightPanelLayout, setResetRightPanelLayout] =
useState<boolean>(false); useState<boolean>(false);
const [layout, setLayout] = useState<Array<WidgetConfig>>([ const [layout, setLayout] = useState<Array<WidgetConfig>>([
...(initialPageData.data?.page?.layout ?? LANDING_PAGE_LAYOUT), ...(initialPageData.data?.page?.layout ??
customizePageClassBase.landingPageDefaultLayout),
{ {
h: 2, h: 2,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER, i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
@ -89,6 +84,7 @@ function CustomizeMyData({
LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
); );
const [isWidgetModalOpen, setIsWidgetModalOpen] = useState<boolean>(false); const [isWidgetModalOpen, setIsWidgetModalOpen] = useState<boolean>(false);
const [isResetModalOpen, setIsResetModalOpen] = useState<boolean>(false);
const { isAuthDisabled } = useAuth(location.pathname); const { isAuthDisabled } = useAuth(location.pathname);
const [followedData, setFollowedData] = useState<Array<EntityReference>>(); const [followedData, setFollowedData] = useState<Array<EntityReference>>();
const [followedDataCount, setFollowedDataCount] = useState(0); const [followedDataCount, setFollowedDataCount] = useState(0);
@ -124,12 +120,17 @@ function CustomizeMyData({
}, []); }, []);
const handleAddWidget = useCallback( const handleAddWidget = useCallback(
(newWidgetData: Document, placeholderWidgetKey: string) => { (
newWidgetData: Document,
placeholderWidgetKey: string,
widgetSize: number
) => {
setLayout( setLayout(
getAddWidgetHandler( getAddWidgetHandler(
newWidgetData, newWidgetData,
placeholderWidgetKey, placeholderWidgetKey,
LANDING_PAGE_MAX_GRID_SIZE widgetSize,
customizePageClassBase.landingPageMaxGridSize
) )
); );
setIsWidgetModalOpen(false); setIsWidgetModalOpen(false);
@ -146,6 +147,14 @@ function CustomizeMyData({
[layout] [layout]
); );
const handleOpenResetModal = useCallback(() => {
setIsResetModalOpen(true);
}, []);
const handleCloseResetModal = useCallback(() => {
setIsResetModalOpen(false);
}, []);
const handleOpenAddWidgetModal = useCallback(() => { const handleOpenAddWidgetModal = useCallback(() => {
setIsWidgetModalOpen(true); setIsWidgetModalOpen(true);
}, []); }, []);
@ -212,7 +221,7 @@ function CustomizeMyData({
); );
} }
const Widget = CustomizePageClassBase.getWidgetsFromKey(widgetConfig.i); const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return ( return (
<Widget <Widget
@ -222,6 +231,7 @@ function CustomizeMyData({
followedDataCount={followedDataCount} followedDataCount={followedDataCount}
handleRemoveWidget={handleRemoveWidget} handleRemoveWidget={handleRemoveWidget}
isLoadingOwnedData={isLoadingOwnedData} isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i} widgetKey={widgetConfig.i}
/> />
); );
@ -315,7 +325,7 @@ function CustomizeMyData({
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
setLayout([ setLayout([
...LANDING_PAGE_LAYOUT, ...customizePageClassBase.landingPageDefaultLayout,
{ {
h: 2, h: 2,
i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER, i: LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER,
@ -326,6 +336,7 @@ function CustomizeMyData({
}, },
]); ]);
setResetRightPanelLayout(true); setResetRightPanelLayout(true);
setIsResetModalOpen(false);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -363,8 +374,8 @@ function CustomizeMyData({
<Button size="small" onClick={handleCancel}> <Button size="small" onClick={handleCancel}>
{t('label.cancel')} {t('label.cancel')}
</Button> </Button>
<Button size="small" onClick={handleReset}> <Button size="small" onClick={handleOpenResetModal}>
{t('label.reset-default-layout')} {t('label.reset')}
</Button> </Button>
<Button size="small" type="primary" onClick={handleSave}> <Button size="small" type="primary" onClick={handleSave}>
{t('label.save')} {t('label.save')}
@ -380,8 +391,11 @@ function CustomizeMyData({
cols={{ lg: 4, md: 4, sm: 4, xs: 4, xxs: 4 }} cols={{ lg: 4, md: 4, sm: 4, xs: 4, xxs: 4 }}
draggableHandle=".drag-widget-icon" draggableHandle=".drag-widget-icon"
isResizable={false} isResizable={false}
margin={[LANDING_PAGE_WIDGET_MARGIN, LANDING_PAGE_WIDGET_MARGIN]} margin={[
rowHeight={LANDING_PAGE_ROW_HEIGHT} customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={customizePageClassBase.landingPageRowHeight}
style={{ style={{
backgroundImage: `url(${gridBgImg})`, backgroundImage: `url(${gridBgImg})`,
}} }}
@ -393,11 +407,23 @@ function CustomizeMyData({
addedWidgetsList={addedWidgetsList} addedWidgetsList={addedWidgetsList}
handleAddWidget={handleAddWidget} handleAddWidget={handleAddWidget}
handleCloseAddWidgetModal={handleCloseAddWidgetModal} handleCloseAddWidgetModal={handleCloseAddWidgetModal}
maxGridSizeSupport={LANDING_PAGE_MAX_GRID_SIZE} maxGridSizeSupport={customizePageClassBase.landingPageMaxGridSize}
open={isWidgetModalOpen} open={isWidgetModalOpen}
placeholderWidgetKey={placeholderWidgetKey} placeholderWidgetKey={placeholderWidgetKey}
/> />
)} )}
{isResetModalOpen && (
<Modal
centered
cancelText={t('label.no')}
okText={t('label.yes')}
open={isResetModalOpen}
title={t('label.reset-default-layout')}
onCancel={handleCloseResetModal}
onOk={handleReset}>
{t('message.reset-layout-confirmation')}
</Modal>
)}
</ActivityFeedProvider> </ActivityFeedProvider>
</Col> </Col>
</Row> </Row>

View File

@ -32,6 +32,7 @@ import {
} from '../../constants/constants'; } from '../../constants/constants';
import { KPI_WIDGET_GRAPH_COLORS } from '../../constants/DataInsight.constants'; import { KPI_WIDGET_GRAPH_COLORS } from '../../constants/DataInsight.constants';
import { DATA_INSIGHT_DOCS } from '../../constants/docs.constants'; import { DATA_INSIGHT_DOCS } from '../../constants/docs.constants';
import { WidgetWidths } from '../../enums/CustomizablePage.enum';
import { Kpi, KpiResult } from '../../generated/dataInsight/kpi/kpi'; import { Kpi, KpiResult } from '../../generated/dataInsight/kpi/kpi';
import { UIKpiResult } from '../../interface/data-insight.interface'; import { UIKpiResult } from '../../interface/data-insight.interface';
import { import {
@ -88,6 +89,7 @@ const KPIWidget = ({
selectedDays = CHART_WIDGET_DAYS_DURATION, selectedDays = CHART_WIDGET_DAYS_DURATION,
handleRemoveWidget, handleRemoveWidget,
widgetKey, widgetKey,
selectedGridSize = WidgetWidths.medium,
}: KPIWidgetProps) => { }: KPIWidgetProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [kpiList, setKpiList] = useState<Array<Kpi>>([]); const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
@ -187,6 +189,11 @@ const KPIWidget = ({
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey); !isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
}, [widgetKey]); }, [widgetKey]);
const isWidgetSizeMedium = useMemo(
() => selectedGridSize === WidgetWidths.medium,
[selectedGridSize]
);
useEffect(() => { useEffect(() => {
fetchKpiList().catch(() => { fetchKpiList().catch(() => {
// catch handled in parent function // catch handled in parent function
@ -211,34 +218,38 @@ const KPIWidget = ({
data-testid="kpi-card" data-testid="kpi-card"
id="kpi-charts" id="kpi-charts"
loading={isKPIListLoading || isLoading}> loading={isKPIListLoading || isLoading}>
<Row justify="end">
<Col>
{isEditView && (
<Space align="center">
<DragOutlined
className="drag-widget-icon cursor-pointer"
size={14}
/>
<CloseOutlined size={14} onClick={handleCloseClick} />
</Space>
)}
</Col>
</Row>
<Row align="middle" justify="space-between"> <Row align="middle" justify="space-between">
<Col> <Col>
<Typography.Text className="font-medium"> <Typography.Text className="font-medium">
{t('label.kpi-title')} {t('label.kpi-title')}
</Typography.Text> </Typography.Text>
</Col> </Col>
{isEditView && (
<Space align="center">
<DragOutlined
className="drag-widget-icon cursor-pointer"
size={14}
/>
<CloseOutlined size={14} onClick={handleCloseClick} />
</Space>
)}
</Row> </Row>
{kpiList.length > 0 ? ( {kpiList.length > 0 ? (
<Row> <Row className="p-t-md">
{graphData.length ? ( {graphData.length ? (
<> <>
<Col span={14}> <Col span={isWidgetSizeMedium ? 14 : 24}>
<ResponsiveContainer debounce={1} height={250} width="100%"> <ResponsiveContainer debounce={1} height={250} width="100%">
<LineChart <LineChart
data={graphData} data={graphData}
margin={{ margin={{
top: 10, top: 10,
right: 50, right: isWidgetSizeMedium ? 50 : 20,
left: -20, left: -30,
bottom: 0, bottom: 0,
}}> }}>
<CartesianGrid <CartesianGrid
@ -258,16 +269,18 @@ const KPIWidget = ({
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
</Col> </Col>
{!isUndefined(kpiLatestResults) && !isEmpty(kpiLatestResults) && ( {!isUndefined(kpiLatestResults) &&
<Col span={10}> !isEmpty(kpiLatestResults) &&
<KPILatestResultsV1 isWidgetSizeMedium && (
kpiLatestResultsRecord={kpiLatestResults} <Col span={10}>
/> <KPILatestResultsV1
</Col> kpiLatestResultsRecord={kpiLatestResults}
)} />
</Col>
)}
</> </>
) : ( ) : (
<Col className="justify-center" span={24}> <Col span={24}>
<EmptyPlaceholder /> <EmptyPlaceholder />
</Col> </Col>
)} )}

View File

@ -37,6 +37,7 @@ const MyDataWidgetInternal = ({
const currentUserDetails = AppState.getCurrentUserDetails(); const currentUserDetails = AppState.getCurrentUserDetails();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState<EntityReference[]>([]); const [data, setData] = useState<EntityReference[]>([]);
const [totalOwnedAssetsCount, setTotalOwnedAssetsCount] = useState<number>(0);
const fetchMyDataAssets = async () => { const fetchMyDataAssets = async () => {
if (!currentUserDetails || !currentUserDetails.id) { if (!currentUserDetails || !currentUserDetails.id) {
@ -54,7 +55,8 @@ const MyDataWidgetInternal = ({
includeData.includes(data.type as AssetsType) includeData.includes(data.type as AssetsType)
); );
setData(includedOwnsData.slice(0, 8)); setData(includedOwnsData.slice(0, 9));
setTotalOwnedAssetsCount(includedOwnsData.length);
} }
} catch (err) { } catch (err) {
setData([]); setData([]);
@ -87,7 +89,7 @@ const MyDataWidgetInternal = ({
<span className="text-grey-muted font-normal text-xs"> <span className="text-grey-muted font-normal text-xs">
{t('label.view-all')}{' '} {t('label.view-all')}{' '}
<span data-testid="my-data-total-count"> <span data-testid="my-data-total-count">
{`(${data.length})`} {`(${totalOwnedAssetsCount})`}
</span> </span>
</span> </span>
</Link> </Link>

View File

@ -38,7 +38,7 @@ function AnnouncementsWidget({
}, [widgetKey]); }, [widgetKey]);
return ( return (
<div className="bg-white h-full"> <div className="bg-white h-max-full overflow-y-scroll">
<Row justify="space-between"> <Row justify="space-between">
<Col> <Col>
<Typography.Paragraph className="right-panel-label m-b-sm"> <Typography.Paragraph className="right-panel-label m-b-sm">

View File

@ -13,12 +13,6 @@
import { isEmpty, isUndefined, uniqBy } from 'lodash'; import { isEmpty, isUndefined, uniqBy } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Layout, Responsive, WidthProvider } from 'react-grid-layout'; import { Layout, Responsive, WidthProvider } from 'react-grid-layout';
import {
LANDING_PAGE_RIGHT_CONTAINER_MAX_GRID_SIZE,
LANDING_PAGE_ROW_HEIGHT,
LANDING_PAGE_WIDGET_MARGIN,
RIGHT_PANEL_LAYOUT,
} from '../../../constants/CustomizePage.constants';
import { SIZE } from '../../../enums/common.enum'; import { SIZE } from '../../../enums/common.enum';
import { LandingPageWidgetKeys } from '../../../enums/CustomizablePage.enum'; import { LandingPageWidgetKeys } from '../../../enums/CustomizablePage.enum';
import { Document } from '../../../generated/entity/docStore/document'; import { Document } from '../../../generated/entity/docStore/document';
@ -28,7 +22,7 @@ import {
getLayoutUpdateHandler, getLayoutUpdateHandler,
getRemoveWidgetHandler, getRemoveWidgetHandler,
} from '../../../utils/CustomizableLandingPageUtils'; } from '../../../utils/CustomizableLandingPageUtils';
import { CustomizePageClassBase } from '../../../utils/CustomizePageClassBase'; import customizePageClassBase from '../../../utils/CustomizePageClassBase';
import AddWidgetModal from '../../CustomizableComponents/AddWidgetModal/AddWidgetModal'; import AddWidgetModal from '../../CustomizableComponents/AddWidgetModal/AddWidgetModal';
import EmptyWidgetPlaceholder from '../../CustomizableComponents/EmptyWidgetPlaceholder/EmptyWidgetPlaceholder'; import EmptyWidgetPlaceholder from '../../CustomizableComponents/EmptyWidgetPlaceholder/EmptyWidgetPlaceholder';
import './right-sidebar.less'; import './right-sidebar.less';
@ -86,12 +80,17 @@ const RightSidebar = ({
}, []); }, []);
const handleAddWidget = useCallback( const handleAddWidget = useCallback(
(newWidgetData: Document, placeholderWidgetKey: string) => { (
newWidgetData: Document,
placeholderWidgetKey: string,
widgetSize: number
) => {
setLayout( setLayout(
getAddWidgetHandler( getAddWidgetHandler(
newWidgetData, newWidgetData,
placeholderWidgetKey, placeholderWidgetKey,
LANDING_PAGE_RIGHT_CONTAINER_MAX_GRID_SIZE widgetSize,
customizePageClassBase.landingPageRightContainerMaxGridSize
) )
); );
setIsWidgetModalOpen(false); setIsWidgetModalOpen(false);
@ -117,7 +116,7 @@ const RightSidebar = ({
); );
} }
const Widget = CustomizePageClassBase.getWidgetsFromKey(widgetConfig.i); const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return ( return (
<Widget <Widget
@ -127,6 +126,7 @@ const RightSidebar = ({
handleRemoveWidget={handleRemoveWidget} handleRemoveWidget={handleRemoveWidget}
isEditView={isEditView} isEditView={isEditView}
isLoadingOwnedData={isLoadingOwnedData} isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i} widgetKey={widgetConfig.i}
/> />
); );
@ -207,7 +207,7 @@ const RightSidebar = ({
useEffect(() => { useEffect(() => {
if (resetLayout && handleResetLayout) { if (resetLayout && handleResetLayout) {
setLayout([ setLayout([
...RIGHT_PANEL_LAYOUT, ...customizePageClassBase.rightPanelDefaultLayout,
...(isEditView ...(isEditView
? [ ? [
{ {
@ -230,11 +230,14 @@ const RightSidebar = ({
<ResponsiveGridLayout <ResponsiveGridLayout
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 1, md: 1, sm: 1, xs: 1, xxs: 1 }} cols={{ lg: 1, md: 1, sm: 1, xs: 1, xxs: 1 }}
containerPadding={[0, LANDING_PAGE_WIDGET_MARGIN]} containerPadding={[0, customizePageClassBase.landingPageWidgetMargin]}
draggableHandle=".drag-widget-icon" draggableHandle=".drag-widget-icon"
isResizable={false} isResizable={false}
margin={[LANDING_PAGE_WIDGET_MARGIN, LANDING_PAGE_WIDGET_MARGIN]} margin={[
rowHeight={LANDING_PAGE_ROW_HEIGHT} customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={customizePageClassBase.landingPageRowHeight}
onLayoutChange={handleLayoutUpdate}> onLayoutChange={handleLayoutUpdate}>
{widgets} {widgets}
</ResponsiveGridLayout> </ResponsiveGridLayout>
@ -243,7 +246,9 @@ const RightSidebar = ({
addedWidgetsList={addedWidgetsList} addedWidgetsList={addedWidgetsList}
handleAddWidget={handleAddWidget} handleAddWidget={handleAddWidget}
handleCloseAddWidgetModal={handleCloseAddWidgetModal} handleCloseAddWidgetModal={handleCloseAddWidgetModal}
maxGridSizeSupport={LANDING_PAGE_RIGHT_CONTAINER_MAX_GRID_SIZE} maxGridSizeSupport={
customizePageClassBase.landingPageRightContainerMaxGridSize
}
open={isWidgetModalOpen} open={isWidgetModalOpen}
placeholderWidgetKey={placeholderWidgetKey} placeholderWidgetKey={placeholderWidgetKey}
/> />

View File

@ -11,9 +11,6 @@
* limitations under the License. * limitations under the License.
*/ */
.announcement-container-list { .announcement-container-list {
max-height: 360px;
overflow-y: auto;
overflow-x: hidden;
.feed-card-body { .feed-card-body {
padding: 0; padding: 0;
} }

View File

@ -33,6 +33,7 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_WIDGET_DAYS_DURATION } from '../../constants/constants'; import { CHART_WIDGET_DAYS_DURATION } from '../../constants/constants';
import { TOTAL_ENTITY_CHART_COLOR } from '../../constants/DataInsight.constants'; import { TOTAL_ENTITY_CHART_COLOR } from '../../constants/DataInsight.constants';
import { WidgetWidths } from '../../enums/CustomizablePage.enum';
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
import { import {
DataInsightChartResult, DataInsightChartResult,
@ -57,6 +58,7 @@ const TotalDataAssetsWidget = ({
selectedDays = CHART_WIDGET_DAYS_DURATION, selectedDays = CHART_WIDGET_DAYS_DURATION,
handleRemoveWidget, handleRemoveWidget,
widgetKey, widgetKey,
selectedGridSize,
}: TotalDataAssetsWidgetProps) => { }: TotalDataAssetsWidgetProps) => {
const [totalEntitiesByType, setTotalEntitiesByType] = const [totalEntitiesByType, setTotalEntitiesByType] =
useState<DataInsightChartResult>(); useState<DataInsightChartResult>();
@ -96,6 +98,11 @@ const TotalDataAssetsWidget = ({
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey); !isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
}, [widgetKey]); }, [widgetKey]);
const isWidgetSizeLarge = useMemo(
() => selectedGridSize === WidgetWidths.large,
[selectedGridSize]
);
useEffect(() => { useEffect(() => {
fetchTotalEntitiesByType(); fetchTotalEntitiesByType();
}, [selectedDays]); }, [selectedDays]);
@ -121,18 +128,18 @@ const TotalDataAssetsWidget = ({
)} )}
{data.length ? ( {data.length ? (
<Row> <Row>
<Col span={14}> <Col span={isWidgetSizeLarge ? 14 : 24}>
<Typography.Text className="font-medium"> <Typography.Text className="font-medium">
{t('label.data-insight-total-entity-summary')} {t('label.data-insight-total-entity-summary')}
</Typography.Text> </Typography.Text>
<div className="p-t-lg"> <div className="p-t-md">
<ResponsiveContainer height={250} width="100%"> <ResponsiveContainer height={250} width="100%">
<AreaChart <AreaChart
data={data} data={data}
margin={{ margin={{
top: 10, top: 10,
right: 50, right: isWidgetSizeLarge ? 50 : 20,
left: -20, left: -30,
bottom: 0, bottom: 0,
}} }}
syncId="anyId"> syncId="anyId">
@ -152,15 +159,17 @@ const TotalDataAssetsWidget = ({
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
</Col> </Col>
<Col span={10}> {isWidgetSizeLarge && (
<TotalEntityInsightSummary <Col span={10}>
entities={entities} <TotalEntityInsightSummary
latestData={latestData} entities={entities}
relativePercentage={relativePercentage} latestData={latestData}
selectedDays={selectedDays} relativePercentage={relativePercentage}
total={total} selectedDays={selectedDays}
/> total={total}
</Col> />
</Col>
)}
</Row> </Row>
) : ( ) : (
<Row> <Row>

View File

@ -16,7 +16,6 @@ import { isUndefined } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import AppState from '../../../AppState';
import ActivityFeedListV1 from '../../../components/ActivityFeed/ActivityFeedList/ActivityFeedListV1.component'; import ActivityFeedListV1 from '../../../components/ActivityFeed/ActivityFeedList/ActivityFeedListV1.component';
import { useActivityFeedProvider } from '../../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from '../../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
@ -32,6 +31,7 @@ import { WidgetCommonProps } from '../../../pages/CustomizablePage/CustomizableP
import { getFeedsWithFilter } from '../../../rest/feedsAPI'; import { getFeedsWithFilter } from '../../../rest/feedsAPI';
import { getCountBadge, getEntityDetailLink } from '../../../utils/CommonUtils'; import { getCountBadge, getEntityDetailLink } from '../../../utils/CommonUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import FeedsFilterPopover from '../../common/FeedsFilterPopover/FeedsFilterPopover.component'; import FeedsFilterPopover from '../../common/FeedsFilterPopover/FeedsFilterPopover.component';
import './feeds-widget.less'; import './feeds-widget.less';
@ -43,18 +43,16 @@ const FeedsWidget = ({
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const { isTourOpen } = useTourProvider(); const { isTourOpen } = useTourProvider();
const { currentUser } = useAuthContext();
const [activeTab, setActiveTab] = useState<ActivityFeedTabs>( const [activeTab, setActiveTab] = useState<ActivityFeedTabs>(
ActivityFeedTabs.ALL ActivityFeedTabs.ALL
); );
const { loading, entityThread, entityPaging, getFeedData } = const { loading, entityThread, entityPaging, getFeedData } =
useActivityFeedProvider(); useActivityFeedProvider();
const [taskCount, setTaskCount] = useState(0); const [taskCount, setTaskCount] = useState(0);
const currentUser = useMemo(
() => AppState.getCurrentUserDetails(),
[AppState.userDetails, AppState.nonSecureUserDetails]
);
const [defaultFilter, setDefaultFilter] = useState<FeedFilter>( const [defaultFilter, setDefaultFilter] = useState<FeedFilter>(
FeedFilter.OWNER_OR_FOLLOWS currentUser?.isAdmin ? FeedFilter.ALL : FeedFilter.OWNER_OR_FOLLOWS
); );
useEffect(() => { useEffect(() => {

View File

@ -1,107 +0,0 @@
/*
* Copyright 2023 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.
*/
import { LandingPageWidgetKeys } from '../enums/CustomizablePage.enum';
export const DEFAULT_WIDGET_HEIGHT = 3;
export const LANDING_PAGE_WIDGET_MARGIN = 16;
export const LANDING_PAGE_ROW_HEIGHT = 100;
export const LANDING_PAGE_RIGHT_CONTAINER_EDIT_HEIGHT = 16;
export const LANDING_PAGE_MAX_GRID_SIZE = 3;
export const LANDING_PAGE_RIGHT_CONTAINER_MAX_GRID_SIZE = 1;
export const LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS = {
activityFeed: 5,
rightSidebar: 11.5,
announcements: 3.9,
following: 2.4,
recentlyViewed: 2.1,
myData: 2.8,
kpi: 2.8,
totalDataAssets: 3.42,
};
export const RIGHT_PANEL_LAYOUT = [
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.announcements,
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
w: 1,
x: 0,
y: 0,
static: false,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.following,
i: LandingPageWidgetKeys.FOLLOWING,
w: 1,
x: 0,
y: 1.5,
static: false,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.recentlyViewed,
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
w: 1,
x: 0,
y: 3,
static: false,
},
];
export const LANDING_PAGE_LAYOUT = [
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.activityFeed,
i: LandingPageWidgetKeys.ACTIVITY_FEED,
w: 3,
x: 0,
y: 0,
static: false,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.rightSidebar,
i: LandingPageWidgetKeys.RIGHT_PANEL,
w: 1,
x: 3,
y: 0,
data: {
page: {
layout: RIGHT_PANEL_LAYOUT,
},
},
static: true,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.myData,
i: LandingPageWidgetKeys.MY_DATA,
w: 1,
x: 0,
y: 6,
static: false,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.kpi,
i: LandingPageWidgetKeys.KPI,
w: 2,
x: 1,
y: 6,
static: false,
},
{
h: LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.totalDataAssets,
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
w: 3,
x: 0,
y: 9.1,
static: false,
},
];

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Benennen Sie den Namen und die Anzeigebezeichnung für das {{entity}} um.", "rename-entity": "Benennen Sie den Namen und die Anzeigebezeichnung für das {{entity}} um.",
"request-description": "Beschreibung der Anfrage", "request-description": "Beschreibung der Anfrage",
"request-update-description": "Aktualisierung der Anfragebeschreibung", "request-update-description": "Aktualisierung der Anfragebeschreibung",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "Der Zurücksetzungslink wurde an Ihre E-Mail gesendet", "reset-link-has-been-sent": "Der Zurücksetzungslink wurde an Ihre E-Mail gesendet",
"restore-action-description": "Durch das Wiederherstellen dieses {{entityType}} werden seine Metadaten in OpenMetadata wiederhergestellt.", "restore-action-description": "Durch das Wiederherstellen dieses {{entityType}} werden seine Metadaten in OpenMetadata wiederhergestellt.",
"restore-deleted-team": "Das Wiederherstellen des Teams fügt alle Metadaten wieder zu OpenMetadata hinzu.", "restore-deleted-team": "Das Wiederherstellen des Teams fügt alle Metadaten wieder zu OpenMetadata hinzu.",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Rename the Name and Display Name for the {{entity}}.", "rename-entity": "Rename the Name and Display Name for the {{entity}}.",
"request-description": "Request description", "request-description": "Request description",
"request-update-description": "Request update description", "request-update-description": "Request update description",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "Reset link has been sent to your email", "reset-link-has-been-sent": "Reset link has been sent to your email",
"restore-action-description": "Restoring this {{entityType}} will restore its metadata in OpenMetadata.", "restore-action-description": "Restoring this {{entityType}} will restore its metadata in OpenMetadata.",
"restore-deleted-team": " Restoring the Team will add all the metadata back to OpenMetadata", "restore-deleted-team": " Restoring the Team will add all the metadata back to OpenMetadata",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Renombrar el nombre y nombre visualizado para el {{entity}}.", "rename-entity": "Renombrar el nombre y nombre visualizado para el {{entity}}.",
"request-description": "Descripción de la solicitud", "request-description": "Descripción de la solicitud",
"request-update-description": "Descripción de la actualización de la solicitud", "request-update-description": "Descripción de la actualización de la solicitud",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "Se ha enviado un enlace de restablecimiento a tu correo electrónico", "reset-link-has-been-sent": "Se ha enviado un enlace de restablecimiento a tu correo electrónico",
"restore-action-description": "Restaurar este {{entityType}} restaurará sus metadatos en OpenMetadata.", "restore-action-description": "Restaurar este {{entityType}} restaurará sus metadatos en OpenMetadata.",
"restore-deleted-team": "Restaurar el equipo agregará todos los metadatos de nuevo a OpenMetadata", "restore-deleted-team": "Restaurar el equipo agregará todos los metadatos de nuevo a OpenMetadata",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Renommer le nom et le nom d'affichage pour {{entity}}.", "rename-entity": "Renommer le nom et le nom d'affichage pour {{entity}}.",
"request-description": "Demander une description", "request-description": "Demander une description",
"request-update-description": "Mettre à jour la demande de description", "request-update-description": "Mettre à jour la demande de description",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "Lien de réinitialisation a été envoyé à votre adresse e-mail.", "reset-link-has-been-sent": "Lien de réinitialisation a été envoyé à votre adresse e-mail.",
"restore-action-description": "Restaurer cette {{entityType}} restaurera les métadonnées dans OpenMetadata.", "restore-action-description": "Restaurer cette {{entityType}} restaurera les métadonnées dans OpenMetadata.",
"restore-deleted-team": " Restaurer cette Equipe ajoutera toutes les métadonnées dans OpenMetadata", "restore-deleted-team": " Restaurer cette Equipe ajoutera toutes les métadonnées dans OpenMetadata",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Rename the Name and Display Name for the {{entity}}.", "rename-entity": "Rename the Name and Display Name for the {{entity}}.",
"request-description": "Request description", "request-description": "Request description",
"request-update-description": "Request update description", "request-update-description": "Request update description",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "パスワードリセット用のリンクがあなたのメールアドレスに送信されました", "reset-link-has-been-sent": "パスワードリセット用のリンクがあなたのメールアドレスに送信されました",
"restore-action-description": "この{{entityType}}をリストアすると、OpenMetadataのメタデータがリストアされます。", "restore-action-description": "この{{entityType}}をリストアすると、OpenMetadataのメタデータがリストアされます。",
"restore-deleted-team": "チームをリストアすると全てのメタデータがOpenMetadataに戻されます。", "restore-deleted-team": "チームをリストアすると全てのメタデータがOpenMetadataに戻されます。",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Rename the Name and Display Name for the {{entity}}.", "rename-entity": "Rename the Name and Display Name for the {{entity}}.",
"request-description": "Descrição da solicitação", "request-description": "Descrição da solicitação",
"request-update-description": "Atualizar descrição da solicitação", "request-update-description": "Atualizar descrição da solicitação",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "O link de redefinição foi enviado para o seu e-mail", "reset-link-has-been-sent": "O link de redefinição foi enviado para o seu e-mail",
"restore-action-description": "Restaurar esta {{entityType}} irá restaurar seus metadados no OpenMetadata.", "restore-action-description": "Restaurar esta {{entityType}} irá restaurar seus metadados no OpenMetadata.",
"restore-deleted-team": "Restaurar a equipe irá adicionar todos os metadados de volta ao OpenMetadata", "restore-deleted-team": "Restaurar a equipe irá adicionar todos os metadados de volta ao OpenMetadata",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "Измените имя и отображаемое имя для {{entity}}.", "rename-entity": "Измените имя и отображаемое имя для {{entity}}.",
"request-description": "Запросить описание", "request-description": "Запросить описание",
"request-update-description": "Запросить обновление описания", "request-update-description": "Запросить обновление описания",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "Ссылка для сброса отправлена на вашу электронную почту", "reset-link-has-been-sent": "Ссылка для сброса отправлена на вашу электронную почту",
"restore-action-description": "Восстановление этого {{entityType}} восстановит его метаданные в OpenMetadata.", "restore-action-description": "Восстановление этого {{entityType}} восстановит его метаданные в OpenMetadata.",
"restore-deleted-team": "Восстановление команды добавит все метаданные обратно в OpenMetadata.", "restore-deleted-team": "Восстановление команды добавит все метаданные обратно в OpenMetadata.",

View File

@ -1467,6 +1467,7 @@
"rename-entity": "修改{{entity}}的名称和显示名", "rename-entity": "修改{{entity}}的名称和显示名",
"request-description": "请求详细描述", "request-description": "请求详细描述",
"request-update-description": "请求更新描述", "request-update-description": "请求更新描述",
"reset-layout-confirmation": "Are you sure you want to apply the \"Default Layout\"?",
"reset-link-has-been-sent": "重置链接已发送到您的电子邮箱", "reset-link-has-been-sent": "重置链接已发送到您的电子邮箱",
"restore-action-description": "还原此{{entityType}}将在 OpenMetadata 中还原其元数据", "restore-action-description": "还原此{{entityType}}将在 OpenMetadata 中还原其元数据",
"restore-deleted-team": "还原团队将会把所有元数据添加回 OpenMetadata", "restore-deleted-team": "还原团队将会把所有元数据添加回 OpenMetadata",

View File

@ -23,6 +23,7 @@ export interface WidgetConfig
} }
export interface WidgetCommonProps { export interface WidgetCommonProps {
selectedGridSize?: number;
isEditView?: boolean; isEditView?: boolean;
widgetKey: string; widgetKey: string;
handleRemoveWidget?: (widgetKey: string) => void; handleRemoveWidget?: (widgetKey: string) => void;

View File

@ -16,7 +16,6 @@ import React, { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import CustomizeMyData from '../../components/CustomizableComponents/CustomizeMyData/CustomizeMyData'; import CustomizeMyData from '../../components/CustomizableComponents/CustomizeMyData/CustomizeMyData';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
import { LANDING_PAGE_LAYOUT } from '../../constants/CustomizePage.constants';
import { ClientErrors } from '../../enums/axios.enum'; import { ClientErrors } from '../../enums/axios.enum';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { Document } from '../../generated/entity/docStore/document'; import { Document } from '../../generated/entity/docStore/document';
@ -27,6 +26,7 @@ import {
updateDocument, updateDocument,
} from '../../rest/DocStoreAPI'; } from '../../rest/DocStoreAPI';
import { getFinalLandingPage } from '../../utils/CustomizableLandingPageUtils'; import { getFinalLandingPage } from '../../utils/CustomizableLandingPageUtils';
import customizePageClassBase from '../../utils/CustomizePageClassBase';
export const CustomizablePage = () => { export const CustomizablePage = () => {
const { fqn, pageFqn } = useParams<{ fqn: string; pageFqn: PageType }>(); const { fqn, pageFqn } = useParams<{ fqn: string; pageFqn: PageType }>();
@ -57,7 +57,7 @@ export const CustomizablePage = () => {
entityType: EntityType.PAGE, entityType: EntityType.PAGE,
data: { data: {
page: { page: {
layout: LANDING_PAGE_LAYOUT, layout: customizePageClassBase.landingPageDefaultLayout,
}, },
}, },
}, },

View File

@ -31,10 +31,6 @@ import Loader from '../../components/Loader/Loader';
import RightSidebar from '../../components/MyData/RightSidebar/RightSidebar.component'; import RightSidebar from '../../components/MyData/RightSidebar/RightSidebar.component';
import WelcomeScreen from '../../components/WelcomeScreen/WelcomeScreen.component'; import WelcomeScreen from '../../components/WelcomeScreen/WelcomeScreen.component';
import { LOGGED_IN_USER_STORAGE_KEY } from '../../constants/constants'; import { LOGGED_IN_USER_STORAGE_KEY } from '../../constants/constants';
import {
LANDING_PAGE_LAYOUT,
LANDING_PAGE_WIDGET_MARGIN,
} from '../../constants/CustomizePage.constants';
import { LandingPageWidgetKeys } from '../../enums/CustomizablePage.enum'; import { LandingPageWidgetKeys } from '../../enums/CustomizablePage.enum';
import { AssetsType, EntityType } from '../../enums/entity.enum'; import { AssetsType, EntityType } from '../../enums/entity.enum';
import { Thread } from '../../generated/entity/feed/thread'; import { Thread } from '../../generated/entity/feed/thread';
@ -44,7 +40,7 @@ import { useAuth } from '../../hooks/authHooks';
import { getDocumentByFQN } from '../../rest/DocStoreAPI'; import { getDocumentByFQN } from '../../rest/DocStoreAPI';
import { getActiveAnnouncement } from '../../rest/feedsAPI'; import { getActiveAnnouncement } from '../../rest/feedsAPI';
import { getUserById } from '../../rest/userAPI'; import { getUserById } from '../../rest/userAPI';
import { CustomizePageClassBase } from '../../utils/CustomizePageClassBase'; import customizePageClassBase from '../../utils/CustomizePageClassBase';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import { WidgetConfig } from '../CustomizablePage/CustomizablePage.interface'; import { WidgetConfig } from '../CustomizablePage/CustomizablePage.interface';
import './my-data.less'; import './my-data.less';
@ -86,10 +82,10 @@ const MyDataPageV1 = () => {
const pageData = await getDocumentByFQN(pageFQN); const pageData = await getDocumentByFQN(pageFQN);
setLayout(pageData.data.page.layout); setLayout(pageData.data.page.layout);
} else { } else {
setLayout(LANDING_PAGE_LAYOUT); setLayout(customizePageClassBase.landingPageDefaultLayout);
} }
} catch { } catch {
setLayout(LANDING_PAGE_LAYOUT); setLayout(customizePageClassBase.landingPageDefaultLayout);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -173,7 +169,7 @@ const MyDataPageV1 = () => {
); );
} }
const Widget = CustomizePageClassBase.getWidgetsFromKey(widgetConfig.i); const Widget = customizePageClassBase.getWidgetsFromKey(widgetConfig.i);
return ( return (
<Widget <Widget
@ -181,6 +177,7 @@ const MyDataPageV1 = () => {
followedData={followedData ?? []} followedData={followedData ?? []}
followedDataCount={followedDataCount} followedDataCount={followedDataCount}
isLoadingOwnedData={isLoadingOwnedData} isLoadingOwnedData={isLoadingOwnedData}
selectedGridSize={widgetConfig.w}
widgetKey={widgetConfig.i} widgetKey={widgetConfig.i}
/> />
); );
@ -255,7 +252,10 @@ const MyDataPageV1 = () => {
cols={{ lg: 4, md: 4, sm: 4, xs: 4, xxs: 4 }} cols={{ lg: 4, md: 4, sm: 4, xs: 4, xxs: 4 }}
draggableHandle=".drag-widget-icon" draggableHandle=".drag-widget-icon"
isResizable={false} isResizable={false}
margin={[LANDING_PAGE_WIDGET_MARGIN, LANDING_PAGE_WIDGET_MARGIN]} margin={[
customizePageClassBase.landingPageWidgetMargin,
customizePageClassBase.landingPageWidgetMargin,
]}
rowHeight={100}> rowHeight={100}>
{widgets} {widgets}
</ResponsiveGridLayout> </ResponsiveGridLayout>

View File

@ -273,6 +273,9 @@
.h-full { .h-full {
height: 100%; height: 100%;
} }
.h-max-full {
max-height: 100%;
}
.h-auto { .h-auto {
height: auto; height: auto;
} }
@ -284,3 +287,11 @@
.max-width-md { .max-width-md {
max-width: 768px; max-width: 768px;
} }
.overflow-y-scroll {
overflow-y: scroll;
}
.overflow-x-scroll {
overflow-x: scroll;
}

View File

@ -21,30 +21,26 @@ import {
uniqueId, uniqueId,
} from 'lodash'; } from 'lodash';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import {
DEFAULT_WIDGET_HEIGHT,
LANDING_PAGE_RIGHT_CONTAINER_EDIT_HEIGHT,
LANDING_PAGE_ROW_HEIGHT,
LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS,
LANDING_PAGE_WIDGET_MARGIN,
} from '../constants/CustomizePage.constants';
import { import {
LandingPageWidgetKeys, LandingPageWidgetKeys,
WidgetWidths, WidgetWidths,
} from '../enums/CustomizablePage.enum'; } from '../enums/CustomizablePage.enum';
import { Document } from '../generated/entity/docStore/document'; import { Document } from '../generated/entity/docStore/document';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface'; import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import customizePageClassBase from './CustomizePageClassBase';
export const getAddWidgetHandler = export const getAddWidgetHandler =
( (
newWidgetData: Document, newWidgetData: Document,
placeholderWidgetKey: string, placeholderWidgetKey: string,
widgetWidth: number,
maxGridSize: number maxGridSize: number
) => ) =>
(currentLayout: Array<WidgetConfig>) => { (currentLayout: Array<WidgetConfig>) => {
const widgetFQN = uniqueId(`${newWidgetData.fullyQualifiedName}-`); const widgetFQN = uniqueId(`${newWidgetData.fullyQualifiedName}-`);
const widgetWidth = getWidgetWidth(newWidgetData); const widgetHeight = customizePageClassBase.getWidgetHeight(
const widgetHeight = getWidgetHeight(newWidgetData.name); newWidgetData.name
);
// The widget with key "ExtraWidget.EmptyWidgetPlaceholder" will always remain in the bottom // The widget with key "ExtraWidget.EmptyWidgetPlaceholder" will always remain in the bottom
// and is not meant to be replaced hence // and is not meant to be replaced hence
@ -142,29 +138,6 @@ export const getWidgetWidth = (widget: Document) => {
return widgetSize as number; return widgetSize as number;
}; };
export const getWidgetHeight = (widgetName: string) => {
switch (widgetName) {
case 'ActivityFeed':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.activityFeed;
case 'RightSidebar':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.rightSidebar;
case 'Announcements':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.announcements;
case 'Following':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.following;
case 'RecentlyViewed':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.recentlyViewed;
case 'MyData':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.myData;
case 'KPI':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.kpi;
case 'TotalDataAssets':
return LANDING_PAGE_WIDGET_DEFAULT_HEIGHTS.totalDataAssets;
default:
return DEFAULT_WIDGET_HEIGHT;
}
};
const getAllWidgetsArray = (layout: WidgetConfig[]) => { const getAllWidgetsArray = (layout: WidgetConfig[]) => {
const widgetsArray: WidgetConfig[] = []; const widgetsArray: WidgetConfig[] = [];
@ -191,13 +164,15 @@ const getLayoutWithCalculatedRightPanelHeight = (
const floorHeightAndPosValue = floor(widgetHeightAndPos); const floorHeightAndPosValue = floor(widgetHeightAndPos);
const heightOfWidget = const heightOfWidget =
widgetHeightAndPos * LANDING_PAGE_ROW_HEIGHT + widgetHeightAndPos * customizePageClassBase.landingPageRowHeight +
(floorHeightAndPosValue + 1) * LANDING_PAGE_WIDGET_MARGIN; (floorHeightAndPosValue + 1) *
customizePageClassBase.landingPageWidgetMargin;
return { return {
h: round( h: round(
(heightOfWidget + LANDING_PAGE_WIDGET_MARGIN) / (heightOfWidget + customizePageClassBase.landingPageWidgetMargin) /
(LANDING_PAGE_ROW_HEIGHT + LANDING_PAGE_WIDGET_MARGIN), (customizePageClassBase.landingPageRowHeight +
customizePageClassBase.landingPageWidgetMargin),
2 2
), ),
height: heightOfWidget, height: heightOfWidget,
@ -211,7 +186,7 @@ const getLayoutWithCalculatedRightPanelHeight = (
? { ? {
...widget, ...widget,
h: increaseHeight h: increaseHeight
? LANDING_PAGE_RIGHT_CONTAINER_EDIT_HEIGHT ? customizePageClassBase.landingPageRightContainerEditHeight
: maxHeight?.h ?? widget.h, : maxHeight?.h ?? widget.h,
} }
: widget : widget

View File

@ -30,10 +30,121 @@ import FollowingWidget, {
import RecentlyViewed from '../components/recently-viewed/RecentlyViewed'; import RecentlyViewed from '../components/recently-viewed/RecentlyViewed';
import TotalDataAssetsWidget from '../components/TotalDataAssetsWidget/TotalDataAssetsWidget.component'; import TotalDataAssetsWidget from '../components/TotalDataAssetsWidget/TotalDataAssetsWidget.component';
import FeedsWidget from '../components/Widgets/FeedsWidget/FeedsWidget.component'; import FeedsWidget from '../components/Widgets/FeedsWidget/FeedsWidget.component';
import { LandingPageWidgetKeys } from '../enums/CustomizablePage.enum'; import {
import { WidgetCommonProps } from '../pages/CustomizablePage/CustomizablePage.interface'; LandingPageWidgetKeys,
WidgetWidths,
} from '../enums/CustomizablePage.enum';
import {
WidgetCommonProps,
WidgetConfig,
} from '../pages/CustomizablePage/CustomizablePage.interface';
class CustomizePageClassBase {
defaultWidgetHeight = 3;
landingPageWidgetMargin = 16;
landingPageRowHeight = 100;
landingPageRightContainerEditHeight = 16;
landingPageMaxGridSize = 3;
landingPageRightContainerMaxGridSize = 1;
landingPageWidgetDefaultHeights: Record<string, number> = {
activityFeed: 5,
rightSidebar: 11.5,
announcements: 3.1,
following: 2.4,
recentlyViewed: 2.1,
myData: 3.1,
kpi: 3.1,
totalAssets: 3.1,
};
rightPanelDefaultLayout: Array<WidgetConfig> = [
{
h: this.landingPageWidgetDefaultHeights.announcements,
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
w: 1,
x: 0,
y: 0,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.following,
i: LandingPageWidgetKeys.FOLLOWING,
w: 1,
x: 0,
y: 1.5,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.recentlyViewed,
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
w: 1,
x: 0,
y: 3,
static: false,
},
];
landingPageDefaultLayout: Array<WidgetConfig> = [
{
h: this.landingPageWidgetDefaultHeights.activityFeed,
i: LandingPageWidgetKeys.ACTIVITY_FEED,
w: 3,
x: 0,
y: 0,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.rightSidebar,
i: LandingPageWidgetKeys.RIGHT_PANEL,
w: 1,
x: 3,
y: 0,
data: {
page: {
layout: this.rightPanelDefaultLayout,
},
},
static: true,
},
{
h: this.landingPageWidgetDefaultHeights.myData,
i: LandingPageWidgetKeys.MY_DATA,
w: 1,
x: 0,
y: 6,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.kpi,
i: LandingPageWidgetKeys.KPI,
w: 2,
x: 1,
y: 6,
static: false,
},
{
h: this.landingPageWidgetDefaultHeights.totalAssets,
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
w: 3,
x: 0,
y: 9.1,
static: false,
},
];
protected updateRightPanelDefaultLayout(layout: Array<WidgetConfig>) {
this.rightPanelDefaultLayout = layout;
}
protected updateLandingPageDefaultLayout(layout: Array<WidgetConfig>) {
this.landingPageDefaultLayout = layout;
}
protected updateLandingPageWidgetDefaultHeights(obj: Record<string, number>) {
this.landingPageWidgetDefaultHeights = obj;
}
export class CustomizePageClassBase {
/** /**
* *
* @param string widgetKey * @param string widgetKey
@ -49,11 +160,9 @@ export class CustomizePageClassBase {
} }
> >
*/ */
static getWidgetsFromKey = ( public getWidgetsFromKey(
widgetKey: string widgetKey: string
): FC< ): FC<WidgetCommonProps & AnnouncementsWidgetProps & FollowingWidgetProps> {
WidgetCommonProps & AnnouncementsWidgetProps & FollowingWidgetProps
> => {
if (widgetKey.startsWith(LandingPageWidgetKeys.ACTIVITY_FEED)) { if (widgetKey.startsWith(LandingPageWidgetKeys.ACTIVITY_FEED)) {
return FeedsWidget; return FeedsWidget;
} }
@ -77,9 +186,9 @@ export class CustomizePageClassBase {
} }
return (() => null) as React.FC; return (() => null) as React.FC;
}; }
static getWidgetImageFromKey = (widgetKey: string): string => { public getWidgetImageFromKey(widgetKey: string, size?: number): string {
switch (widgetKey) { switch (widgetKey) {
case LandingPageWidgetKeys.ACTIVITY_FEED: { case LandingPageWidgetKeys.ACTIVITY_FEED: {
return ActivityFeedImg; return ActivityFeedImg;
@ -88,9 +197,17 @@ export class CustomizePageClassBase {
return MyDataImg; return MyDataImg;
} }
case LandingPageWidgetKeys.KPI: { case LandingPageWidgetKeys.KPI: {
if (size === WidgetWidths.small) {
return '';
}
return KPIImg; return KPIImg;
} }
case LandingPageWidgetKeys.TOTAL_DATA_ASSETS: { case LandingPageWidgetKeys.TOTAL_DATA_ASSETS: {
if (size === WidgetWidths.medium) {
return '';
}
return TotalAssetsImg; return TotalAssetsImg;
} }
case LandingPageWidgetKeys.ANNOUNCEMENTS: { case LandingPageWidgetKeys.ANNOUNCEMENTS: {
@ -106,5 +223,33 @@ export class CustomizePageClassBase {
return ''; return '';
} }
} }
}; }
public getWidgetHeight(widgetName: string) {
switch (widgetName) {
case 'ActivityFeed':
return this.landingPageWidgetDefaultHeights.activityFeed;
case 'RightSidebar':
return this.landingPageWidgetDefaultHeights.rightSidebar;
case 'Announcements':
return this.landingPageWidgetDefaultHeights.announcements;
case 'Following':
return this.landingPageWidgetDefaultHeights.following;
case 'RecentlyViewed':
return this.landingPageWidgetDefaultHeights.recentlyViewed;
case 'MyData':
return this.landingPageWidgetDefaultHeights.myData;
case 'KPI':
return this.landingPageWidgetDefaultHeights.kpi;
case 'TotalAssets':
return this.landingPageWidgetDefaultHeights.totalAssets;
default:
return this.defaultWidgetHeight;
}
}
} }
const customizePageClassBase = new CustomizePageClassBase();
export default customizePageClassBase;
export { CustomizePageClassBase };

View File

@ -328,18 +328,18 @@ export const getGlobalSettingsMenuWithPermission = (
category: i18next.t('label.open-metadata'), category: i18next.t('label.open-metadata'),
key: 'openMetadata', key: 'openMetadata',
items: [ items: [
{
label: i18next.t('label.email'),
isProtected: Boolean(isAdminUser),
key: 'openMetadata.email',
icon: <EmailSettingsIcon className="w-4 side-panel-icons" />,
},
{ {
label: i18next.t('label.customize-landing-page'), label: i18next.t('label.customize-landing-page'),
isProtected: Boolean(isAdminUser), isProtected: Boolean(isAdminUser),
key: 'openMetadata.customizeLandingPage', key: 'openMetadata.customizeLandingPage',
icon: <CustomDashboardLogoIcon className="w-4 side-panel-icons" />, icon: <CustomDashboardLogoIcon className="w-4 side-panel-icons" />,
}, },
{
label: i18next.t('label.email'),
isProtected: Boolean(isAdminUser),
key: 'openMetadata.email',
icon: <EmailSettingsIcon className="w-4 side-panel-icons" />,
},
{ {
label: i18next.t('label.custom-logo'), label: i18next.t('label.custom-logo'),
isProtected: Boolean(isAdminUser), isProtected: Boolean(isAdminUser),