mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-24 22:18:41 +00:00
ui: worked on DQ feedback part 2 (#12140)
* ui: worked on DQ feedback part 2 * addressed the review comments
This commit is contained in:
parent
d0bc44127e
commit
64e4bffcc2
@ -70,5 +70,5 @@ export interface EditTestCaseModalProps {
|
||||
visible: boolean;
|
||||
testCase: TestCase;
|
||||
onCancel: () => void;
|
||||
onUpdate?: () => void;
|
||||
onUpdate?: (testCase: TestCase) => void;
|
||||
}
|
||||
|
||||
@ -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') })
|
||||
);
|
||||
|
||||
@ -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]}>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
) : (
|
||||
'--'
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user