Profiler & Data Quality UI feedback & improvement #7090 Part 2 (#7114)

* Profiler & Data Quality UI feedback & improvement #7090 Part 2

* addressing comment

* fixed failing unit test
This commit is contained in:
Shailesh Parmar 2022-09-01 16:38:53 +05:30 committed by GitHub
parent bcf2fc3057
commit e180cb15af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 126 deletions

View File

@ -17,12 +17,11 @@ import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch';
import { EditorContentRef } from 'Models';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import {
getTestDefinitionById,
updateTestCaseById,
} from '../../axiosAPIs/testAPI';
import { codeMirrorOption } from '../../constants/profiler.constant';
import { CSMode } from '../../enums/codemirror.enum';
import { TestCaseParameterValue } from '../../generated/tests/testCase';
import {
TestDataType,
@ -32,6 +31,7 @@ import jsonData from '../../jsons/en';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
import Loader from '../Loader/Loader';
import SchemaEditor from '../schema-editor/SchemaEditor';
import { EditTestCaseModalProps } from './AddDataQualityTest.interface';
import ParameterForm from './components/ParameterForm';
@ -63,16 +63,13 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
<Typography.Paragraph className="tw-mb-1.5">
Profile Sample Query
</Typography.Paragraph>
<CodeMirror
data-testid="sql-editor"
options={codeMirrorOption}
<SchemaEditor
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
value={sqlQuery.value || ''}
onBeforeChange={(_Editor, _EditorChange, value) => {
setSqlQuery((pre) => ({ ...pre, value }));
}}
onChange={(_Editor, _EditorChange, value) => {
setSqlQuery((pre) => ({ ...pre, value }));
}}
onChange={(value) => setSqlQuery((pre) => ({ ...pre, value }))}
/>
</Col>
</Row>
@ -171,6 +168,12 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
testDefinition: testCase?.testDefinition?.name,
params: getParamsValue(),
});
setSqlQuery(
testCase?.parameterValues?.[0] ?? {
name: 'sqlExpression',
value: '',
}
);
}
}, [testCase]);
@ -190,6 +193,7 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
<Loader />
) : (
<Form
className="tw-h-70vh tw-overflow-auto"
form={form}
layout="vertical"
name="tableTestForm"

View File

@ -33,13 +33,16 @@ import {
COLORS,
PROFILER_FILTER_RANGE,
} from '../../../constants/profiler.constant';
import { CSMode } from '../../../enums/codemirror.enum';
import {
TestCaseResult,
TestCaseStatus,
} from '../../../generated/tests/tableTest';
import { TestCaseParameterValue } from '../../../generated/tests/testCase';
import { showErrorToast } from '../../../utils/ToastUtils';
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
import Loader from '../../Loader/Loader';
import SchemaEditor from '../../schema-editor/SchemaEditor';
import { TestSummaryProps } from '../profilerDashboard.interface';
type ChartDataType = {
@ -146,6 +149,34 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
fetchTestResults();
}, [selectedTimeRange]);
const showParamsData = (param: TestCaseParameterValue) => {
const isSqlQuery = param.name === 'sqlExpression';
if (isSqlQuery) {
return (
<div key={param.name}>
<Typography.Text>{param.name}: </Typography.Text>
<SchemaEditor
className="tw-w-11/12 tw-mt-1"
editorClass="table-query-editor"
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
}}
value={param.value ?? ''}
/>
</div>
);
}
return (
<div key={param.name}>
<Typography.Text>{param.name}: </Typography.Text>
<Typography.Text>{param.value}</Typography.Text>
</div>
);
};
const referenceArea = () => {
const yValues = data.parameterValues?.reduce((acc, curr, i) => {
return { ...acc, [`y${i + 1}`]: parseInt(curr.value || '') };
@ -215,13 +246,14 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
<Col span={24}>
<Typography.Text type="secondary">Parameter: </Typography.Text>
</Col>
<Col offset={2} span={24}>
{data.parameterValues?.map((param) => (
<Typography key={param.name}>
<Typography.Text>{param.name}: </Typography.Text>
<Typography.Text>{param.value}</Typography.Text>
</Typography>
))}
<Col offset={1} span={24}>
{data.parameterValues && data.parameterValues.length > 0 ? (
data.parameterValues.map(showParamsData)
) : (
<Typography.Text type="secondary">
No Parameter Available
</Typography.Text>
)}
</Col>
<Col className="tw-flex tw-gap-2" span={24}>

View File

@ -27,35 +27,14 @@ import TableProfilerV1 from './TableProfilerV1';
// mock library imports
jest.mock('react-router-dom', () => ({
useHistory: jest.fn().mockImplementation(() => {
jest.fn();
}),
Link: jest
.fn()
.mockImplementation(({ children }) => <a href="#">{children}</a>),
}));
jest.mock('antd', () => ({
Button: jest
.fn()
.mockImplementation(({ children, ...props }) => (
<button {...props}>{children}</button>
)),
Col: jest
.fn()
.mockImplementation(({ children, ...props }) => (
<div {...props}>{children}</div>
)),
Row: jest
.fn()
.mockImplementation(({ children, ...props }) => (
<div {...props}>{children}</div>
)),
Empty: jest
.fn()
.mockImplementation(({ description }) => <div>{description}</div>),
Link: jest.fn().mockImplementation(({ children }) => <a>{children}</a>),
Tooltip: jest.fn().mockImplementation(({ children }) => <p>{children}</p>),
}));
// mock internal imports
jest.mock('./Component/ProfilerSettingsModal', () => {
return jest.fn().mockImplementation(() => {
@ -106,21 +85,6 @@ describe('Test TableProfiler component', () => {
expect(addTableTest).toBeInTheDocument();
});
it('No data placeholder should be visible where there is no profiler', async () => {
render(
<TableProfilerV1
{...mockProps}
table={{ ...mockProps.table, profile: undefined }}
/>
);
const noProfiler = await screen.findByTestId(
'no-profiler-placeholder-container'
);
expect(noProfiler).toBeInTheDocument();
});
it('CTA: Add table test should work properly', async () => {
render(<TableProfilerV1 {...mockProps} />);

View File

@ -11,12 +11,20 @@
* limitations under the License.
*/
import { Button, Col, Empty, Row, Tooltip } from 'antd';
import {
Button,
Col,
Radio,
RadioChangeEvent,
Row,
Space,
Tooltip,
} from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { isEmpty, isUndefined } from 'lodash';
import { isEmpty } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Link, useHistory } from 'react-router-dom';
import { getListTestCase } from '../../axiosAPIs/testAPI';
import { API_RES_MAX_SIZE } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
@ -35,6 +43,7 @@ import {
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { generateEntityLink } from '../../utils/TableUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import { ProfilerDashboardTab } from '../ProfilerDashboard/profilerDashboard.interface';
import ColumnProfileTable from './Component/ColumnProfileTable';
import ProfilerSettingsModal from './Component/ProfilerSettingsModal';
import {
@ -50,12 +59,16 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
hasEditAccess,
}) => {
const { profile, columns } = table;
const history = useHistory();
const [settingModalVisible, setSettingModalVisible] = useState(false);
const [columnTests, setColumnTests] = useState<TestCase[]>([]);
const [tableTests, setTableTests] = useState<TableTestsType>({
tests: [],
results: INITIAL_TEST_RESULT_SUMMARY,
});
const [activeTab] = useState<ProfilerDashboardTab>(
ProfilerDashboardTab.SUMMARY
);
const handleSettingModal = (value: boolean) => {
setSettingModalVisible(value);
@ -92,6 +105,24 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
];
}, [profile, tableTests]);
const tabOptions = useMemo(() => {
return Object.values(ProfilerDashboardTab).filter(
(value) => value !== ProfilerDashboardTab.PROFILER
);
}, []);
const handleTabChange = (e: RadioChangeEvent) => {
const value = e.target.value as ProfilerDashboardTab;
if (ProfilerDashboardTab.DATA_QUALITY === value) {
history.push(
getProfilerDashboardWithFqnPath(
ProfilerDashboardType.TABLE,
table.fullyQualifiedName || ''
)
);
}
};
const fetchAllTests = async () => {
try {
const { data } = await getListTestCase({
@ -130,67 +161,49 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
fetchAllTests();
}, [table]);
if (isUndefined(profile)) {
return (
<div
className=" tw-m-2 tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8"
data-testid="no-profiler-placeholder-container">
<Empty
description={
<p>
<span>
Data Profiler is an optional configuration in Ingestion. Please
enable the data profiler by following the documentation
</span>
<Link
className="tw-ml-1"
target="_blank"
to={{
pathname:
'https://docs.open-metadata.org/openmetadata/ingestion/workflows/profiler',
}}>
here.
</Link>
</p>
}
/>
</div>
);
}
return (
<div
className="table-profiler-container"
data-testid="table-profiler-container">
<div className="tw-flex tw-justify-end tw-gap-4 tw-mb-4">
<Tooltip title={hasEditAccess ? '' : NO_PERMISSION_FOR_ACTION}>
<Link
to={
hasEditAccess
? getAddDataQualityTableTestPath(
ProfilerDashboardType.TABLE,
table.fullyQualifiedName || ''
)
: '#'
}>
<Button
className="tw-rounded"
data-testid="profiler-add-table-test-btn"
disabled={!hasEditAccess}
type="primary">
Add Test
</Button>
</Link>
</Tooltip>
<Button
className="profiler-setting-btn tw-border tw-border-primary tw-rounded tw-text-primary"
data-testid="profiler-setting-btn"
icon={<SVGIcons alt="setting" icon={Icons.SETTINGS_PRIMERY} />}
type="default"
onClick={() => handleSettingModal(true)}>
Settings
</Button>
</div>
<Row className="tw-mb-4" justify="space-between">
<Radio.Group
buttonStyle="solid"
optionType="button"
options={tabOptions}
value={activeTab}
onChange={handleTabChange}
/>
<Space>
<Tooltip title={hasEditAccess ? '' : NO_PERMISSION_FOR_ACTION}>
<Link
to={
hasEditAccess
? getAddDataQualityTableTestPath(
ProfilerDashboardType.TABLE,
`${table.fullyQualifiedName}`
)
: '#'
}>
<Button
className="tw-rounded"
data-testid="profiler-add-table-test-btn"
disabled={!hasEditAccess}
type="primary">
Add Test
</Button>
</Link>
</Tooltip>
<Button
className="profiler-setting-btn tw-border tw-border-primary tw-rounded tw-text-primary"
data-testid="profiler-setting-btn"
icon={<SVGIcons alt="setting" icon={Icons.SETTINGS_PRIMERY} />}
type="default"
onClick={() => handleSettingModal(true)}>
Settings
</Button>
</Space>
</Row>
<Row className="tw-rounded tw-border tw-p-4 tw-mb-4">
{overallSummery.map((summery) => (
@ -211,15 +224,6 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
</p>
</Col>
))}
<Col className="tw-flex tw-justify-end" span={24}>
<Link
to={getProfilerDashboardWithFqnPath(
ProfilerDashboardType.TABLE,
table.fullyQualifiedName || ''
)}>
View more detail
</Link>
</Col>
</Row>
<ColumnProfileTable

View File

@ -41,14 +41,17 @@ const SchemaEditor = ({
},
options,
editorClass,
onChange,
}: {
value: string;
className?: string;
mode?: Mode;
readOnly?: boolean;
options?: {
[key: string]: string | boolean | Array<string>;
};
editorClass?: string;
onChange?: (value: string) => void;
}) => {
const defaultOptions = {
tabSize: JSON_TAB_SIZE,
@ -75,6 +78,13 @@ const SchemaEditor = ({
): void => {
setInternalValue(getSchemaEditorValue(value));
};
const handleEditorInputChange = (
_editor: Editor,
_data: EditorChange,
value: string
): void => {
onChange && onChange(getSchemaEditorValue(value));
};
return (
<div className={className}>
@ -83,6 +93,7 @@ const SchemaEditor = ({
options={defaultOptions}
value={internalValue}
onBeforeChange={handleEditorInputBeforeChange}
onChange={handleEditorInputChange}
/>
</div>
);