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:
Aniket Katkar 2025-02-08 09:04:42 +05:30 committed by OpenMetadata Release Bot
parent 9a6480ba74
commit 1dc8da2e21
17 changed files with 609 additions and 148 deletions

View File

@ -27,6 +27,7 @@ export interface FetchOptionsResponse {
export interface DataAssetAsyncSelectListProps {
mode?: 'multiple';
autoFocus?: boolean;
id?: string;
className?: string;
placeholder?: string;

View File

@ -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}

View File

@ -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) {

View File

@ -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,
};

View File

@ -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>;
}

View File

@ -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();
});
});

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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;

View File

@ -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,
]);
}
};

View File

@ -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,

View File

@ -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>
>;

View File

@ -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;
};

View File

@ -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
),
},

View File

@ -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);
});
});
});

View File

@ -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;
};