UI : Data profiler and Feed changes (#3267)

This commit is contained in:
Sachin Chaurasiya 2022-03-08 18:27:34 +05:30 committed by GitHub
parent 643eadb118
commit b93a8c8d0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 178 additions and 59 deletions

View File

@ -23,6 +23,8 @@ import React, {
} from 'react';
import { getFeedById } from '../../../axiosAPIs/feedsAPI';
import { getEntityField, getReplyText } from '../../../utils/FeedUtils';
import { Button } from '../../buttons/Button/Button';
import PopOver from '../../common/popover/PopOver';
import Loader from '../../Loader/Loader';
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
@ -39,6 +41,7 @@ interface FeedPanelHeaderProp
Pick<ActivityFeedPanelProp, 'onCancel'> {
entityField: string;
noun?: string;
onShowNewConversation?: (v: boolean) => void;
}
interface FeedPanelOverlayProp
extends HTMLAttributes<HTMLButtonElement>,
@ -53,6 +56,7 @@ export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
entityField,
className,
noun,
onShowNewConversation,
}) => {
return (
<header className={className}>
@ -61,21 +65,41 @@ export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
{noun ? noun : 'Conversation'} on{' '}
<span className="tw-heading">{entityField}</span>
</p>
<svg
className="tw-w-5 tw-h-5 tw-ml-1 tw-cursor-pointer"
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 className="tw-flex">
{onShowNewConversation ? (
<PopOver
position="bottom"
title="Start conversation"
trigger="mouseenter">
<Button
className={classNames('tw-h-7 tw-px-2')}
data-testid="add-teams"
size="small"
theme="primary"
variant="outlined"
onClick={() => {
onShowNewConversation?.(true);
}}>
<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>
<hr className="tw--mx-4" />
</header>

View File

@ -225,9 +225,15 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
const [threads, setThreads] = useState<EntityThread[]>([]);
const [selectedThread, setSelectedThread] = useState<EntityThread>();
const [selectedThreadId, setSelectedThreadId] = useState<string>('');
const [showNewConversation, setShowNewConversation] =
useState<boolean>(false);
const entityField = getEntityField(threadLink);
const onShowNewConversation = (value: boolean) => {
setShowNewConversation(value);
};
const getThreads = () => {
getAllFeeds(threadLink).then((res: AxiosResponse) => {
const { data } = res.data;
@ -310,6 +316,9 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
entityField={entityField as string}
noun="Conversations"
onCancel={onCancel}
onShowNewConversation={
threads.length > 0 ? onShowNewConversation : undefined
}
/>
{!isUndefined(selectedThread) ? (
<Fragment>
@ -326,6 +335,20 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
</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
className="tw-py-6 tw-pl-5"
postFeed={postFeed}
@ -334,12 +357,6 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
onThreadIdSelect={onThreadIdSelect}
onThreadSelect={onThreadSelect}
/>
<ActivityFeedEditor
buttonClass="tw-mr-4"
className="tw-ml-5 tw-mr-2 tw-mb-6"
placeHolder="Enter a message"
onSave={onPostThread}
/>
</Fragment>
)}
</div>

View File

@ -81,7 +81,7 @@ export interface DatasetDetailsProps {
showTestForm: boolean;
qualityTestFormHandler: (
tabValue: number,
testMode: DatasetTestModeType
testMode?: DatasetTestModeType
) => void;
handleShowTestForm: (value: boolean) => void;
handleTestModeChange: (mode: DatasetTestModeType) => void;

View File

@ -22,6 +22,7 @@ import {
ColumnTest,
DatasetTestModeType,
} from '../../interface/dataQuality.interface';
import { getRoundedValue } from '../../utils/ProfilerUtils';
import { getConstraintIcon } from '../../utils/TableUtils';
import { Button } from '../buttons/Button/Button';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
@ -38,29 +39,26 @@ type Props = {
}>;
qualityTestFormHandler: (
tabValue: number,
testMode: DatasetTestModeType
testMode?: DatasetTestModeType
) => void;
};
const PercentageGraph = ({
percentage,
title,
}: {
percentage: number;
title: string;
}) => {
return (
<PopOver
position="top"
title={`${percentage}% ${title}`}
trigger="mouseenter">
<div className="tw-border tw-border-primary tw-h-6 tw-w-20">
<div
className="tw-bg-primary tw-opacity-40 tw-h-full"
style={{ width: `${percentage}%` }}
/>
<div className="tw-border tw-inline-block tw-border-primary tw-h-5 tw-w-20 ">
<div
className="tw-bg-primary-hover-lite tw-h-full"
style={{ width: `${percentage}%` }}>
<div className="tw-w-20 tw-text-grey-body tw-font-medium tw-text-xs tw-leading-5 tw-text-center">
{getRoundedValue(percentage)}%
</div>
</div>
</PopOver>
</div>
);
};
@ -91,10 +89,12 @@ const TableProfiler: FC<Props> = ({
return {
name: column,
columnMetrics: Object.entries(data?.[0] ?? {}).map((d) => ({
key: d[0],
value: d[1],
})),
columnMetrics: Object.entries(data?.[0] ?? {})
.map((d) => ({
key: d[0],
value: d[1],
}))
.filter((m) => !excludedMetrics.includes(m.key)),
columnTests: column.colTests,
data,
type: column.colType,
@ -112,9 +112,9 @@ const TableProfiler: FC<Props> = ({
<tr className="tableHead-row">
<th className="tableHead-cell">Column Name</th>
<th className="tableHead-cell">Type</th>
<th className="tableHead-cell">Null</th>
<th className="tableHead-cell">Unique</th>
<th className="tableHead-cell">Distinct</th>
<th className="tableHead-cell">Null %</th>
<th className="tableHead-cell">Unique %</th>
<th className="tableHead-cell">Distinct %</th>
<th className="tableHead-cell">Metrics</th>
<th className="tableHead-cell">Tests</th>
<th className="tableHead-cell" />
@ -139,7 +139,32 @@ const TableProfiler: FC<Props> = ({
)}
</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>
</td>
<td
@ -188,9 +213,7 @@ const TableProfiler: FC<Props> = ({
<td className="tw-relative tableBody-cell profiler-graph">
<PercentageGraph
percentage={
((col.data?.[0]?.distinctCount ?? 0) /
(col.data?.[0]?.rows ?? 0)) *
100
(col.data?.[0]?.distinctProportion ?? 0) * 100
}
title="distinct value"
/>
@ -198,17 +221,21 @@ const TableProfiler: FC<Props> = ({
<td
className="tw-relative 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
.filter((m) => !excludedMetrics.includes(m.key))
.map((m, i) => (
{col.columnMetrics.length ? (
<div className=" tw-rounded tw-max-h-44 tw-overflow-y-auto">
{col.columnMetrics.map((m, i) => (
<p className="tw-mb-1 tw-flex" key={i}>
<span className="tw-mx-1">{m.key}</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>
))}
</div>
</div>
) : (
`No metrics available`
)}
</td>
<td
className="tw-relative tableBody-cell"
@ -216,7 +243,7 @@ const TableProfiler: FC<Props> = ({
data-testid="tableBody-cell">
<div className="tw-flex tw-justify-between">
{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) => (
<div className="tw-flex tw-mb-2" key={i}>
<p className="tw-mr-2">
@ -230,7 +257,9 @@ const TableProfiler: FC<Props> = ({
)}
</p>
<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}
</span>
<div className="tw-mx-1">
@ -263,6 +292,7 @@ const TableProfiler: FC<Props> = ({
}
)}
size="custom"
theme="primary"
type="button"
variant="outlined"
onClick={() =>

View File

@ -24,6 +24,7 @@ import { getVersion } from '../../axiosAPIs/miscAPI';
import {
getExplorePathWithSearch,
getTeamDetailsPath,
getUserPath,
navLinkSettings,
ROUTES,
} from '../../constants/constants';
@ -149,7 +150,10 @@ const Appbar: React.FC = (): JSX.Element => {
return (
<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" />
{(roles?.length ?? 0) > 0 ? (
<div>

View File

@ -20,4 +20,7 @@ export const excludedMetrics = [
'uniqueProportion',
'rows',
'histogram',
'missingCount',
'missingPercentage',
'distinctProportion',
];

View File

@ -632,6 +632,8 @@ export interface ColumnProfile {
minLength?: number;
maxLength?: number;
distinctProportion?: number;
}
export interface HistogramObject {

View File

@ -193,11 +193,13 @@ const DatasetDetailsPage: FunctionComponent = () => {
const qualityTestFormHandler = (
tabValue: number,
testMode: DatasetTestModeType
testMode?: DatasetTestModeType
) => {
activeTabHandler(tabValue);
setTestMode(testMode);
setShowTestForm(true);
if (testMode) {
setTestMode(testMode);
setShowTestForm(true);
}
};
const getLineageData = () => {

View File

@ -410,7 +410,7 @@ const RolesPage = () => {
return (
<span
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
)}>
Default

View File

@ -485,7 +485,7 @@ export const getRandomColor = (name: string) => {
const b = num & 255;
return {
color: 'rgb(' + r + ', ' + g + ', ' + b + ', 0.3)',
color: 'rgb(' + r + ', ' + g + ', ' + b + ', 0.6)',
character: firstAlphabet.toUpperCase(),
};
};

View File

@ -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;
}
};