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