mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-01 05:03:10 +00:00
NoSchedule as ScheduleType for Slack App (#18715)
* fix: NoSchedule as scheduleType for Slack App * fix: remove callback url from the config. * no render of schedule for noSchedule type * fix: Migrations and add ScheduleType.NoSchedule * fix: Migrations --------- Co-authored-by: karanh37 <karanh37@gmail.com>
This commit is contained in:
parent
23a52934ab
commit
702c34c0af
@ -103,7 +103,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
|
|||||||
static final String FIELDS = "owners";
|
static final String FIELDS = "owners";
|
||||||
private SearchRepository searchRepository;
|
private SearchRepository searchRepository;
|
||||||
public static final List<ScheduleType> SCHEDULED_TYPES =
|
public static final List<ScheduleType> SCHEDULED_TYPES =
|
||||||
List.of(ScheduleType.Scheduled, ScheduleType.ScheduledOrManual);
|
List.of(ScheduleType.Scheduled, ScheduleType.ScheduledOrManual, ScheduleType.NoSchedule);
|
||||||
public static final String SLACK_APPLICATION = "SlackApplication";
|
public static final String SLACK_APPLICATION = "SlackApplication";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,20 +17,8 @@
|
|||||||
"signingSecret": {
|
"signingSecret": {
|
||||||
"description": "Signing Secret of the Application. Confirm that each request comes from Slack by verifying its unique signature.",
|
"description": "Signing Secret of the Application. Confirm that each request comes from Slack by verifying its unique signature.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
|
||||||
"scopes": {
|
|
||||||
"description": "Scopes to Request in OAuth",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"callbackUrl": {
|
|
||||||
"description": "The callback URL where temporary authorization code is exchanged for access tokens",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"callbackRedirectURL": {
|
|
||||||
"description": "The URL where the application redirects after handling the OAuth callback",
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["clientId", "clientSecret","signingSecret","scopes","callbackUrl","callbackRedirectURL"],
|
"required": ["clientId", "clientSecret","signingSecret"],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
@ -14,7 +14,8 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"Live",
|
"Live",
|
||||||
"Scheduled",
|
"Scheduled",
|
||||||
"ScheduledOrManual"
|
"ScheduledOrManual",
|
||||||
|
"NoSchedule"
|
||||||
],
|
],
|
||||||
"javaEnums": [
|
"javaEnums": [
|
||||||
{
|
{
|
||||||
@ -25,6 +26,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ScheduledOrManual"
|
"name": "ScheduledOrManual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NoSchedule"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,7 @@ import { ServiceCategory } from '../../../../enums/service.enum';
|
|||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
ScheduleTimeline,
|
ScheduleTimeline,
|
||||||
|
ScheduleType,
|
||||||
} from '../../../../generated/entity/applications/app';
|
} from '../../../../generated/entity/applications/app';
|
||||||
import { Include } from '../../../../generated/type/include';
|
import { Include } from '../../../../generated/type/include';
|
||||||
import { useFqn } from '../../../../hooks/useFqn';
|
import { useFqn } from '../../../../hooks/useFqn';
|
||||||
@ -359,31 +360,40 @@ const AppDetails = () => {
|
|||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const showScheduleTab = appData?.scheduleType !== ScheduleType.NoSchedule;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
...(showScheduleTab
|
||||||
label: (
|
? [
|
||||||
<TabsLabel id={ApplicationTabs.SCHEDULE} name={t('label.schedule')} />
|
{
|
||||||
),
|
label: (
|
||||||
key: ApplicationTabs.SCHEDULE,
|
<TabsLabel
|
||||||
children: (
|
id={ApplicationTabs.SCHEDULE}
|
||||||
<div className="p-lg">
|
name={t('label.schedule')}
|
||||||
{appData && (
|
/>
|
||||||
<AppSchedule
|
),
|
||||||
appData={appData}
|
key: ApplicationTabs.SCHEDULE,
|
||||||
loading={{
|
children: (
|
||||||
isRunLoading: loadingState.isRunLoading,
|
<div className="p-lg">
|
||||||
isDeployLoading: loadingState.isDeployLoading,
|
{appData && (
|
||||||
}}
|
<AppSchedule
|
||||||
onDemandTrigger={onDemandTrigger}
|
appData={appData}
|
||||||
onDeployTrigger={onDeployTrigger}
|
loading={{
|
||||||
onSave={onAppScheduleSave}
|
isRunLoading: loadingState.isRunLoading,
|
||||||
/>
|
isDeployLoading: loadingState.isDeployLoading,
|
||||||
)}
|
}}
|
||||||
</div>
|
onDemandTrigger={onDemandTrigger}
|
||||||
),
|
onDeployTrigger={onDeployTrigger}
|
||||||
},
|
onSave={onAppScheduleSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...tabConfiguration,
|
...tabConfiguration,
|
||||||
...(!appData?.deleted
|
...(!appData?.deleted && showScheduleTab
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
waitForElementToBeRemoved,
|
waitForElementToBeRemoved,
|
||||||
|
within,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -218,6 +219,31 @@ describe('AppDetails component', () => {
|
|||||||
expect(mockPatchApplication).toHaveBeenCalled();
|
expect(mockPatchApplication).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Schedule and Recent Runs tab should not be visible for NoScheduleApps', async () => {
|
||||||
|
mockGetApplicationByName.mockReturnValueOnce({
|
||||||
|
...mockApplicationData,
|
||||||
|
scheduleType: 'NoSchedule',
|
||||||
|
deleted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await renderAppDetails();
|
||||||
|
|
||||||
|
// Narrow the scope to the tablist within the container
|
||||||
|
const tabList = screen.getByTestId('tabs');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
within(tabList).getByRole('tab', { name: 'label.configuration' })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
within(tabList).queryByRole('tab', { name: 'label.schedule' })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
within(tabList).queryByRole('tab', { name: 'label.recent-run-plural' })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('Schedule tab Actions check', async () => {
|
it('Schedule tab Actions check', async () => {
|
||||||
await renderAppDetails();
|
await renderAppDetails();
|
||||||
|
|
||||||
|
@ -40,7 +40,10 @@ import {
|
|||||||
CreateAppRequest,
|
CreateAppRequest,
|
||||||
ScheduleTimeline,
|
ScheduleTimeline,
|
||||||
} from '../../generated/entity/applications/createAppRequest';
|
} from '../../generated/entity/applications/createAppRequest';
|
||||||
import { AppMarketPlaceDefinition } from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
import {
|
||||||
|
AppMarketPlaceDefinition,
|
||||||
|
ScheduleType,
|
||||||
|
} from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
||||||
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';
|
||||||
@ -72,13 +75,17 @@ const AppInstall = () => {
|
|||||||
(feature) => feature.name === 'app'
|
(feature) => feature.name === 'app'
|
||||||
) ?? {};
|
) ?? {};
|
||||||
|
|
||||||
const stepperList = useMemo(
|
const stepperList = useMemo(() => {
|
||||||
() =>
|
if (appData?.scheduleType === ScheduleType.NoSchedule) {
|
||||||
!appData?.allowConfiguration
|
return STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 3);
|
||||||
? STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 2)
|
}
|
||||||
: STEPS_FOR_APP_INSTALL,
|
|
||||||
[appData]
|
if (!appData?.allowConfiguration) {
|
||||||
);
|
return STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return STEPS_FOR_APP_INSTALL;
|
||||||
|
}, [appData]);
|
||||||
|
|
||||||
const { initialOptions, defaultValue } = useMemo(() => {
|
const { initialOptions, defaultValue } = useMemo(() => {
|
||||||
if (!appData) {
|
if (!appData) {
|
||||||
@ -123,22 +130,10 @@ const AppInstall = () => {
|
|||||||
history.push(getSettingPath(GlobalSettingOptions.APPLICATIONS));
|
history.push(getSettingPath(GlobalSettingOptions.APPLICATIONS));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (updatedValue: WorkflowExtraConfig) => {
|
const installApp = async (data: CreateAppRequest) => {
|
||||||
const { cron } = updatedValue;
|
|
||||||
try {
|
try {
|
||||||
setIsSavingLoading(true);
|
setIsSavingLoading(true);
|
||||||
const data: CreateAppRequest = {
|
|
||||||
appConfiguration: appConfiguration ?? appData?.appConfiguration,
|
|
||||||
appSchedule: {
|
|
||||||
scheduleTimeline: isEmpty(cron)
|
|
||||||
? ScheduleTimeline.None
|
|
||||||
: ScheduleTimeline.Custom,
|
|
||||||
...(cron ? { cronExpression: cron } : {}),
|
|
||||||
},
|
|
||||||
name: fqn,
|
|
||||||
description: appData?.description,
|
|
||||||
displayName: appData?.displayName,
|
|
||||||
};
|
|
||||||
await installApplication(data);
|
await installApplication(data);
|
||||||
|
|
||||||
showSuccessToast(t('message.app-installed-successfully'));
|
showSuccessToast(t('message.app-installed-successfully'));
|
||||||
@ -154,10 +149,37 @@ const AppInstall = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (updatedValue: WorkflowExtraConfig) => {
|
||||||
|
const { cron } = updatedValue;
|
||||||
|
const data: CreateAppRequest = {
|
||||||
|
appConfiguration: appConfiguration ?? appData?.appConfiguration,
|
||||||
|
appSchedule: {
|
||||||
|
scheduleTimeline: isEmpty(cron)
|
||||||
|
? ScheduleTimeline.None
|
||||||
|
: ScheduleTimeline.Custom,
|
||||||
|
...(cron ? { cronExpression: cron } : {}),
|
||||||
|
},
|
||||||
|
name: fqn,
|
||||||
|
description: appData?.description,
|
||||||
|
displayName: appData?.displayName,
|
||||||
|
};
|
||||||
|
installApp(data);
|
||||||
|
};
|
||||||
|
|
||||||
const onSaveConfiguration = (data: IChangeEvent) => {
|
const onSaveConfiguration = (data: IChangeEvent) => {
|
||||||
const updatedFormData = formatFormDataForSubmit(data.formData);
|
const updatedFormData = formatFormDataForSubmit(data.formData);
|
||||||
setAppConfiguration(updatedFormData);
|
setAppConfiguration(updatedFormData);
|
||||||
setActiveServiceStep(3);
|
if (appData?.scheduleType !== ScheduleType.NoSchedule) {
|
||||||
|
setActiveServiceStep(3);
|
||||||
|
} else {
|
||||||
|
const data: CreateAppRequest = {
|
||||||
|
appConfiguration: updatedFormData,
|
||||||
|
name: fqn,
|
||||||
|
description: appData?.description,
|
||||||
|
displayName: appData?.displayName,
|
||||||
|
};
|
||||||
|
installApp(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderSelectedTab = useCallback(() => {
|
const RenderSelectedTab = useCallback(() => {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
import { act, render, screen } from '@testing-library/react';
|
import { act, render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ScheduleType } from '../../generated/entity/applications/app';
|
||||||
import { AppMarketPlaceDefinition } from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
import { AppMarketPlaceDefinition } from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
|
||||||
import AppInstall from './AppInstall.component';
|
import AppInstall from './AppInstall.component';
|
||||||
|
|
||||||
@ -29,6 +30,11 @@ const MARKETPLACE_DATA = {
|
|||||||
allowConfiguration: true,
|
allowConfiguration: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NO_SCHEDULE_DATA = {
|
||||||
|
scheduleType: ScheduleType.NoSchedule,
|
||||||
|
allowConfiguration: true,
|
||||||
|
};
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
useHistory: jest.fn().mockImplementation(() => ({
|
useHistory: jest.fn().mockImplementation(() => ({
|
||||||
push: mockPush,
|
push: mockPush,
|
||||||
@ -234,6 +240,33 @@ describe('AppInstall component', () => {
|
|||||||
expect(screen.getByText('AppInstallVerifyCard')).toBeInTheDocument();
|
expect(screen.getByText('AppInstallVerifyCard')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('actions check with schedule type noSchedule', async () => {
|
||||||
|
mockGetMarketPlaceApplicationByFqn.mockResolvedValueOnce(NO_SCHEDULE_DATA);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<AppInstall />);
|
||||||
|
});
|
||||||
|
|
||||||
|
// change ActiveServiceStep to 2
|
||||||
|
act(() => {
|
||||||
|
userEvent.click(
|
||||||
|
screen.getByRole('button', { name: 'Save AppInstallVerifyCard' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('FormBuilder')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('AppInstallVerifyCard')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// submit the form here
|
||||||
|
act(() => {
|
||||||
|
userEvent.click(
|
||||||
|
screen.getByRole('button', { name: 'Submit FormBuilder' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockInstallApplication).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('errors check in fetching application data', async () => {
|
it('errors check in fetching application data', async () => {
|
||||||
mockGetMarketPlaceApplicationByFqn.mockRejectedValueOnce(ERROR);
|
mockGetMarketPlaceApplicationByFqn.mockRejectedValueOnce(ERROR);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user