Fix(UI): Add service form bugs (#23613)

* Add default filter pattern fields for custom and share point drive connections

* Add translations for additional properties

* Fix the additional properties field showing in the default filters form
Improve the additional properties field input in the form to make it clear with a label

* Update generated TypeScript types

* Empty commit

* refactor(tests): streamline imports in Directory, File, Spreadsheet, and Worksheet class tests

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Aniket Katkar 2025-10-01 15:18:24 +05:30 committed by GitHub
parent 1012247a7e
commit ae39c7e68e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 507 additions and 21 deletions

View File

@ -28,6 +28,26 @@
"title": "Connection Arguments",
"$ref": "../connectionBasicType.json#/definitions/connectionArguments"
},
"directoryFilterPattern": {
"title": "Default Directory Filter Pattern",
"description": "Regex to only include/exclude directories that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"fileFilterPattern": {
"title": "Default File Filter Pattern",
"description": "Regex to only include/exclude files that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"spreadsheetFilterPattern": {
"title": "Default Spreadsheet Filter Pattern",
"description": "Regex to only include/exclude spreadsheets that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"worksheetFilterPattern": {
"title": "Default Worksheet Filter Pattern",
"description": "Regex to only include/exclude worksheets that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"supportsMetadataExtraction": {
"title": "Supports Metadata Extraction",
"$ref": "../connectionBasicType.json#/definitions/supportsMetadataExtraction"

View File

@ -61,6 +61,26 @@
"title": "Connection Arguments",
"$ref": "../connectionBasicType.json#/definitions/connectionArguments"
},
"directoryFilterPattern": {
"title": "Default Directory Filter Pattern",
"description": "Regex to only include/exclude directories that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"fileFilterPattern": {
"title": "Default File Filter Pattern",
"description": "Regex to only include/exclude files that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"spreadsheetFilterPattern": {
"title": "Default Spreadsheet Filter Pattern",
"description": "Regex to only include/exclude spreadsheets that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"worksheetFilterPattern": {
"title": "Default Worksheet Filter Pattern",
"description": "Regex to only include/exclude worksheets that matches the pattern.",
"$ref": "../../../../type/filterPattern.json#/definitions/filterPattern"
},
"supportsMetadataExtraction": {
"title": "Supports Metadata Extraction",
"$ref": "../connectionBasicType.json#/definitions/supportsMetadataExtraction"

View File

@ -0,0 +1,333 @@
/*
* 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 { IChangeEvent } from '@rjsf/core';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { LoadingState } from 'Models';
import React from 'react';
import { ServiceCategory } from '../../../../enums/service.enum';
import { DatabaseServiceType } from '../../../../generated/entity/services/databaseService';
import { ConfigData } from '../../../../interface/service.interface';
import FiltersConfigForm from './FiltersConfigForm';
import { FiltersConfigFormProps } from './FiltersConfigForm.interface';
jest.mock('../../../../hooks/useApplicationStore', () => ({
useApplicationStore: jest.fn().mockReturnValue({
inlineAlertDetails: undefined,
}),
}));
jest.mock('../../../../utils/JSONSchemaFormUtils', () => ({
formatFormDataForSubmit: jest.fn((data) => data),
}));
jest.mock('../../../../utils/ServiceConnectionUtils', () => ({
getConnectionSchemas: jest.fn().mockReturnValue({
connSch: {
schema: {
type: 'object',
properties: {
filter1: { type: 'string' },
filter2: { type: 'string' },
someOtherProperty: { type: 'string' },
},
additionalProperties: true,
},
},
validConfig: {},
}),
getFilteredSchema: jest.fn((properties) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { filter1, filter2, ...rest } = properties as Record<string, unknown>;
return rest;
}),
}));
const MockFormBuilder = React.forwardRef<
unknown,
{
onSubmit: (data: IChangeEvent<ConfigData>) => void;
onCancel: () => void;
children?: React.ReactNode;
}
>(({ onSubmit, onCancel, children }, ref) => {
React.useImperativeHandle(ref, () => ({}));
return (
<div data-testid="form-builder">
<button
data-testid="submit-button"
onClick={() => onSubmit({ formData: {} } as IChangeEvent<ConfigData>)}>
Submit
</button>
<button data-testid="cancel-button" onClick={onCancel}>
Cancel
</button>
{children}
</div>
);
});
jest.mock('../../../common/FormBuilder/FormBuilder', () => {
return jest.fn().mockImplementation((props) => {
return <MockFormBuilder {...props} />;
});
});
jest.mock('../../../common/InlineAlert/InlineAlert', () => {
return jest
.fn()
.mockImplementation(() => <div data-testid="inline-alert">Alert</div>);
});
const mockGetConnectionSchemas = jest.requireMock(
'../../../../utils/ServiceConnectionUtils'
).getConnectionSchemas;
const mockGetFilteredSchema = jest.requireMock(
'../../../../utils/ServiceConnectionUtils'
).getFilteredSchema;
const mockFormatFormDataForSubmit = jest.requireMock(
'../../../../utils/JSONSchemaFormUtils'
).formatFormDataForSubmit;
const mockUseApplicationStore = jest.requireMock(
'../../../../hooks/useApplicationStore'
).useApplicationStore;
describe('FiltersConfigForm', () => {
const mockOnSave = jest.fn();
const mockOnCancel = jest.fn();
const mockOnFocus = jest.fn();
const defaultProps: FiltersConfigFormProps = {
data: undefined,
serviceType: DatabaseServiceType.Mysql,
serviceCategory: ServiceCategory.DATABASE_SERVICES,
status: 'initial' as LoadingState,
onSave: mockOnSave,
onCancel: mockOnCancel,
onFocus: mockOnFocus,
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('Schema Filtering', () => {
it('should remove filter properties from the schema', () => {
render(<FiltersConfigForm {...defaultProps} />);
expect(mockGetFilteredSchema).toHaveBeenCalledWith(
{
filter1: { type: 'string' },
filter2: { type: 'string' },
someOtherProperty: { type: 'string' },
},
false
);
});
it('should set additionalProperties to false in the filtered schema', () => {
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(<FiltersConfigForm {...defaultProps} />);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.schema.additionalProperties).toBe(false);
});
it('should pass the filtered schema to FormBuilder', () => {
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(<FiltersConfigForm {...defaultProps} />);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.schema).toEqual({
type: 'object',
properties: {
someOtherProperty: { type: 'string' },
},
additionalProperties: false,
});
});
});
describe('Form Submission', () => {
it('should format and save form data on submit', async () => {
mockFormatFormDataForSubmit.mockReturnValue({ formatted: 'data' });
render(<FiltersConfigForm {...defaultProps} />);
const submitButton = screen.getByTestId('submit-button');
fireEvent.click(submitButton);
await waitFor(() => {
expect(mockFormatFormDataForSubmit).toHaveBeenCalledWith({});
expect(mockOnSave).toHaveBeenCalledWith({
formData: { formatted: 'data' },
});
});
});
it('should call onCancel when cancel button is clicked', () => {
render(<FiltersConfigForm {...defaultProps} />);
const cancelButton = screen.getByTestId('cancel-button');
fireEvent.click(cancelButton);
expect(mockOnCancel).toHaveBeenCalled();
});
});
describe('Empty Schema Handling', () => {
it('should not show no config message with default mock (schema has properties)', () => {
// Default mock has properties, so no-config message shouldn't show
render(<FiltersConfigForm {...defaultProps} />);
expect(
screen.queryByTestId('no-config-available')
).not.toBeInTheDocument();
});
it('should not show no config message when schema has properties', () => {
mockGetFilteredSchema.mockReturnValue({
someProperty: { type: 'string' },
});
render(<FiltersConfigForm {...defaultProps} />);
expect(
screen.queryByTestId('no-config-available')
).not.toBeInTheDocument();
});
});
describe('Inline Alert', () => {
it('should render inline alert when inlineAlertDetails is present', () => {
mockUseApplicationStore.mockReturnValue({
inlineAlertDetails: {
type: 'error',
message: 'Error message',
},
});
render(<FiltersConfigForm {...defaultProps} />);
expect(screen.getByTestId('inline-alert')).toBeInTheDocument();
});
it('should not render inline alert when inlineAlertDetails is undefined', () => {
mockUseApplicationStore.mockReturnValue({
inlineAlertDetails: undefined,
});
render(<FiltersConfigForm {...defaultProps} />);
expect(screen.queryByTestId('inline-alert')).not.toBeInTheDocument();
});
});
describe('Props Handling', () => {
it('should use custom okText and cancelText when provided', () => {
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(
<FiltersConfigForm
{...defaultProps}
cancelText="Custom Cancel"
okText="Custom Save"
/>
);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.okText).toBe('Custom Save');
expect(formBuilderCall.cancelText).toBe('Custom Cancel');
});
it('should use default okText and cancelText when not provided', () => {
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(<FiltersConfigForm {...defaultProps} />);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.okText).toBe('Save');
expect(formBuilderCall.cancelText).toBe('Cancel');
});
it('should pass all required props to FormBuilder', () => {
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(<FiltersConfigForm {...defaultProps} />);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.serviceCategory).toBe(
ServiceCategory.DATABASE_SERVICES
);
expect(formBuilderCall.status).toBe('initial');
expect(formBuilderCall.onFocus).toBe(mockOnFocus);
expect(formBuilderCall.showFormHeader).toBe(true);
});
});
describe('Connection Schema Integration', () => {
it('should call getConnectionSchemas with correct parameters', () => {
render(<FiltersConfigForm {...defaultProps} />);
expect(mockGetConnectionSchemas).toHaveBeenCalledWith({
data: undefined,
serviceCategory: ServiceCategory.DATABASE_SERVICES,
serviceType: DatabaseServiceType.Mysql,
});
});
it('should use validConfig from getConnectionSchemas', () => {
mockGetConnectionSchemas.mockReturnValue({
connSch: {
schema: {
type: 'object',
properties: {},
},
},
validConfig: { customConfig: 'value' },
});
const mockFormBuilder = jest.requireMock(
'../../../common/FormBuilder/FormBuilder'
);
render(<FiltersConfigForm {...defaultProps} />);
const formBuilderCall = mockFormBuilder.mock.calls[0][0];
expect(formBuilderCall.formData).toEqual({ customConfig: 'value' });
});
});
});

View File

@ -69,6 +69,7 @@ function FiltersConfigForm({
return {
...connSch.schema,
properties: propertiesWithoutFilters,
additionalProperties: false, // Disable additional properties for default filters form
};
}, [connSch.schema.properties]);

View File

@ -230,6 +230,14 @@ describe('ObjectFieldTemplate', () => {
} as RJSFSchema,
};
it('should render additional properties label when additionalProperties is true', () => {
render(<ObjectFieldTemplate {...propsWithAdditional} />);
expect(
screen.getByText('label.additional-property-plural')
).toBeInTheDocument();
});
it('should render add button when additionalProperties is true', () => {
render(<ObjectFieldTemplate {...propsWithAdditional} />);
@ -238,6 +246,14 @@ describe('ObjectFieldTemplate', () => {
expect(addButton).toBeInTheDocument();
});
it('should not render additional properties label when additionalProperties is false', () => {
render(<ObjectFieldTemplate {...defaultProps} />);
expect(
screen.queryByText('label.additional-property-plural')
).not.toBeInTheDocument();
});
it('should not render add button when additionalProperties is false', () => {
render(<ObjectFieldTemplate {...defaultProps} />);

View File

@ -71,7 +71,7 @@ export const ObjectFieldTemplate: FunctionComponent<ObjectFieldTemplateProps> =
const fieldElement = (
<Fragment>
<Space className="w-full justify-between header-title-wrapper m-y-sm">
<Space className="w-full justify-between header-title-wrapper m-t-sm">
<label
className={classNames('control-label', {
'font-medium text-base-color text-md':
@ -80,8 +80,16 @@ export const ObjectFieldTemplate: FunctionComponent<ObjectFieldTemplateProps> =
id={`${idSchema.$id}__title`}>
{title}
</label>
</Space>
{schema.additionalProperties && (
<Space className="w-full justify-between m-t-sm">
<label
className="font-medium text-base-color text-md"
id={`${idSchema.$id}__AdditionalProperties-label`}>
{t('label.additional-property-plural')}
</label>
{schema.additionalProperties && (
<Button
data-testid={`add-item-${title}`}
icon={
@ -99,8 +107,8 @@ export const ObjectFieldTemplate: FunctionComponent<ObjectFieldTemplateProps> =
}
}}
/>
)}
</Space>
</Space>
)}
{AdditionalField &&
createElement(AdditionalField, {
@ -119,7 +127,7 @@ export const ObjectFieldTemplate: FunctionComponent<ObjectFieldTemplateProps> =
{!isEmpty(advancedProperties) && (
<Collapse
destroyInactivePanel
className="advanced-properties-collapse m-y-sm"
className="advanced-properties-collapse m-t-sm"
expandIconPosition="end">
<Panel header={`${title} ${t('label.advanced-config')}`} key="1">
{advancedProperties.map((element, index) => (

View File

@ -14,16 +14,54 @@
* Custom Drive Connection to build a source that is not supported.
*/
export interface CustomDriveConnection {
connectionArguments?: { [key: string]: any };
connectionOptions?: { [key: string]: string };
connectionArguments?: { [key: string]: any };
connectionOptions?: { [key: string]: string };
/**
* Regex to only include/exclude directories that matches the pattern.
*/
directoryFilterPattern?: FilterPattern;
/**
* Regex to only include/exclude files that matches the pattern.
*/
fileFilterPattern?: FilterPattern;
/**
* Regex to only include/exclude spreadsheets that matches the pattern.
*/
spreadsheetFilterPattern?: FilterPattern;
supportsMetadataExtraction?: boolean;
/**
* Service Type
*/
type?: CustomDriveType;
/**
* Regex to only include/exclude worksheets that matches the pattern.
*/
worksheetFilterPattern?: FilterPattern;
[property: string]: any;
}
/**
* Regex to only include/exclude directories that matches the pattern.
*
* Regex to only fetch entities that matches the pattern.
*
* Regex to only include/exclude files that matches the pattern.
*
* Regex to only include/exclude spreadsheets that matches the pattern.
*
* Regex to only include/exclude worksheets that matches the pattern.
*/
export interface FilterPattern {
/**
* List of strings/regex patterns to match and exclude only database entities that match.
*/
excludes?: string[];
/**
* List of strings/regex patterns to match and include only database entities that match.
*/
includes?: string[];
}
/**
* Service Type
*

View File

@ -24,10 +24,18 @@ export interface SharePointConnection {
clientSecret: string;
connectionArguments?: { [key: string]: any };
connectionOptions?: { [key: string]: string };
/**
* Regex to only include/exclude directories that matches the pattern.
*/
directoryFilterPattern?: FilterPattern;
/**
* SharePoint drive ID. If not provided, default document library will be used
*/
driveId?: string;
/**
* Regex to only include/exclude files that matches the pattern.
*/
fileFilterPattern?: FilterPattern;
/**
* SharePoint site name
*/
@ -35,7 +43,11 @@ export interface SharePointConnection {
/**
* SharePoint site URL
*/
siteUrl: string;
siteUrl: string;
/**
* Regex to only include/exclude spreadsheets that matches the pattern.
*/
spreadsheetFilterPattern?: FilterPattern;
supportsMetadataExtraction?: boolean;
/**
* Directory (tenant) ID from Azure Active Directory
@ -45,6 +57,32 @@ export interface SharePointConnection {
* Service Type
*/
type?: SharePointType;
/**
* Regex to only include/exclude worksheets that matches the pattern.
*/
worksheetFilterPattern?: FilterPattern;
}
/**
* Regex to only include/exclude directories that matches the pattern.
*
* Regex to only fetch entities that matches the pattern.
*
* Regex to only include/exclude files that matches the pattern.
*
* Regex to only include/exclude spreadsheets that matches the pattern.
*
* Regex to only include/exclude worksheets that matches the pattern.
*/
export interface FilterPattern {
/**
* List of strings/regex patterns to match and exclude only database entities that match.
*/
excludes?: string[];
/**
* List of strings/regex patterns to match and include only database entities that match.
*/
includes?: string[];
}
/**

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "noch hinzugefügt",
"adding-new-classification": "Neue Klassifizierung hinzufügen",
"adding-new-tag": "Neuen Tag zu {{categoryName}} hinzufügen",
"additional-property-plural": "Zusätzliche Eigenschaften",
"address": "Adresse",
"admin": "Administrator",
"admin-plural": "Administratoren",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "added yet.",
"adding-new-classification": "Adding New Classification",
"adding-new-tag": "Adding new tag on {{categoryName}}",
"additional-property-plural": "Additional Properties",
"address": "Address",
"admin": "Admin",
"admin-plural": "Admins",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "aún no añadido.",
"adding-new-classification": "Añadiendo nueva clasificación",
"adding-new-tag": "Añadiendo una nueva etiqueta en {{categoryName}}",
"additional-property-plural": "Propiedades Adicionales",
"address": "Dirección",
"admin": "Administrador",
"admin-plural": "Administradores",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "encore ajouté",
"adding-new-classification": "Ajouter une Nouvelle Classification",
"adding-new-tag": "Ajouter une nouvelle balise à {{categoryName}}",
"additional-property-plural": "Propriétés Additionnelles",
"address": "Adresse",
"admin": "Administrateur",
"admin-plural": "Administrateurs",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "aínda engadido.",
"adding-new-classification": "Engadindo unha nova clasificación",
"adding-new-tag": "Engadindo unha nova etiqueta en {{categoryName}}",
"additional-property-plural": "Propiedades Adicionales",
"address": "Enderezo",
"admin": "Administrador",
"admin-plural": "Administradores",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "נוסף כעת",
"adding-new-classification": "הוספת סיווג חדש",
"adding-new-tag": "הוספת תג חדש ב- {{categoryName}}",
"additional-property-plural": "מאפיינים נוספים",
"address": "כתובת",
"admin": "מנהל",
"admin-plural": "מנהלים",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "未追加",
"adding-new-classification": "新しい分類を追加",
"adding-new-tag": "{{categoryName}}に新しいタグを追加",
"additional-property-plural": "追加のプロパティ",
"address": "アドレス",
"admin": "管理者",
"admin-plural": "管理者",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "아직 추가되지 않음.",
"adding-new-classification": "새 분류 추가 중",
"adding-new-tag": "{{categoryName}}에 새 태그 추가 중",
"additional-property-plural": "추가 속성",
"address": "주소",
"admin": "관리자",
"admin-plural": "관리자들",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "अजून जोडले.",
"adding-new-classification": "नवीन वर्गीकरण जोडत आहे",
"adding-new-tag": "{{categoryName}} वर नवीन टॅग जोडत आहे",
"additional-property-plural": "अतिरिक्त गुणधर्म",
"address": "पत्ता",
"admin": "प्रशासक",
"admin-plural": "प्रशासक",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "toch toegevoegd.",
"adding-new-classification": "Nieuwe classificatie toevoegen",
"adding-new-tag": "Toevoegen van nieuwe tag aan {{categoryName}}",
"additional-property-plural": "Aanvullende eigenschappen",
"address": "Adres",
"admin": "Beheerder",
"admin-plural": "Beheerders",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "هنوز اضافه نشده است.",
"adding-new-classification": "اضافه کردن طبقه‌بندی جدید",
"adding-new-tag": "اضافه کردن تگ جدید به {{categoryName }}",
"additional-property-plural": "ویژگی‌های اضافی",
"address": "آدرس",
"admin": "مدیر",
"admin-plural": "مدیران",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "ainda não adicionado.",
"adding-new-classification": "Adicionando Nova Classificação",
"adding-new-tag": "Adicionando nova tag em {{categoryName}}",
"additional-property-plural": "Propriedades Adicionais",
"address": "Endereço",
"admin": "Administrador",
"admin-plural": "Administradores",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "ainda não adicionado.",
"adding-new-classification": "Adicionando Nova Classificação",
"adding-new-tag": "Adicionando nova tag em {{categoryName}}",
"additional-property-plural": "Propriedades Adicionais",
"address": "Endereço",
"admin": "Admin",
"admin-plural": "Admins",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "уже добавлено",
"adding-new-classification": "Добавление новой классификации",
"adding-new-tag": "Добавление нового тега {{categoryName}}",
"additional-property-plural": "Дополнительные свойства",
"address": "Адрес",
"admin": "Админ",
"admin-plural": "Админы",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "เพิ่มแล้ว.",
"adding-new-classification": "กำลังเพิ่มการจำแนกประเภทใหม่",
"adding-new-tag": "กำลังเพิ่มแท็กใหม่ที่ {{categoryName}}",
"additional-property-plural": "คุณสมบัติเพิ่มเติม",
"address": "ที่อยู่",
"admin": "ผู้ดูแลระบบ",
"admin-plural": "ผู้ดูแลระบบหลายคน",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "henüz eklendi.",
"adding-new-classification": "Yeni Sınıflandırma Ekleniyor",
"adding-new-tag": "{{categoryName}} üzerinde yeni etiket ekleniyor",
"additional-property-plural": "Ekstra Özellikler",
"address": "Adres",
"admin": "Yönetici",
"admin-plural": "Yöneticiler",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "已添加",
"adding-new-classification": "正在添加新分类",
"adding-new-tag": "正在添加新标签到{{categoryName}}",
"additional-property-plural": "附加属性",
"address": "地址",
"admin": "管理员",
"admin-plural": "管理员",

View File

@ -66,6 +66,7 @@
"added-yet-lowercase": "尚未新增。",
"adding-new-classification": "正在新增分類",
"adding-new-tag": "在 {{categoryName}} 上新增標籤",
"additional-property-plural": "附加屬性",
"address": "地址",
"admin": "管理員",
"admin-plural": "管理員",

View File

@ -26,10 +26,8 @@ import { DetailPageWidgetKeys } from '../enums/CustomizeDetailPage.enum';
import { EntityTabs } from '../enums/entity.enum';
import { Directory } from '../generated/entity/data/directory';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import directoryClassBase, {
DirectoryClassBase,
DirectoryDetailPageTabProps,
} from './DirectoryClassBase';
import directoryClassBase, { DirectoryClassBase } from './DirectoryClassBase';
import { DirectoryDetailPageTabProps } from './DirectoryDetailsUtils';
// Mock dependencies
jest.mock('../constants/CustomizeWidgets.constants', () => ({

View File

@ -25,10 +25,8 @@ import { DetailPageWidgetKeys } from '../enums/CustomizeDetailPage.enum';
import { EntityTabs } from '../enums/entity.enum';
import { File } from '../generated/entity/data/file';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import fileClassBase, {
FileClassBase,
FileDetailPageTabProps,
} from './FileClassBase';
import fileClassBase, { FileClassBase } from './FileClassBase';
import { FileDetailPageTabProps } from './FileDetailsUtils';
// Mock dependencies
jest.mock('../constants/CustomizeWidgets.constants', () => ({

View File

@ -28,8 +28,8 @@ import { Spreadsheet } from '../generated/entity/data/spreadsheet';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import spreadsheetClassBase, {
SpreadsheetClassBase,
SpreadsheetDetailPageTabProps,
} from './SpreadsheetClassBase';
import { SpreadsheetDetailPageTabProps } from './SpreadsheetDetailsUtils';
// Mock dependencies
jest.mock('../constants/CustomizeWidgets.constants', () => ({

View File

@ -26,10 +26,8 @@ import { DetailPageWidgetKeys } from '../enums/CustomizeDetailPage.enum';
import { EntityTabs } from '../enums/entity.enum';
import { Worksheet } from '../generated/entity/data/worksheet';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import worksheetClassBase, {
WorksheetClassBase,
WorksheetDetailPageTabProps,
} from './WorksheetClassBase';
import worksheetClassBase, { WorksheetClassBase } from './WorksheetClassBase';
import { WorksheetDetailPageTabProps } from './WorksheetDetailsUtils';
// Mock dependencies
jest.mock('../constants/CustomizeWidgets.constants', () => ({