feat: Remove hardcoding of upcoming integrations from client codebase #40047 (#40271)

This commit is contained in:
vivek-appsmith 2025-04-17 20:11:06 +05:30 committed by GitHub
parent a94c75b868
commit c8a132f88d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 216 additions and 54 deletions

View File

@ -84,3 +84,7 @@ export const fetchPluginFormConfig = ({
export const fetchDefaultPlugins = (): ReduxActionWithoutPayload => ({ export const fetchDefaultPlugins = (): ReduxActionWithoutPayload => ({
type: ReduxActionTypes.GET_DEFAULT_PLUGINS_REQUEST, type: ReduxActionTypes.GET_DEFAULT_PLUGINS_REQUEST,
}); });
export const fetchUpcomingPlugins = (): ReduxActionWithoutPayload => ({
type: ReduxActionTypes.GET_UPCOMING_PLUGINS_REQUEST,
});

View File

@ -3,7 +3,12 @@ import type { AxiosPromise } from "axios";
import type { ApiResponse } from "api/ApiResponses"; import type { ApiResponse } from "api/ApiResponses";
import type { DependencyMap } from "utils/DynamicBindingUtils"; import type { DependencyMap } from "utils/DynamicBindingUtils";
import { FILE_UPLOAD_TRIGGER_TIMEOUT_MS } from "ee/constants/ApiConstants"; import { FILE_UPLOAD_TRIGGER_TIMEOUT_MS } from "ee/constants/ApiConstants";
import type { DefaultPlugin, Plugin } from "entities/Plugin"; import type {
DefaultPlugin,
Plugin,
UpcomingIntegration,
} from "entities/Plugin";
import { objectKeys } from "@appsmith/utils";
export interface PluginFormPayload { export interface PluginFormPayload {
// TODO: Fix this the next time the file is edited // TODO: Fix this the next time the file is edited
@ -55,6 +60,12 @@ class PluginsApi extends Api {
return Api.get(PluginsApi.url + `/default/icons`); return Api.get(PluginsApi.url + `/default/icons`);
} }
static async fetchUpcomingIntegrations(): Promise<
AxiosPromise<ApiResponse<UpcomingIntegration[]>>
> {
return Api.get(PluginsApi.url + "/upcoming-integrations");
}
static async uploadFiles( static async uploadFiles(
pluginId: string, pluginId: string,
files: File[], files: File[],
@ -70,7 +81,7 @@ class PluginsApi extends Api {
}); });
if (params) { if (params) {
Object.keys(params).forEach((key) => { objectKeys(params).forEach((key) => {
formData.append(key, params[key]); formData.append(key, params[key]);
}); });
} }

View File

@ -0,0 +1,73 @@
import PluginsApi from "api/PluginApi";
import Api from "api/Api";
import type { UpcomingIntegration } from "entities/Plugin";
// Mock the Api module with a class that can be extended
jest.mock("api/Api", () => {
return {
// Export a class that can be extended
__esModule: true,
default: class MockApi {
static get: jest.Mock = jest.fn();
},
};
});
describe("PluginsApi", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("fetchUpcomingIntegrations", () => {
it("should call the correct API endpoint", async () => {
// Setup mock API response
const mockResponse = {
data: {
responseMeta: {
success: true,
},
data: [
{
name: "Test Integration",
iconLocation: "test-icon-location",
},
{
name: "Another Test",
iconLocation: "another-test-icon",
},
] as UpcomingIntegration[],
},
};
(Api.get as jest.Mock).mockResolvedValue(mockResponse);
// Call the function
const result = await PluginsApi.fetchUpcomingIntegrations();
// Verify API was called correctly
expect(Api.get).toHaveBeenCalledWith(
PluginsApi.url + "/upcoming-integrations",
);
// Verify response matches mock
expect(result).toEqual(mockResponse);
});
it("should handle API errors", async () => {
// Setup mock API to throw error
const mockError = new Error("API error");
(Api.get as jest.Mock).mockRejectedValue(mockError);
// Call the function and expect it to throw
await expect(PluginsApi.fetchUpcomingIntegrations()).rejects.toThrow(
mockError,
);
// Verify API was called
expect(Api.get).toHaveBeenCalledWith(
PluginsApi.url + "/upcoming-integrations",
);
});
});
});

View File

@ -751,12 +751,15 @@ const PluginActionTypes = {
GET_PLUGIN_FORM_CONFIG_INIT: "GET_PLUGIN_FORM_CONFIG_INIT", GET_PLUGIN_FORM_CONFIG_INIT: "GET_PLUGIN_FORM_CONFIG_INIT",
GET_DEFAULT_PLUGINS_REQUEST: "GET_DEFAULT_PLUGINS_REQUEST", GET_DEFAULT_PLUGINS_REQUEST: "GET_DEFAULT_PLUGINS_REQUEST",
GET_DEFAULT_PLUGINS_SUCCESS: "GET_DEFAULT_PLUGINS_SUCCESS", GET_DEFAULT_PLUGINS_SUCCESS: "GET_DEFAULT_PLUGINS_SUCCESS",
GET_UPCOMING_PLUGINS_REQUEST: "GET_UPCOMING_PLUGINS_REQUEST",
GET_UPCOMING_PLUGINS_SUCCESS: "GET_UPCOMING_PLUGINS_SUCCESS",
}; };
const PluginActionErrorTypes = { const PluginActionErrorTypes = {
FETCH_PLUGINS_ERROR: "FETCH_PLUGINS_ERROR", FETCH_PLUGINS_ERROR: "FETCH_PLUGINS_ERROR",
FETCH_PLUGIN_FORM_CONFIGS_ERROR: "FETCH_PLUGIN_FORM_CONFIGS_ERROR", FETCH_PLUGIN_FORM_CONFIGS_ERROR: "FETCH_PLUGIN_FORM_CONFIGS_ERROR",
FETCH_PLUGIN_FORM_ERROR: "FETCH_PLUGIN_FORM_ERROR", FETCH_PLUGIN_FORM_ERROR: "FETCH_PLUGIN_FORM_ERROR",
GET_DEFAULT_PLUGINS_ERROR: "GET_DEFAULT_PLUGINS_ERROR", GET_DEFAULT_PLUGINS_ERROR: "GET_DEFAULT_PLUGINS_ERROR",
GET_UPCOMING_PLUGINS_ERROR: "GET_UPCOMING_PLUGINS_ERROR",
}; };
const UQIFormActionTypes = { const UQIFormActionTypes = {

View File

@ -8,7 +8,7 @@ import type { ExplorerURLParams } from "ee/pages/Editor/Explorer/helpers";
import type { DependentFeatureFlags } from "ee/selectors/engineSelectors"; import type { DependentFeatureFlags } from "ee/selectors/engineSelectors";
import { fetchDatasources } from "actions/datasourceActions"; import { fetchDatasources } from "actions/datasourceActions";
import { fetchPageDSLs } from "actions/pageActions"; import { fetchPageDSLs } from "actions/pageActions";
import { fetchPlugins } from "actions/pluginActions"; import { fetchPlugins, fetchUpcomingPlugins } from "actions/pluginActions";
import type { Plugin } from "entities/Plugin"; import type { Plugin } from "entities/Plugin";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useParams } from "react-router"; import { useParams } from "react-router";
@ -37,6 +37,7 @@ export const getPageDependencyActions = (
fetchPlugins({ plugins }), fetchPlugins({ plugins }),
fetchDatasources({ datasources }), fetchDatasources({ datasources }),
fetchPageDSLs({ pagesWithMigratedDsl }), fetchPageDSLs({ pagesWithMigratedDsl }),
fetchUpcomingPlugins(), // Not adding success and error actions for this as it's not a blocker for the app to load
] as Array<ReduxAction<unknown>>; ] as Array<ReduxAction<unknown>>;
const successActions = [ const successActions = [

View File

@ -22,6 +22,10 @@ const defaultStoreState = {
...unitTestBaseMockStore.entities, ...unitTestBaseMockStore.entities,
plugins: { plugins: {
list: [], list: [],
upcomingPlugins: {
list: [],
loading: false,
},
}, },
datasources: { datasources: {
list: [], list: [],

View File

@ -1799,3 +1799,8 @@ export const getJSCollectionActionSchemaDirtyState = createSelector(
return action.isDirtyMap?.SCHEMA_GENERATION; return action.isDirtyMap?.SCHEMA_GENERATION;
}, },
); );
export const getUpcomingPlugins = createSelector(
(state: AppState) => state.entities.plugins.upcomingPlugins,
(upcomingPlugins) => upcomingPlugins.list,
);

View File

@ -91,3 +91,8 @@ export interface DefaultPlugin {
iconLocation?: string; iconLocation?: string;
allowUserDatasources?: boolean; allowUserDatasources?: boolean;
} }
export interface UpcomingIntegration {
name: string;
iconLocation: string;
}

View File

@ -11,11 +11,13 @@ import {
type Plugin, type Plugin,
PluginPackageName, PluginPackageName,
PluginType, PluginType,
type UpcomingIntegration,
} from "entities/Plugin"; } from "entities/Plugin";
import { getQueryParams } from "utils/URLUtils"; import { getQueryParams } from "utils/URLUtils";
import { import {
getGenerateCRUDEnabledPluginMap, getGenerateCRUDEnabledPluginMap,
getPlugins, getPlugins,
getUpcomingPlugins,
} from "ee/selectors/entitiesSelector"; } from "ee/selectors/entitiesSelector";
import { getIsGeneratePageInitiator } from "utils/GenerateCrudUtil"; import { getIsGeneratePageInitiator } from "utils/GenerateCrudUtil";
import { getAssetUrl, isAirgapped } from "ee/utils/airgapHelpers"; import { getAssetUrl, isAirgapped } from "ee/utils/airgapHelpers";
@ -43,10 +45,7 @@ import {
import scrollIntoView from "scroll-into-view-if-needed"; import scrollIntoView from "scroll-into-view-if-needed";
import PremiumDatasources from "./PremiumDatasources"; import PremiumDatasources from "./PremiumDatasources";
import { pluginSearchSelector } from "./CreateNewDatasourceHeader"; import { pluginSearchSelector } from "./CreateNewDatasourceHeader";
import { import { getFilteredUpcomingIntegrations } from "./PremiumDatasources/Constants";
getFilteredPremiumIntegrations,
type PremiumIntegration,
} from "./PremiumDatasources/Constants";
import { getDatasourcesLoadingState } from "selectors/datasourceSelectors"; import { getDatasourcesLoadingState } from "selectors/datasourceSelectors";
import { getIDETypeByUrl } from "ee/entities/IDE/utils"; import { getIDETypeByUrl } from "ee/entities/IDE/utils";
import type { IDEType } from "ee/IDE/Interfaces/IDETypes"; import type { IDEType } from "ee/IDE/Interfaces/IDETypes";
@ -75,7 +74,7 @@ interface CreateAPIOrSaasPluginsProps {
apiType: string, apiType: string,
) => void; ) => void;
isPremiumDatasourcesViewEnabled?: boolean; isPremiumDatasourcesViewEnabled?: boolean;
premiumPlugins: PremiumIntegration[]; upcomingIntegrations: UpcomingIntegration[];
authApiPlugin?: Plugin; authApiPlugin?: Plugin;
restAPIVisible?: boolean; restAPIVisible?: boolean;
graphQLAPIVisible?: boolean; graphQLAPIVisible?: boolean;
@ -235,7 +234,7 @@ function APIOrSaasPlugins(props: CreateAPIOrSaasPluginsProps) {
/> />
))} ))}
{!props.isIntegrationsEnabledForPaid && ( {!props.isIntegrationsEnabledForPaid && (
<PremiumDatasources plugins={props.premiumPlugins} /> <PremiumDatasources plugins={props.upcomingIntegrations} />
)} )}
</DatasourceContainer> </DatasourceContainer>
); );
@ -263,7 +262,7 @@ function CreateAPIOrSaasPlugins(props: CreateAPIOrSaasPluginsProps) {
if (isAirgappedInstance && props.showSaasAPIs) return null; if (isAirgappedInstance && props.showSaasAPIs) return null;
if ( if (
props.premiumPlugins.length === 0 && props.upcomingIntegrations.length === 0 &&
props.plugins.length === 0 && props.plugins.length === 0 &&
!props.restAPIVisible && !props.restAPIVisible &&
!props.graphQLAPIVisible !props.graphQLAPIVisible
@ -281,7 +280,8 @@ function CreateAPIOrSaasPlugins(props: CreateAPIOrSaasPluginsProps) {
</DatasourceSectionHeading> </DatasourceSectionHeading>
<APIOrSaasPlugins {...props} /> <APIOrSaasPlugins {...props} />
</DatasourceSection> </DatasourceSection>
{props.premiumPlugins.length > 0 && props.isIntegrationsEnabledForPaid ? ( {props.upcomingIntegrations.length > 0 &&
props.isIntegrationsEnabledForPaid ? (
<DatasourceSection id="upcoming-saas-integrations"> <DatasourceSection id="upcoming-saas-integrations">
<DatasourceSectionHeading kind="heading-m"> <DatasourceSectionHeading kind="heading-m">
{createMessage(UPCOMING_SAAS_INTEGRATIONS)} {createMessage(UPCOMING_SAAS_INTEGRATIONS)}
@ -289,7 +289,7 @@ function CreateAPIOrSaasPlugins(props: CreateAPIOrSaasPluginsProps) {
<DatasourceContainer data-testid="upcoming-datasource-card-container"> <DatasourceContainer data-testid="upcoming-datasource-card-container">
<PremiumDatasources <PremiumDatasources
isIntegrationsEnabledForPaid isIntegrationsEnabledForPaid
plugins={props.premiumPlugins} plugins={props.upcomingIntegrations}
/> />
</DatasourceContainer> </DatasourceContainer>
</DatasourceSection> </DatasourceSection>
@ -310,6 +310,8 @@ const mapStateToProps = (
pluginSearchSelector(state, "search") || "" pluginSearchSelector(state, "search") || ""
).toLocaleLowerCase(); ).toLocaleLowerCase();
const upcomingPlugins = getUpcomingPlugins(state);
const allPlugins = getPlugins(state); const allPlugins = getPlugins(state);
let plugins = allPlugins.filter((p) => let plugins = allPlugins.filter((p) =>
@ -354,15 +356,16 @@ const mapStateToProps = (
plugin.name.toLocaleLowerCase(), plugin.name.toLocaleLowerCase(),
); );
const premiumPlugins = const upcomingIntegrations =
props.showSaasAPIs && props.isPremiumDatasourcesViewEnabled props.showSaasAPIs && props.isPremiumDatasourcesViewEnabled
? (filterSearch( ? (filterSearch(
getFilteredPremiumIntegrations( getFilteredUpcomingIntegrations(
isExternalSaasEnabled || isIntegrationsEnabledForPaid, isExternalSaasEnabled || isIntegrationsEnabledForPaid,
pluginNames, pluginNames,
upcomingPlugins,
), ),
searchedPlugin, searchedPlugin,
) as PremiumIntegration[]) ) as UpcomingIntegration[])
: []; : [];
const restAPIVisible = const restAPIVisible =
@ -380,7 +383,7 @@ const mapStateToProps = (
return { return {
plugins, plugins,
premiumPlugins, upcomingIntegrations,
authApiPlugin, authApiPlugin,
restAPIVisible, restAPIVisible,
graphQLAPIVisible, graphQLAPIVisible,

View File

@ -10,8 +10,8 @@ import {
} from "ee/constants/messages"; } from "ee/constants/messages";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { pluginSearchSelector } from "./CreateNewDatasourceHeader"; import { pluginSearchSelector } from "./CreateNewDatasourceHeader";
import { getPlugins } from "ee/selectors/entitiesSelector"; import { getPlugins, getUpcomingPlugins } from "ee/selectors/entitiesSelector";
import { getFilteredPremiumIntegrations } from "./PremiumDatasources/Constants"; import { getFilteredUpcomingIntegrations } from "./PremiumDatasources/Constants";
import styled from "styled-components"; import styled from "styled-components";
import { filterSearch } from "./util"; import { filterSearch } from "./util";
import type { MockDatasource } from "entities/Datasource"; import type { MockDatasource } from "entities/Datasource";
@ -34,6 +34,8 @@ export default function EmptySearchedPlugins({
pluginSearchSelector(state, "search"), pluginSearchSelector(state, "search"),
); );
const upcomingPlugins = useSelector(getUpcomingPlugins);
searchedPlugin = (searchedPlugin || "").toLocaleLowerCase(); searchedPlugin = (searchedPlugin || "").toLocaleLowerCase();
const plugins = useSelector(getPlugins); const plugins = useSelector(getPlugins);
@ -59,9 +61,10 @@ export default function EmptySearchedPlugins({
{ name: createMessage(CREATE_NEW_DATASOURCE_AUTHENTICATED_REST_API) }, { name: createMessage(CREATE_NEW_DATASOURCE_AUTHENTICATED_REST_API) },
...mockDatasources, ...mockDatasources,
...(isPremiumDatasourcesViewEnabled ...(isPremiumDatasourcesViewEnabled
? getFilteredPremiumIntegrations( ? getFilteredUpcomingIntegrations(
isExternalSaasEnabled || isIntegrationsEnabledForPaid, isExternalSaasEnabled || isIntegrationsEnabledForPaid,
pluginNames, pluginNames,
upcomingPlugins,
) )
: []), : []),
], ],

View File

@ -1,40 +1,24 @@
import { getAssetUrl } from "ee/utils/airgapHelpers"; import type { UpcomingIntegration } from "entities/Plugin";
import { ASSETS_CDN_URL } from "../../../../constants/ThirdPartyConstants";
export interface PremiumIntegration { /**
name: string; * Filters upcoming integrations based on available plugins.
icon: string; * Returns cached integrations synchronously.
} *
* @param isExternalSaasEnabled Whether external SaaS integrations are enabled
const PREMIUM_INTEGRATIONS: PremiumIntegration[] = [ * @param pluginNames List of installed plugin names (lowercase)
{ * @returns Filtered list of upcoming integrations
name: "Zendesk", */
icon: getAssetUrl(`${ASSETS_CDN_URL}/zendesk-icon.png`), export const getFilteredUpcomingIntegrations = (
},
{
name: "Salesforce",
icon: getAssetUrl(`${ASSETS_CDN_URL}/salesforce-image.png`),
},
{
name: "Slack",
icon: getAssetUrl(`${ASSETS_CDN_URL}/slack.png`),
},
{
name: "Jira",
icon: getAssetUrl(`${ASSETS_CDN_URL}/jira.png`),
},
];
export const getFilteredPremiumIntegrations = (
isExternalSaasEnabled: boolean, isExternalSaasEnabled: boolean,
pluginNames: string[], pluginNames: string[],
) => { upcomingPlugins: UpcomingIntegration[],
): UpcomingIntegration[] => {
return isExternalSaasEnabled return isExternalSaasEnabled
? PREMIUM_INTEGRATIONS.filter( ? upcomingPlugins.filter(
(integration) => (integration) =>
!pluginNames.includes(integration.name.toLocaleLowerCase()), !pluginNames.includes(integration.name.toLocaleLowerCase()),
) )
: PREMIUM_INTEGRATIONS; : upcomingPlugins;
}; };
export const PREMIUM_INTEGRATION_CONTACT_FORM = export const PREMIUM_INTEGRATION_CONTACT_FORM =

View File

@ -5,9 +5,9 @@ import styled from "styled-components";
import ContactForm from "./ContactForm"; import ContactForm from "./ContactForm";
import { handlePremiumDatasourceClick } from "./Helpers"; import { handlePremiumDatasourceClick } from "./Helpers";
import DatasourceItem from "../DatasourceItem"; import DatasourceItem from "../DatasourceItem";
import type { PremiumIntegration } from "./Constants";
import { createMessage } from "ee/constants/messages"; import { createMessage } from "ee/constants/messages";
import { PREMIUM_DATASOURCES } from "ee/constants/messages"; import { PREMIUM_DATASOURCES } from "ee/constants/messages";
import type { UpcomingIntegration } from "entities/Plugin";
const ModalContentWrapper = styled(ModalContent)` const ModalContentWrapper = styled(ModalContent)`
max-width: 518px; max-width: 518px;
@ -26,7 +26,7 @@ const PremiumTag = styled(Tag)`
`; `;
export default function PremiumDatasources(props: { export default function PremiumDatasources(props: {
plugins: PremiumIntegration[]; plugins: UpcomingIntegration[];
isIntegrationsEnabledForPaid?: boolean; isIntegrationsEnabledForPaid?: boolean;
}) { }) {
const [selectedIntegration, setSelectedIntegration] = useState<string>(""); const [selectedIntegration, setSelectedIntegration] = useState<string>("");
@ -49,7 +49,7 @@ export default function PremiumDatasources(props: {
handleOnClick={() => { handleOnClick={() => {
handleOnClick(integration.name); handleOnClick(integration.name);
}} }}
icon={getAssetUrl(integration.icon)} icon={getAssetUrl(integration.iconLocation)}
key={integration.name} key={integration.name}
name={integration.name} name={integration.name}
rightSibling={ rightSibling={

View File

@ -4,7 +4,11 @@ import {
ReduxActionTypes, ReduxActionTypes,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "ee/constants/ReduxActionConstants"; } from "ee/constants/ReduxActionConstants";
import type { DefaultPlugin, Plugin } from "entities/Plugin"; import type {
DefaultPlugin,
Plugin,
UpcomingIntegration,
} from "entities/Plugin";
import type { import type {
PluginFormPayloadWithId, PluginFormPayloadWithId,
PluginFormsPayload, PluginFormsPayload,
@ -30,6 +34,10 @@ export interface PluginDataState {
datasourceFormButtonConfigs: FormDatasourceButtonConfigs; datasourceFormButtonConfigs: FormDatasourceButtonConfigs;
fetchingSinglePluginForm: Record<string, boolean>; fetchingSinglePluginForm: Record<string, boolean>;
fetchingDefaultPlugins: boolean; fetchingDefaultPlugins: boolean;
upcomingPlugins: {
list: UpcomingIntegration[];
loading: boolean;
};
} }
const initialState: PluginDataState = { const initialState: PluginDataState = {
@ -43,6 +51,10 @@ const initialState: PluginDataState = {
dependencies: {}, dependencies: {},
fetchingSinglePluginForm: {}, fetchingSinglePluginForm: {},
fetchingDefaultPlugins: false, fetchingDefaultPlugins: false,
upcomingPlugins: {
list: [],
loading: false,
},
}; };
const pluginsReducer = createReducer(initialState, { const pluginsReducer = createReducer(initialState, {
@ -142,6 +154,33 @@ const pluginsReducer = createReducer(initialState, {
defaultPluginList: action.payload, defaultPluginList: action.payload,
}; };
}, },
[ReduxActionTypes.GET_UPCOMING_PLUGINS_REQUEST]: (state: PluginDataState) => {
return {
...state,
upcomingPlugins: { ...state.upcomingPlugins, loading: true },
};
},
[ReduxActionTypes.GET_UPCOMING_PLUGINS_SUCCESS]: (
state: PluginDataState,
action: ReduxAction<UpcomingIntegration[]>,
) => {
return {
...state,
upcomingPlugins: {
...state.upcomingPlugins,
loading: false,
list: action.payload,
},
};
},
[ReduxActionErrorTypes.GET_UPCOMING_PLUGINS_ERROR]: (
state: PluginDataState,
) => {
return {
...state,
upcomingPlugins: { ...state.upcomingPlugins, loading: false },
};
},
}); });
export default pluginsReducer; export default pluginsReducer;

View File

@ -31,7 +31,12 @@ import type { ApiResponse } from "api/ApiResponses";
import PluginApi from "api/PluginApi"; import PluginApi from "api/PluginApi";
import log from "loglevel"; import log from "loglevel";
import { getAppsmithAIPlugin, getGraphQLPlugin } from "entities/Action"; import { getAppsmithAIPlugin, getGraphQLPlugin } from "entities/Action";
import { type DefaultPlugin, type Plugin, PluginType } from "entities/Plugin"; import {
type DefaultPlugin,
type Plugin,
PluginType,
type UpcomingIntegration,
} from "entities/Plugin";
import type { import type {
FormEditorConfigs, FormEditorConfigs,
FormSettingsConfigs, FormSettingsConfigs,
@ -305,6 +310,24 @@ function* getDefaultPluginsSaga() {
} }
} }
function* getUpcomingPluginsSaga() {
try {
const response: ApiResponse<UpcomingIntegration[]> = yield call(
PluginsApi.fetchUpcomingIntegrations,
);
yield put({
type: ReduxActionTypes.GET_UPCOMING_PLUGINS_SUCCESS,
payload: response.data || [],
});
} catch (error) {
yield put({
type: ReduxActionErrorTypes.GET_UPCOMING_PLUGINS_ERROR,
payload: { error },
});
}
}
function* root() { function* root() {
yield all([ yield all([
takeEvery(ReduxActionTypes.FETCH_PLUGINS_REQUEST, fetchPluginsSaga), takeEvery(ReduxActionTypes.FETCH_PLUGINS_REQUEST, fetchPluginsSaga),
@ -320,6 +343,10 @@ function* root() {
ReduxActionTypes.GET_DEFAULT_PLUGINS_REQUEST, ReduxActionTypes.GET_DEFAULT_PLUGINS_REQUEST,
getDefaultPluginsSaga, getDefaultPluginsSaga,
), ),
takeEvery(
ReduxActionTypes.GET_UPCOMING_PLUGINS_REQUEST,
getUpcomingPluginsSaga,
),
]); ]);
} }