feat: improve dashboard view user experience (#3654)

* feat: improve dashboard view user experience

* chore: dashboard ux is updated

* feat: add inter font and set font family in theme configuration

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yunus M 2023-11-20 14:53:13 +05:30 committed by GitHub
parent 5d6eea3045
commit 2a55f3d680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 148 additions and 58 deletions

View File

@ -1,5 +1,5 @@
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Modal, Tooltip } from 'antd';
import { Modal, Tooltip, Typography } from 'antd';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard';
import { useCallback } from 'react';
@ -10,10 +10,22 @@ import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { Data } from '../index';
import { Data } from '..';
import { TableLinkText } from './styles';
function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element {
interface DeleteButtonProps {
createdBy: string;
name: string;
id: string;
isLocked: boolean;
}
function DeleteButton({
createdBy,
name,
id,
isLocked,
}: DeleteButtonProps): JSX.Element {
const [modal, contextHolder] = Modal.useModal();
const { role, user } = useSelector<AppState, AppReducer>((state) => state.app);
const isAuthor = user?.email === createdBy;
@ -26,7 +38,13 @@ function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element {
const openConfirmationDialog = useCallback((): void => {
modal.confirm({
title: 'Do you really want to delete this dashboard?',
title: (
<Typography.Title level={5}>
Are you sure you want to delete the
<span style={{ color: '#e42b35', fontWeight: 500 }}> {name} </span>
dashboard?
</Typography.Title>
),
icon: <ExclamationCircleOutlined style={{ color: '#e42b35' }} />,
onOk() {
deleteDashboardMutation.mutateAsync(undefined, {
@ -39,7 +57,7 @@ function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element {
okButtonProps: { danger: true },
centered: true,
});
}, [modal, deleteDashboardMutation, queryClient]);
}, [modal, name, deleteDashboardMutation, queryClient]);
const getDeleteTooltipContent = (): string => {
if (isLocked) {

View File

@ -1,11 +1,12 @@
import { PlusOutlined } from '@ant-design/icons';
import {
Card,
Col,
Dropdown,
Input,
MenuProps,
Row,
TableColumnProps,
Typography,
} from 'antd';
import { ItemType } from 'antd/es/menu/hooks/useItems';
import createDashboard from 'api/dashboard/create';
@ -18,9 +19,9 @@ import DynamicColumnTable from 'components/ResizeTable/DynamicColumnTable';
import LabelColumn from 'components/TableRenderer/LabelColumn';
import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import history from 'lib/history';
import { Key, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -38,6 +39,8 @@ import DeleteButton from './TableComponents/DeleteButton';
import Name from './TableComponents/Name';
function ListOfAllDashboard(): JSX.Element {
const { Search } = Input;
const {
data: dashboardListResponse = [],
isLoading: isDashboardListLoading,
@ -59,12 +62,21 @@ function ListOfAllDashboard(): JSX.Element {
] = useState<boolean>(false);
const [uploadedGrafana, setUploadedGrafana] = useState<boolean>(false);
const [isFilteringDashboards, setIsFilteringDashboards] = useState(false);
const [filteredDashboards, setFilteredDashboards] = useState<Dashboard[]>();
const [dashboards, setDashboards] = useState<Dashboard[]>();
const sortDashboardsByCreatedAt = (dashboards: Dashboard[]): void => {
const sortedDashboards = dashboards.sort(
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
setDashboards(sortedDashboards);
};
useEffect(() => {
if (dashboardListResponse.length) {
setFilteredDashboards(dashboardListResponse);
sortDashboardsByCreatedAt(dashboardListResponse);
}
}, [dashboardListResponse]);
@ -150,7 +162,7 @@ function ListOfAllDashboard(): JSX.Element {
}, [action]);
const data: Data[] =
filteredDashboards?.map((e) => ({
dashboards?.map((e) => ({
createdAt: e.created_at,
description: e.data.description || '',
id: e.uuid,
@ -255,41 +267,84 @@ function ListOfAllDashboard(): JSX.Element {
[getMenuItems],
);
const searchArrayOfObjects = (searchValue: string): any[] => {
// Convert the searchValue to lowercase for case-insensitive search
const searchValueLowerCase = searchValue.toLowerCase();
// Use the filter method to find matching objects
return dashboardListResponse.filter((item: any) => {
// Convert each property value to lowercase for case-insensitive search
const itemValues = Object.values(item?.data).map((value: any) =>
value.toString().toLowerCase(),
);
// Check if any property value contains the searchValue
return itemValues.some((value) => value.includes(searchValueLowerCase));
});
};
const handleSearch = useDebouncedFn((event: unknown): void => {
setIsFilteringDashboards(true);
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
const filteredDashboards = searchArrayOfObjects(searchText);
setDashboards(filteredDashboards);
setIsFilteringDashboards(false);
}, 500);
const GetHeader = useMemo(
() => (
<Row justify="space-between">
<Typography>Dashboard List</Typography>
<ButtonContainer>
<TextToolTip
{...{
text: `More details on how to create dashboards`,
url: 'https://signoz.io/docs/userguide/dashboards',
}}
<Row gutter={16} align="middle">
<Col span={18}>
<Search
disabled={isDashboardListLoading}
placeholder="Search by Name, Description, Tags"
onChange={handleSearch}
loading={isFilteringDashboards}
style={{ marginBottom: 16, marginTop: 16 }}
/>
{newDashboard && (
<Dropdown
getPopupContainer={popupContainer}
disabled={isDashboardListLoading}
trigger={['click']}
menu={menu}
>
<NewDashboardButton
icon={<PlusOutlined />}
type="primary"
loading={newDashboardState.loading}
danger={newDashboardState.error}
</Col>
<Col
span={6}
style={{
display: 'flex',
justifyContent: 'flex-end',
}}
>
<ButtonContainer>
<TextToolTip
{...{
text: `More details on how to create dashboards`,
url: 'https://signoz.io/docs/userguide/dashboards',
}}
/>
{newDashboard && (
<Dropdown
getPopupContainer={popupContainer}
disabled={isDashboardListLoading}
trigger={['click']}
menu={menu}
>
{getText()}
</NewDashboardButton>
</Dropdown>
)}
</ButtonContainer>
<NewDashboardButton
icon={<PlusOutlined />}
type="primary"
loading={newDashboardState.loading}
danger={newDashboardState.error}
>
{getText()}
</NewDashboardButton>
</Dropdown>
)}
</ButtonContainer>
</Col>
</Row>
),
[
newDashboard,
Search,
isDashboardListLoading,
handleSearch,
isFilteringDashboards,
newDashboard,
menu,
newDashboardState.loading,
newDashboardState.error,
@ -301,13 +356,6 @@ function ListOfAllDashboard(): JSX.Element {
<Card>
{GetHeader}
{!isDashboardListLoading && (
<SearchFilter
searchData={dashboardListResponse}
filterDashboards={setFilteredDashboards}
/>
)}
<TableContainer>
<ImportJSON
isImportJSONModalVisible={isImportJSONModalVisible}
@ -319,8 +367,9 @@ function ListOfAllDashboard(): JSX.Element {
dynamicColumns={dynamicColumns}
columns={columns}
pagination={{
pageSize: 9,
defaultPageSize: 9,
pageSize: 10,
defaultPageSize: 10,
total: data?.length || 0,
}}
showHeader
bordered

View File

@ -3,12 +3,16 @@ import { Tabs } from 'antd';
import GeneralDashboardSettings from './General';
import VariablesSetting from './Variables';
const items = [
{ label: 'General', key: 'general', children: <GeneralDashboardSettings /> },
{ label: 'Variables', key: 'variables', children: <VariablesSetting /> },
];
function DashboardSettingsContent(): JSX.Element {
const items = [
{
label: 'General',
key: 'general',
children: <GeneralDashboardSettings />,
},
{ label: 'Variables', key: 'variables', children: <VariablesSetting /> },
];
return <Tabs items={items} />;
}

View File

@ -68,6 +68,12 @@ export const getOptions = (routes: string): Option[] => {
return Options;
};
export const routesToHideBreadCrumbs = [
ROUTES.SUPPORT,
ROUTES.ALL_DASHBOARD,
ROUTES.DASHBOARD,
];
export const routesToSkip = [
ROUTES.SETTINGS,
ROUTES.LIST_ALL_ALERT,

View File

@ -6,7 +6,11 @@ import { matchPath, useHistory } from 'react-router-dom';
import NewExplorerCTA from '../NewExplorerCTA';
import ShowBreadcrumbs from './Breadcrumbs';
import DateTimeSelector from './DateTimeSelection';
import { routesToDisable, routesToSkip } from './DateTimeSelection/config';
import {
routesToDisable,
routesToHideBreadCrumbs,
routesToSkip,
} from './DateTimeSelection/config';
import { Container } from './styles';
function TopNav(): JSX.Element | null {
@ -20,6 +24,14 @@ function TopNav(): JSX.Element | null {
[location.pathname],
);
const isRouteToHideBreadCrumbs = useMemo(
() =>
routesToHideBreadCrumbs.some((route) =>
matchPath(location.pathname, { path: route, exact: true }),
),
[location.pathname],
);
const isDisabled = useMemo(
() =>
routesToDisable.some((route) =>
@ -33,22 +45,20 @@ function TopNav(): JSX.Element | null {
[location.pathname],
);
const hideBreadcrumbs = location.pathname === ROUTES.SUPPORT;
if (isSignUpPage || isDisabled) {
return null;
}
return (
<Container>
{!hideBreadcrumbs && (
{!isRouteToHideBreadCrumbs && (
<Col span={16}>
<ShowBreadcrumbs />
</Col>
)}
{!isRouteToSkip && (
<Col span={8}>
<Col span={isRouteToHideBreadCrumbs ? 24 : 8}>
<Row justify="end">
<Space align="start" size={60} direction="horizontal">
<NewExplorerCTA />

View File

@ -74,7 +74,8 @@ export const useThemeConfig = (): ThemeConfig => {
borderRadiusLG: 2,
borderRadiusSM: 2,
borderRadiusXS: 2,
fontFamily: 'Open Sans',
fontFamily: 'Inter',
fontSize: 13,
},
};
};

View File

@ -62,10 +62,12 @@
href="https://unpkg.com/uplot@1.6.26/dist/uPlot.min.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap"
rel="stylesheet"
/>
</head>