mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-09 14:10:18 +00:00
Minor: OSS requirements for the custom property automation action (#19507)
* Fix the return type for the custom property API function * Break down the EditTableTypePropertyModal.tsx component * Fix the custom property inputs for date-time type properties * auto lint formatting changes * Add the autoFocus prop for DataAssetAsyncSelectList * Fix and add the unit tests for new code (cherry picked from commit 638e988dc697bb77538b74b3aab562031066ae05)
This commit is contained in:
parent
9a6480ba74
commit
1dc8da2e21
@ -27,6 +27,7 @@ export interface FetchOptionsResponse {
|
||||
|
||||
export interface DataAssetAsyncSelectListProps {
|
||||
mode?: 'multiple';
|
||||
autoFocus?: boolean;
|
||||
id?: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
|
||||
@ -36,6 +36,7 @@ import {
|
||||
|
||||
const DataAssetAsyncSelectList: FC<DataAssetAsyncSelectListProps> = ({
|
||||
mode,
|
||||
autoFocus = true,
|
||||
onChange,
|
||||
debounceTimeout = 800,
|
||||
initialOptions,
|
||||
@ -242,8 +243,8 @@ const DataAssetAsyncSelectList: FC<DataAssetAsyncSelectListProps> = ({
|
||||
return (
|
||||
<Select
|
||||
allowClear
|
||||
autoFocus
|
||||
showSearch
|
||||
autoFocus={autoFocus}
|
||||
data-testid="asset-select-list"
|
||||
dropdownRender={dropdownRender}
|
||||
filterOption={false}
|
||||
|
||||
@ -224,24 +224,16 @@ export const AdvanceSearchProvider = ({
|
||||
|
||||
Object.entries(res).forEach(([_, fields]) => {
|
||||
if (Array.isArray(fields) && fields.length > 0) {
|
||||
fields.forEach(
|
||||
(field: {
|
||||
name: string;
|
||||
type: string;
|
||||
customPropertyConfig: {
|
||||
config: string | string[];
|
||||
fields.forEach((field) => {
|
||||
if (field.name && field.type) {
|
||||
const { subfieldsKey, dataObject } =
|
||||
advancedSearchClassBase.getCustomPropertiesSubFields(field);
|
||||
subfields[subfieldsKey] = {
|
||||
...dataObject,
|
||||
valueSources: dataObject.valueSources as ValueSource[],
|
||||
};
|
||||
}) => {
|
||||
if (field.name && field.type) {
|
||||
const { subfieldsKey, dataObject } =
|
||||
advancedSearchClassBase.getCustomPropertiesSubFields(field);
|
||||
subfields[subfieldsKey] = {
|
||||
...dataObject,
|
||||
valueSources: dataObject.valueSources as ValueSource[],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -37,7 +37,6 @@ import {
|
||||
noop,
|
||||
omitBy,
|
||||
toNumber,
|
||||
toUpper,
|
||||
} from 'lodash';
|
||||
import moment, { Moment } from 'moment';
|
||||
import React, {
|
||||
@ -65,6 +64,7 @@ import { CSMode } from '../../../enums/codemirror.enum';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { Config } from '../../../generated/type/customProperty';
|
||||
import { getCustomPropertyMomentFormat } from '../../../utils/CustomProperty.utils';
|
||||
import { calculateInterval } from '../../../utils/date-time/DateTimeUtils';
|
||||
import entityUtilClassBase from '../../../utils/EntityUtilClassBase';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
@ -278,9 +278,9 @@ export const PropertyValue: FC<PropertyValueProps> = ({
|
||||
|
||||
case 'date-cp':
|
||||
case 'dateTime-cp': {
|
||||
// Default format is 'yyyy-mm-dd'
|
||||
const format = toUpper(
|
||||
(property.customPropertyConfig?.config as string) ?? 'yyyy-mm-dd'
|
||||
const format = getCustomPropertyMomentFormat(
|
||||
propertyType.name,
|
||||
property.customPropertyConfig?.config
|
||||
);
|
||||
|
||||
const initialValues = {
|
||||
@ -327,8 +327,11 @@ export const PropertyValue: FC<PropertyValueProps> = ({
|
||||
}
|
||||
|
||||
case 'time-cp': {
|
||||
const format =
|
||||
(property.customPropertyConfig?.config as string) ?? 'HH:mm:ss';
|
||||
const format = getCustomPropertyMomentFormat(
|
||||
propertyType.name,
|
||||
property.customPropertyConfig?.config
|
||||
);
|
||||
|
||||
const initialValues = {
|
||||
time: value ? moment(value, format) : undefined,
|
||||
};
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 { CustomProperty } from '../../../../generated/entity/type';
|
||||
import { TableTypePropertyValueType } from '../CustomPropertyTable.interface';
|
||||
|
||||
export interface EditTableTypePropertyModalProps {
|
||||
isVisible: boolean;
|
||||
isUpdating: boolean;
|
||||
property: CustomProperty;
|
||||
columns: string[];
|
||||
rows: Record<string, string>[];
|
||||
onCancel: () => void;
|
||||
onSave: (data: TableTypePropertyValueType) => Promise<void>;
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom/extend-expect';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { CustomProperty } from '../../../../generated/type/customProperty';
|
||||
import EditTableTypePropertyModal from './EditTableTypePropertyModal';
|
||||
import { EditTableTypePropertyModalProps } from './EditTableTypePropertyModal.interface';
|
||||
|
||||
jest.mock('./TableTypePropertyEditTable', () =>
|
||||
jest.fn().mockImplementation(() => <div>TableTypePropertyEditTable</div>)
|
||||
);
|
||||
|
||||
jest.mock('./TableTypePropertyView', () =>
|
||||
jest.fn().mockImplementation(() => <div>TableTypePropertyView</div>)
|
||||
);
|
||||
|
||||
const mockOnCancel = jest.fn();
|
||||
const mockOnSave = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
const defaultProps: EditTableTypePropertyModalProps = {
|
||||
isVisible: true,
|
||||
isUpdating: false,
|
||||
property: {} as CustomProperty,
|
||||
columns: ['column1', 'column2'],
|
||||
rows: [{ column1: 'value1', column2: 'value2' }],
|
||||
onCancel: mockOnCancel,
|
||||
onSave: mockOnSave,
|
||||
};
|
||||
|
||||
describe('EditTableTypePropertyModal', () => {
|
||||
it('should render the modal with the correct title', () => {
|
||||
const { getByText } = render(
|
||||
<EditTableTypePropertyModal {...defaultProps} />
|
||||
);
|
||||
|
||||
expect(getByText('label.edit-entity-name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onCancel when the cancel button is clicked', () => {
|
||||
const { getByTestId } = render(
|
||||
<EditTableTypePropertyModal {...defaultProps} />
|
||||
);
|
||||
fireEvent.click(getByTestId('cancel-update-table-type-property'));
|
||||
|
||||
expect(mockOnCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onSave with the correct data when the update button is clicked', async () => {
|
||||
const { getByTestId } = render(
|
||||
<EditTableTypePropertyModal {...defaultProps} />
|
||||
);
|
||||
fireEvent.click(getByTestId('update-table-type-property'));
|
||||
await waitFor(() =>
|
||||
expect(mockOnSave).toHaveBeenCalledWith({
|
||||
rows: [{ column1: 'value1', column2: 'value2' }],
|
||||
columns: ['column1', 'column2'],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a new row when the add new row button is clicked', () => {
|
||||
const { getByTestId, getByText } = render(
|
||||
<EditTableTypePropertyModal {...defaultProps} />
|
||||
);
|
||||
fireEvent.click(getByTestId('add-new-row'));
|
||||
|
||||
expect(getByText('TableTypePropertyEditTable')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render TableTypePropertyView when dataSource is empty', () => {
|
||||
const props = { ...defaultProps, rows: [] };
|
||||
const { getByText } = render(<EditTableTypePropertyModal {...props} />);
|
||||
|
||||
expect(getByText('TableTypePropertyView')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render TableTypePropertyEditTable when dataSource is not empty', () => {
|
||||
const { getByText } = render(
|
||||
<EditTableTypePropertyModal {...defaultProps} />
|
||||
);
|
||||
|
||||
expect(getByText('TableTypePropertyEditTable')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -10,31 +10,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ReactDataGrid from '@inovua/reactdatagrid-community';
|
||||
import '@inovua/reactdatagrid-community/index.css';
|
||||
import { TypeComputedProps } from '@inovua/reactdatagrid-community/types';
|
||||
import { Button, Modal, Typography } from 'antd';
|
||||
import { isEmpty, omit } from 'lodash';
|
||||
import React, { FC, MutableRefObject, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CustomProperty } from '../../../../generated/type/customProperty';
|
||||
import { getEntityName } from '../../../../utils/EntityUtils';
|
||||
import { TableTypePropertyValueType } from '../CustomPropertyTable.interface';
|
||||
import './edit-table-type-property.less';
|
||||
import { EditTableTypePropertyModalProps } from './EditTableTypePropertyModal.interface';
|
||||
import TableTypePropertyEditTable from './TableTypePropertyEditTable';
|
||||
import TableTypePropertyView from './TableTypePropertyView';
|
||||
|
||||
interface EditTableTypePropertyModalProps {
|
||||
isVisible: boolean;
|
||||
isUpdating: boolean;
|
||||
property: CustomProperty;
|
||||
columns: string[];
|
||||
rows: Record<string, string>[];
|
||||
onCancel: () => void;
|
||||
onSave: (data: TableTypePropertyValueType) => Promise<void>;
|
||||
}
|
||||
|
||||
let inEdit = false;
|
||||
|
||||
const EditTableTypePropertyModal: FC<EditTableTypePropertyModalProps> = ({
|
||||
isVisible,
|
||||
isUpdating,
|
||||
@ -54,91 +42,16 @@ const EditTableTypePropertyModal: FC<EditTableTypePropertyModalProps> = ({
|
||||
MutableRefObject<TypeComputedProps | null>
|
||||
>({ current: null });
|
||||
|
||||
const filterColumns = columns.map((column) => ({
|
||||
name: column,
|
||||
header: column,
|
||||
defaultFlex: 1,
|
||||
sortable: false,
|
||||
minWidth: 180,
|
||||
}));
|
||||
|
||||
const onEditComplete = useCallback(
|
||||
({ value, columnId, rowId }) => {
|
||||
const data = [...dataSource];
|
||||
|
||||
data[rowId][columnId] = value;
|
||||
|
||||
setDataSource(data);
|
||||
const handleEditGridRef = useCallback(
|
||||
(ref: MutableRefObject<TypeComputedProps | null>) => {
|
||||
setGridRef(ref);
|
||||
},
|
||||
[dataSource]
|
||||
[]
|
||||
);
|
||||
|
||||
const onEditStart = () => {
|
||||
inEdit = true;
|
||||
};
|
||||
|
||||
const onEditStop = () => {
|
||||
requestAnimationFrame(() => {
|
||||
inEdit = false;
|
||||
gridRef.current?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (inEdit) {
|
||||
if (event.key === 'Escape') {
|
||||
const [rowIndex, colIndex] = gridRef.current?.computedActiveCell ?? [
|
||||
0, 0,
|
||||
];
|
||||
const column = gridRef.current?.getColumnBy(colIndex);
|
||||
|
||||
gridRef.current?.cancelEdit?.({
|
||||
rowIndex,
|
||||
columnId: column?.name ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const grid = gridRef.current;
|
||||
if (!grid) {
|
||||
return;
|
||||
}
|
||||
let [rowIndex, colIndex] = grid.computedActiveCell ?? [0, 0];
|
||||
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
const column = grid.getColumnBy(colIndex);
|
||||
grid.startEdit?.({ columnId: column.name ?? '', rowIndex });
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'Tab') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const direction = event.shiftKey ? -1 : 1;
|
||||
|
||||
const columns = grid.visibleColumns;
|
||||
const rowCount = grid.count;
|
||||
|
||||
colIndex += direction;
|
||||
if (colIndex === -1) {
|
||||
colIndex = columns.length - 1;
|
||||
rowIndex -= 1;
|
||||
}
|
||||
if (colIndex === columns.length) {
|
||||
rowIndex += 1;
|
||||
colIndex = 0;
|
||||
}
|
||||
if (rowIndex < 0 || rowIndex === rowCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid?.setActiveCell([rowIndex, colIndex]);
|
||||
};
|
||||
const handleEditDataSource = useCallback((data: Record<string, string>[]) => {
|
||||
setDataSource(data);
|
||||
}, []);
|
||||
|
||||
const handleAddRow = useCallback(() => {
|
||||
setDataSource((data) => {
|
||||
@ -207,20 +120,12 @@ const EditTableTypePropertyModal: FC<EditTableTypePropertyModalProps> = ({
|
||||
{isEmpty(dataSource) ? (
|
||||
<TableTypePropertyView columns={columns} rows={rows} />
|
||||
) : (
|
||||
<ReactDataGrid
|
||||
editable
|
||||
className="edit-table-type-property"
|
||||
columns={filterColumns}
|
||||
<TableTypePropertyEditTable
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
handle={setGridRef}
|
||||
idProperty="id"
|
||||
minRowHeight={30}
|
||||
showZebraRows={false}
|
||||
style={{ height: '350px' }}
|
||||
onEditComplete={onEditComplete}
|
||||
onEditStart={onEditStart}
|
||||
onEditStop={onEditStop}
|
||||
onKeyDown={onKeyDown}
|
||||
gridRef={gridRef}
|
||||
handleEditDataSource={handleEditDataSource}
|
||||
handleEditGridRef={handleEditGridRef}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { TypeComputedProps } from '@inovua/reactdatagrid-community/types';
|
||||
import { MutableRefObject } from 'react';
|
||||
|
||||
export interface TableTypePropertyEditTableProps {
|
||||
columns: string[];
|
||||
dataSource: Record<string, string>[];
|
||||
gridRef: MutableRefObject<TypeComputedProps | null>;
|
||||
handleEditGridRef: (ref: MutableRefObject<TypeComputedProps | null>) => void;
|
||||
handleEditDataSource: (data: Record<string, string>[]) => void;
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 { TypeComputedProps } from '@inovua/reactdatagrid-community/types';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React, { MutableRefObject } from 'react';
|
||||
import TableTypePropertyEditTable from './TableTypePropertyEditTable';
|
||||
import { TableTypePropertyEditTableProps } from './TableTypePropertyEditTable.interface';
|
||||
|
||||
const mockDataSource: { value: Record<string, string>[] } = {
|
||||
value: [
|
||||
{
|
||||
name: 'Property 1',
|
||||
value: 'Value 1',
|
||||
id: '0',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockDataGridRefObj: {
|
||||
value: MutableRefObject<TypeComputedProps | null>;
|
||||
} = {
|
||||
value: { current: null },
|
||||
};
|
||||
|
||||
const props: TableTypePropertyEditTableProps = {
|
||||
columns: ['name', 'value'],
|
||||
dataSource: mockDataSource.value,
|
||||
gridRef: mockDataGridRefObj.value,
|
||||
handleEditGridRef: jest
|
||||
.fn()
|
||||
.mockImplementation((data: Record<string, string>[]) => {
|
||||
mockDataSource.value = data;
|
||||
}),
|
||||
handleEditDataSource: jest
|
||||
.fn()
|
||||
.mockImplementation((data: MutableRefObject<TypeComputedProps | null>) => {
|
||||
mockDataGridRefObj.value = data;
|
||||
}),
|
||||
};
|
||||
|
||||
describe('TableTypePropertyEditTable', () => {
|
||||
it('should render the table with given columns and dataSource', async () => {
|
||||
await act(async () => {
|
||||
render(<TableTypePropertyEditTable {...props} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Property 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Value 1')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 ReactDataGrid from '@inovua/reactdatagrid-community';
|
||||
import React, { useCallback } from 'react';
|
||||
import { TableTypePropertyEditTableProps } from './TableTypePropertyEditTable.interface';
|
||||
|
||||
let inEdit = false;
|
||||
|
||||
const TableTypePropertyEditTable = ({
|
||||
dataSource,
|
||||
columns,
|
||||
gridRef,
|
||||
handleEditGridRef,
|
||||
handleEditDataSource,
|
||||
}: TableTypePropertyEditTableProps) => {
|
||||
const filterColumns = columns.map((column) => ({
|
||||
name: column,
|
||||
header: column,
|
||||
defaultFlex: 1,
|
||||
sortable: false,
|
||||
minWidth: 180,
|
||||
}));
|
||||
|
||||
const onEditComplete = useCallback(
|
||||
({ value, columnId, rowId }) => {
|
||||
const data = [...dataSource];
|
||||
|
||||
data[rowId][columnId] = value;
|
||||
|
||||
handleEditDataSource(data);
|
||||
},
|
||||
[dataSource]
|
||||
);
|
||||
|
||||
const onEditStart = () => {
|
||||
inEdit = true;
|
||||
};
|
||||
|
||||
const onEditStop = () => {
|
||||
requestAnimationFrame(() => {
|
||||
inEdit = false;
|
||||
gridRef.current?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (inEdit) {
|
||||
if (event.key === 'Escape') {
|
||||
const [rowIndex, colIndex] = gridRef.current?.computedActiveCell ?? [
|
||||
0, 0,
|
||||
];
|
||||
const column = gridRef.current?.getColumnBy(colIndex);
|
||||
|
||||
gridRef.current?.cancelEdit?.({
|
||||
rowIndex,
|
||||
columnId: column?.name ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const grid = gridRef.current;
|
||||
if (!grid) {
|
||||
return;
|
||||
}
|
||||
let [rowIndex, colIndex] = grid.computedActiveCell ?? [0, 0];
|
||||
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
const column = grid.getColumnBy(colIndex);
|
||||
grid.startEdit?.({ columnId: column.name ?? '', rowIndex });
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'Tab') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const direction = event.shiftKey ? -1 : 1;
|
||||
|
||||
const columns = grid.visibleColumns;
|
||||
const rowCount = grid.count;
|
||||
|
||||
colIndex += direction;
|
||||
if (colIndex === -1) {
|
||||
colIndex = columns.length - 1;
|
||||
rowIndex -= 1;
|
||||
}
|
||||
if (colIndex === columns.length) {
|
||||
rowIndex += 1;
|
||||
colIndex = 0;
|
||||
}
|
||||
if (rowIndex < 0 || rowIndex === rowCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid?.setActiveCell([rowIndex, colIndex]);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactDataGrid
|
||||
editable
|
||||
className="edit-table-type-property"
|
||||
columns={filterColumns}
|
||||
dataSource={dataSource}
|
||||
handle={handleEditGridRef}
|
||||
idProperty="id"
|
||||
minRowHeight={30}
|
||||
showZebraRows={false}
|
||||
style={{ height: '350px' }}
|
||||
onEditComplete={onEditComplete}
|
||||
onEditStart={onEditStart}
|
||||
onEditStop={onEditStop}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableTypePropertyEditTable;
|
||||
@ -69,7 +69,6 @@ const DomainSelectablTree: FC<DomainSelectableTreeProps> = ({
|
||||
} else {
|
||||
let retn: EntityReference[] = [];
|
||||
if (selectedDomains.length > 0) {
|
||||
|
||||
const domain = getEntityReferenceFromEntity<Domain>(
|
||||
selectedDomains[0],
|
||||
EntityType.DOMAIN
|
||||
@ -97,10 +96,11 @@ const DomainSelectablTree: FC<DomainSelectableTreeProps> = ({
|
||||
|
||||
const onSelect = (selectedKeys: React.Key[]) => {
|
||||
if (!isMultiple) {
|
||||
|
||||
const selectedData = [];
|
||||
for (const item of selectedKeys) {
|
||||
selectedData.push(findItemByFqn(domains, item as string, false) as Domain);
|
||||
selectedData.push(
|
||||
findItemByFqn(domains, item as string, false) as Domain
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedDomains(selectedData);
|
||||
@ -113,12 +113,20 @@ const DomainSelectablTree: FC<DomainSelectableTreeProps> = ({
|
||||
if (Array.isArray(checked)) {
|
||||
const selectedData = [];
|
||||
for (const item of checked) {
|
||||
selectedData.push(findItemByFqn(domains, item as string, false) as Domain);
|
||||
selectedData.push(
|
||||
findItemByFqn(domains, item as string, false) as Domain
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedDomains(selectedData);
|
||||
} else {
|
||||
setSelectedDomains([findItemByFqn(domains, checked.checked as unknown as string, false) as Domain]);
|
||||
setSelectedDomains([
|
||||
findItemByFqn(
|
||||
domains,
|
||||
checked.checked as unknown as string,
|
||||
false
|
||||
) as Domain,
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -139,6 +139,29 @@ export const SUPPORTED_DATE_TIME_FORMATS = [
|
||||
// supported time formats on backend
|
||||
export const SUPPORTED_TIME_FORMATS = ['HH:mm:ss'];
|
||||
|
||||
export const SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING = {
|
||||
'yyyy-MM-dd': 'YYYY-MM-DD',
|
||||
'dd MMM yyyy': 'DD MMM YYYY',
|
||||
'MM/dd/yyyy': 'MM/DD/YYYY',
|
||||
'dd/MM/yyyy': 'DD/MM/YYYY',
|
||||
'dd-MM-yyyy': 'DD-MM-YYYY',
|
||||
yyyyDDD: 'YYYYDDD',
|
||||
'd MMMM yyyy': 'D MMMM YYYY',
|
||||
'MMM dd HH:mm:ss yyyy': 'MMM DD HH:mm:ss YYYY',
|
||||
'yyyy-MM-dd HH:mm:ss': 'YYYY-MM-DD HH:mm:ss',
|
||||
'MM/dd/yyyy HH:mm:ss': 'MM/DD/YYYY HH:mm:ss',
|
||||
'dd/MM/yyyy HH:mm:ss': 'DD/MM/YYYY HH:mm:ss',
|
||||
'dd-MM-yyyy HH:mm:ss': 'DD-MM-YYYY HH:mm:ss',
|
||||
'yyyy-MM-dd HH:mm:ss.SSS': 'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
'yyyy-MM-dd HH:mm:ss.SSSSSS': 'YYYY-MM-DD HH:mm:ss.SSSSSS',
|
||||
'dd MMMM yyyy HH:mm:ss': 'DD MMMM YYYY HH:mm:ss',
|
||||
'HH:mm:ss': 'HH:mm:ss',
|
||||
};
|
||||
|
||||
export const DEFAULT_TIME_FORMAT = 'HH:mm:ss';
|
||||
export const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd';
|
||||
export const DEFAULT_DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss';
|
||||
|
||||
export const SUPPORTED_FORMAT_MAP = {
|
||||
'date-cp': SUPPORTED_DATE_FORMATS,
|
||||
'dateTime-cp': SUPPORTED_DATE_TIME_FORMATS,
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { CustomPropertyConfig } from '../generated/entity/type';
|
||||
|
||||
export interface CustomPropertySummary {
|
||||
name: string;
|
||||
type: string;
|
||||
customPropertyConfig?: CustomPropertyConfig;
|
||||
}
|
||||
|
||||
export type CustomPropertiesForAssets = Record<
|
||||
string,
|
||||
Array<CustomPropertySummary>
|
||||
>;
|
||||
@ -19,6 +19,7 @@ import { CustomProperty } from '../generated/type/customProperty';
|
||||
import { Paging } from '../generated/type/paging';
|
||||
import { getEncodedFqn } from '../utils/StringsUtils';
|
||||
import APIClient from './index';
|
||||
import { CustomPropertiesForAssets } from './metadataTypeAPI.interface';
|
||||
|
||||
export type FieldData = {
|
||||
name: string;
|
||||
@ -56,7 +57,7 @@ export const getTypeByFQN = async (typeFQN: string) => {
|
||||
|
||||
export const getAllCustomProperties = async () => {
|
||||
const path = `/metadata/types/customProperties`;
|
||||
const response = await APIClient.get<Type>(path);
|
||||
const response = await APIClient.get<CustomPropertiesForAssets>(path);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
} from '../constants/AdvancedSearch.constants';
|
||||
import { EntityFields, SuggestionField } from '../enums/AdvancedSearch.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { CustomPropertySummary } from '../rest/metadataTypeAPI.interface';
|
||||
import { getAggregateFieldOptions } from '../rest/miscAPI';
|
||||
import {
|
||||
getCustomPropertyAdvanceSearchEnumOptions,
|
||||
@ -899,13 +900,7 @@ class AdvancedSearchClassBase {
|
||||
};
|
||||
};
|
||||
|
||||
public getCustomPropertiesSubFields(field: {
|
||||
name: string;
|
||||
type: string;
|
||||
customPropertyConfig: {
|
||||
config: string | string[] | CustomPropertyEnumConfig;
|
||||
};
|
||||
}) {
|
||||
public getCustomPropertiesSubFields(field: CustomPropertySummary) {
|
||||
{
|
||||
switch (field.type) {
|
||||
case 'array<entityReference>':
|
||||
@ -918,7 +913,7 @@ class AdvancedSearchClassBase {
|
||||
fieldSettings: {
|
||||
asyncFetch: this.autocomplete({
|
||||
searchIndex: (
|
||||
(field.customPropertyConfig.config ?? []) as string[]
|
||||
(field.customPropertyConfig?.config ?? []) as string[]
|
||||
).join(',') as SearchIndex,
|
||||
entityField: EntityFields.DISPLAY_NAME_KEYWORD,
|
||||
}),
|
||||
@ -937,7 +932,7 @@ class AdvancedSearchClassBase {
|
||||
listValues: getCustomPropertyAdvanceSearchEnumOptions(
|
||||
(
|
||||
field.customPropertyConfig
|
||||
.config as CustomPropertyEnumConfig
|
||||
?.config as CustomPropertyEnumConfig
|
||||
).values
|
||||
),
|
||||
},
|
||||
|
||||
@ -10,7 +10,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { getCustomPropertyEntityPathname } from './CustomProperty.utils';
|
||||
import {
|
||||
DEFAULT_DATE_FORMAT,
|
||||
DEFAULT_DATE_TIME_FORMAT,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING,
|
||||
} from '../constants/CustomProperty.constants';
|
||||
import {
|
||||
getCustomPropertyDateTimeDefaultFormat,
|
||||
getCustomPropertyEntityPathname,
|
||||
getCustomPropertyMomentFormat,
|
||||
} from './CustomProperty.utils';
|
||||
|
||||
describe('CustomProperty.utils', () => {
|
||||
it('getCustomPropertyEntityPathname should return entityPath[0] if entityPath is found', () => {
|
||||
@ -31,4 +41,131 @@ describe('CustomProperty.utils', () => {
|
||||
|
||||
expect(getCustomPropertyEntityPathname(entityType)).toEqual('glossaries');
|
||||
});
|
||||
|
||||
describe('getCustomPropertyDateTimeDefaultFormat', () => {
|
||||
it('should return DEFAULT_DATE_FORMAT for date-cp type', () => {
|
||||
const type = 'date-cp';
|
||||
|
||||
const result = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
expect(result).toBe(DEFAULT_DATE_FORMAT);
|
||||
});
|
||||
|
||||
it('should return DEFAULT_DATE_TIME_FORMAT for dateTime-cp type', () => {
|
||||
const type = 'dateTime-cp';
|
||||
|
||||
const result = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
expect(result).toBe(DEFAULT_DATE_TIME_FORMAT);
|
||||
});
|
||||
|
||||
it('should return DEFAULT_TIME_FORMAT for time-cp type', () => {
|
||||
const type = 'time-cp';
|
||||
|
||||
const result = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
expect(result).toBe(DEFAULT_TIME_FORMAT);
|
||||
});
|
||||
|
||||
it('should return empty string for unknown type', () => {
|
||||
const type = 'unknown-type';
|
||||
|
||||
const result = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string for empty type', () => {
|
||||
const type = '';
|
||||
|
||||
const result = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomPropertyMomentFormat', () => {
|
||||
it('should return mapped format for valid date-cp type and backend format', () => {
|
||||
const type = 'date-cp';
|
||||
const backendFormat = 'MM/dd/yyyy';
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[backendFormat];
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should return mapped format for valid dateTime-cp type and backend format', () => {
|
||||
const type = 'dateTime-cp';
|
||||
const backendFormat = 'yyyy-MM-dd HH:mm:ss';
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[backendFormat];
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should return mapped format for valid time-cp type and backend format', () => {
|
||||
const type = 'time-cp';
|
||||
const backendFormat = 'HH:mm:ss';
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[backendFormat];
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should fallback to default format when backend format is undefined', () => {
|
||||
const type = 'date-cp';
|
||||
const backendFormat = undefined;
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[DEFAULT_DATE_FORMAT];
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should fallback to default format when backend format is not supported', () => {
|
||||
const type = 'date-cp';
|
||||
const backendFormat = 'INVALID-FORMAT';
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[DEFAULT_DATE_FORMAT];
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should handle empty type with valid backend format', () => {
|
||||
const type = '';
|
||||
const backendFormat = 'yyyy-MM-dd';
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[backendFormat];
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
|
||||
it('should handle both empty type and undefined backend format', () => {
|
||||
const type = '';
|
||||
const backendFormat = undefined;
|
||||
|
||||
const result = getCustomPropertyMomentFormat(type, backendFormat);
|
||||
|
||||
const expectedFormat =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[
|
||||
'' as keyof typeof SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING
|
||||
];
|
||||
|
||||
expect(result).toBe(expectedFormat);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,6 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ENTITY_PATH } from '../constants/constants';
|
||||
import {
|
||||
DEFAULT_DATE_FORMAT,
|
||||
DEFAULT_DATE_TIME_FORMAT,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING,
|
||||
} from '../constants/CustomProperty.constants';
|
||||
import { CustomPropertyConfig } from '../generated/entity/type';
|
||||
|
||||
export const getCustomPropertyEntityPathname = (entityType: string) => {
|
||||
const entityPathEntries = Object.entries(ENTITY_PATH);
|
||||
@ -18,3 +25,34 @@ export const getCustomPropertyEntityPathname = (entityType: string) => {
|
||||
|
||||
return entityPath ? entityPath[0] : '';
|
||||
};
|
||||
|
||||
export const getCustomPropertyDateTimeDefaultFormat = (type: string) => {
|
||||
switch (type) {
|
||||
case 'date-cp':
|
||||
return DEFAULT_DATE_FORMAT;
|
||||
case 'dateTime-cp':
|
||||
return DEFAULT_DATE_TIME_FORMAT;
|
||||
case 'time-cp':
|
||||
return DEFAULT_TIME_FORMAT;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const getCustomPropertyMomentFormat = (
|
||||
type: string,
|
||||
backendFormat: CustomPropertyConfig['config']
|
||||
) => {
|
||||
const defaultFormat = getCustomPropertyDateTimeDefaultFormat(type);
|
||||
|
||||
const format =
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[
|
||||
((backendFormat as string) ??
|
||||
defaultFormat) as keyof typeof SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING
|
||||
] ??
|
||||
SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING[
|
||||
defaultFormat as keyof typeof SUPPORTED_DATE_TIME_FORMATS_ANTD_FORMAT_MAPPING
|
||||
];
|
||||
|
||||
return format;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user