mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-01 11:52:12 +00:00
fix(ui): extend application configuration component (#23310)
* extend application configuration component * fix tests
This commit is contained in:
parent
f3cb001d2b
commit
95962596e8
@ -49,6 +49,7 @@ import {
|
|||||||
ScheduleTimeline,
|
ScheduleTimeline,
|
||||||
ScheduleType,
|
ScheduleType,
|
||||||
} from '../../../../generated/entity/applications/app';
|
} from '../../../../generated/entity/applications/app';
|
||||||
|
import { EntityReference } from '../../../../generated/entity/type';
|
||||||
import { Include } from '../../../../generated/type/include';
|
import { Include } from '../../../../generated/type/include';
|
||||||
import { useFqn } from '../../../../hooks/useFqn';
|
import { useFqn } from '../../../../hooks/useFqn';
|
||||||
import {
|
import {
|
||||||
@ -70,7 +71,6 @@ import { ManageButtonItemLabel } from '../../../common/ManageButtonContentItem/M
|
|||||||
import TabsLabel from '../../../common/TabsLabel/TabsLabel.component';
|
import TabsLabel from '../../../common/TabsLabel/TabsLabel.component';
|
||||||
import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal';
|
||||||
import PageLayoutV1 from '../../../PageLayoutV1/PageLayoutV1';
|
import PageLayoutV1 from '../../../PageLayoutV1/PageLayoutV1';
|
||||||
import ApplicationConfiguration from '../ApplicationConfiguration/ApplicationConfiguration';
|
|
||||||
import { useApplicationsProvider } from '../ApplicationsProvider/ApplicationsProvider';
|
import { useApplicationsProvider } from '../ApplicationsProvider/ApplicationsProvider';
|
||||||
import AppLogo from '../AppLogo/AppLogo.component';
|
import AppLogo from '../AppLogo/AppLogo.component';
|
||||||
import AppRunsHistory from '../AppRunsHistory/AppRunsHistory.component';
|
import AppRunsHistory from '../AppRunsHistory/AppRunsHistory.component';
|
||||||
@ -235,13 +235,19 @@ const AppDetails = () => {
|
|||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
const onConfigSave = async (data: IChangeEvent) => {
|
const onConfigSave = async (
|
||||||
|
data: IChangeEvent & { ingestionRunner?: EntityReference }
|
||||||
|
) => {
|
||||||
if (appData) {
|
if (appData) {
|
||||||
setLoadingState((prev) => ({ ...prev, isSaveLoading: true }));
|
setLoadingState((prev) => ({ ...prev, isSaveLoading: true }));
|
||||||
const updatedFormData = formatFormDataForSubmit(data.formData);
|
|
||||||
|
const { formData, ingestionRunner } = data;
|
||||||
|
|
||||||
|
const updatedFormData = formatFormDataForSubmit(formData);
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...appData,
|
...appData,
|
||||||
appConfiguration: updatedFormData,
|
appConfiguration: updatedFormData,
|
||||||
|
...(ingestionRunner && { ingestionRunner }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonPatch = compare(appData, updatedData);
|
const jsonPatch = compare(appData, updatedData);
|
||||||
@ -337,6 +343,9 @@ const AppDetails = () => {
|
|||||||
}, [appData?.name, plugins]);
|
}, [appData?.name, plugins]);
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
const tabs = useMemo(() => {
|
||||||
|
const ApplicationConfigurationComponent =
|
||||||
|
applicationsClassBase.getApplicationConfigurationComponent();
|
||||||
|
|
||||||
const tabConfiguration =
|
const tabConfiguration =
|
||||||
appData?.appConfiguration && appData.allowConfiguration && jsonSchema
|
appData?.appConfiguration && appData.allowConfiguration && jsonSchema
|
||||||
? [
|
? [
|
||||||
@ -349,7 +358,7 @@ const AppDetails = () => {
|
|||||||
),
|
),
|
||||||
key: ApplicationTabs.CONFIGURATION,
|
key: ApplicationTabs.CONFIGURATION,
|
||||||
children: (
|
children: (
|
||||||
<ApplicationConfiguration
|
<ApplicationConfigurationComponent
|
||||||
appData={appData}
|
appData={appData}
|
||||||
isLoading={loadingState.isSaveLoading}
|
isLoading={loadingState.isSaveLoading}
|
||||||
jsonSchema={jsonSchema}
|
jsonSchema={jsonSchema}
|
||||||
|
@ -154,6 +154,9 @@ jest.mock('../AppSchedule/AppSchedule.component', () =>
|
|||||||
jest.mock('./ApplicationsClassBase', () => ({
|
jest.mock('./ApplicationsClassBase', () => ({
|
||||||
importSchema: jest.fn().mockReturnValue({ default: ['table'] }),
|
importSchema: jest.fn().mockReturnValue({ default: ['table'] }),
|
||||||
getJSONUISchema: jest.fn().mockReturnValue({}),
|
getJSONUISchema: jest.fn().mockReturnValue({}),
|
||||||
|
getApplicationConfigurationComponent: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(() => <div>MockApplicationConfiguration</div>),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
|
@ -11,9 +11,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC } from 'react';
|
import { ComponentType, FC } from 'react';
|
||||||
import { AppType } from '../../../../generated/entity/applications/app';
|
import { AppType } from '../../../../generated/entity/applications/app';
|
||||||
import { getScheduleOptionsFromSchedules } from '../../../../utils/SchedularUtils';
|
import { getScheduleOptionsFromSchedules } from '../../../../utils/SchedularUtils';
|
||||||
|
import ApplicationConfiguration, {
|
||||||
|
ApplicationConfigurationProps,
|
||||||
|
} from '../ApplicationConfiguration/ApplicationConfiguration';
|
||||||
import { AppPlugin } from '../plugins/AppPlugin';
|
import { AppPlugin } from '../plugins/AppPlugin';
|
||||||
|
|
||||||
class ApplicationsClassBase {
|
class ApplicationsClassBase {
|
||||||
@ -84,6 +87,14 @@ class ApplicationsClassBase {
|
|||||||
? getScheduleOptionsFromSchedules(pipelineSchedules)
|
? getScheduleOptionsFromSchedules(pipelineSchedules)
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ApplicationConfiguration component to use.
|
||||||
|
* Base implementation returns the standard component.
|
||||||
|
*/
|
||||||
|
public getApplicationConfigurationComponent(): ComponentType<ApplicationConfigurationProps> {
|
||||||
|
return ApplicationConfiguration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const applicationsClassBase = new ApplicationsClassBase();
|
const applicationsClassBase = new ApplicationsClassBase();
|
||||||
|
@ -41,6 +41,11 @@ let mockGetApplicationRuns = jest.fn().mockReturnValue({
|
|||||||
const mockShowErrorToast = jest.fn();
|
const mockShowErrorToast = jest.fn();
|
||||||
const mockNavigate = jest.fn();
|
const mockNavigate = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('../../../../constants/LeftSidebar.constants', () => ({
|
||||||
|
SIDEBAR_NESTED_KEYS: {},
|
||||||
|
SIDEBAR_LIST: [],
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../../../../utils/EntityUtils', () => ({
|
jest.mock('../../../../utils/EntityUtils', () => ({
|
||||||
getEntityName: jest.fn().mockReturnValue('username'),
|
getEntityName: jest.fn().mockReturnValue('username'),
|
||||||
}));
|
}));
|
||||||
@ -105,6 +110,7 @@ jest.mock('../../../../utils/date-time/DateTimeUtils', () => ({
|
|||||||
|
|
||||||
return 'formatDateTime';
|
return 'formatDateTime';
|
||||||
}),
|
}),
|
||||||
|
getCurrentMillis: jest.fn().mockReturnValue(1234567890000),
|
||||||
getEpochMillisForPastDays: jest.fn().mockReturnValue('startDay'),
|
getEpochMillisForPastDays: jest.fn().mockReturnValue('startDay'),
|
||||||
getIntervalInMilliseconds: jest.fn().mockReturnValue('interval'),
|
getIntervalInMilliseconds: jest.fn().mockReturnValue('interval'),
|
||||||
formatDuration: jest.fn().mockReturnValue('formatDuration'),
|
formatDuration: jest.fn().mockReturnValue('formatDuration'),
|
||||||
|
@ -26,6 +26,10 @@ jest.mock('../../../common/RichTextEditor/RichTextEditorPreviewerV1', () =>
|
|||||||
jest.fn().mockImplementation(({ markdown }) => <div>{markdown}</div>)
|
jest.fn().mockImplementation(({ markdown }) => <div>{markdown}</div>)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
jest.mock('../AppLogo/AppLogo.component', () =>
|
||||||
|
jest.fn().mockImplementation(() => <div>AppLogo</div>)
|
||||||
|
);
|
||||||
|
|
||||||
describe('ApplicationCard', () => {
|
describe('ApplicationCard', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@ -39,9 +43,6 @@ describe('ApplicationCard', () => {
|
|||||||
it('renders the title correctly', () => {
|
it('renders the title correctly', () => {
|
||||||
render(<ApplicationCard {...props} />);
|
render(<ApplicationCard {...props} />);
|
||||||
|
|
||||||
// Fast-forward until all timers have been executed
|
|
||||||
jest.runAllTimers();
|
|
||||||
|
|
||||||
expect(screen.getByText('Search Index')).toBeInTheDocument();
|
expect(screen.getByText('Search Index')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Hello World')).toBeInTheDocument();
|
expect(screen.getByText('Hello World')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -17,18 +17,23 @@ import { isEmpty } from 'lodash';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ServiceCategory } from '../../../../enums/service.enum';
|
import { ServiceCategory } from '../../../../enums/service.enum';
|
||||||
import { App } from '../../../../generated/entity/applications/app';
|
import {
|
||||||
|
App,
|
||||||
|
EntityReference,
|
||||||
|
} from '../../../../generated/entity/applications/app';
|
||||||
import { AppMarketPlaceDefinition } from '../../../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
import { AppMarketPlaceDefinition } from '../../../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
||||||
import FormBuilder from '../../../common/FormBuilder/FormBuilder';
|
import FormBuilder from '../../../common/FormBuilder/FormBuilder';
|
||||||
import ResizablePanels from '../../../common/ResizablePanels/ResizablePanels';
|
import ResizablePanels from '../../../common/ResizablePanels/ResizablePanels';
|
||||||
import ServiceDocPanel from '../../../common/ServiceDocPanel/ServiceDocPanel';
|
import ServiceDocPanel from '../../../common/ServiceDocPanel/ServiceDocPanel';
|
||||||
import applicationsClassBase from '../AppDetails/ApplicationsClassBase';
|
import applicationsClassBase from '../AppDetails/ApplicationsClassBase';
|
||||||
|
|
||||||
interface ApplicationConfigurationProps {
|
export interface ApplicationConfigurationProps {
|
||||||
appData: App | AppMarketPlaceDefinition;
|
appData: App | AppMarketPlaceDefinition;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
jsonSchema: RJSFSchema;
|
jsonSchema: RJSFSchema;
|
||||||
onConfigSave: (data: IChangeEvent) => void;
|
onConfigSave: (
|
||||||
|
data: IChangeEvent & { ingestionRunner?: EntityReference }
|
||||||
|
) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import {
|
|||||||
default as applicationsClassBase,
|
default as applicationsClassBase,
|
||||||
} from '../../components/Settings/Applications/AppDetails/ApplicationsClassBase';
|
} from '../../components/Settings/Applications/AppDetails/ApplicationsClassBase';
|
||||||
import AppInstallVerifyCard from '../../components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component';
|
import AppInstallVerifyCard from '../../components/Settings/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component';
|
||||||
import ApplicationConfiguration from '../../components/Settings/Applications/ApplicationConfiguration/ApplicationConfiguration';
|
|
||||||
import { AppPlugin } from '../../components/Settings/Applications/plugins/AppPlugin';
|
import { AppPlugin } from '../../components/Settings/Applications/plugins/AppPlugin';
|
||||||
import ScheduleInterval from '../../components/Settings/Services/AddIngestion/Steps/ScheduleInterval';
|
import ScheduleInterval from '../../components/Settings/Services/AddIngestion/Steps/ScheduleInterval';
|
||||||
import { WorkflowExtraConfig } from '../../components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface';
|
import { WorkflowExtraConfig } from '../../components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface';
|
||||||
@ -41,8 +40,10 @@ import {
|
|||||||
} from '../../generated/entity/applications/createAppRequest';
|
} from '../../generated/entity/applications/createAppRequest';
|
||||||
import {
|
import {
|
||||||
AppMarketPlaceDefinition,
|
AppMarketPlaceDefinition,
|
||||||
|
AppType,
|
||||||
ScheduleType,
|
ScheduleType,
|
||||||
} from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
} from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
||||||
|
import { EntityReference } from '../../generated/entity/type';
|
||||||
import { useFqn } from '../../hooks/useFqn';
|
import { useFqn } from '../../hooks/useFqn';
|
||||||
import { installApplication } from '../../rest/applicationAPI';
|
import { installApplication } from '../../rest/applicationAPI';
|
||||||
import { getMarketPlaceApplicationByFqn } from '../../rest/applicationMarketPlaceAPI';
|
import { getMarketPlaceApplicationByFqn } from '../../rest/applicationMarketPlaceAPI';
|
||||||
@ -68,6 +69,11 @@ const AppInstall = () => {
|
|||||||
const [jsonSchema, setJsonSchema] = useState<RJSFSchema>();
|
const [jsonSchema, setJsonSchema] = useState<RJSFSchema>();
|
||||||
const [pluginComponent, setPluginComponent] = useState<FC | null>(null);
|
const [pluginComponent, setPluginComponent] = useState<FC | null>(null);
|
||||||
const { config, getResourceLimit } = useLimitStore();
|
const { config, getResourceLimit } = useLimitStore();
|
||||||
|
const [selectedIngestionRunner, setSelectedIngestionRunner] = useState<
|
||||||
|
EntityReference | undefined
|
||||||
|
>(undefined);
|
||||||
|
const shouldShowIngestionRunner =
|
||||||
|
appData?.appType === AppType.External && appData?.supportsIngestionRunner;
|
||||||
|
|
||||||
const { pipelineSchedules } =
|
const { pipelineSchedules } =
|
||||||
config?.limits?.config.featureLimits.find(
|
config?.limits?.config.featureLimits.find(
|
||||||
@ -177,23 +183,41 @@ const AppInstall = () => {
|
|||||||
name: fqn,
|
name: fqn,
|
||||||
description: appData?.description,
|
description: appData?.description,
|
||||||
displayName: appData?.displayName,
|
displayName: appData?.displayName,
|
||||||
|
ingestionRunner: shouldShowIngestionRunner
|
||||||
|
? selectedIngestionRunner
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
installApp(data);
|
installApp(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSaveConfiguration = (data: IChangeEvent) => {
|
const onSaveConfiguration = (
|
||||||
const updatedFormData = formatFormDataForSubmit(data.formData);
|
data: IChangeEvent & { ingestionRunner?: EntityReference }
|
||||||
|
) => {
|
||||||
|
const { formData, ingestionRunner } = data;
|
||||||
|
|
||||||
|
const updatedFormData = formatFormDataForSubmit(formData);
|
||||||
setAppConfiguration(updatedFormData);
|
setAppConfiguration(updatedFormData);
|
||||||
|
const ingestionRunnerRef = ingestionRunner
|
||||||
|
? {
|
||||||
|
id: ingestionRunner.id,
|
||||||
|
type: 'ingestionRunner',
|
||||||
|
name: ingestionRunner.name,
|
||||||
|
fullyQualifiedName: ingestionRunner.fullyQualifiedName,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
setSelectedIngestionRunner(ingestionRunnerRef);
|
||||||
|
|
||||||
if (appData?.scheduleType !== ScheduleType.NoSchedule) {
|
if (appData?.scheduleType !== ScheduleType.NoSchedule) {
|
||||||
setActiveServiceStep(3);
|
setActiveServiceStep(3);
|
||||||
} else {
|
} else {
|
||||||
const data: CreateAppRequest = {
|
const requestData: CreateAppRequest = {
|
||||||
appConfiguration: updatedFormData,
|
appConfiguration: updatedFormData,
|
||||||
name: fqn,
|
name: fqn,
|
||||||
description: appData?.description,
|
description: appData?.description,
|
||||||
displayName: appData?.displayName,
|
displayName: appData?.displayName,
|
||||||
|
...(ingestionRunnerRef ? { ingestionRunner: ingestionRunnerRef } : {}),
|
||||||
};
|
};
|
||||||
installApp(data);
|
installApp(requestData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -202,6 +226,9 @@ const AppInstall = () => {
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ApplicationConfigurationComponent =
|
||||||
|
applicationsClassBase.getApplicationConfigurationComponent();
|
||||||
|
|
||||||
switch (activeServiceStep) {
|
switch (activeServiceStep) {
|
||||||
case 1:
|
case 1:
|
||||||
return (
|
return (
|
||||||
@ -221,7 +248,7 @@ const AppInstall = () => {
|
|||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<ApplicationConfiguration
|
<ApplicationConfigurationComponent
|
||||||
appData={appData}
|
appData={appData}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
jsonSchema={jsonSchema}
|
jsonSchema={jsonSchema}
|
||||||
|
@ -61,6 +61,17 @@ jest.mock(
|
|||||||
() => ({
|
() => ({
|
||||||
importSchema: jest.fn().mockResolvedValue({}),
|
importSchema: jest.fn().mockResolvedValue({}),
|
||||||
getJSONUISchema: jest.fn().mockReturnValue({}),
|
getJSONUISchema: jest.fn().mockReturnValue({}),
|
||||||
|
getApplicationConfigurationComponent: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(({ onConfigSave, onCancel }: any) => (
|
||||||
|
<div>
|
||||||
|
FormBuilder
|
||||||
|
<button onClick={() => onConfigSave({ formData: {} })}>
|
||||||
|
Submit FormBuilder
|
||||||
|
</button>
|
||||||
|
<button onClick={onCancel}>Cancel FormBuilder</button>
|
||||||
|
</div>
|
||||||
|
)),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user