mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-23 06:05:54 +00:00
Update data contract details fields (#22746)
* update data contract details fields * fix the select option chip not properly displaying the data * supported new form field for UserTeamSelectInput Selector * remove component changes that we not needed * fix the selected owner chip design * supported the userPopoverList width as per screen size with max and min limitation * fix the tab inside userTeamSelect list not proper as the content and some oprimiztion on component side * fix the playwright because of owner changes * fix the description box overflow and owner selcect box overlapping on select input * enum label fix * remove the comment code and fix the localization and sematic status changing issue are contract validation run
This commit is contained in:
parent
882d858972
commit
accc05a494
@ -100,7 +100,7 @@ test.describe('Data Contracts', () => {
|
||||
DATA_CONTRACT_DETAILS.description
|
||||
);
|
||||
|
||||
await page.getByTestId('add-owner').click();
|
||||
await page.getByTestId('select-owners').click();
|
||||
await page.getByRole('tab', { name: 'Users' }).click();
|
||||
await page
|
||||
.getByTestId('owner-select-users-search-bar')
|
||||
@ -114,9 +114,7 @@ test.describe('Data Contracts', () => {
|
||||
await page.getByTestId('selectable-list-update-btn').click();
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('owner-link')
|
||||
.getByTestId(user.responseData.displayName)
|
||||
page.getByTestId('user-tag').getByText(user.responseData.name)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@ -130,10 +130,10 @@
|
||||
}
|
||||
|
||||
.ant-btn-group.spaced {
|
||||
.ant-btn.data-contract-latest-result-button {
|
||||
.ant-btn.ant-btn-default.data-contract-latest-result-button {
|
||||
font-size: 14px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px !important;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0px 2px 2px -1px @grey-35, 0px 4px 6px -2px @grey-35,
|
||||
0px 12px 16px -4px @grey-35;
|
||||
@ -153,11 +153,6 @@
|
||||
color: @orange-7;
|
||||
border: 1px solid @orange-200;
|
||||
background-color: @orange-50;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid @red-19;
|
||||
background-color: @red-2;
|
||||
}
|
||||
}
|
||||
|
||||
&.running {
|
||||
|
||||
@ -10,20 +10,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Icon, { PlusOutlined } from '@ant-design/icons';
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Button, Card, Form, Typography } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as RightIcon } from '../../../assets/svg/right-arrow.svg';
|
||||
import { DataContract } from '../../../generated/entity/data/dataContract';
|
||||
import { EntityReference } from '../../../generated/type/entityReference';
|
||||
import {
|
||||
FieldProp,
|
||||
FieldTypes,
|
||||
FormItemLayout,
|
||||
} from '../../../interface/FormUtils.interface';
|
||||
import { FieldProp, FieldTypes } from '../../../interface/FormUtils.interface';
|
||||
import { generateFormFields } from '../../../utils/formUtils';
|
||||
import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
|
||||
import './contract-detail-form-tab.less';
|
||||
|
||||
export const ContractDetailFormTab: React.FC<{
|
||||
initialValues?: Partial<DataContract>;
|
||||
@ -34,8 +29,6 @@ export const ContractDetailFormTab: React.FC<{
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const owners = Form.useWatch<EntityReference[]>('owners', form);
|
||||
|
||||
const fields: FieldProp[] = [
|
||||
{
|
||||
label: t('label.contract-title'),
|
||||
@ -47,6 +40,22 @@ export const ContractDetailFormTab: React.FC<{
|
||||
'data-testid': 'contract-name',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('label.owner-plural'),
|
||||
name: 'owners',
|
||||
id: 'root/owner',
|
||||
type: FieldTypes.USER_TEAM_SELECT_INPUT,
|
||||
required: false,
|
||||
props: {
|
||||
owner: initialValues?.owners,
|
||||
hasPermission: true,
|
||||
multiple: { user: true, team: false },
|
||||
},
|
||||
formItemProps: {
|
||||
valuePropName: 'owners',
|
||||
trigger: 'onUpdate',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('label.description'),
|
||||
id: 'description',
|
||||
@ -58,31 +67,6 @@ export const ContractDetailFormTab: React.FC<{
|
||||
initialValue: initialValues?.description ?? '',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('label.owner-plural'),
|
||||
id: 'owners',
|
||||
name: 'owners',
|
||||
type: FieldTypes.USER_TEAM_SELECT,
|
||||
required: false,
|
||||
props: {
|
||||
owner: initialValues?.owners,
|
||||
hasPermission: true,
|
||||
children: (
|
||||
<Button
|
||||
data-testid="add-owner"
|
||||
icon={<PlusOutlined style={{ color: 'white', fontSize: '12px' }} />}
|
||||
size="small"
|
||||
type="primary"
|
||||
/>
|
||||
),
|
||||
multiple: { user: true, team: false },
|
||||
},
|
||||
formItemLayout: FormItemLayout.HORIZONTAL,
|
||||
formItemProps: {
|
||||
valuePropName: 'owners',
|
||||
trigger: 'onUpdate',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@ -109,13 +93,11 @@ export const ContractDetailFormTab: React.FC<{
|
||||
|
||||
<div className="contract-form-content-container">
|
||||
<Form
|
||||
className="contract-detail-form"
|
||||
className="new-form-style contract-detail-form"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={onChange}>
|
||||
{generateFormFields(fields)}
|
||||
|
||||
{owners?.length > 0 && <OwnerLabel owners={owners} />}
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
.contract-detail-form {
|
||||
.block-editor-wrapper.block-editor-wrapper--bar-menu {
|
||||
.om-block-editor {
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,7 +266,6 @@ const ContractDetail: React.FC<{
|
||||
setValidateLoading(true);
|
||||
await validateContractById(contract.id);
|
||||
showSuccessToast(t('message.contract-validation-trigger-successfully'));
|
||||
fetchLatestContractResults();
|
||||
} catch (err) {
|
||||
showErrorToast(err as AxiosError);
|
||||
} finally {
|
||||
|
||||
@ -246,7 +246,9 @@ export const ContractQualityFormTab: React.FC<{
|
||||
},
|
||||
}}
|
||||
searchProps={{
|
||||
placeholder: t('label.search-by-name'),
|
||||
placeholder: t('label.search-by-type', {
|
||||
type: t('label.name'),
|
||||
}),
|
||||
onSearch: (value) => {
|
||||
fetchAllTests({
|
||||
offset: 0,
|
||||
|
||||
@ -11,10 +11,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Space, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { isUndefined, toString } from 'lodash';
|
||||
import { ReactComponent as CloseOutlined } from '../../../assets/svg/close.svg';
|
||||
import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
import './user-tag.less';
|
||||
import { UserTags, UserTagSize } from './UserTag.interface';
|
||||
@ -66,7 +67,14 @@ export const UserTag = ({
|
||||
width={toString(width[size])}
|
||||
/>
|
||||
<Typography.Text className={fontSizes[size]}>{name}</Typography.Text>
|
||||
{closable && <CloseOutlined size={width[size]} onClick={onRemove} />}
|
||||
{closable && (
|
||||
<Icon
|
||||
component={CloseOutlined}
|
||||
data-testid="close-icon"
|
||||
size={width[size]}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
@ -63,9 +63,7 @@ describe('UserTag Component', () => {
|
||||
|
||||
it('calls onRemove when close icon is clicked', () => {
|
||||
render(<UserTag {...userTagProps} />);
|
||||
const closeIcon = screen
|
||||
.getByTestId('user-tag')
|
||||
.querySelector('.anticon-close');
|
||||
const closeIcon = screen.getByTestId('close-icon');
|
||||
|
||||
// Simulate click on the close icon
|
||||
closeIcon && fireEvent.click(closeIcon);
|
||||
|
||||
@ -51,12 +51,7 @@
|
||||
.ant-select-item {
|
||||
padding: 8px 12px;
|
||||
|
||||
// &:hover {
|
||||
// background-color: @item-hover-bg;
|
||||
// }
|
||||
|
||||
&.selected-option {
|
||||
// background-color: @primary-color-1;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
@ -116,14 +111,6 @@
|
||||
&.ant-select-open {
|
||||
.ant-select-selector {
|
||||
border-color: @primary-color;
|
||||
// box-shadow: 0 0 0 2px fade(@primary-color, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-select-disabled {
|
||||
.ant-select-selector {
|
||||
// background-color: @disabled-bg;
|
||||
// color: @disabled-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
*/
|
||||
import Icon from '@ant-design/icons/lib/components/Icon';
|
||||
import { Popover, Space, Tabs, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { isArray, isEmpty, noop, toString } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -64,6 +65,7 @@ export const UserTeamSelectableList = ({
|
||||
previewSelected = false,
|
||||
listHeight = ADD_USER_CONTAINER_HEIGHT,
|
||||
tooltipText,
|
||||
overlayClassName,
|
||||
}: UserSelectDropdownProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
@ -352,7 +354,10 @@ export const UserTeamSelectableList = ({
|
||||
</FocusTrapWithContainer>
|
||||
}
|
||||
open={popupVisible}
|
||||
overlayClassName="user-team-select-popover card-shadow"
|
||||
overlayClassName={classNames(
|
||||
'user-team-select-popover card-shadow',
|
||||
overlayClassName
|
||||
)}
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
trigger="click"
|
||||
|
||||
@ -31,4 +31,5 @@ export interface UserSelectDropdownProps {
|
||||
previewSelected?: boolean;
|
||||
listHeight?: number;
|
||||
tooltipText?: string;
|
||||
overlayClassName?: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2025 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 { Select } from 'antd';
|
||||
import { noop } from 'lodash';
|
||||
import type { CustomTagProps } from 'rc-select/lib/BaseSelect';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EntityReference } from '../../../generated/entity/teams/user';
|
||||
import { UserTag } from '../UserTag/UserTag.component';
|
||||
import { UserTagSize } from '../UserTag/UserTag.interface';
|
||||
import { UserTeamSelectableList } from '../UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { UserSelectDropdownProps } from '../UserTeamSelectableList/UserTeamSelectableList.interface';
|
||||
import './user-team-selectable-list-search-input.less';
|
||||
|
||||
interface UserTeamSelectableListSearchProps extends UserSelectDropdownProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const UserTeamSelectableListSearchInput: React.FC<UserTeamSelectableListSearchProps> =
|
||||
({
|
||||
disabled,
|
||||
hasPermission,
|
||||
owner,
|
||||
onUpdate = noop,
|
||||
onClose,
|
||||
multiple,
|
||||
label,
|
||||
previewSelected = false,
|
||||
listHeight,
|
||||
tooltipText,
|
||||
}) => {
|
||||
const [popoverVisible, setPopoverVisible] = useState(false);
|
||||
const [selectedUsers, setSelectedUsers] = useState<EntityReference[]>([]);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
setPopoverVisible(true);
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setPopoverVisible(false);
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePopoverVisibleChange = (visible: boolean) => {
|
||||
setPopoverVisible(visible);
|
||||
if (!visible && onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (updatedUser?: EntityReference[]) => {
|
||||
if (onUpdate) {
|
||||
setSelectedUsers(updatedUser ?? []);
|
||||
handleClose();
|
||||
onUpdate(updatedUser);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnChangeSelect = (value: string[]) => {
|
||||
if (onUpdate) {
|
||||
const updatedUser = selectedUsers.filter((item) =>
|
||||
value.includes(item.name ?? '')
|
||||
);
|
||||
|
||||
setSelectedUsers(updatedUser ?? []);
|
||||
handleClose();
|
||||
onUpdate(updatedUser);
|
||||
}
|
||||
};
|
||||
|
||||
const selectedValues = useMemo(
|
||||
() =>
|
||||
selectedUsers
|
||||
.map((user) => user.name)
|
||||
.filter((name): name is string => Boolean(name)),
|
||||
[selectedUsers]
|
||||
);
|
||||
|
||||
const customTagRender = (props: CustomTagProps) => {
|
||||
const { value, closable, onClose } = props;
|
||||
|
||||
const selectedAssignee = selectedUsers?.find(
|
||||
(option) => option.name === value
|
||||
);
|
||||
|
||||
const tagProps = {
|
||||
id: selectedAssignee?.name ?? value,
|
||||
name: selectedAssignee?.name ?? value,
|
||||
closable: closable,
|
||||
onRemove: onClose,
|
||||
size: UserTagSize.small,
|
||||
isTeam: selectedAssignee?.type === 'team',
|
||||
className: 'assignee-tag',
|
||||
};
|
||||
|
||||
return <UserTag {...tagProps} />;
|
||||
};
|
||||
|
||||
const selectInput = useMemo(() => {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
className="select-owners"
|
||||
data-testid="select-owners"
|
||||
defaultActiveFirstOption={false}
|
||||
disabled={disabled}
|
||||
filterOption={false}
|
||||
mode="multiple"
|
||||
notFoundContent={null}
|
||||
suffixIcon={null}
|
||||
tagRender={customTagRender}
|
||||
value={selectedValues}
|
||||
onChange={handleOnChangeSelect}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
disabled,
|
||||
selectedValues,
|
||||
customTagRender,
|
||||
handleOnChangeSelect,
|
||||
handleFocus,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedUsers(owner ?? []);
|
||||
}, [owner]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{popoverVisible && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={hasPermission}
|
||||
label={label}
|
||||
listHeight={listHeight}
|
||||
multiple={multiple}
|
||||
overlayClassName="user-team-selectable-list-search-input-popover"
|
||||
owner={selectedUsers}
|
||||
popoverProps={{
|
||||
open: popoverVisible,
|
||||
onOpenChange: handlePopoverVisibleChange,
|
||||
trigger: 'click',
|
||||
placement: 'bottomLeft',
|
||||
}}
|
||||
previewSelected={previewSelected}
|
||||
tooltipText={tooltipText}
|
||||
onClose={handleClose}
|
||||
onUpdate={handleUpdate}>
|
||||
{/* Have to pass the selectInput as children, so popover can become targetComponent
|
||||
and popover don't overflow on it */}
|
||||
{selectInput}
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
{/* Conditionally render the select input, to avoid the UserTeamSelectableList component
|
||||
render unnecessarily */}
|
||||
{!popoverVisible && selectInput}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTeamSelectableListSearchInput;
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 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 (reference) url('../../../styles/variables.less');
|
||||
|
||||
.ant-popover.user-team-selectable-list-search-input-popover {
|
||||
width: calc(50vw - 50px);
|
||||
min-width: 260px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.select-owners {
|
||||
.assignee-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
background: @white;
|
||||
padding: 2px 12px;
|
||||
height: 28px;
|
||||
|
||||
border-radius: 6px;
|
||||
gap: 4px;
|
||||
border: 1px solid @grey-300;
|
||||
|
||||
.ant-typography {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: @grey-700;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: @grey-400;
|
||||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.ant-select-selection-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ant-select-selection-item-remove {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ export enum FieldTypes {
|
||||
DESCRIPTION = 'description',
|
||||
TAG_SUGGESTION = 'tag_suggestion',
|
||||
USER_TEAM_SELECT = 'user_team_select',
|
||||
USER_TEAM_SELECT_INPUT = 'user_team_select_input',
|
||||
USER_MULTI_SELECT = 'user_multi_select',
|
||||
COLOR_PICKER = 'color_picker',
|
||||
DOMAIN_SELECT = 'domain_select',
|
||||
|
||||
@ -916,7 +916,7 @@ export const migrateJsonLogic = (
|
||||
};
|
||||
|
||||
return migrateNode(jsonLogic) as Record<string, unknown>;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFieldsByKeys = (
|
||||
keys: EntityReferenceFields[],
|
||||
|
||||
@ -49,6 +49,7 @@ import { UserSelectableList } from '../components/common/UserSelectableList/User
|
||||
import { UserSelectableListProps } from '../components/common/UserSelectableList/UserSelectableList.interface';
|
||||
import { UserTeamSelectableList } from '../components/common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { UserSelectDropdownProps } from '../components/common/UserTeamSelectableList/UserTeamSelectableList.interface';
|
||||
import UserTeamSelectableListSearchInput from '../components/common/UserTeamSelectableListSearchInput/UserTeamSelectableListSearchInput.component';
|
||||
import { HTTP_STATUS_CODE } from '../constants/Auth.constants';
|
||||
import {
|
||||
FieldProp,
|
||||
@ -225,6 +226,17 @@ export const getField = (field: FieldProp) => {
|
||||
|
||||
break;
|
||||
|
||||
case FieldTypes.USER_TEAM_SELECT_INPUT:
|
||||
{
|
||||
fieldElement = (
|
||||
<UserTeamSelectableListSearchInput
|
||||
{...(props as unknown as UserSelectDropdownProps)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FieldTypes.USER_MULTI_SELECT:
|
||||
{
|
||||
const { children, ...rest } = props;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user