ui: worked on DQ feedback part 2 (#12140)

* ui: worked on DQ feedback part 2

* addressed the review comments
This commit is contained in:
Shailesh Parmar 2023-06-25 21:41:15 +05:30 committed by GitHub
parent d0bc44127e
commit 64e4bffcc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 151 additions and 81 deletions

View File

@ -70,5 +70,5 @@ export interface EditTestCaseModalProps {
visible: boolean;
testCase: TestCase;
onCancel: () => void;
onUpdate?: () => void;
onUpdate?: (testCase: TestCase) => void;
}

View File

@ -112,14 +112,22 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
const handleFormSubmit: FormProps['onFinish'] = async (value) => {
const { parameterValues, description } = createTestCaseObj(value);
const updatedTestCase = { ...testCase, parameterValues, description };
const updatedTestCase = {
...testCase,
parameterValues,
description,
name: value.name,
};
const jsonPatch = compare(testCase, updatedTestCase);
if (jsonPatch.length) {
try {
setIsLoadingOnSave(true);
await updateTestCaseById(testCase.id || '', jsonPatch);
onUpdate && onUpdate();
const updateRes = await updateTestCaseById(
testCase.id || '',
jsonPatch
);
onUpdate && onUpdate(updateRes);
showSuccessToast(
t('server.update-entity-success', { entity: t('label.test-case') })
);

View File

@ -19,8 +19,9 @@ import { useTranslation } from 'react-i18next';
import { getTestCaseExecutionSummary } from 'rest/testAPI';
import {} from 'utils/CommonUtils';
import { showErrorToast } from 'utils/ToastUtils';
import { SummaryPanelProps } from './SummaryPanel.interface';
export const SummaryPanel = () => {
export const SummaryPanel = ({ testSuiteId }: SummaryPanelProps) => {
const { t } = useTranslation();
const [summary, setSummary] = useState<TestSummary>();
@ -29,7 +30,7 @@ export const SummaryPanel = () => {
const fetchTestSummary = async () => {
setIsLoading(true);
try {
const response = await getTestCaseExecutionSummary();
const response = await getTestCaseExecutionSummary(testSuiteId);
setSummary(response);
} catch (error) {
showErrorToast(error as AxiosError);
@ -40,7 +41,7 @@ export const SummaryPanel = () => {
useEffect(() => {
fetchTestSummary();
}, []);
}, [testSuiteId]);
return (
<Row wrap gutter={[16, 16]}>

View File

@ -10,31 +10,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import url('../../../styles/variables.less');
.test-case-table-container {
.resolution {
width: max-content;
&.ack {
background-color: @blue-1;
border: 1px solid @blue-2;
.ant-typography {
color: @blue-2;
}
}
&.new {
background-color: @purple-1;
border: 1px solid @purple-2;
.ant-typography {
color: @purple-2;
}
}
&.resolved {
background-color: @green-4;
border: 1px solid @green-5;
.ant-typography {
color: @green-5;
}
}
}
export interface SummaryPanelProps {
testSuiteId?: string;
}

View File

@ -22,7 +22,7 @@ import {
import { startCase } from 'lodash';
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getCurrentUTCDateTimeStamp } from 'utils/TimeUtils';
import { getCurrentUTCDateTimeMillis } from 'utils/TimeUtils';
import { TestCaseStatusModalProps } from './TestCaseStatusModal.interface';
export const TestCaseStatusModal = ({
@ -36,17 +36,12 @@ export const TestCaseStatusModal = ({
const markdownRef = useRef<EditorContentRef>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [description, setDescription] = useState<string | undefined>(
data?.testCaseFailureComment
);
const statusType = Form.useWatch('testCaseFailureStatusType', form);
const handleFormSubmit = (data: TestCaseFailureStatus) => {
const updatedData: TestCaseFailureStatus = {
...data,
testCaseFailureComment: description,
updatedAt: getCurrentUTCDateTimeStamp(),
updatedAt: getCurrentUTCDateTimeMillis(),
updatedBy: AppState.getCurrentUserDetails()?.fullyQualifiedName,
};
onSubmit(updatedData).finally(() => {
@ -115,15 +110,27 @@ export const TestCaseStatusModal = ({
))}
</Select>
</Form.Item>
<Form.Item label={t('label.comment')} name="testCaseFailureComment">
<Form.Item
label={t('label.comment')}
name="testCaseFailureComment"
rules={[
{
required: true,
message: t('label.field-required', {
field: t('label.comment'),
}),
},
]}>
<RichTextEditor
height="200px"
initialValue={description ?? ''}
initialValue={data?.testCaseFailureComment ?? ''}
placeHolder={t('message.write-your-text', {
text: t('label.comment'),
})}
ref={markdownRef}
onTextChange={(value) => setDescription(value)}
onTextChange={(value) =>
form.setFieldValue('testCaseFailureComment', value)
}
/>
</Form.Item>
</>

View File

@ -33,7 +33,6 @@ import { getListTestCase, ListTestCaseParams } from 'rest/testAPI';
import { showErrorToast } from 'utils/ToastUtils';
import { DataQualitySearchParams } from '../DataQuality.interface';
import { SummaryPanel } from '../SummaryPannel/SummaryPanel.component';
import './test-cases.style.less';
export const TestCases = () => {
const history = useHistory();
@ -68,6 +67,18 @@ export const TestCases = () => {
});
};
const handleTestCaseUpdate = (data?: TestCase) => {
if (data) {
setTestCase((prev) => {
const updatedTestCase = prev.data.map((test) =>
test.id === data.id ? { ...test, ...data } : test
);
return { ...prev, data: updatedTestCase };
});
}
};
const fetchTestCases = async (params?: ListTestCaseParams) => {
setIsLoading(true);
try {
@ -172,6 +183,7 @@ export const TestCases = () => {
}}
testCases={testCase.data}
onTestCaseResultUpdate={handleStatusSubmit}
onTestUpdate={handleTestCaseUpdate}
/>
</Col>
</Row>

View File

@ -83,20 +83,25 @@ export const TestSuites = () => {
dataIndex: 'name',
key: 'name',
render: (_, record) => {
const path =
tab === DataQualityPageTabs.TABLES
? {
pathname: getTableTabPath(
record.executableEntityReference?.fullyQualifiedName ?? '',
EntityTabs.PROFILER
),
search: QueryString.stringify({
activeTab: TableProfilerTab.DATA_QUALITY,
}),
}
: getTestSuitePath(record.fullyQualifiedName ?? record.name);
return <Link to={path}>{getEntityName(record)}</Link>;
return record.executable ? (
<Link
to={{
pathname: getTableTabPath(
record.executableEntityReference?.fullyQualifiedName ?? '',
EntityTabs.PROFILER
),
search: QueryString.stringify({
activeTab: TableProfilerTab.DATA_QUALITY,
}),
}}>
{getEntityName(record.executableEntityReference)}
</Link>
) : (
<Link
to={getTestSuitePath(record.fullyQualifiedName ?? record.name)}>
{getEntityName(record)}
</Link>
);
},
},
{

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.test-case-summary-table {
// Removed padding for expanded rows to prevent unnecessary scrolling behavior for the expanded row content
.ant-table.ant-table-small .ant-table-tbody > tr.ant-table-expanded-row > td {
@ -18,3 +18,29 @@
padding-right: 0px;
}
}
.test-case-table-container {
.resolution {
width: max-content;
&.ack {
background-color: @blue-1;
border: 1px solid @blue-2;
.ant-typography {
color: @blue-2;
}
}
&.new {
background-color: @purple-1;
border: 1px solid @purple-2;
.ant-typography {
color: @purple-2;
}
}
&.resolved {
background-color: @green-4;
border: 1px solid @green-3;
.ant-typography {
color: @green-3;
}
}
}
}

View File

@ -35,6 +35,7 @@ import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider
import { TestCaseStatus } from 'generated/configuration/testResultNotificationConfiguration';
import { Operation } from 'generated/entity/policies/policy';
import { isUndefined, sortBy } from 'lodash';
import QueryString from 'qs';
import { putTestCaseResult, removeTestCaseFromTestSuite } from 'rest/testAPI';
import { checkPermission } from 'utils/PermissionsUtils';
import { showErrorToast } from 'utils/ToastUtils';
@ -51,11 +52,15 @@ import {
getEntityFqnFromEntityLink,
getTableExpandableConfig,
} from '../../../utils/TableUtils';
import { getFormattedDateFromSeconds } from '../../../utils/TimeUtils';
import {
getFormattedDateFromMilliSeconds,
getFormattedDateFromSeconds,
} from '../../../utils/TimeUtils';
import DeleteWidgetModal from '../../common/DeleteWidget/DeleteWidgetModal';
import Loader from '../../Loader/Loader';
import {
DataQualityTabProps,
TableProfilerTab,
TestCaseAction,
} from '../profilerDashboard.interface';
import './DataQualityTab.style.less';
@ -191,7 +196,12 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
return (
<Link
data-testid="table-link"
to={getTableTabPath(tableFqn, 'profiler')}
to={{
pathname: getTableTabPath(tableFqn, 'profiler'),
search: QueryString.stringify({
activeTab: TableProfilerTab.DATA_QUALITY,
}),
}}
onClick={(e) => e.stopPropagation()}>
{name}
</Link>
@ -242,12 +252,33 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
width: 100,
render: (value: TestCaseResult) => {
const label = value?.testCaseFailureStatus?.testCaseFailureStatusType;
const failureStatus = value?.testCaseFailureStatus;
return label ? (
<AppBadge
className={classNames('resolution', label.toLocaleLowerCase())}
label={label}
/>
<Tooltip
placement="bottom"
title={
failureStatus?.updatedAt &&
`${getFormattedDateFromMilliSeconds(
failureStatus.updatedAt,
'MMM dd, yyyy HH:mm'
)}
${
failureStatus.updatedBy
? 'by ' + failureStatus.updatedBy
: ''
}`
}>
<div>
<AppBadge
className={classNames(
'resolution',
label.toLocaleLowerCase()
)}
label={label}
/>
</div>
</Tooltip>
) : (
'--'
);

View File

@ -100,7 +100,7 @@ export interface ProfilerSummaryCardProps {
export interface DataQualityTabProps {
testCases: TestCase[];
onTestUpdate?: () => void;
onTestUpdate?: (testCase?: TestCase) => void;
showTableColumn?: boolean;
isLoading?: boolean;
onTestCaseResultUpdate?: (data: TestCase) => void;

View File

@ -57,7 +57,7 @@ export const QualityTab = ({
return (
<Row gutter={[0, 16]}>
<Col span={24}>
<SummaryPanel />
<SummaryPanel testSuiteId={testSuite?.id} />
</Col>
<Col span={24}>
<Tabs items={tabs} />

View File

@ -15,13 +15,14 @@ import { Button, Form, Input, Space } from 'antd';
import RichTextEditor from 'components/common/rich-text-editor/RichTextEditor';
import Loader from 'components/Loader/Loader';
import { ENTITY_NAME_REGEX } from 'constants/regex.constants';
import { DataQualityPageTabs } from 'pages/DataQuality/DataQualityPage.interface';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { getListTestSuites } from 'rest/testAPI';
import { getDataQualityPagePath } from 'utils/RouterUtils';
import {
PAGE_SIZE_MEDIUM,
ROUTES,
VALIDATION_MESSAGES,
} from '../../../constants/constants';
import { TestSuite } from '../../../generated/tests/testSuite';
@ -52,6 +53,10 @@ const AddTestSuiteForm: React.FC<AddTestSuiteFormProps> = ({
}
};
const handleCancelClick = () => {
history.push(getDataQualityPagePath(DataQualityPageTabs.TEST_SUITES));
};
useEffect(() => {
fetchTestSuites();
}, []);
@ -120,9 +125,7 @@ const AddTestSuiteForm: React.FC<AddTestSuiteFormProps> = ({
<Form.Item noStyle>
<Space className="w-full justify-end" size={16}>
<Button
data-testid="cancel-button"
onClick={() => history.push(ROUTES.DATA_QUALITY)}>
<Button data-testid="cancel-button" onClick={handleCancelClick}>
{t('label.cancel')}
</Button>
<Button data-testid="submit-button" htmlType="submit" type="primary">

View File

@ -16,3 +16,4 @@ export const GREEN_3 = '#48ca9e';
export const GREEN_3_OPACITY = '#48ca9e30';
export const YELLOW_2 = '#ffbe0e';
export const RED_3 = '#f24822';
export const PURPLE_2 = '#7147e8';

View File

@ -29,6 +29,7 @@ import {
ProfileSampleType,
} from '../generated/entity/data/table';
import { TestCaseStatus } from '../generated/tests/testCase';
import { GREEN_3, PURPLE_2, RED_3 } from './Color.constants';
import { JSON_TAB_SIZE } from './constants';
export const excludedMetrics = [
@ -256,7 +257,7 @@ export const INITIAL_ROW_METRIC_VALUE = {
entity: t('label.row'),
}),
dataKey: 'rowCount',
color: '#43a047',
color: GREEN_3,
},
],
data: [],
@ -267,17 +268,17 @@ export const INITIAL_OPERATION_METRIC_VALUE = {
{
title: t('label.insert'),
dataKey: DMLOperationType.Insert,
color: '#7147e8',
color: GREEN_3,
},
{
title: t('label.update'),
dataKey: DMLOperationType.Update,
color: '#43a047',
color: PURPLE_2,
},
{
title: t('label.delete'),
dataKey: DMLOperationType.Delete,
color: '#ff7c50',
color: RED_3,
},
],
data: [],

View File

@ -128,7 +128,7 @@ export const updateTestCaseById = async (id: string, data: Operation[]) => {
headers: { 'Content-type': 'application/json-patch+json' },
};
const response = await APIClient.patch<Operation[], AxiosResponse<TestSuite>>(
const response = await APIClient.patch<Operation[], AxiosResponse<TestCase>>(
`${testCaseUrl}/${id}`,
data,
configOptions
@ -137,9 +137,10 @@ export const updateTestCaseById = async (id: string, data: Operation[]) => {
return response.data;
};
export const getTestCaseExecutionSummary = async () => {
export const getTestCaseExecutionSummary = async (testSuiteId?: string) => {
const response = await APIClient.get<TestSummary>(
`${testCaseUrl}/executionSummary`
`${testCaseUrl}/executionSummary`,
{ params: { testSuiteId } }
);
return response.data;

View File

@ -19,8 +19,7 @@
@green-1: #28a744;
@green-2: #ebf9f4;
@green-3: #48ca9e;
@green-4: #e3ece3;
@green-5: #43a047;
@green-4: #48ca9e30;
@warning-color: #ffc34e;
@yellow-1: #fbf2db;
@yellow-2: #ffbe0e;