mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-19 22:49:00 +00:00
UI : Data profiler and Feed changes (#3267)
This commit is contained in:
parent
643eadb118
commit
b93a8c8d0d
@ -23,6 +23,8 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { getFeedById } from '../../../axiosAPIs/feedsAPI';
|
import { getFeedById } from '../../../axiosAPIs/feedsAPI';
|
||||||
import { getEntityField, getReplyText } from '../../../utils/FeedUtils';
|
import { getEntityField, getReplyText } from '../../../utils/FeedUtils';
|
||||||
|
import { Button } from '../../buttons/Button/Button';
|
||||||
|
import PopOver from '../../common/popover/PopOver';
|
||||||
import Loader from '../../Loader/Loader';
|
import Loader from '../../Loader/Loader';
|
||||||
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
|
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
|
||||||
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
||||||
@ -39,6 +41,7 @@ interface FeedPanelHeaderProp
|
|||||||
Pick<ActivityFeedPanelProp, 'onCancel'> {
|
Pick<ActivityFeedPanelProp, 'onCancel'> {
|
||||||
entityField: string;
|
entityField: string;
|
||||||
noun?: string;
|
noun?: string;
|
||||||
|
onShowNewConversation?: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
interface FeedPanelOverlayProp
|
interface FeedPanelOverlayProp
|
||||||
extends HTMLAttributes<HTMLButtonElement>,
|
extends HTMLAttributes<HTMLButtonElement>,
|
||||||
@ -53,6 +56,7 @@ export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
|
|||||||
entityField,
|
entityField,
|
||||||
className,
|
className,
|
||||||
noun,
|
noun,
|
||||||
|
onShowNewConversation,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<header className={className}>
|
<header className={className}>
|
||||||
@ -61,21 +65,41 @@ export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
|
|||||||
{noun ? noun : 'Conversation'} on{' '}
|
{noun ? noun : 'Conversation'} on{' '}
|
||||||
<span className="tw-heading">{entityField}</span>
|
<span className="tw-heading">{entityField}</span>
|
||||||
</p>
|
</p>
|
||||||
<svg
|
<div className="tw-flex">
|
||||||
className="tw-w-5 tw-h-5 tw-ml-1 tw-cursor-pointer"
|
{onShowNewConversation ? (
|
||||||
data-testid="closeDrawer"
|
<PopOver
|
||||||
fill="none"
|
position="bottom"
|
||||||
stroke="#6B7280"
|
title="Start conversation"
|
||||||
viewBox="0 0 24 24"
|
trigger="mouseenter">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<Button
|
||||||
onClick={onCancel}>
|
className={classNames('tw-h-7 tw-px-2')}
|
||||||
<path
|
data-testid="add-teams"
|
||||||
d="M6 18L18 6M6 6l12 12"
|
size="small"
|
||||||
strokeLinecap="round"
|
theme="primary"
|
||||||
strokeLinejoin="round"
|
variant="outlined"
|
||||||
strokeWidth="2"
|
onClick={() => {
|
||||||
/>
|
onShowNewConversation?.(true);
|
||||||
</svg>
|
}}>
|
||||||
|
<i aria-hidden="true" className="fa fa-plus" />
|
||||||
|
</Button>
|
||||||
|
</PopOver>
|
||||||
|
) : null}
|
||||||
|
<svg
|
||||||
|
className="tw-w-5 tw-h-5 tw-ml-2 tw-cursor-pointer tw-self-center"
|
||||||
|
data-testid="closeDrawer"
|
||||||
|
fill="none"
|
||||||
|
stroke="#6B7280"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
onClick={onCancel}>
|
||||||
|
<path
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="tw--mx-4" />
|
<hr className="tw--mx-4" />
|
||||||
</header>
|
</header>
|
||||||
|
@ -225,9 +225,15 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
|||||||
const [threads, setThreads] = useState<EntityThread[]>([]);
|
const [threads, setThreads] = useState<EntityThread[]>([]);
|
||||||
const [selectedThread, setSelectedThread] = useState<EntityThread>();
|
const [selectedThread, setSelectedThread] = useState<EntityThread>();
|
||||||
const [selectedThreadId, setSelectedThreadId] = useState<string>('');
|
const [selectedThreadId, setSelectedThreadId] = useState<string>('');
|
||||||
|
const [showNewConversation, setShowNewConversation] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
const entityField = getEntityField(threadLink);
|
const entityField = getEntityField(threadLink);
|
||||||
|
|
||||||
|
const onShowNewConversation = (value: boolean) => {
|
||||||
|
setShowNewConversation(value);
|
||||||
|
};
|
||||||
|
|
||||||
const getThreads = () => {
|
const getThreads = () => {
|
||||||
getAllFeeds(threadLink).then((res: AxiosResponse) => {
|
getAllFeeds(threadLink).then((res: AxiosResponse) => {
|
||||||
const { data } = res.data;
|
const { data } = res.data;
|
||||||
@ -310,6 +316,9 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
|||||||
entityField={entityField as string}
|
entityField={entityField as string}
|
||||||
noun="Conversations"
|
noun="Conversations"
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
onShowNewConversation={
|
||||||
|
threads.length > 0 ? onShowNewConversation : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{!isUndefined(selectedThread) ? (
|
{!isUndefined(selectedThread) ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -326,6 +335,20 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
{showNewConversation || threads.length === 0 ? (
|
||||||
|
<div className="tw-pt-6">
|
||||||
|
<p className="tw-ml-9 tw-mr-2 tw-my-2">
|
||||||
|
You are starting a new conversation
|
||||||
|
</p>
|
||||||
|
<ActivityFeedEditor
|
||||||
|
buttonClass="tw-mr-4"
|
||||||
|
className="tw-ml-5 tw-mr-2"
|
||||||
|
placeHolder="Enter a message"
|
||||||
|
onSave={onPostThread}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ActivityThreadList
|
<ActivityThreadList
|
||||||
className="tw-py-6 tw-pl-5"
|
className="tw-py-6 tw-pl-5"
|
||||||
postFeed={postFeed}
|
postFeed={postFeed}
|
||||||
@ -334,12 +357,6 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
|||||||
onThreadIdSelect={onThreadIdSelect}
|
onThreadIdSelect={onThreadIdSelect}
|
||||||
onThreadSelect={onThreadSelect}
|
onThreadSelect={onThreadSelect}
|
||||||
/>
|
/>
|
||||||
<ActivityFeedEditor
|
|
||||||
buttonClass="tw-mr-4"
|
|
||||||
className="tw-ml-5 tw-mr-2 tw-mb-6"
|
|
||||||
placeHolder="Enter a message"
|
|
||||||
onSave={onPostThread}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +81,7 @@ export interface DatasetDetailsProps {
|
|||||||
showTestForm: boolean;
|
showTestForm: boolean;
|
||||||
qualityTestFormHandler: (
|
qualityTestFormHandler: (
|
||||||
tabValue: number,
|
tabValue: number,
|
||||||
testMode: DatasetTestModeType
|
testMode?: DatasetTestModeType
|
||||||
) => void;
|
) => void;
|
||||||
handleShowTestForm: (value: boolean) => void;
|
handleShowTestForm: (value: boolean) => void;
|
||||||
handleTestModeChange: (mode: DatasetTestModeType) => void;
|
handleTestModeChange: (mode: DatasetTestModeType) => void;
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
ColumnTest,
|
ColumnTest,
|
||||||
DatasetTestModeType,
|
DatasetTestModeType,
|
||||||
} from '../../interface/dataQuality.interface';
|
} from '../../interface/dataQuality.interface';
|
||||||
|
import { getRoundedValue } from '../../utils/ProfilerUtils';
|
||||||
import { getConstraintIcon } from '../../utils/TableUtils';
|
import { getConstraintIcon } from '../../utils/TableUtils';
|
||||||
import { Button } from '../buttons/Button/Button';
|
import { Button } from '../buttons/Button/Button';
|
||||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||||
@ -38,29 +39,26 @@ type Props = {
|
|||||||
}>;
|
}>;
|
||||||
qualityTestFormHandler: (
|
qualityTestFormHandler: (
|
||||||
tabValue: number,
|
tabValue: number,
|
||||||
testMode: DatasetTestModeType
|
testMode?: DatasetTestModeType
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PercentageGraph = ({
|
const PercentageGraph = ({
|
||||||
percentage,
|
percentage,
|
||||||
title,
|
|
||||||
}: {
|
}: {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
title: string;
|
title: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<PopOver
|
<div className="tw-border tw-inline-block tw-border-primary tw-h-5 tw-w-20 ">
|
||||||
position="top"
|
<div
|
||||||
title={`${percentage}% ${title}`}
|
className="tw-bg-primary-hover-lite tw-h-full"
|
||||||
trigger="mouseenter">
|
style={{ width: `${percentage}%` }}>
|
||||||
<div className="tw-border tw-border-primary tw-h-6 tw-w-20">
|
<div className="tw-w-20 tw-text-grey-body tw-font-medium tw-text-xs tw-leading-5 tw-text-center">
|
||||||
<div
|
{getRoundedValue(percentage)}%
|
||||||
className="tw-bg-primary tw-opacity-40 tw-h-full"
|
</div>
|
||||||
style={{ width: `${percentage}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</PopOver>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,10 +89,12 @@ const TableProfiler: FC<Props> = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: column,
|
name: column,
|
||||||
columnMetrics: Object.entries(data?.[0] ?? {}).map((d) => ({
|
columnMetrics: Object.entries(data?.[0] ?? {})
|
||||||
key: d[0],
|
.map((d) => ({
|
||||||
value: d[1],
|
key: d[0],
|
||||||
})),
|
value: d[1],
|
||||||
|
}))
|
||||||
|
.filter((m) => !excludedMetrics.includes(m.key)),
|
||||||
columnTests: column.colTests,
|
columnTests: column.colTests,
|
||||||
data,
|
data,
|
||||||
type: column.colType,
|
type: column.colType,
|
||||||
@ -112,9 +112,9 @@ const TableProfiler: FC<Props> = ({
|
|||||||
<tr className="tableHead-row">
|
<tr className="tableHead-row">
|
||||||
<th className="tableHead-cell">Column Name</th>
|
<th className="tableHead-cell">Column Name</th>
|
||||||
<th className="tableHead-cell">Type</th>
|
<th className="tableHead-cell">Type</th>
|
||||||
<th className="tableHead-cell">Null</th>
|
<th className="tableHead-cell">Null %</th>
|
||||||
<th className="tableHead-cell">Unique</th>
|
<th className="tableHead-cell">Unique %</th>
|
||||||
<th className="tableHead-cell">Distinct</th>
|
<th className="tableHead-cell">Distinct %</th>
|
||||||
<th className="tableHead-cell">Metrics</th>
|
<th className="tableHead-cell">Metrics</th>
|
||||||
<th className="tableHead-cell">Tests</th>
|
<th className="tableHead-cell">Tests</th>
|
||||||
<th className="tableHead-cell" />
|
<th className="tableHead-cell" />
|
||||||
@ -139,7 +139,32 @@ const TableProfiler: FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span>{col.name.colName}</span>
|
<div className="tw-flex">
|
||||||
|
{col.name.colName.length > 25 ? (
|
||||||
|
<span>
|
||||||
|
<PopOver
|
||||||
|
html={
|
||||||
|
<div className="tw-break-words">
|
||||||
|
<span>{col.name.colName}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="bottom"
|
||||||
|
theme="light"
|
||||||
|
trigger="click">
|
||||||
|
<div className="tw-cursor-pointer tw-underline tw-inline-block">
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={`${col.name.colName.slice(
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
)}...`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PopOver>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
col.name.colName
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
@ -188,9 +213,7 @@ const TableProfiler: FC<Props> = ({
|
|||||||
<td className="tw-relative tableBody-cell profiler-graph">
|
<td className="tw-relative tableBody-cell profiler-graph">
|
||||||
<PercentageGraph
|
<PercentageGraph
|
||||||
percentage={
|
percentage={
|
||||||
((col.data?.[0]?.distinctCount ?? 0) /
|
(col.data?.[0]?.distinctProportion ?? 0) * 100
|
||||||
(col.data?.[0]?.rows ?? 0)) *
|
|
||||||
100
|
|
||||||
}
|
}
|
||||||
title="distinct value"
|
title="distinct value"
|
||||||
/>
|
/>
|
||||||
@ -198,17 +221,21 @@ const TableProfiler: FC<Props> = ({
|
|||||||
<td
|
<td
|
||||||
className="tw-relative tableBody-cell"
|
className="tw-relative tableBody-cell"
|
||||||
data-testid="tableBody-cell">
|
data-testid="tableBody-cell">
|
||||||
<div className="tw-border tw-border-main tw-rounded tw-p-2 tw-min-h-32 tw-max-h-44 tw-overflow-y-auto">
|
{col.columnMetrics.length ? (
|
||||||
{col.columnMetrics
|
<div className=" tw-rounded tw-max-h-44 tw-overflow-y-auto">
|
||||||
.filter((m) => !excludedMetrics.includes(m.key))
|
{col.columnMetrics.map((m, i) => (
|
||||||
.map((m, i) => (
|
|
||||||
<p className="tw-mb-1 tw-flex" key={i}>
|
<p className="tw-mb-1 tw-flex" key={i}>
|
||||||
<span className="tw-mx-1">{m.key}</span>
|
<span className="tw-mx-1">{m.key}</span>
|
||||||
<span className="tw-mx-1">-</span>
|
<span className="tw-mx-1">-</span>
|
||||||
<span className="tw-mx-1">{m.value}</span>
|
<span className="tw-mx-1">
|
||||||
|
{getRoundedValue(m.value)}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
`No metrics available`
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="tw-relative tableBody-cell"
|
className="tw-relative tableBody-cell"
|
||||||
@ -216,7 +243,7 @@ const TableProfiler: FC<Props> = ({
|
|||||||
data-testid="tableBody-cell">
|
data-testid="tableBody-cell">
|
||||||
<div className="tw-flex tw-justify-between">
|
<div className="tw-flex tw-justify-between">
|
||||||
{col.columnTests ? (
|
{col.columnTests ? (
|
||||||
<div className="tw-border tw-border-main tw-rounded tw-p-2 tw-min-h-32 tw-max-h-44 tw-overflow-y-auto tw-flex-1">
|
<div className="tw-rounded tw-max-h-44 tw-overflow-y-auto tw-flex-1">
|
||||||
{col.columnTests.map((m, i) => (
|
{col.columnTests.map((m, i) => (
|
||||||
<div className="tw-flex tw-mb-2" key={i}>
|
<div className="tw-flex tw-mb-2" key={i}>
|
||||||
<p className="tw-mr-2">
|
<p className="tw-mr-2">
|
||||||
@ -230,7 +257,9 @@ const TableProfiler: FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<span className="tw-mx-1 tw-font-medium">
|
<span
|
||||||
|
className="tw-mx-1 tw-font-medium tw-cursor-pointer"
|
||||||
|
onClick={() => qualityTestFormHandler(6)}>
|
||||||
{m.testCase.columnTestType}
|
{m.testCase.columnTestType}
|
||||||
</span>
|
</span>
|
||||||
<div className="tw-mx-1">
|
<div className="tw-mx-1">
|
||||||
@ -263,6 +292,7 @@ const TableProfiler: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
size="custom"
|
size="custom"
|
||||||
|
theme="primary"
|
||||||
type="button"
|
type="button"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -24,6 +24,7 @@ import { getVersion } from '../../axiosAPIs/miscAPI';
|
|||||||
import {
|
import {
|
||||||
getExplorePathWithSearch,
|
getExplorePathWithSearch,
|
||||||
getTeamDetailsPath,
|
getTeamDetailsPath,
|
||||||
|
getUserPath,
|
||||||
navLinkSettings,
|
navLinkSettings,
|
||||||
ROUTES,
|
ROUTES,
|
||||||
} from '../../constants/constants';
|
} from '../../constants/constants';
|
||||||
@ -149,7 +150,10 @@ const Appbar: React.FC = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="greeting-text">
|
<div data-testid="greeting-text">
|
||||||
<span className="tw-font-medium">{name}</span>
|
<Link to={getUserPath(currentUser?.name as string)}>
|
||||||
|
{' '}
|
||||||
|
<span className="tw-font-medium tw-cursor-pointer">{name}</span>
|
||||||
|
</Link>
|
||||||
<hr className="tw-my-1.5" />
|
<hr className="tw-my-1.5" />
|
||||||
{(roles?.length ?? 0) > 0 ? (
|
{(roles?.length ?? 0) > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
|
@ -20,4 +20,7 @@ export const excludedMetrics = [
|
|||||||
'uniqueProportion',
|
'uniqueProportion',
|
||||||
'rows',
|
'rows',
|
||||||
'histogram',
|
'histogram',
|
||||||
|
'missingCount',
|
||||||
|
'missingPercentage',
|
||||||
|
'distinctProportion',
|
||||||
];
|
];
|
||||||
|
@ -632,6 +632,8 @@ export interface ColumnProfile {
|
|||||||
minLength?: number;
|
minLength?: number;
|
||||||
|
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
|
|
||||||
|
distinctProportion?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistogramObject {
|
export interface HistogramObject {
|
||||||
|
@ -193,11 +193,13 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
|||||||
|
|
||||||
const qualityTestFormHandler = (
|
const qualityTestFormHandler = (
|
||||||
tabValue: number,
|
tabValue: number,
|
||||||
testMode: DatasetTestModeType
|
testMode?: DatasetTestModeType
|
||||||
) => {
|
) => {
|
||||||
activeTabHandler(tabValue);
|
activeTabHandler(tabValue);
|
||||||
setTestMode(testMode);
|
if (testMode) {
|
||||||
setShowTestForm(true);
|
setTestMode(testMode);
|
||||||
|
setShowTestForm(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLineageData = () => {
|
const getLineageData = () => {
|
||||||
|
@ -410,7 +410,7 @@ const RolesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'tw-border tw-border-grey-muted tw-rounded tw-px-1 tw-font-normal',
|
'tw-border tw-border-grey-muted tw-rounded tw-px-1 tw-font-normal tw-text-sm',
|
||||||
className
|
className
|
||||||
)}>
|
)}>
|
||||||
Default
|
Default
|
||||||
|
@ -485,7 +485,7 @@ export const getRandomColor = (name: string) => {
|
|||||||
const b = num & 255;
|
const b = num & 255;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
color: 'rgb(' + r + ', ' + g + ', ' + b + ', 0.3)',
|
color: 'rgb(' + r + ', ' + g + ', ' + b + ', 0.6)',
|
||||||
character: firstAlphabet.toUpperCase(),
|
character: firstAlphabet.toUpperCase(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 { HistogramObject } from '../generated/entity/data/table';
|
||||||
|
|
||||||
|
export const getRoundedValue = (
|
||||||
|
value:
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| Date
|
||||||
|
| HistogramObject
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
| any[]
|
||||||
|
) => {
|
||||||
|
if (typeof value == 'number' && !isNaN(value)) {
|
||||||
|
if (Number.isInteger(value)) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return value.toFixed(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user