Skip to content

Commit

Permalink
NoSchedule as ScheduleType for Slack App (#18715)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and harshach committed Nov 24, 2024
1 parent d797e0c commit 2106078
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
static final String FIELDS = "owners";
private SearchRepository searchRepository;
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";

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,8 @@
"signingSecret": {
"description": "Signing Secret of the Application. Confirm that each request comes from Slack by verifying its unique signature.",
"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
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"enum": [
"Live",
"Scheduled",
"ScheduledOrManual"
"ScheduledOrManual",
"NoSchedule"
],
"javaEnums": [
{
Expand All @@ -25,6 +26,9 @@
},
{
"name": "ScheduledOrManual"
},
{
"name": "NoSchedule"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { ServiceCategory } from '../../../../enums/service.enum';
import {
App,
ScheduleTimeline,
ScheduleType,
} from '../../../../generated/entity/applications/app';
import { Include } from '../../../../generated/type/include';
import { useFqn } from '../../../../hooks/useFqn';
Expand Down Expand Up @@ -359,31 +360,40 @@ const AppDetails = () => {
]
: [];

const showScheduleTab = appData?.scheduleType !== ScheduleType.NoSchedule;

return [
{
label: (
<TabsLabel id={ApplicationTabs.SCHEDULE} name={t('label.schedule')} />
),
key: ApplicationTabs.SCHEDULE,
children: (
<div className="p-lg">
{appData && (
<AppSchedule
appData={appData}
loading={{
isRunLoading: loadingState.isRunLoading,
isDeployLoading: loadingState.isDeployLoading,
}}
onDemandTrigger={onDemandTrigger}
onDeployTrigger={onDeployTrigger}
onSave={onAppScheduleSave}
/>
)}
</div>
),
},
...(showScheduleTab
? [
{
label: (
<TabsLabel
id={ApplicationTabs.SCHEDULE}
name={t('label.schedule')}
/>
),
key: ApplicationTabs.SCHEDULE,
children: (
<div className="p-lg">
{appData && (
<AppSchedule
appData={appData}
loading={{
isRunLoading: loadingState.isRunLoading,
isDeployLoading: loadingState.isDeployLoading,
}}
onDemandTrigger={onDemandTrigger}
onDeployTrigger={onDeployTrigger}
onSave={onAppScheduleSave}
/>
)}
</div>
),
},
]
: []),
...tabConfiguration,
...(!appData?.deleted
...(!appData?.deleted && showScheduleTab
? [
{
label: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
render,
screen,
waitForElementToBeRemoved,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
Expand Down Expand Up @@ -218,6 +219,31 @@ describe('AppDetails component', () => {
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 () => {
await renderAppDetails();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ import {
CreateAppRequest,
ScheduleTimeline,
} 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 { installApplication } from '../../rest/applicationAPI';
import { getMarketPlaceApplicationByFqn } from '../../rest/applicationMarketPlaceAPI';
Expand Down Expand Up @@ -72,13 +75,17 @@ const AppInstall = () => {
(feature) => feature.name === 'app'
) ?? {};

const stepperList = useMemo(
() =>
!appData?.allowConfiguration
? STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 2)
: STEPS_FOR_APP_INSTALL,
[appData]
);
const stepperList = useMemo(() => {
if (appData?.scheduleType === ScheduleType.NoSchedule) {
return STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 3);
}

if (!appData?.allowConfiguration) {
return STEPS_FOR_APP_INSTALL.filter((item) => item.step !== 2);
}

return STEPS_FOR_APP_INSTALL;
}, [appData]);

const { initialOptions, defaultValue } = useMemo(() => {
if (!appData) {
Expand Down Expand Up @@ -123,22 +130,10 @@ const AppInstall = () => {
history.push(getSettingPath(GlobalSettingOptions.APPLICATIONS));
};

const onSubmit = async (updatedValue: WorkflowExtraConfig) => {
const { cron } = updatedValue;
const installApp = async (data: CreateAppRequest) => {
try {
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);

showSuccessToast(t('message.app-installed-successfully'));
Expand All @@ -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 updatedFormData = formatFormDataForSubmit(data.formData);
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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { ScheduleType } from '../../generated/entity/applications/app';
import { AppMarketPlaceDefinition } from '../../generated/entity/applications/marketplace/appMarketPlaceDefinition';
import AppInstall from './AppInstall.component';

Expand All @@ -29,6 +30,11 @@ const MARKETPLACE_DATA = {
allowConfiguration: true,
};

const NO_SCHEDULE_DATA = {
scheduleType: ScheduleType.NoSchedule,
allowConfiguration: true,
};

jest.mock('react-router-dom', () => ({
useHistory: jest.fn().mockImplementation(() => ({
push: mockPush,
Expand Down Expand Up @@ -234,6 +240,33 @@ describe('AppInstall component', () => {
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 () => {
mockGetMarketPlaceApplicationByFqn.mockRejectedValueOnce(ERROR);

Expand Down

0 comments on commit 2106078

Please sign in to comment.